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

difftreelog

perf(evaluator) faster std.equals

Лач2020-07-02parent: #5c0b9a1.patch.diff
in: master

4 files changed

modifiedcrates/jrsonnet-evaluator/build.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/build.rs
+++ b/crates/jrsonnet-evaluator/build.rs
@@ -35,7 +35,7 @@
 								Member::Field(FieldMember {
 									name: FieldName::Fixed(name),
 									..
-								}) if **name == *"join" || **name == *"manifestJsonEx" || **name == *"escapeStringJson"
+								}) if **name == *"join" || **name == *"manifestJsonEx" || **name == *"escapeStringJson" || **name == *"equals"
 							)
 						})
 						.collect(),
modifiedcrates/jrsonnet-evaluator/src/evaluate.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/evaluate.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate.rs
@@ -484,7 +484,14 @@
 				0, a, vec![];
 				1, b, vec![];
 			], {
-				Val::Bool(a == b)
+				Val::Bool(primitive_equals(&a, &b)?)
+			}),
+			// faster
+			("std", "equals") => parse_args!(context, "std.equals", args, 2, [
+				0, a, vec![];
+				1, b, vec![];
+			], {
+				Val::Bool(equals(&a, &b)?)
 			}),
 			("std", "modulo") => parse_args!(context, "std.modulo", args, 2, [
 				0, a: [Val::Num]!!Val::Num, vec![ValType::Num];
modifiedcrates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth
380#[cfg(test)]380#[cfg(test)]
381pub mod tests {381pub mod tests {
382 use super::Val;382 use super::Val;
383 use crate::EvaluationState;383 use crate::{create_error, EvaluationState, primitive_equals};
384 use jrsonnet_parser::*;384 use jrsonnet_parser::*;
385 use std::{path::PathBuf, rc::Rc};385 use std::{path::PathBuf, rc::Rc};
386386
411 fn eval_state_standard() {411 fn eval_state_standard() {
412 let state = EvaluationState::default();412 let state = EvaluationState::default();
413 state.with_stdlib();413 state.with_stdlib();
414 assert_eq!(414 assert!(
415 primitive_equals(
415 state416 &state.parse_evaluate_raw(r#"std.assertEqual(std.base64("test"), "dGVzdA==")"#).unwrap(),
416 .parse_evaluate_raw(r#"std.assertEqual(std.base64("test"), "dGVzdA==")"#)
417 .unwrap(),
418 Val::Bool(true)417 &Val::Bool(true),
418 ).unwrap()
419 );419 );
420 }420 }
421421
445 /// Asserts given code returns `true`445 /// Asserts given code returns `true`
446 macro_rules! assert_eval {446 macro_rules! assert_eval {
447 ($str: expr) => {447 ($str: expr) => {
448 assert_eq!(eval!($str), Val::Bool(true))448 assert!(primitive_equals(&eval!($str), &Val::Bool(true)).unwrap())
449 };449 };
450 }450 }
451451
452 /// Asserts given code returns `false`452 /// Asserts given code returns `false`
453 macro_rules! assert_eval_neg {453 macro_rules! assert_eval_neg {
454 ($str: expr) => {454 ($str: expr) => {
455 assert_eq!(eval!($str), Val::Bool(false))455 assert!(primitive_equals(&eval!($str), &Val::Bool(false)).unwrap())
456 };456 };
457 }457 }
458 macro_rules! assert_json {458 macro_rules! assert_json {
663663
664 #[test]664 #[test]
665 fn string_is_string() {665 fn string_is_string() {
666 assert_eq!(666 assert!(
667 primitive_equals(
667 eval!("local arr = 'hello'; (!std.isArray(arr)) && (!std.isString(arr))"),668 &eval!("local arr = 'hello'; (!std.isArray(arr)) && (!std.isString(arr))"),
668 Val::Bool(false)669 &Val::Bool(false),
670 ).unwrap()
669 );671 );
670 }672 }
671673
768 })770 })
769 }771 }
772
773 #[test]
774 fn equality(){
775 println!("{:?}", jrsonnet_parser::parse("{ x: 1, y: 2 } == { x: 1, y: 2 }", &ParserSettings::default()));
776 assert_eval!("{ x: 1, y: 2 } == { x: 1, y: 2 }")
777 }
770}778}
771779
modifiedcrates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -111,7 +111,7 @@
 	}
 }
 
-#[derive(Debug, PartialEq, Clone)]
+#[derive(Debug, Clone)]
 pub enum Val {
 	Bool(bool),
 	Null,
@@ -212,6 +212,67 @@
 	}
 }
 
+fn is_function_like(val: &Val) -> bool {
+	matches!(val, Val::Func(_) | Val::Intristic(_, _))
+}
+
+/// Implements std.primitiveEquals builtin
+pub fn primitive_equals(val_a: &Val, val_b: &Val) -> Result<bool> {
+	Ok(match (val_a.unwrap_if_lazy()?, val_b.unwrap_if_lazy()?) {
+		(Val::Bool(a), Val::Bool(b)) => a == b,
+		(Val::Null, Val::Null) => true,
+		(Val::Str(a), Val::Str(b)) => a == b,
+		(Val::Num(a), Val::Num(b)) => (a - b).abs() <= f64::EPSILON,
+		(Val::Arr(_), Val::Arr(_)) => create_error_result(Error::RuntimeError(
+			"primitiveEquals operates on primitive types, got array".into(),
+		))?,
+		(Val::Obj(_), Val::Obj(_)) => create_error_result(Error::RuntimeError(
+			"primitiveEquals operates on primitive types, got object".into(),
+		))?,
+		(a, b) if is_function_like(&a) && is_function_like(&b) => create_error_result(
+			Error::RuntimeError("cannot test equality of functions".into()),
+		)?,
+		(_, _) => false,
+	})
+}
+
+/// Native implementation of std.equals
+pub fn equals(val_a: &Val, val_b: &Val) -> Result<bool> {
+	let val_a = val_a.unwrap_if_lazy()?;
+	let val_b = val_b.unwrap_if_lazy()?;
+
+	if val_a.value_type()? != val_b.value_type()? {
+		return Ok(false);
+	}
+	match (val_a, val_b) {
+		// Cant test for ptr equality, because all fields needs to be evaluated
+		(Val::Arr(a), Val::Arr(b)) => {
+			if a.len() != b.len() {
+				return Ok(false);
+			}
+			for (a, b) in a.iter().zip(b.iter()) {
+				if !equals(&a.unwrap_if_lazy()?, &b.unwrap_if_lazy()?)? {
+					return Ok(false);
+				}
+			}
+			Ok(true)
+		}
+		(Val::Obj(a), Val::Obj(b)) => {
+			let fields = a.visible_fields();
+			if fields != b.visible_fields() {
+				return Ok(false);
+			}
+			for field in fields {
+				if !equals(&a.get(field.clone())?.unwrap(), &b.get(field)?.unwrap())? {
+					return Ok(false);
+				}
+			}
+			Ok(true)
+		}
+		(a, b) => Ok(primitive_equals(&a, &b)?),
+	}
+}
+
 pub fn manifest_json_ex(val: &Val, padding: &str) -> Result<String> {
 	let mut out = String::new();
 	manifest_json_ex_buf(val, &mut out, padding, &mut String::new())?;