git.delta.rocks / jrsonnet / refs/commits / 8d448f754f46

difftreelog

feat(evaluator) propogate EvaluationState

Лач2020-06-01parent: #1450eba.patch.diff
in: master

4 files changed

addedcrates/jsonnet-evaluator/src/error.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jsonnet-evaluator/src/error.rs
@@ -0,0 +1 @@
+pub enum Error {}
modifiedcrates/jsonnet-evaluator/src/evaluate.rsdiffbeforeafterboth
--- a/crates/jsonnet-evaluator/src/evaluate.rs
+++ b/crates/jsonnet-evaluator/src/evaluate.rs
@@ -1,7 +1,7 @@
 use crate::{
 	binding, bool_val, context_creator, function_default, function_rhs, future_wrapper,
-	lazy_binding, lazy_val, Context, ContextCreator, FuncDesc, LazyBinding, ObjMember, ObjValue,
-	Val,
+	lazy_binding, lazy_val, Context, ContextCreator, EvaluationState, FuncDesc, LazyBinding,
+	ObjMember, ObjValue, Val,
 };
 use closure::closure;
 use jsonnet_parser::{
@@ -13,15 +13,20 @@
 	rc::Rc,
 };
 
-pub fn evaluate_binding(b: &BindSpec, context_creator: ContextCreator) -> (String, LazyBinding) {
+pub fn evaluate_binding(
+	eval_state: EvaluationState,
+	b: &BindSpec,
+	context_creator: ContextCreator,
+) -> (String, LazyBinding) {
 	let b = b.clone();
 	if let Some(args) = &b.params {
 		let args = args.clone();
 		(
 			b.name.clone(),
 			lazy_binding!(move |this, super_obj| lazy_val!(
-				closure!(clone b, clone args, clone context_creator, || evaluate_method(
+				closure!(clone b, clone args, clone context_creator, clone eval_state, || evaluate_method(
 					context_creator.0(this.clone(), super_obj.clone()),
+					eval_state.clone(),
 					&b.value,
 					args.clone()
 				))
@@ -31,29 +36,45 @@
 		(
 			b.name.clone(),
 			lazy_binding!(move |this, super_obj| {
-				lazy_val!(closure!(clone context_creator, clone b, || evaluate(
-					context_creator.0(this.clone(), super_obj.clone()),
-					&b.value
-				)))
+				lazy_val!(
+					closure!(clone context_creator, clone b, clone eval_state, || evaluate(
+						context_creator.0(this.clone(), super_obj.clone()),
+						eval_state.clone(),
+						&b.value
+					))
+				)
 			}),
 		)
 	}
 }
 
-pub fn evaluate_method(ctx: Context, expr: &LocExpr, arg_spec: ParamsDesc) -> Val {
+pub fn evaluate_method(
+	ctx: Context,
+	eval_state: EvaluationState,
+	expr: &LocExpr,
+	arg_spec: ParamsDesc,
+) -> Val {
 	Val::Func(FuncDesc {
 		ctx,
 		params: arg_spec,
-		eval_rhs: function_rhs!(closure!(clone expr, |ctx| evaluate(ctx, &expr))),
-		eval_default: function_default!(|ctx, default| evaluate(ctx, &default)),
+		eval_rhs: function_rhs!(
+			closure!(clone expr, clone eval_state, |ctx| evaluate(ctx, eval_state.clone(), &expr))
+		),
+		eval_default: function_default!(
+			closure!(clone eval_state, |ctx, default| evaluate(ctx, eval_state.clone(), &default))
+		),
 	})
 }
 
-pub fn evaluate_field_name(context: Context, field_name: &jsonnet_parser::FieldName) -> String {
+pub fn evaluate_field_name(
+	context: Context,
+	eval_state: EvaluationState,
+	field_name: &jsonnet_parser::FieldName,
+) -> String {
 	match field_name {
 		jsonnet_parser::FieldName::Fixed(n) => n.clone(),
 		jsonnet_parser::FieldName::Dyn(expr) => {
-			let name = evaluate(context, expr).unwrap_if_lazy();
+			let name = evaluate(context, eval_state, expr).unwrap_if_lazy();
 			match name {
 				Val::Str(n) => n,
 				_ => panic!(
@@ -132,7 +153,7 @@
 future_wrapper!(ObjValue, FutureObjValue);
 
 // TODO: Asserts
-pub fn evaluate_object(context: Context, object: ObjBody) -> ObjValue {
+pub fn evaluate_object(context: Context, eval_state: EvaluationState, object: ObjBody) -> ObjValue {
 	match object {
 		ObjBody::MemberList(members) => {
 			let new_bindings = FutureNewBindings::new();
@@ -155,7 +176,7 @@
 						Member::BindStmt(b) => Some(b.clone()),
 						_ => None,
 					})
-					.map(|b| evaluate_binding(&b, context_creator.clone()))
+					.map(|b| evaluate_binding(eval_state.clone(), &b, context_creator.clone()))
 				{
 					bindings.insert(n, b);
 				}
@@ -172,18 +193,19 @@
 						visibility,
 						value,
 					}) => {
-						let name = evaluate_field_name(context.clone(), &name);
+						let name = evaluate_field_name(context.clone(), eval_state.clone(), &name);
 						new_members.insert(
 							name,
 							ObjMember {
 								add: plus,
 								visibility: visibility.clone(),
 								invoke: binding!(
-									closure!(clone value, clone context_creator, |this, super_obj| {
+									closure!(clone value, clone context_creator, clone eval_state, |this, super_obj| {
 										let context = context_creator.0(this, super_obj);
 										// TODO: Assert
 										evaluate(
 											context,
+											eval_state.clone(),
 											&value,
 										).unwrap_if_lazy()
 									})
@@ -197,17 +219,18 @@
 						value,
 						..
 					}) => {
-						let name = evaluate_field_name(context.clone(), &name);
+						let name = evaluate_field_name(context.clone(), eval_state.clone(), &name);
 						new_members.insert(
 							name,
 							ObjMember {
 								add: false,
 								visibility: Visibility::Hidden,
 								invoke: binding!(
-									closure!(clone value, clone context_creator, |this, super_obj| {
+									closure!(clone value, clone context_creator, clone eval_state, |this, super_obj| {
 										// TODO: Assert
 										evaluate_method(
 											context_creator.0(this, super_obj),
+											eval_state.clone(),
 											&value.clone(),
 											params.clone(),
 										)
@@ -226,165 +249,171 @@
 	}
 }
 
-pub fn evaluate(context: Context, expr: &LocExpr) -> Val {
+pub fn evaluate(context: Context, eval_state: EvaluationState, expr: &LocExpr) -> Val {
+	println!("===");
+	eval_state.print_stack_trace();
 	use Expr::*;
-	let LocExpr(expr, loc) = expr;
-	match &**expr {
-		Literal(LiteralType::This) => Val::Obj(
-			context
-				.this()
-				.clone()
-				.unwrap_or_else(|| panic!("this not found")),
-		),
-		Literal(LiteralType::Super) => Val::Obj(
-			context
-				.super_obj()
-				.clone()
-				.unwrap_or_else(|| panic!("super not found")),
-		),
-		Literal(LiteralType::True) => Val::Bool(true),
-		Literal(LiteralType::False) => Val::Bool(false),
-		Literal(LiteralType::Null) => Val::Null,
-		Parened(e) => evaluate(context, e),
-		Str(v) => Val::Str(v.clone()),
-		Num(v) => Val::Num(*v),
-		BinaryOp(v1, o, v2) => {
-			evaluate_binary_op(&evaluate(context.clone(), v1), *o, &evaluate(context, v2))
-		}
-		UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(context, v)),
-		Var(name) => Val::Lazy(context.binding(&name)).unwrap_if_lazy(),
-		Index(value, index) => {
-			match (
-				evaluate(context.clone(), value).unwrap_if_lazy(),
-				evaluate(context.clone(), index),
-			) {
-				(Val::Obj(v), Val::Str(s)) => v
-					.get(&s)
-					.unwrap_or_else(closure!(clone context, || {
-						if let Some(n) = v.get("__intristic_namespace__") {
-							if let Val::Str(n) = n.unwrap_if_lazy() {
-								Val::Intristic(n, s)
+	eval_state.clone().push(expr.clone(), "expr".to_owned(), || {
+		let LocExpr(expr, loc) = expr;
+		match &**expr {
+			Literal(LiteralType::This) => Val::Obj(
+				context
+					.this()
+					.clone()
+					.unwrap_or_else(|| panic!("this not found")),
+			),
+			Literal(LiteralType::Super) => Val::Obj(
+				context
+					.super_obj()
+					.clone()
+					.unwrap_or_else(|| panic!("super not found")),
+			),
+			Literal(LiteralType::True) => Val::Bool(true),
+			Literal(LiteralType::False) => Val::Bool(false),
+			Literal(LiteralType::Null) => Val::Null,
+			Parened(e) => evaluate(context, eval_state.clone(), e),
+			Str(v) => Val::Str(v.clone()),
+			Num(v) => Val::Num(*v),
+			BinaryOp(v1, o, v2) => evaluate_binary_op(
+				&evaluate(context.clone(), eval_state.clone(), v1),
+				*o,
+				&evaluate(context, eval_state.clone(), v2),
+			),
+			UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(context, eval_state, v)),
+			Var(name) => Val::Lazy(context.binding(&name)).unwrap_if_lazy(),
+			Index(value, index) => {
+				match (
+					evaluate(context.clone(), eval_state.clone(), value).unwrap_if_lazy(),
+					evaluate(context.clone(), eval_state.clone(), index),
+				) {
+					(Val::Obj(v), Val::Str(s)) => v
+						.get(&s)
+						.unwrap_or_else(closure!(clone context, || {
+							if let Some(n) = v.get("__intristic_namespace__") {
+								if let Val::Str(n) = n.unwrap_if_lazy() {
+									Val::Intristic(n, s)
+								} else {
+									panic!("__intristic_namespace__ should be string");
+								}
 							} else {
-								panic!("__intristic_namespace__ should be string");
+								panic!("{} not found in {:?}", s, v)
 							}
-						} else {
-							panic!("{} not found in {:?}", s, v)
-						}
-					}))
-					.unwrap_if_lazy(),
-				(Val::Arr(v), Val::Num(n)) => v
-					.get(n as usize)
-					.unwrap_or_else(|| panic!("out of bounds"))
-					.clone(),
-				(Val::Str(s), Val::Num(n)) => {
-					Val::Str(s.chars().skip(n as usize).take(1).collect())
+						}))
+						.unwrap_if_lazy(),
+					(Val::Arr(v), Val::Num(n)) => v
+						.get(n as usize)
+						.unwrap_or_else(|| panic!("out of bounds"))
+						.clone(),
+					(Val::Str(s), Val::Num(n)) => {
+						Val::Str(s.chars().skip(n as usize).take(1).collect())
+					}
+					(v, i) => todo!("not implemented: {:?}[{:?}]", v, i.unwrap_if_lazy()),
 				}
-				(v, i) => todo!("not implemented: {:?}[{:?}]", v, i.unwrap_if_lazy()),
 			}
-		}
-		LocalExpr(bindings, returned) => {
-			let mut new_bindings: HashMap<String, LazyBinding> = HashMap::new();
-			let future_context = Context::new_future();
+			LocalExpr(bindings, returned) => {
+				let mut new_bindings: HashMap<String, LazyBinding> = HashMap::new();
+				let future_context = Context::new_future();
 
-			let context_creator = context_creator!(
-				closure!(clone future_context, |_, _| future_context.clone().unwrap())
-			);
+				let context_creator = context_creator!(
+					closure!(clone future_context, |_, _| future_context.clone().unwrap())
+				);
 
-			for (k, v) in bindings
-				.iter()
-				.map(move |b| evaluate_binding(b, context_creator.clone()))
-			{
-				new_bindings.insert(k, v);
-			}
+				for (k, v) in bindings
+					.iter()
+					.map(|b| evaluate_binding(eval_state.clone(), b, context_creator.clone()))
+				{
+					new_bindings.insert(k, v);
+				}
 
-			let context = context
-				.extend(new_bindings, None, None, None)
-				.into_future(future_context);
-			evaluate(context, &returned.clone())
-		}
-		Obj(body) => Val::Obj(evaluate_object(context, body.clone())),
-		Apply(value, ArgsDesc(args)) => {
-			let value = evaluate(context.clone(), value).unwrap_if_lazy();
-			match value {
-				// TODO: Capture context of application
-				Val::Intristic(ns, name) => match (&ns as &str, &name as &str) {
-					("std", "length") => {
-						assert_eq!(args.len(), 1);
-						let expr = &args.get(0).unwrap().1;
-						match evaluate(context, expr) {
-							Val::Str(n) => Val::Num(n.chars().count() as f64),
-							Val::Arr(i) => Val::Num(i.len() as f64),
-							v => panic!("can't get length of {:?}", v),
+				let context = context
+					.extend(new_bindings, None, None, None)
+					.into_future(future_context);
+				evaluate(context, eval_state.clone(), &returned.clone())
+			}
+			Obj(body) => Val::Obj(evaluate_object(context, eval_state, body.clone())),
+			Apply(value, ArgsDesc(args)) => {
+				let value = evaluate(context.clone(), eval_state.clone(), value).unwrap_if_lazy();
+				match value {
+					// TODO: Capture context of application
+					Val::Intristic(ns, name) => match (&ns as &str, &name as &str) {
+						("std", "length") => {
+							assert_eq!(args.len(), 1);
+							let expr = &args.get(0).unwrap().1;
+							match evaluate(context, eval_state.clone(), expr) {
+								Val::Str(n) => Val::Num(n.chars().count() as f64),
+								Val::Arr(i) => Val::Num(i.len() as f64),
+								v => panic!("can't get length of {:?}", v),
+							}
+						}
+						("std", "type") => {
+							assert_eq!(args.len(), 1);
+							let expr = &args.get(0).unwrap().1;
+							Val::Str(evaluate(context, eval_state, expr).type_of().to_owned())
 						}
-					}
-					("std", "type") => {
-						assert_eq!(args.len(), 1);
-						let expr = &args.get(0).unwrap().1;
-						Val::Str(evaluate(context, expr).type_of().to_owned())
-					}
-					("std", "makeArray") => {
-						assert_eq!(args.len(), 2);
-						if let (Val::Num(v), Val::Func(d)) = (
-							evaluate(context.clone(), &args[0].1),
-							evaluate(context, &args[1].1),
-						) {
-							assert!(v > 0.0);
-							let mut out = Vec::with_capacity(v as usize);
-							for i in 0..v as usize {
-								out.push(d.evaluate(vec![(None, Val::Num(i as f64))]))
+						("std", "makeArray") => {
+							assert_eq!(args.len(), 2);
+							if let (Val::Num(v), Val::Func(d)) = (
+								evaluate(context.clone(), eval_state.clone(), &args[0].1),
+								evaluate(context, eval_state, &args[1].1),
+							) {
+								assert!(v > 0.0);
+								let mut out = Vec::with_capacity(v as usize);
+								for i in 0..v as usize {
+									out.push(d.evaluate(vec![(None, Val::Num(i as f64))]))
+								}
+								Val::Arr(out)
+							} else {
+								panic!("bad makeArray call");
 							}
-							Val::Arr(out)
-						} else {
-							panic!("bad makeArray call");
 						}
-					}
-					("std", "codepoint") => {
-						assert_eq!(args.len(), 1);
-						if let Val::Str(s) = evaluate(context, &args[0].1) {
-							assert!(
-								s.chars().count() == 1,
-								"std.codepoint should receive single char string"
-							);
-							Val::Num(s.chars().take(1).next().unwrap() as u32 as f64)
-						} else {
-							panic!("bad codepoint call");
+						("std", "codepoint") => {
+							assert_eq!(args.len(), 1);
+							if let Val::Str(s) = evaluate(context, eval_state, &args[0].1) {
+								assert!(
+									s.chars().count() == 1,
+									"std.codepoint should receive single char string"
+								);
+								Val::Num(s.chars().take(1).next().unwrap() as u32 as f64)
+							} else {
+								panic!("bad codepoint call");
+							}
 						}
-					}
-					(ns, name) => panic!("Intristic not found: {}.{}", ns, name),
+						(ns, name) => panic!("Intristic not found: {}.{}", ns, name),
+					},
+					Val::Func(f) => f.evaluate(
+						args.clone()
+							.into_iter()
+							.map(move |a| {
+								(
+									a.clone().0,
+									Val::Lazy(lazy_val!(
+										closure!(clone context, clone a, clone eval_state, || evaluate(context.clone(), eval_state.clone(), &a.clone().1))
+									)),
+								)
+							})
+							.collect(),
+					),
+					_ => panic!("{:?} is not a function", value),
+				}
+			}
+			Function(params, body) => evaluate_method(context, eval_state, body, params.clone()),
+			Error(e) => panic!("error: {}", evaluate(context, eval_state, e)),
+			IfElse {
+				cond,
+				cond_then,
+				cond_else,
+			} => match evaluate(context.clone(), eval_state.clone(), &cond.0).unwrap_if_lazy() {
+				Val::Bool(true) => evaluate(context, eval_state.clone(), cond_then),
+				Val::Bool(false) => match cond_else {
+					Some(v) => evaluate(context, eval_state, v),
+					None => Val::Bool(false),
 				},
-				Val::Func(f) => f.evaluate(
-					args.clone()
-						.into_iter()
-						.map(|a| {
-							(
-								a.clone().0,
-								Val::Lazy(lazy_val!(
-									closure!(clone context, clone a, || evaluate(context.clone(), &a.clone().1))
-								)),
-							)
-						})
-						.collect(),
-				),
-				_ => panic!("{:?} is not a function", value),
-			}
-		}
-		Function(params, body) => evaluate_method(context, body, params.clone()),
-		Error(e) => panic!("error: {}", evaluate(context, e)),
-		IfElse {
-			cond,
-			cond_then,
-			cond_else,
-		} => match evaluate(context.clone(), &cond.0).unwrap_if_lazy() {
-			Val::Bool(true) => evaluate(context, cond_then),
-			Val::Bool(false) => match cond_else {
-				Some(v) => evaluate(context, v),
-				None => Val::Bool(false),
+				v => panic!("if condition evaluated to {:?} (boolean needed instead)", v),
 			},
-			v => panic!("if condition evaluated to {:?} (boolean needed instead)", v),
-		},
-		_ => panic!(
-			"evaluation not implemented: {:?}",
-			LocExpr(expr.clone(), loc.clone())
-		),
-	}
+			_ => panic!(
+				"evaluation not implemented: {:?}",
+				LocExpr(expr.clone(), loc.clone())
+			),
+		}
+	})
 }
modifiedcrates/jsonnet-evaluator/src/lib.rsdiffbeforeafterboth
before · crates/jsonnet-evaluator/src/lib.rs
1#![feature(box_syntax, box_patterns)]2#![feature(type_alias_impl_trait)]3#![feature(debug_non_exhaustive)]4#![allow(macro_expanded_macro_exports_accessed_by_absolute_paths)]5mod ctx;6mod dynamic;7mod evaluate;8mod obj;9mod val;1011pub use ctx::*;12pub use dynamic::*;13pub use evaluate::*;14use jsonnet_parser::*;15pub use obj::*;16use std::{cell::RefCell, collections::HashMap, rc::Rc};17pub use val::*;1819rc_fn_helper!(20	Binding,21	binding,22	dyn Fn(Option<ObjValue>, Option<ObjValue>) -> Val23);24rc_fn_helper!(25	LazyBinding,26	lazy_binding,27	dyn Fn(Option<ObjValue>, Option<ObjValue>) -> LazyVal28);29rc_fn_helper!(FunctionRhs, function_rhs, dyn Fn(Context) -> Val);30rc_fn_helper!(31	FunctionDefault,32	function_default,33	dyn Fn(Context, LocExpr) -> Val34);3536pub struct ExitGuard<'s>(&'s EvaluationState);37impl<'s> Drop for ExitGuard<'s> {38	fn drop(&mut self) {39		self.0.stack.borrow_mut().pop();40	}41}4243pub struct EvaluationState {44	pub stack: Rc<RefCell<Vec<LocExpr>>>,45	pub files: Rc<RefCell<HashMap<String, String>>>,46}47impl EvaluationState {48	#[must_use = "should keep exit guard before exit from function"]49	pub fn push(&self, e: LocExpr) -> ExitGuard {50		self.stack.borrow_mut().push(e);51		ExitGuard(self)52	}53	pub fn print_stack_trace(&self) {54		for e in self55			.stack56			.borrow()57			.iter()58			.rev()59			.map(|e| e.1.clone())60			.flatten()61		{62			println!("{:?}", e)63		}64	}65}66impl Default for EvaluationState {67	fn default() -> Self {68		EvaluationState {69			stack: Rc::new(RefCell::new(Vec::new())),70			files: Rc::new(RefCell::new(HashMap::new())),71		}72	}73}7475#[cfg(test)]76pub mod tests {77	use super::{evaluate, Context, Val};78	use crate::EvaluationState;79	use jsonnet_parser::*;8081	#[test]82	fn eval_state_stacktrace() {83		let state = EvaluationState::default();84		let _v = state.push(loc_expr!(85			Expr::Num(0.0),86			true,87			("test.jsonnet".to_owned(), 10, 20)88		));8990		state.print_stack_trace()91	}9293	macro_rules! eval {94		($str: expr) => {95			evaluate(96				Context::new(),97				&parse(98					$str,99					&ParserSettings {100						loc_data: true,101						file_name: "test.jsonnet".to_owned(),102					},103					)104				.unwrap(),105				)106		};107	}108109	macro_rules! eval_stdlib {110		($str: expr) => {{111			let std = "local std = ".to_owned() + jsonnet_stdlib::STDLIB_STR + ";";112			evaluate(113				Context::new(),114				&parse(115					&(std + $str),116					&ParserSettings {117						loc_data: true,118						file_name: "test.jsonnet".to_owned(),119					},120					)121				.unwrap(),122				)123			}};124	}125126	macro_rules! assert_eval {127		($str: expr) => {128			assert_eq!(129				evaluate(130					Context::new(),131					&parse(132						$str,133						&ParserSettings {134							loc_data: true,135							file_name: "test.jsonnet".to_owned(),136						}137						)138					.unwrap()139					),140				Val::Bool(true)141				)142		};143	}144	macro_rules! assert_json {145		($str: expr, $out: expr) => {146			assert_eq!(147				format!(148					"{}",149					evaluate(150						Context::new(),151						&parse(152							$str,153							&ParserSettings {154								loc_data: true,155								file_name: "test.jsonnet".to_owned(),156							}157						)158						.unwrap()159						)160					),161				$out162				)163		};164	}165	macro_rules! assert_json_stdlib {166		($str: expr, $out: expr) => {167			assert_eq!(format!("{}", eval_stdlib!($str)), $out)168		};169	}170	macro_rules! assert_eval_neg {171		($str: expr) => {172			assert_eq!(173				evaluate(174					Context::new(),175					&parse(176						$str,177						&ParserSettings {178							loc_data: true,179							file_name: "test.jsonnet".to_owned(),180						}181						)182					.unwrap()183					),184				Val::Bool(false)185				)186		};187	}188189	/// Sanity checking, before trusting to another tests190	#[test]191	fn equality_operator() {192		assert_eval!("2 == 2");193		assert_eval_neg!("2 != 2");194		assert_eval!("2 != 3");195		assert_eval_neg!("2 == 3");196		assert_eval!("'Hello' == 'Hello'");197		assert_eval_neg!("'Hello' != 'Hello'");198		assert_eval!("'Hello' != 'World'");199		assert_eval_neg!("'Hello' == 'World'");200	}201202	#[test]203	fn math_evaluation() {204		assert_eval!("2 + 2 * 2 == 6");205		assert_eval!("3 + (2 + 2 * 2) == 9");206	}207208	#[test]209	fn string_concat() {210		assert_eval!("'Hello' + 'World' == 'HelloWorld'");211		assert_eval!("'Hello' * 3 == 'HelloHelloHello'");212		assert_eval!("'Hello' + 'World' * 3 == 'HelloWorldWorldWorld'");213	}214215	#[test]216	fn local() {217		assert_eval!("local a = 2; local b = 3; a + b == 5");218		assert_eval!("local a = 1, b = a + 1; a + b == 3");219		assert_eval!("local a = 1; local a = 2; a == 2");220	}221222	#[test]223	fn object_lazyness() {224		assert_json!("local a = {a:error 'test'}; {}", r#"{}"#);225	}226227	#[test]228	fn object_inheritance() {229		assert_json!("{a: self.b} + {b:3}", r#"{"a":3,"b":3}"#);230	}231232	#[test]233	fn test_object() {234		assert_json!("{a:2}", r#"{"a":2}"#);235		assert_json!("{a:2+2}", r#"{"a":4}"#);236		assert_json!("{a:2}+{b:2}", r#"{"a":2,"b":2}"#);237		assert_json!("{b:3}+{b:2}", r#"{"b":2}"#);238		assert_json!("{b:3}+{b+:2}", r#"{"b":5}"#);239		assert_json!("local test='a'; {[test]:2}", r#"{"a":2}"#);240		assert_json!(241			r#"242				{243					name: "Alice",244					welcome: "Hello " + self.name + "!",245				}246			"#,247			r#"{"name":"Alice","welcome":"Hello Alice!"}"#248		);249		assert_json!(250			r#"251				{252					name: "Alice",253					welcome: "Hello " + self.name + "!",254				} + {255					name: "Bob"256				}257			"#,258			r#"{"name":"Bob","welcome":"Hello Bob!"}"#259		);260	}261262	#[test]263	fn functions() {264		assert_json!(r#"local a = function(b, c = 2) b + c; a(2)"#, "4");265		assert_json!(266			r#"local a = function(b, c = "Dear") b + c + d, d = "World"; a("Hello")"#,267			r#""HelloDearWorld""#268		);269	}270271	#[test]272	fn local_methods() {273		assert_json!(r#"local a(b, c = 2) = b + c; a(2)"#, "4");274		assert_json!(275			r#"local a(b, c = "Dear") = b + c + d, d = "World"; a("Hello")"#,276			r#""HelloDearWorld""#277		);278	}279280	#[test]281	fn object_locals() {282		assert_json!(r#"{local a = 3, b: a}"#, r#"{"b":3}"#);283		assert_json!(r#"{local a = 3, local c = a, b: c}"#, r#"{"b":3}"#);284		assert_json!(285			r#"{local a = function (b) {[b]:4}, test: a("test")}"#,286			r#"{"test":{"test":4}}"#287		);288	}289290	#[test]291	fn direct_self() {292		println!(293			"{:#?}",294			eval!(295				r#"296					{297						local me = self,298						a: 3,299						b(): me.a,300					}301				"#302			)303		);304	}305306	#[test]307	fn indirect_self() {308		// `self` assigned to `me` was lost when being309		// referenced from field310		eval_stdlib!(311			r#"{312				local me = self,313				a: 3,314				b: me.a,315			}.b"#316		);317	}318319	// We can't trust other tests (And official jsonnet testsuite), if assert is not working correctly320	#[test]321	fn std_assert_ok() {322		eval_stdlib!("std.assertEqual(4.5 << 2, 16)");323	}324325	#[test]326	#[should_panic]327	fn std_assert_failure() {328		eval_stdlib!("std.assertEqual(4.5 << 2, 15)");329	}330331	#[test]332	fn string_is_string() {333		assert_eq!(334			eval_stdlib!("local arr = 'hello'; (!std.isArray(arr)) && (!std.isString(arr))"),335			Val::Bool(false)336		);337	}338339	#[test]340	fn base64_works() {341		assert_json_stdlib!(r#"std.base64("test")"#, r#""dGVzdA==""#);342	}343344	#[test]345	fn utf8_chars() {346		assert_json_stdlib!(347			r#"local c="😎";{c:std.codepoint(c),l:std.length(c)}"#,348			r#"{"c":128526,"l":1}"#349		)350	}351352	#[test]353	fn json() {354		println!("{:?}", eval_stdlib!(r#"std.manifestJson({a:3, b:4, c:6})"#));355	}356357	#[test]358	fn sjsonnet() {359		eval!(360			r#"361			local x0 = {k: 1};362			local x1 = {k: x0.k + x0.k};363			local x2 = {k: x1.k + x1.k};364			local x3 = {k: x2.k + x2.k};365			local x4 = {k: x3.k + x3.k};366			local x5 = {k: x4.k + x4.k};367			local x6 = {k: x5.k + x5.k};368			local x7 = {k: x6.k + x6.k};369			local x8 = {k: x7.k + x7.k};370			local x9 = {k: x8.k + x8.k};371			local x10 = {k: x9.k + x9.k};372			local x11 = {k: x10.k + x10.k};373			local x12 = {k: x11.k + x11.k};374			local x13 = {k: x12.k + x12.k};375			local x14 = {k: x13.k + x13.k};376			local x15 = {k: x14.k + x14.k};377			local x16 = {k: x15.k + x15.k};378			local x17 = {k: x16.k + x16.k};379			local x18 = {k: x17.k + x17.k};380			local x19 = {k: x18.k + x18.k};381			local x20 = {k: x19.k + x19.k};382			local x21 = {k: x20.k + x20.k};383			x21.k384		"#385		);386	}387}
after · crates/jsonnet-evaluator/src/lib.rs
1#![feature(box_syntax, box_patterns)]2#![feature(type_alias_impl_trait)]3#![feature(debug_non_exhaustive)]4#![allow(macro_expanded_macro_exports_accessed_by_absolute_paths)]5mod ctx;6mod dynamic;7mod error;8mod evaluate;9mod obj;10mod val;1112pub use ctx::*;13pub use dynamic::*;14pub use error::*;15pub use evaluate::*;16use jsonnet_parser::*;17pub use obj::*;18use std::{cell::RefCell, collections::HashMap, rc::Rc};19pub use val::*;2021rc_fn_helper!(22	Binding,23	binding,24	dyn Fn(Option<ObjValue>, Option<ObjValue>) -> Val25);26rc_fn_helper!(27	LazyBinding,28	lazy_binding,29	dyn Fn(Option<ObjValue>, Option<ObjValue>) -> LazyVal30);31rc_fn_helper!(FunctionRhs, function_rhs, dyn Fn(Context) -> Val);32rc_fn_helper!(33	FunctionDefault,34	function_default,35	dyn Fn(Context, LocExpr) -> Val36);3738#[derive(Default, Clone)]39pub struct EvaluationState {40	/// Used for stack-overflows and stacktraces41	pub stack: Rc<RefCell<Vec<(LocExpr, String)>>>,42	/// Contains file source codes and evaluated results for imports and pretty printing stacktraces43	pub files: Rc<RefCell<HashMap<String, (String, Option<Val>)>>>,44}45impl EvaluationState {46	pub fn push<T>(&self, e: LocExpr, comment: String, f: impl FnOnce() -> T) -> T {47		self.stack.borrow_mut().push((e, comment));48		let result = f();49		self.stack.borrow_mut().pop();50		result51	}52	pub fn print_stack_trace(&self) {53		for e in self54			.stack55			.borrow()56			.iter()57			.rev()58			.map(|(loc, comment)| loc.1.clone().map(|v| (v, comment.clone())))59			.flatten()60		{61			println!("{:?} - {:?}", e.0, e.1)62		}63	}64	pub fn stack_trace(&self) -> Vec<(LocExpr, String)> {65		self.stack66			.borrow()67			.iter()68			.rev()69			.map(|e| e.clone())70			.collect()71	}72}7374#[cfg(test)]75pub mod tests {76	use super::{evaluate, Context, Val};77	use crate::EvaluationState;78	use jsonnet_parser::*;7980	#[test]81	fn eval_state_stacktrace() {82		let state = EvaluationState::default();83		state.push(84			loc_expr!(Expr::Num(0.0), true, ("test1.jsonnet".to_owned(), 10, 20)),85			"outer".to_owned(),86			|| {87				state.push(88					loc_expr!(Expr::Num(0.0), true, ("test2.jsonnet".to_owned(), 30, 40)),89					"inner".to_owned(),90					|| state.print_stack_trace(),91				);92			},93		);94	}9596	macro_rules! eval {97		($str: expr) => {98			evaluate(99				Context::new(),100				EvaluationState::default(),101				&parse(102					$str,103					&ParserSettings {104						loc_data: true,105						file_name: "test.jsonnet".to_owned(),106					},107					)108				.unwrap(),109				)110		};111	}112113	macro_rules! eval_stdlib {114		($str: expr) => {{115			let std = "local std = ".to_owned() + jsonnet_stdlib::STDLIB_STR + ";";116			evaluate(117				Context::new(),118				EvaluationState::default(),119				&parse(120					&(std + $str),121					&ParserSettings {122						loc_data: true,123						file_name: "test.jsonnet".to_owned(),124					},125					)126				.unwrap(),127				)128			}};129	}130131	macro_rules! assert_eval {132		($str: expr) => {133			assert_eq!(134				evaluate(135					Context::new(),136					EvaluationState::default(),137					&parse(138						$str,139						&ParserSettings {140							loc_data: true,141							file_name: "test.jsonnet".to_owned(),142						}143						)144					.unwrap()145					),146				Val::Bool(true)147				)148		};149	}150	macro_rules! assert_json {151		($str: expr, $out: expr) => {152			assert_eq!(153				format!(154					"{}",155					evaluate(156						Context::new(),157						EvaluationState::default(),158						&parse(159							$str,160							&ParserSettings {161								loc_data: true,162								file_name: "test.jsonnet".to_owned(),163							}164						)165						.unwrap()166						)167					),168				$out169				)170		};171	}172	macro_rules! assert_json_stdlib {173		($str: expr, $out: expr) => {174			assert_eq!(format!("{}", eval_stdlib!($str)), $out)175		};176	}177	macro_rules! assert_eval_neg {178		($str: expr) => {179			assert_eq!(180				evaluate(181					Context::new(),182					EvaluationState::default(),183					&parse(184						$str,185						&ParserSettings {186							loc_data: true,187							file_name: "test.jsonnet".to_owned(),188						}189						)190					.unwrap()191					),192				Val::Bool(false)193				)194		};195	}196197	/// Sanity checking, before trusting to another tests198	#[test]199	fn equality_operator() {200		assert_eval!("2 == 2");201		assert_eval_neg!("2 != 2");202		assert_eval!("2 != 3");203		assert_eval_neg!("2 == 3");204		assert_eval!("'Hello' == 'Hello'");205		assert_eval_neg!("'Hello' != 'Hello'");206		assert_eval!("'Hello' != 'World'");207		assert_eval_neg!("'Hello' == 'World'");208	}209210	#[test]211	fn math_evaluation() {212		assert_eval!("2 + 2 * 2 == 6");213		assert_eval!("3 + (2 + 2 * 2) == 9");214	}215216	#[test]217	fn string_concat() {218		assert_eval!("'Hello' + 'World' == 'HelloWorld'");219		assert_eval!("'Hello' * 3 == 'HelloHelloHello'");220		assert_eval!("'Hello' + 'World' * 3 == 'HelloWorldWorldWorld'");221	}222223	#[test]224	fn local() {225		assert_eval!("local a = 2; local b = 3; a + b == 5");226		assert_eval!("local a = 1, b = a + 1; a + b == 3");227		assert_eval!("local a = 1; local a = 2; a == 2");228	}229230	#[test]231	fn object_lazyness() {232		assert_json!("local a = {a:error 'test'}; {}", r#"{}"#);233	}234235	#[test]236	fn object_inheritance() {237		assert_json!("{a: self.b} + {b:3}", r#"{"a":3,"b":3}"#);238	}239240	#[test]241	fn test_object() {242		assert_json!("{a:2}", r#"{"a":2}"#);243		assert_json!("{a:2+2}", r#"{"a":4}"#);244		assert_json!("{a:2}+{b:2}", r#"{"a":2,"b":2}"#);245		assert_json!("{b:3}+{b:2}", r#"{"b":2}"#);246		assert_json!("{b:3}+{b+:2}", r#"{"b":5}"#);247		assert_json!("local test='a'; {[test]:2}", r#"{"a":2}"#);248		assert_json!(249			r#"250				{251					name: "Alice",252					welcome: "Hello " + self.name + "!",253				}254			"#,255			r#"{"name":"Alice","welcome":"Hello Alice!"}"#256		);257		assert_json!(258			r#"259				{260					name: "Alice",261					welcome: "Hello " + self.name + "!",262				} + {263					name: "Bob"264				}265			"#,266			r#"{"name":"Bob","welcome":"Hello Bob!"}"#267		);268	}269270	#[test]271	fn functions() {272		assert_json!(r#"local a = function(b, c = 2) b + c; a(2)"#, "4");273		assert_json!(274			r#"local a = function(b, c = "Dear") b + c + d, d = "World"; a("Hello")"#,275			r#""HelloDearWorld""#276		);277	}278279	#[test]280	fn local_methods() {281		assert_json!(r#"local a(b, c = 2) = b + c; a(2)"#, "4");282		assert_json!(283			r#"local a(b, c = "Dear") = b + c + d, d = "World"; a("Hello")"#,284			r#""HelloDearWorld""#285		);286	}287288	#[test]289	fn object_locals() {290		assert_json!(r#"{local a = 3, b: a}"#, r#"{"b":3}"#);291		assert_json!(r#"{local a = 3, local c = a, b: c}"#, r#"{"b":3}"#);292		assert_json!(293			r#"{local a = function (b) {[b]:4}, test: a("test")}"#,294			r#"{"test":{"test":4}}"#295		);296	}297298	#[test]299	fn direct_self() {300		println!(301			"{:#?}",302			eval!(303				r#"304					{305						local me = self,306						a: 3,307						b(): me.a,308					}309				"#310			)311		);312	}313314	#[test]315	fn indirect_self() {316		// `self` assigned to `me` was lost when being317		// referenced from field318		eval_stdlib!(319			r#"{320				local me = self,321				a: 3,322				b: me.a,323			}.b"#324		);325	}326327	// We can't trust other tests (And official jsonnet testsuite), if assert is not working correctly328	#[test]329	fn std_assert_ok() {330		eval_stdlib!("std.assertEqual(4.5 << 2, 16)");331	}332333	#[test]334	#[should_panic]335	fn std_assert_failure() {336		eval_stdlib!("std.assertEqual(4.5 << 2, 15)");337	}338339	#[test]340	fn string_is_string() {341		assert_eq!(342			eval_stdlib!("local arr = 'hello'; (!std.isArray(arr)) && (!std.isString(arr))"),343			Val::Bool(false)344		);345	}346347	#[test]348	fn base64_works() {349		assert_json_stdlib!(r#"std.base64("test")"#, r#""dGVzdA==""#);350	}351352	#[test]353	fn utf8_chars() {354		assert_json_stdlib!(355			r#"local c="😎";{c:std.codepoint(c),l:std.length(c)}"#,356			r#"{"c":128526,"l":1}"#357		)358	}359360	#[test]361	fn json() {362		println!("{:?}", eval_stdlib!(r#"std.manifestJson({a:3, b:4, c:6})"#));363	}364365	#[test]366	fn sjsonnet() {367		eval!(368			r#"369			local x0 = {k: 1};370			local x1 = {k: x0.k + x0.k};371			local x2 = {k: x1.k + x1.k};372			local x3 = {k: x2.k + x2.k};373			local x4 = {k: x3.k + x3.k};374			local x5 = {k: x4.k + x4.k};375			local x6 = {k: x5.k + x5.k};376			local x7 = {k: x6.k + x6.k};377			local x8 = {k: x7.k + x7.k};378			local x9 = {k: x8.k + x8.k};379			local x10 = {k: x9.k + x9.k};380			local x11 = {k: x10.k + x10.k};381			local x12 = {k: x11.k + x11.k};382			local x13 = {k: x12.k + x12.k};383			local x14 = {k: x13.k + x13.k};384			local x15 = {k: x14.k + x14.k};385			local x16 = {k: x15.k + x15.k};386			local x17 = {k: x16.k + x16.k};387			local x18 = {k: x17.k + x17.k};388			local x19 = {k: x18.k + x18.k};389			local x20 = {k: x19.k + x19.k};390			local x21 = {k: x20.k + x20.k};391		"#392		);393	}394}
modifiedcrates/jsonnet-parser/src/expr.rsdiffbeforeafterboth
--- a/crates/jsonnet-parser/src/expr.rs
+++ b/crates/jsonnet-parser/src/expr.rs
@@ -246,7 +246,11 @@
 		LocExpr(
 			std::rc::Rc::new($expr),
 			if $need_loc {
-				Some(std::rc::Rc::new(ExprLocation($name.to_owned(), $start, $end)))
+				Some(std::rc::Rc::new(ExprLocation(
+					$name.to_owned(),
+					$start,
+					$end,
+				)))
 			} else {
 				None
 				},