git.delta.rocks / jrsonnet / refs/commits / d09f1c2861da

difftreelog

perf use fxhash

Lach2020-08-23parent: #84fe36c.patch.diff
in: master

8 files changed

modifiedCargo.lockdiffbeforeafterboth
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -172,6 +172,7 @@
  "jrsonnet-stdlib",
  "md5",
  "pathdiff",
+ "rustc-hash",
  "serde",
  "serde_json",
  "structdump",
@@ -322,6 +323,12 @@
 ]
 
 [[package]]
+name = "rustc-hash"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
+
+[[package]]
 name = "ryu"
 version = "1.0.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
modifiedcrates/jrsonnet-evaluator/Cargo.tomldiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/Cargo.toml
+++ b/crates/jrsonnet-evaluator/Cargo.toml
@@ -9,7 +9,7 @@
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [features]
-default = ["serialized-stdlib", "faster", "explaining-traces"]
+default = ["serialized-stdlib", "faster", "explaining-traces", "serde-json"]
 # Serializes standard library AST instead of parsing them every run
 serialized-stdlib = ["serde", "bincode", "jrsonnet-parser/deserialize"]
 # Allow to convert Val into serde_json::Value and backwards
@@ -35,6 +35,7 @@
 
 md5 = "0.7.0"
 base64 = "0.12.3"
+rustc-hash = "1.1.0"
 
 # Serialized stdlib
 [dependencies.serde]
modifiedcrates/jrsonnet-evaluator/src/builtin/mod.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/builtin/mod.rs
+++ b/crates/jrsonnet-evaluator/src/builtin/mod.rs
@@ -158,7 +158,7 @@
 		("std", "native") => parse_args!(context, "std.native", args, 1, [
 			0, x: [Val::Str]!!Val::Str, vec![ValType::Str];
 		], {
-			Ok(with_state(|s| s.settings().ext_natives.get(&x).cloned()).map(|v| Val::Func(FuncVal::NativeExt(x.clone(), v))).ok_or_else(
+			Ok(with_state(|s| s.settings().ext_natives.get(&x).cloned()).map(|v| Val::Func(Rc::new(FuncVal::NativeExt(x.clone(), v)))).ok_or_else(
 				|| UndefinedExternalFunction(x),
 			)?)
 		})?,
@@ -212,7 +212,7 @@
 			if arr.len() <= 1 {
 				return Ok(Val::Arr(arr))
 			}
-			Ok(Val::Arr(sort::sort(context, arr, keyF)?))
+			Ok(Val::Arr(sort::sort(context, arr, &keyF)?))
 		})?,
 		// faster
 		("std", "format") => parse_args!(context, "std.format", args, 2, [
modifiedcrates/jrsonnet-evaluator/src/ctx.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/ctx.rs
+++ b/crates/jrsonnet-evaluator/src/ctx.rs
@@ -2,6 +2,8 @@
 	error::Error::*, future_wrapper, map::LayeredHashMap, rc_fn_helper, resolved_lazy_val,
 	LazyBinding, LazyVal, ObjValue, Result, Val,
 };
+use rustc_hash::FxHashMap;
+use std::hash::BuildHasherDefault;
 use std::{cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc};
 
 rc_fn_helper!(
@@ -71,14 +73,15 @@
 	}
 
 	pub fn with_var(self, name: Rc<str>, value: Val) -> Context {
-		let mut new_bindings = HashMap::with_capacity(1);
+		let mut new_bindings =
+			FxHashMap::with_capacity_and_hasher(1, BuildHasherDefault::default());
 		new_bindings.insert(name, resolved_lazy_val!(value));
 		self.extend(new_bindings, None, None, None)
 	}
 
 	pub fn extend(
 		self,
-		new_bindings: HashMap<Rc<str>, LazyVal>,
+		new_bindings: FxHashMap<Rc<str>, LazyVal>,
 		new_dollar: Option<ObjValue>,
 		new_this: Option<ObjValue>,
 		new_super_obj: Option<ObjValue>,
@@ -127,7 +130,8 @@
 	) -> Result<Context> {
 		let this = new_this.or_else(|| self.0.this.clone());
 		let super_obj = new_super_obj.or_else(|| self.0.super_obj.clone());
-		let mut new = HashMap::with_capacity(new_bindings.len());
+		let mut new =
+			FxHashMap::with_capacity_and_hasher(new_bindings.len(), BuildHasherDefault::default());
 		for (k, v) in new_bindings.into_iter() {
 			new.insert(k, v.evaluate(this.clone(), super_obj.clone())?);
 		}
modifiedcrates/jrsonnet-evaluator/src/evaluate.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate.rs
@@ -9,6 +9,7 @@
 	ForSpecData, IfSpecData, LiteralType, LocExpr, Member, ObjBody, ParamsDesc, UnaryOpType,
 	Visibility,
 };
+use rustc_hash::FxHashMap;
 use std::{collections::HashMap, rc::Rc};
 
 pub fn evaluate_binding(b: &BindSpec, context_creator: ContextCreator) -> (Rc<str>, LazyBinding) {
@@ -45,7 +46,7 @@
 }
 
 pub fn evaluate_method(ctx: Context, name: Rc<str>, params: ParamsDesc, body: LocExpr) -> Val {
-	Val::Func(FuncVal::Normal(Rc::new(FuncDesc {
+	Val::Func(Rc::new(FuncVal::Normal(FuncDesc {
 		name,
 		ctx,
 		params,
@@ -351,7 +352,7 @@
 					let key = evaluate(ctx.clone(), &obj.key)?;
 					let value = LazyBinding::Bindable(Rc::new(
 						closure!(clone ctx, clone obj.value, |this, _super_obj| {
-							Ok(LazyVal::new_resolved(evaluate(ctx.clone().extend(HashMap::new(), None, this, None), &value)?))
+							Ok(LazyVal::new_resolved(evaluate(ctx.clone().extend(FxHashMap::default(), None, this, None), &value)?))
 						}),
 					));
 
@@ -468,7 +469,7 @@
 							} else if let Some(Val::Str(n)) =
 								v.get("__intristic_namespace__".into())?
 							{
-								Ok(Val::Func(FuncVal::Intristic(n, s)))
+								Ok(Val::Func(Rc::new(FuncVal::Intristic(n, s))))
 							} else {
 								throw!(NoSuchField(s))
 							}
modifiedcrates/jrsonnet-evaluator/src/function.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/function.rs
+++ b/crates/jrsonnet-evaluator/src/function.rs
@@ -1,7 +1,8 @@
 use crate::{error::Error::*, evaluate, lazy_val, resolved_lazy_val, throw, Context, Result, Val};
 use closure::closure;
 use jrsonnet_parser::{ArgsDesc, ParamsDesc};
-use std::{collections::HashMap, rc::Rc};
+use rustc_hash::FxHashMap;
+use std::{collections::HashMap, hash::BuildHasherDefault, rc::Rc};
 
 const NO_DEFAULT_CONTEXT: &str =
 	"no default context set for call with defined default parameter value";
@@ -20,7 +21,7 @@
 	args: &ArgsDesc,
 	tailstrict: bool,
 ) -> Result<Context> {
-	let mut out = HashMap::with_capacity(params.len());
+	let mut out = HashMap::with_capacity_and_hasher(params.len(), BuildHasherDefault::default());
 	let mut positioned_args = vec![None; params.0.len()];
 	for (id, arg) in args.iter().enumerate() {
 		let idx = if let Some(name) = &arg.0 {
@@ -67,7 +68,7 @@
 	args: &HashMap<Rc<str>, Val>,
 	tailstrict: bool,
 ) -> Result<Context> {
-	let mut out = HashMap::with_capacity(params.len());
+	let mut out = FxHashMap::with_capacity_and_hasher(params.len(), BuildHasherDefault::default());
 	let mut positioned_args = vec![None; params.0.len()];
 	for (name, val) in args.iter() {
 		let idx = params
@@ -115,7 +116,7 @@
 	params: &ParamsDesc,
 	args: &[Val],
 ) -> Result<Context> {
-	let mut out = HashMap::with_capacity(params.len());
+	let mut out = FxHashMap::with_capacity_and_hasher(params.len(), BuildHasherDefault::default());
 	let mut positioned_args = vec![None; params.0.len()];
 	for (id, arg) in args.iter().enumerate() {
 		if id >= params.len() {
modifiedcrates/jrsonnet-evaluator/src/map.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/map.rs
+++ b/crates/jrsonnet-evaluator/src/map.rs
@@ -1,16 +1,17 @@
-use std::{borrow::Borrow, collections::HashMap, hash::Hash, rc::Rc};
+use rustc_hash::FxHashMap;
+use std::{borrow::Borrow, hash::Hash, rc::Rc};
 
 #[derive(Default, Debug)]
 struct LayeredHashMapInternals<K: Hash, V> {
 	parent: Option<LayeredHashMap<K, V>>,
-	current: HashMap<K, V>,
+	current: FxHashMap<K, V>,
 }
 
 #[derive(Debug)]
 pub struct LayeredHashMap<K: Hash, V>(Rc<LayeredHashMapInternals<K, V>>);
 
 impl<K: Hash + Eq, V> LayeredHashMap<K, V> {
-	pub fn extend(self, new_layer: HashMap<K, V>) -> Self {
+	pub fn extend(self, new_layer: FxHashMap<K, V>) -> Self {
 		match Rc::try_unwrap(self.0) {
 			Ok(mut map) => {
 				map.current.extend(new_layer);
@@ -45,7 +46,7 @@
 	fn default() -> Self {
 		LayeredHashMap(Rc::new(LayeredHashMapInternals {
 			parent: None,
-			current: HashMap::new(),
+			current: FxHashMap::default(),
 		}))
 	}
 }
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/val.rs
1use crate::{2	builtin::{3		call_builtin,4		manifest::{manifest_json_ex, ManifestJsonOptions, ManifestType},5	},6	error::Error::*,7	evaluate,8	function::{parse_function_call, parse_function_call_map, place_args},9	native::NativeCallback,10	throw, with_state, Context, ObjValue, Result,11};12use jrsonnet_parser::{el, Arg, ArgsDesc, Expr, ExprLocation, LocExpr, ParamsDesc};13use std::{14	cell::RefCell,15	collections::HashMap,16	fmt::{Debug, Display},17	rc::Rc,18};1920enum LazyValInternals {21	Computed(Val),22	Waiting(Box<dyn Fn() -> Result<Val>>),23}24#[derive(Clone)]25pub struct LazyVal(Rc<RefCell<LazyValInternals>>);26impl LazyVal {27	pub fn new(f: Box<dyn Fn() -> Result<Val>>) -> Self {28		LazyVal(Rc::new(RefCell::new(LazyValInternals::Waiting(f))))29	}30	pub fn new_resolved(val: Val) -> Self {31		LazyVal(Rc::new(RefCell::new(LazyValInternals::Computed(val))))32	}33	pub fn evaluate(&self) -> Result<Val> {34		let new_value = match &*self.0.borrow() {35			LazyValInternals::Computed(v) => return Ok(v.clone()),36			LazyValInternals::Waiting(f) => f()?,37		};38		*self.0.borrow_mut() = LazyValInternals::Computed(new_value.clone());39		Ok(new_value)40	}41}4243#[macro_export]44macro_rules! lazy_val {45	($f: expr) => {46		$crate::LazyVal::new(Box::new($f))47	};48}49#[macro_export]50macro_rules! resolved_lazy_val {51	($f: expr) => {52		$crate::LazyVal::new_resolved($f)53	};54}55impl Debug for LazyVal {56	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {57		write!(f, "Lazy")58	}59}60impl PartialEq for LazyVal {61	fn eq(&self, other: &Self) -> bool {62		Rc::ptr_eq(&self.0, &other.0)63	}64}6566#[derive(Debug, PartialEq)]67pub struct FuncDesc {68	pub name: Rc<str>,69	pub ctx: Context,70	pub params: ParamsDesc,71	pub body: LocExpr,72}7374#[derive(Debug, Clone)]75pub enum FuncVal {76	/// Plain function implemented in jsonnet77	Normal(Rc<FuncDesc>),78	/// Standard library function79	Intristic(Rc<str>, Rc<str>),80	/// Library functions implemented in native81	NativeExt(Rc<str>, Rc<NativeCallback>),82}83impl PartialEq for FuncVal {84	fn eq(&self, other: &Self) -> bool {85		match (self, other) {86			(FuncVal::Normal(a), FuncVal::Normal(b)) => a == b,87			(FuncVal::Intristic(ans, an), FuncVal::Intristic(bns, bn)) => ans == bns && an == bn,88			(FuncVal::NativeExt(an, _), FuncVal::NativeExt(bn, _)) => an == bn,89			(..) => false,90		}91	}92}93impl FuncVal {94	pub fn is_ident(&self) -> bool {95		matches!(&self, FuncVal::Intristic(ns, n) if ns as &str == "std" && n as &str == "id")96	}97	pub fn name(&self) -> Rc<str> {98		match self {99			FuncVal::Normal(normal) => normal.name.clone(),100			FuncVal::Intristic(ns, name) => format!("intristic.{}.{}", ns, name).into(),101			FuncVal::NativeExt(n, _) => format!("native.{}", n).into(),102		}103	}104	pub fn evaluate(105		&self,106		call_ctx: Context,107		loc: &Option<ExprLocation>,108		args: &ArgsDesc,109		tailstrict: bool,110	) -> Result<Val> {111		match self {112			FuncVal::Normal(func) => {113				let ctx = parse_function_call(114					call_ctx,115					Some(func.ctx.clone()),116					&func.params,117					args,118					tailstrict,119				)?;120				evaluate(ctx, &func.body)121			}122			FuncVal::Intristic(ns, name) => call_builtin(call_ctx, loc, &ns, &name, args),123			FuncVal::NativeExt(_name, handler) => {124				let args = parse_function_call(call_ctx, None, &handler.params, args, true)?;125				let mut out_args = Vec::with_capacity(handler.params.len());126				for p in handler.params.0.iter() {127					out_args.push(args.binding(p.0.clone())?.evaluate()?);128				}129				Ok(handler.call(&out_args)?)130			}131		}132	}133134	pub fn evaluate_map(135		&self,136		call_ctx: Context,137		args: &HashMap<Rc<str>, Val>,138		tailstrict: bool,139	) -> Result<Val> {140		match self {141			FuncVal::Normal(func) => {142				let ctx = parse_function_call_map(143					call_ctx,144					Some(func.ctx.clone()),145					&func.params,146					args,147					tailstrict,148				)?;149				evaluate(ctx, &func.body)150			}151			FuncVal::Intristic(_, _) => todo!(),152			FuncVal::NativeExt(_, _) => todo!(),153		}154	}155156	pub fn evaluate_values(&self, call_ctx: Context, args: &[Val]) -> Result<Val> {157		match self {158			FuncVal::Normal(func) => {159				let ctx = place_args(call_ctx, Some(func.ctx.clone()), &func.params, args)?;160				evaluate(ctx, &func.body)161			}162			FuncVal::Intristic(_, _) => todo!(),163			FuncVal::NativeExt(_, _) => todo!(),164		}165	}166}167168#[derive(Debug, Clone, Copy, PartialEq)]169pub enum ValType {170	Bool,171	Null,172	Str,173	Num,174	Arr,175	Obj,176	Func,177}178impl ValType {179	pub fn name(&self) -> &'static str {180		use ValType::*;181		match self {182			Bool => "boolean",183			Null => "null",184			Str => "string",185			Num => "number",186			Arr => "array",187			Obj => "object",188			Func => "function",189		}190	}191}192impl Display for ValType {193	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {194		write!(f, "{}", self.name())195	}196}197198#[derive(Clone)]199pub enum ManifestFormat {200	YamlStream(Box<ManifestFormat>),201	Yaml(usize),202	Json(usize),203	String,204}205206#[derive(Debug, Clone)]207pub enum Val {208	Bool(bool),209	Null,210	Str(Rc<str>),211	Num(f64),212	Lazy(LazyVal),213	Arr(Rc<Vec<Val>>),214	Obj(ObjValue),215	Func(FuncVal),216}217macro_rules! matches_unwrap {218	($e: expr, $p: pat, $r: expr) => {219		match $e {220			$p => $r,221			_ => panic!("no match"),222			}223	};224}225impl Val {226	/// Creates Val::Num after checking for overflow. As numbers are f64, we can just check for finity227	pub fn new_checked_num(num: f64) -> Result<Val> {228		if num.is_finite() {229			Ok(Val::Num(num))230		} else {231			throw!(RuntimeError("overflow".into()))232		}233	}234235	pub fn assert_type(&self, context: &'static str, val_type: ValType) -> Result<()> {236		let this_type = self.value_type()?;237		if this_type != val_type {238			throw!(TypeMismatch(context, vec![val_type], this_type))239		} else {240			Ok(())241		}242	}243	pub fn try_cast_bool(self, context: &'static str) -> Result<bool> {244		self.assert_type(context, ValType::Bool)?;245		Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Bool(v), v))246	}247	pub fn try_cast_str(self, context: &'static str) -> Result<Rc<str>> {248		self.assert_type(context, ValType::Str)?;249		Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Str(v), v))250	}251	pub fn try_cast_num(self, context: &'static str) -> Result<f64> {252		self.assert_type(context, ValType::Num)?;253		Ok(matches_unwrap!(self.unwrap_if_lazy()?, Val::Num(v), v))254	}255	pub fn inplace_unwrap(&mut self) -> Result<()> {256		while let Val::Lazy(lazy) = self {257			*self = lazy.evaluate()?;258		}259		Ok(())260	}261	pub fn unwrap_if_lazy(&self) -> Result<Self> {262		Ok(if let Val::Lazy(v) = self {263			v.evaluate()?.unwrap_if_lazy()?264		} else {265			self.clone()266		})267	}268	pub fn value_type(&self) -> Result<ValType> {269		Ok(match self {270			Val::Str(..) => ValType::Str,271			Val::Num(..) => ValType::Num,272			Val::Arr(..) => ValType::Arr,273			Val::Obj(..) => ValType::Obj,274			Val::Bool(_) => ValType::Bool,275			Val::Null => ValType::Null,276			Val::Func(..) => ValType::Func,277			Val::Lazy(_) => self.clone().unwrap_if_lazy()?.value_type()?,278		})279	}280281	pub fn to_string(&self) -> Result<Rc<str>> {282		Ok(match self.unwrap_if_lazy()? {283			Val::Bool(true) => "true".into(),284			Val::Bool(false) => "false".into(),285			Val::Null => "null".into(),286			Val::Str(s) => s,287			v => manifest_json_ex(288				&v,289				&ManifestJsonOptions {290					padding: &"",291					mtype: ManifestType::ToString,292				},293			)?294			.into(),295		})296	}297298	/// Expects value to be object, outputs (key, manifested value) pairs299	pub fn manifest_multi(&self, ty: &ManifestFormat) -> Result<Vec<(Rc<str>, Rc<str>)>> {300		let obj = match self {301			Val::Obj(obj) => obj,302			_ => throw!(MultiManifestOutputIsNotAObject),303		};304		let keys = obj.visible_fields();305		let mut out = Vec::with_capacity(keys.len());306		for key in keys {307			let value = obj308				.get(key.clone())?309				.expect("item in object")310				.manifest(ty)?;311			out.push((key, value));312		}313		Ok(out)314	}315316	/// Expects value to be array, outputs manifested values317	pub fn manifest_stream(&self, ty: &ManifestFormat) -> Result<Vec<Rc<str>>> {318		let arr = match self {319			Val::Arr(a) => a,320			_ => throw!(StreamManifestOutputIsNotAArray),321		};322		let mut out = Vec::with_capacity(arr.len());323		for i in arr.iter() {324			out.push(i.manifest(ty)?);325		}326		Ok(out)327	}328329	pub fn manifest(&self, ty: &ManifestFormat) -> Result<Rc<str>> {330		Ok(match ty {331			ManifestFormat::YamlStream(format) => {332				let arr = match self {333					Val::Arr(a) => a,334					_ => throw!(StreamManifestOutputIsNotAArray),335				};336				let mut out = String::new();337338				match format as &ManifestFormat {339					ManifestFormat::YamlStream(_) => throw!(StreamManifestOutputCannotBeRecursed),340					ManifestFormat::String => throw!(StreamManifestCannotNestString),341					_ => {}342				};343344				if !arr.is_empty() {345					for v in arr.iter() {346						out.push_str("---\n");347						out.push_str(&v.manifest(format)?);348						out.push_str("\n");349					}350					out.push_str("...");351				}352353				out.into()354			}355			ManifestFormat::Yaml(padding) => self.to_yaml(*padding)?,356			ManifestFormat::Json(padding) => self.to_json(*padding)?,357			ManifestFormat::String => match self {358				Val::Str(s) => s.clone(),359				_ => throw!(StringManifestOutputIsNotAString),360			},361		})362	}363364	/// For manifestification365	pub fn to_json(&self, padding: usize) -> Result<Rc<str>> {366		manifest_json_ex(367			self,368			&ManifestJsonOptions {369				padding: &" ".repeat(padding),370				mtype: if padding == 0 {371					ManifestType::Minify372				} else {373					ManifestType::Manifest374				},375			},376		)377		.map(|s| s.into())378	}379380	/// Calls std.manifestJson381	#[cfg(feature = "faster")]382	pub fn to_std_json(&self, padding: usize) -> Result<Rc<str>> {383		manifest_json_ex(384			&self,385			&ManifestJsonOptions {386				padding: &" ".repeat(padding),387				mtype: ManifestType::Std,388			},389		)390		.map(|s| s.into())391	}392393	/// Calls std.manifestJson394	#[cfg(not(feature = "faster"))]395	pub fn to_std_json(&self, padding: usize) -> Result<Rc<str>> {396		with_state(|s| {397			let ctx = s398				.create_default_context()?399				.with_var("__tmp__to_json__".into(), self.clone())?;400			Ok(evaluate(401				ctx,402				&el!(Expr::Apply(403					el!(Expr::Index(404						el!(Expr::Var("std".into())),405						el!(Expr::Str("manifestJsonEx".into()))406					)),407					ArgsDesc(vec![408						Arg(None, el!(Expr::Var("__tmp__to_json__".into()))),409						Arg(None, el!(Expr::Str(" ".repeat(padding).into())))410					]),411					false412				)),413			)?414			.try_cast_str("to json")?)415		})416	}417	pub fn to_yaml(&self, padding: usize) -> Result<Rc<str>> {418		with_state(|s| {419			let ctx = s420				.create_default_context()?421				.with_var("__tmp__to_json__".into(), self.clone());422			Ok(evaluate(423				ctx,424				&el!(Expr::Apply(425					el!(Expr::Index(426						el!(Expr::Var("std".into())),427						el!(Expr::Str("manifestYamlDoc".into()))428					)),429					ArgsDesc(vec![430						Arg(None, el!(Expr::Var("__tmp__to_json__".into()))),431						Arg(None, el!(Expr::Str(" ".repeat(padding).into())))432					]),433					false434				)),435			)?436			.try_cast_str("to json")?)437		})438	}439}440441fn is_function_like(val: &Val) -> bool {442	matches!(val, Val::Func(_))443}444445/// Implements std.primitiveEquals builtin446pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {447	Ok(match (val_a.unwrap_if_lazy()?, val_b.unwrap_if_lazy()?) {448		(Val::Bool(a), Val::Bool(b)) => a == b,449		(Val::Null, Val::Null) => true,450		(Val::Str(a), Val::Str(b)) => a == b,451		(Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,452		(Val::Arr(_), Val::Arr(_)) => throw!(RuntimeError(453			"primitiveEquals operates on primitive types, got array".into(),454		)),455		(Val::Obj(_), Val::Obj(_)) => throw!(RuntimeError(456			"primitiveEquals operates on primitive types, got object".into(),457		)),458		(a, b) if is_function_like(&a) && is_function_like(&b) => {459			throw!(RuntimeError("cannot test equality of functions".into()))460		}461		(_, _) => false,462	})463}464465/// Native implementation of std.equals466pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {467	let val_a = val_a.unwrap_if_lazy()?;468	let val_b = val_b.unwrap_if_lazy()?;469470	if val_a.value_type()? != val_b.value_type()? {471		return Ok(false);472	}473	match (val_a, val_b) {474		// Cant test for ptr equality, because all fields needs to be evaluated475		(Val::Arr(a), Val::Arr(b)) => {476			if a.len() != b.len() {477				return Ok(false);478			}479			for (a, b) in a.iter().zip(b.iter()) {480				if !equals(&a.unwrap_if_lazy()?, &b.unwrap_if_lazy()?)? {481					return Ok(false);482				}483			}484			Ok(true)485		}486		(Val::Obj(a), Val::Obj(b)) => {487			let fields = a.visible_fields();488			if fields != b.visible_fields() {489				return Ok(false);490			}491			for field in fields {492				if !equals(&a.get(field.clone())?.unwrap(), &b.get(field)?.unwrap())? {493					return Ok(false);494				}495			}496			Ok(true)497		}498		(a, b) => Ok(primitive_equals(&a, &b)?),499	}500}