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

difftreelog

refactor(evaluator) extract shared function call parsing code

Лач2020-06-09parent: #f287f46.patch.diff
in: master

8 files changed

modifiedcmds/jrsonnet/src/main.rsdiffbeforeafterboth
--- a/cmds/jrsonnet/src/main.rs
+++ b/cmds/jrsonnet/src/main.rs
@@ -90,7 +90,7 @@
 	let opts: Opts = Opts::parse();
 	let evaluator = jsonnet_evaluator::EvaluationState::default();
 	if !opts.no_stdlib {
-		evaluator.add_stdlib();
+		evaluator.with_stdlib();
 	}
 	let mut input = current_dir().unwrap();
 	input.push(opts.input.clone());
@@ -105,7 +105,7 @@
 			let v = match opts.format {
 				Format::Json => {
 					if opts.no_stdlib {
-						evaluator.add_stdlib();
+						evaluator.with_stdlib();
 					}
 					evaluator.add_global("__tmp__to_json__".to_owned(), v);
 					let v = evaluator.parse_evaluate_raw(&format!(
@@ -122,7 +122,7 @@
 				}
 				Format::Yaml => {
 					if opts.no_stdlib {
-						evaluator.add_stdlib();
+						evaluator.with_stdlib();
 					}
 					evaluator.add_global("__tmp__to_yaml__".to_owned(), v);
 					let v = evaluator
modifiedcrates/jsonnet-evaluator/src/ctx.rsdiffbeforeafterboth
--- a/crates/jsonnet-evaluator/src/ctx.rs
+++ b/crates/jsonnet-evaluator/src/ctx.rs
@@ -1,5 +1,6 @@
 use crate::{
-	future_wrapper, rc_fn_helper, resolved_lazy_val, LazyBinding, LazyVal, ObjValue, Result, Val,
+	future_wrapper, map::LayeredHashMap, rc_fn_helper, resolved_lazy_val, LazyBinding, LazyVal,
+	ObjValue, Result, Val,
 };
 use std::{cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc};
 
@@ -11,12 +12,11 @@
 
 future_wrapper!(Context, FutureContext);
 
-#[derive(Debug)]
 struct ContextInternals {
 	dollar: Option<ObjValue>,
 	this: Option<ObjValue>,
 	super_obj: Option<ObjValue>,
-	bindings: Rc<HashMap<String, LazyVal>>,
+	bindings: LayeredHashMap<String, LazyVal>,
 }
 pub struct Context(Rc<ContextInternals>);
 impl Debug for Context {
@@ -48,7 +48,7 @@
 			dollar: None,
 			this: None,
 			super_obj: None,
-			bindings: Rc::new(HashMap::new()),
+			bindings: LayeredHashMap::default(),
 		}))
 	}
 
@@ -65,14 +65,14 @@
 	}
 
 	pub fn with_var(&self, name: String, value: Val) -> Result<Context> {
-		let mut new_bindings: HashMap<_, LazyBinding> = HashMap::new();
-		new_bindings.insert(name, LazyBinding::Bound(resolved_lazy_val!(value.clone())));
+		let mut new_bindings = HashMap::new();
+		new_bindings.insert(name, resolved_lazy_val!(value));
 		self.extend(new_bindings, None, None, None)
 	}
 
 	pub fn extend(
 		&self,
-		new_bindings: HashMap<String, LazyBinding>,
+		new_bindings: HashMap<String, LazyVal>,
 		new_dollar: Option<ObjValue>,
 		new_this: Option<ObjValue>,
 		new_super_obj: Option<ObjValue>,
@@ -83,14 +83,7 @@
 		let bindings = if new_bindings.is_empty() {
 			self.0.bindings.clone()
 		} else {
-			let mut new = HashMap::new(); // = self.0.bindings.clone();
-			for (k, v) in self.0.bindings.iter() {
-				new.insert(k.clone(), v.clone());
-			}
-			for (k, v) in new_bindings.into_iter() {
-				new.insert(k, v.evaluate(this.clone(), super_obj.clone())?);
-			}
-			Rc::new(new)
+			self.0.bindings.extend(new_bindings)
 		};
 		Ok(Context(Rc::new(ContextInternals {
 			dollar,
@@ -99,6 +92,21 @@
 			bindings,
 		})))
 	}
+	pub fn extend_unbound(
+		&self,
+		new_bindings: HashMap<String, LazyBinding>,
+		new_dollar: Option<ObjValue>,
+		new_this: Option<ObjValue>,
+		new_super_obj: Option<ObjValue>,
+	) -> 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::new();
+		for (k, v) in new_bindings.into_iter() {
+			new.insert(k, v.evaluate(this.clone(), super_obj.clone())?);
+		}
+		self.extend(new, new_dollar, this, super_obj)
+	}
 }
 
 impl Default for Context {
modifiedcrates/jsonnet-evaluator/src/error.rsdiffbeforeafterboth
--- a/crates/jsonnet-evaluator/src/error.rs
+++ b/crates/jsonnet-evaluator/src/error.rs
@@ -7,6 +7,11 @@
 	TypeMismatch(&'static str, Vec<ValType>, ValType),
 	NoSuchField(String),
 
+	UnknownFunctionParameter(String),
+	BindingParameterASecondTime(String),
+	TooManyArgsFunctionHas(usize),
+	FunctionParameterNotBoundInCall(String),
+
 	RuntimeError(String),
 	StackOverflow,
 	FractionalIndex,
modifiedcrates/jsonnet-evaluator/src/evaluate.rsdiffbeforeafterboth
--- a/crates/jsonnet-evaluator/src/evaluate.rs
+++ b/crates/jsonnet-evaluator/src/evaluate.rs
@@ -1,12 +1,12 @@
 use crate::{
-	binding, context_creator, create_error, function_default, function_rhs, future_wrapper,
-	lazy_val, push, Context, ContextCreator, FuncDesc, LazyBinding, LazyVal, ObjMember, ObjValue,
-	Result, Val,
+	binding, context_creator, create_error, future_wrapper, lazy_val, push, Context,
+	ContextCreator, FuncDesc, LazyBinding, ObjMember, ObjValue, Result, Val,
 };
 use closure::closure;
 use jsonnet_parser::{
-	ArgsDesc, AssertStmt, BinaryOpType, BindSpec, CompSpec, Expr, FieldMember, ForSpecData,
-	IfSpecData, LiteralType, LocExpr, Member, ObjBody, ParamsDesc, UnaryOpType, Visibility,
+	el, Arg, ArgsDesc, AssertStmt, BinaryOpType, BindSpec, CompSpec, Expr, FieldMember,
+	ForSpecData, IfSpecData, LiteralType, LocExpr, Member, ObjBody, ParamsDesc, UnaryOpType,
+	Visibility,
 };
 use std::{
 	collections::{BTreeMap, HashMap},
@@ -15,16 +15,16 @@
 
 pub fn evaluate_binding(b: &BindSpec, context_creator: ContextCreator) -> (String, LazyBinding) {
 	let b = b.clone();
-	if let Some(args) = &b.params {
-		let args = args.clone();
+	if let Some(params) = &b.params {
+		let params = params.clone();
 		(
 			b.name.clone(),
 			LazyBinding::Bindable(Rc::new(move |this, super_obj| {
 				Ok(lazy_val!(
-					closure!(clone b, clone args, clone context_creator, || Ok(evaluate_method(
+					closure!(clone b, clone params, clone context_creator, || Ok(evaluate_method(
 						context_creator.0(this.clone(), super_obj.clone())?,
-						&b.value,
-						args.clone()
+						params.clone(),
+						b.value.clone(),
 					)))
 				))
 			})),
@@ -46,13 +46,8 @@
 	}
 }
 
-pub fn evaluate_method(ctx: Context, 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!(closure!(|ctx, default| evaluate(ctx, &default))),
-	})
+pub fn evaluate_method(ctx: Context, params: ParamsDesc, body: LocExpr) -> Val {
+	Val::Func(FuncDesc { ctx, params, body })
 }
 
 pub fn evaluate_field_name(
@@ -214,7 +209,7 @@
 			let future_this = FutureObjValue::new();
 			let context_creator = context_creator!(
 				closure!(clone context, clone new_bindings, clone future_this, |this: Option<ObjValue>, super_obj: Option<ObjValue>| {
-					Ok(context.clone().extend(
+					Ok(context.clone().extend_unbound(
 						new_bindings.clone().unwrap(),
 						context.clone().dollar().clone().or_else(||this.clone()),
 						Some(this.unwrap()),
@@ -292,8 +287,8 @@
 										// TODO: Assert
 										Ok(evaluate_method(
 											context_creator.0(this, super_obj)?,
-											&value.clone(),
 											params.clone(),
+											value.clone(),
 										))
 									})
 								),
@@ -393,7 +388,7 @@
 			}
 
 			let context = context
-				.extend(new_bindings, None, None, None)?
+				.extend_unbound(new_bindings, None, None, None)?
 				.into_future(future_context);
 			evaluate(context, &returned.clone())?
 		}
@@ -417,7 +412,7 @@
 			&evaluate(context.clone(), s)?,
 			&Val::Obj(evaluate_object(context, t.clone())?),
 		)?,
-		Apply(value, ArgsDesc(args), tailstrict) => {
+		Apply(value, args, tailstrict) => {
 			let value = evaluate(context.clone(), value)?.unwrap_if_lazy()?;
 			match value {
 				Val::Intristic(ns, name) => match (&ns as &str, &name as &str) {
@@ -448,7 +443,13 @@
 							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))])?)
+								let call_ctx =
+									Context::new().with_var("v".to_owned(), Val::Num(i as f64))?;
+								out.push(d.evaluate(
+									call_ctx,
+									&ArgsDesc(vec![Arg(None, el!(Expr::Var("v".to_owned())))]),
+									true,
+								)?)
 							}
 							Val::Arr(out)
 						} else {
@@ -536,31 +537,7 @@
 				},
 				Val::Func(f) => {
 					let body = #[inline(always)]
-					|| {
-						f.evaluate(
-							args.clone()
-								.into_iter()
-								.map(
-									#[inline(always)]
-									move |a| {
-										Ok((
-											a.clone().0,
-											if *tailstrict {
-												Val::Lazy(LazyVal::new_resolved(evaluate(
-													context.clone(),
-													&a.1,
-												)?))
-											} else {
-												Val::Lazy(lazy_val!(
-													closure!(clone context, clone a, || evaluate(context.clone(), &a.clone().1))
-												))
-											},
-										))
-									},
-								)
-								.collect::<Result<Vec<_>>>()?,
-						)
-					};
+					|| f.evaluate(context, args, *tailstrict);
 					if *tailstrict {
 						body()?
 					} else {
@@ -570,7 +547,7 @@
 				_ => panic!("{:?} is not a function", value),
 			}
 		}
-		Function(params, body) => evaluate_method(context, body, params.clone()),
+		Function(params, body) => evaluate_method(context, params.clone(), body.clone()),
 		AssertExpr(AssertStmt(value, msg), returned) => {
 			let assertion_result = push(value.clone(), "assertion condition".to_owned(), || {
 				evaluate(context.clone(), &value)?
modifiedcrates/jsonnet-evaluator/src/function.rsdiffbeforeafterboth
--- a/crates/jsonnet-evaluator/src/function.rs
+++ b/crates/jsonnet-evaluator/src/function.rs
@@ -0,0 +1,80 @@
+use crate::{create_error, evaluate, lazy_val, resolved_lazy_val, Context, Error, Result};
+use closure::closure;
+use jsonnet_parser::{ArgsDesc, ParamsDesc};
+use std::collections::HashMap;
+
+/// Creates correct [context](Context) for function body evaluation, returning error on invalid call
+///
+/// * `ctx` used for passed argument expressions execution, and for body execution (if `body_ctx` is not set)
+/// * `body_ctx` used for default parameter values execution, and for body execution (if set)
+/// * `params` function parameters definition
+/// * `args` passed function arguments
+/// * `tailstruct` if true - function arguments is eager executed, otherwise - lazy
+pub fn parse_function_call(
+	ctx: Context,
+	body_ctx: Option<Context>,
+	params: &ParamsDesc,
+	args: &ArgsDesc,
+	tailstrict: bool,
+) -> Result<Context> {
+	inline_parse_function_call(ctx, body_ctx, params, args, tailstrict)
+}
+
+/// See [parse_function_call](parse_function_call)
+///
+/// ## Notes
+/// This function is always inlined for tailstrict
+#[inline(always)]
+pub(crate) fn inline_parse_function_call(
+	ctx: Context,
+	body_ctx: Option<Context>,
+	params: &ParamsDesc,
+	args: &ArgsDesc,
+	tailstrict: bool,
+) -> Result<Context> {
+	let mut out = HashMap::new();
+	let mut positioned_args = vec![None; params.0.len()];
+	for (id, arg) in args.iter().enumerate() {
+		let idx = if let Some(name) = &arg.0 {
+			params.iter().position(|p| &p.0 == name).ok_or_else(|| {
+				create_error::<()>(Error::UnknownFunctionParameter(name.clone()))
+					.err()
+					.unwrap()
+			})?
+		} else {
+			id
+		};
+
+		if idx >= params.len() {
+			create_error(Error::TooManyArgsFunctionHas(params.len()))?;
+		}
+		if positioned_args[idx].is_some() {
+			create_error(Error::BindingParameterASecondTime(params[idx].0.clone()))?;
+		}
+		positioned_args[idx] = Some(arg.1.clone());
+	}
+	// Fill defaults
+	for (id, p) in params.iter().enumerate() {
+		let (ctx, expr) = if let Some(arg) = &positioned_args[id] {
+			(ctx.clone(), arg)
+		} else if let Some(default) = &p.1 {
+			(
+				body_ctx
+					.clone()
+					.expect("no default context set for call with defined default parameter value"),
+				default,
+			)
+		} else {
+			create_error(Error::FunctionParameterNotBoundInCall(p.0.clone()))?;
+			unreachable!()
+		};
+		let val = if tailstrict {
+			resolved_lazy_val!(evaluate(ctx.clone(), expr)?)
+		} else {
+			lazy_val!(closure!(clone ctx, clone expr, ||evaluate(ctx.clone(), &expr)))
+		};
+		out.insert(p.0.clone(), val);
+	}
+
+	Ok(body_ctx.unwrap_or(ctx).extend(out, None, None, None)?)
+}
modifiedcrates/jsonnet-evaluator/src/lib.rsdiffbeforeafterboth
8mod error;8mod error;
9mod evaluate;9mod evaluate;
10mod function;10mod function;
11mod map;
11mod obj;12mod obj;
12mod val;13mod val;
1314
14pub use ctx::*;15pub use ctx::*;
15pub use dynamic::*;16pub use dynamic::*;
16pub use error::*;17pub use error::*;
17pub use evaluate::*;18pub use evaluate::*;
19pub use function::parse_function_call;
18use jsonnet_parser::*;20use jsonnet_parser::*;
19pub use obj::*;21pub use obj::*;
20use std::{cell::RefCell, collections::HashMap, fmt::Debug, path::PathBuf, rc::Rc};22use std::{cell::RefCell, collections::HashMap, fmt::Debug, path::PathBuf, rc::Rc};
25 binding,27 binding,
26 dyn Fn(Option<ObjValue>, Option<ObjValue>) -> Result<Val>28 dyn Fn(Option<ObjValue>, Option<ObjValue>) -> Result<Val>
27);29);
28rc_fn_helper!(FunctionRhs, function_rhs, dyn Fn(Context) -> Result<Val>);30
29rc_fn_helper!(
30 FunctionDefault,
31 function_default,
32 dyn Fn(Context, LocExpr) -> Result<Val>31type BindableFn = dyn Fn(Option<ObjValue>, Option<ObjValue>) -> Result<LazyVal>;
33);
34
35#[derive(Clone)]32#[derive(Clone)]
36pub enum LazyBinding {33pub enum LazyBinding {
37 Bindable(Rc<dyn Fn(Option<ObjValue>, Option<ObjValue>) -> Result<LazyVal>>),34 Bindable(Rc<BindableFn>),
38 Bound(LazyVal),35 Bound(LazyVal),
39}36}
4037
59impl Default for EvaluationSettings {56impl Default for EvaluationSettings {
60 fn default() -> Self {57 fn default() -> Self {
61 EvaluationSettings {58 EvaluationSettings {
62 max_stack_frames: 500,59 max_stack_frames: 200,
63 max_stack_trace_size: 20,60 max_stack_trace_size: 20,
64 }61 }
65 }62 }
181 self.0.globals.borrow_mut().insert(name, value);178 self.0.globals.borrow_mut().insert(name, value);
182 }179 }
183180
184 pub fn add_stdlib(&self) {181 pub fn with_stdlib(&self) -> &Self {
185 self.begin_state();182 self.begin_state();
186 use jsonnet_stdlib::STDLIB_STR;183 use jsonnet_stdlib::STDLIB_STR;
187 if cfg!(feature = "serialized-stdlib") {184 if cfg!(feature = "serialized-stdlib") {
199 let val = self.evaluate_file(&PathBuf::from("std.jsonnet")).unwrap();196 let val = self.evaluate_file(&PathBuf::from("std.jsonnet")).unwrap();
200 self.add_global("std".to_owned(), val);197 self.add_global("std".to_owned(), val);
201 self.end_state();198 self.end_state();
199 self
202 }200 }
203201
204 pub fn create_default_context(&self) -> Result<Context> {202 pub fn create_default_context(&self) -> Result<Context> {
210 LazyBinding::Bound(resolved_lazy_val!(value.clone())),208 LazyBinding::Bound(resolved_lazy_val!(value.clone())),
211 );209 );
212 }210 }
213 Context::new().extend(new_bindings, None, None, None)211 Context::new().extend_unbound(new_bindings, None, None, None)
214 }212 }
215213
216 #[inline(always)]214 #[inline(always)]
297 #[test]295 #[test]
298 fn eval_state_standard() {296 fn eval_state_standard() {
299 let state = EvaluationState::default();297 let state = EvaluationState::default();
300 state.add_stdlib();298 state.with_stdlib();
301 assert_eq!(299 assert_eq!(
302 state300 state
303 .parse_evaluate_raw(r#"std.assertEqual(std.base64("test"), "dGVzdA==")"#)301 .parse_evaluate_raw(r#"std.assertEqual(std.base64("test"), "dGVzdA==")"#)
308306
309 macro_rules! eval {307 macro_rules! eval {
310 ($str: expr) => {308 ($str: expr) => {
311 evaluate(
312 Context::new(),
313 EvaluationState::default(),309 EvaluationState::default()
310 .with_stdlib()
314 &parse(311 .parse_evaluate_raw($str)
315 $str,
316 &ParserSettings {
317 loc_data: true,
318 file_name: "test.jsonnet".to_owned(),
319 },
320 )
321 .unwrap(),312 .unwrap()
322 )
323 };313 };
324 }314 }
325
326 macro_rules! eval_stdlib {315 macro_rules! eval_json {
327 ($str: expr) => {{316 ($str: expr) => {{
328 let std = "local std = ".to_owned() + jsonnet_stdlib::STDLIB_STR + ";";317 let evaluator = EvaluationState::default();
329 evaluate(318 evaluator.with_stdlib();
330 Context::new(),
331 EvaluationState::default(),
332 &parse(
333 &(std + $str),319 let val = evaluator.parse_evaluate_raw($str).unwrap();
334 &ParserSettings {
335 loc_data: true,
336 file_name: "test.jsonnet".to_owned(),320 evaluator.add_global("__tmp__to_yaml__".to_owned(), val);
337 },321 evaluator
338 )322 .parse_evaluate_raw("std.manifestJsonEx(__tmp__to_yaml__, \"\")")
339 .unwrap(),323 .unwrap()
340 )324 .try_cast_str("there should be json string")
325 .unwrap()
326 .clone()
327 .replace("\n", "")
341 }};328 }};
342 }329 }
343330
331 /// Asserts given code returns `true`
344 macro_rules! assert_eval {332 macro_rules! assert_eval {
345 ($str: expr) => {333 ($str: expr) => {
346 assert_eq!(334 assert_eq!(eval!($str), Val::Bool(true))
347 evaluate(
348 Context::new(),
349 EvaluationState::default(),
350 &parse(
351 $str,
352 &ParserSettings {
353 loc_data: true,
354 file_name: "test.jsonnet".to_owned(),
355 }
356 )
357 .unwrap()
358 ),
359 Val::Bool(true)
360 )
361 };335 };
362 }336 }
337
338 /// Asserts given code returns `false`
363 macro_rules! assert_json {339 macro_rules! assert_eval_neg {
364 ($str: expr, $out: expr) => {340 ($str: expr) => {
365 assert_eq!(341 assert_eq!(eval!($str), Val::Bool(false))
366 format!(
367 "{}",
368 evaluate(
369 Context::new(),
370 EvaluationState::default(),
371 &parse(
372 $str,
373 &ParserSettings {
374 loc_data: true,
375 file_name: "test.jsonnet".to_owned(),
376 }
377 )
378 .unwrap()
379 )
380 ),
381 $out
382 )
383 };342 };
384 }343 }
385 macro_rules! assert_json_stdlib {344 macro_rules! assert_json {
386 ($str: expr, $out: expr) => {345 ($str: expr, $out: expr) => {
387 assert_eq!(format!("{}", eval_stdlib!($str)), $out)346 assert_eq!(eval_json!($str), $out.replace("\t", ""))
388 };347 };
389 }348 }
349
350 /// Sanity checking, before trusting to another tests
351 #[test]
352 fn equality_operator() {
353 assert_eval!("2 == 2");
354 assert_eval_neg!("2 != 2");
355 assert_eval!("2 != 3");
356 assert_eval_neg!("2 == 3");
357 assert_eval!("'Hello' == 'Hello'");
358 assert_eval_neg!("'Hello' != 'Hello'");
359 assert_eval!("'Hello' != 'World'");
360 assert_eval_neg!("'Hello' == 'World'");
361 }
362
363 #[test]
364 fn math_evaluation() {
365 assert_eval!("2 + 2 * 2 == 6");
366 assert_eval!("3 + (2 + 2 * 2) == 9");
367 }
368
369 #[test]
370 fn string_concat() {
371 assert_eval!("'Hello' + 'World' == 'HelloWorld'");
372 assert_eval!("'Hello' * 3 == 'HelloHelloHello'");
373 assert_eval!("'Hello' + 'World' * 3 == 'HelloWorldWorldWorld'");
374 }
375
376 #[test]
377 fn function_contexts() {
378 assert_eval!(
379 r#"
380 local k = {
381 t(name = self.h): [self.h, name],
382 h: 3,
383 };
384 local f = {
385 t: k.t(),
386 h: 4,
387 };
388 f.t[0] == f.t[1]
389 "#
390 );
391 }
392
393 #[test]
394 fn local() {
395 assert_eval!("local a = 2; local b = 3; a + b == 5");
396 assert_eval!("local a = 1, b = a + 1; a + b == 3");
397 assert_eval!("local a = 1; local a = 2; a == 2");
398 }
399
400 #[test]
401 fn object_lazyness() {
402 assert_json!("local a = {a:error 'test'}; {}", r#"{}"#);
403 }
404
405 /// FIXME: This test gets stackoverflow in debug build
406 #[test]
407 fn object_inheritance() {
408 assert_json!("{a: self.b} + {b:3}", r#"{"a": 3,"b": 3}"#);
409 }
410
411 #[test]
412 fn test_object() {
413 assert_json!("{a:2}", r#"{"a": 2}"#);
414 assert_json!("{a:2+2}", r#"{"a": 4}"#);
415 assert_json!("{a:2}+{b:2}", r#"{"a": 2,"b": 2}"#);
416 assert_json!("{b:3}+{b:2}", r#"{"b": 2}"#);
417 assert_json!("{b:3}+{b+:2}", r#"{"b": 5}"#);
418 assert_json!("local test='a'; {[test]:2}", r#"{"a": 2}"#);
419 assert_json!(
420 r#"
421 {
422 name: "Alice",
423 welcome: "Hello " + self.name + "!",
424 }
425 "#,
426 r#"{"name": "Alice","welcome": "Hello Alice!"}"#
427 );
428 assert_json!(
429 r#"
430 {
431 name: "Alice",
432 welcome: "Hello " + self.name + "!",
433 } + {
434 name: "Bob"
435 }
436 "#,
437 r#"{"name": "Bob","welcome": "Hello Bob!"}"#
438 );
439 }
440
441 #[test]
442 fn functions() {
443 assert_json!(r#"local a = function(b, c = 2) b + c; a(2)"#, "4");
444 assert_json!(
445 r#"local a = function(b, c = "Dear") b + c + d, d = "World"; a("Hello")"#,
446 r#""HelloDearWorld""#
447 );
448 }
449
450 #[test]
451 fn local_methods() {
452 assert_json!(r#"local a(b, c = 2) = b + c; a(2)"#, "4");
453 assert_json!(
454 r#"local a(b, c = "Dear") = b + c + d, d = "World"; a("Hello")"#,
455 r#""HelloDearWorld""#
456 );
457 }
458
459 #[test]
460 fn object_locals() {
461 assert_json!(r#"{local a = 3, b: a}"#, r#"{"b": 3}"#);
462 assert_json!(r#"{local a = 3, local c = a, b: c}"#, r#"{"b": 3}"#);
463 assert_json!(
464 r#"{local a = function (b) {[b]:4}, test: a("test")}"#,
465 r#"{"test": {"test": 4}}"#
466 );
467 }
468
469 #[test]
470 fn direct_self() {
471 println!(
472 "{:#?}",
473 eval!(
474 r#"
475 {
476 local me = self,
477 a: 3,
478 b(): me.a,
479 }
480 "#
481 )
482 );
483 }
484
485 #[test]
486 fn indirect_self() {
487 // `self` assigned to `me` was lost when being
488 // referenced from field
489 eval!(
490 r#"{
491 local me = self,
492 a: 3,
493 b: me.a,
494 }.b"#
495 );
496 }
497
498 // We can't trust other tests (And official jsonnet testsuite), if assert is not working correctly
499 #[test]
500 fn std_assert_ok() {
501 eval!("std.assertEqual(4.5 << 2, 16)");
502 }
503
504 #[test]
505 #[should_panic]
506 fn std_assert_failure() {
507 eval!("std.assertEqual(4.5 << 2, 15)");
508 }
509
510 #[test]
390 macro_rules! assert_eval_neg {511 fn string_is_string() {
391 ($str: expr) => {
392 assert_eq!(512 assert_eq!(
393 evaluate(
394 Context::new(),
395 EvaluationState::default(),513 eval!("local arr = 'hello'; (!std.isArray(arr)) && (!std.isString(arr))"),
396 &parse(
397 $str,
398 &ParserSettings {
399 loc_data: true,
400 file_name: "test.jsonnet".to_owned(),
401 }
402 )
403 .unwrap()
404 ),
405 Val::Bool(false)514 Val::Bool(false)
406 )515 );
407 };
408 }516 }
409517
410 /*518 #[test]
411 /// Sanity checking, before trusting to another tests519 fn base64_works() {
412 #[test]520 assert_json!(r#"std.base64("test")"#, r#""dGVzdA==""#);
413 fn equality_operator() {521 }
414 assert_eval!("2 == 2");522
415 assert_eval_neg!("2 != 2");523 #[test]
416 assert_eval!("2 != 3");524 fn utf8_chars() {
417 assert_eval_neg!("2 == 3");525 assert_json!(
418 assert_eval!("'Hello' == 'Hello'");526 r#"local c="😎";{c:std.codepoint(c),l:std.length(c)}"#,
419 assert_eval_neg!("'Hello' != 'Hello'");527 r#"{"c": 128526,"l": 1}"#
420 assert_eval!("'Hello' != 'World'");528 )
421 assert_eval_neg!("'Hello' == 'World'");529 }
422 }530
423531 #[test]
424 #[test]532 fn json() {
425 fn math_evaluation() {533 assert_json!(
426 assert_eval!("2 + 2 * 2 == 6");534 r#"std.manifestJsonEx({a:3, b:4, c:6},"")"#,
427 assert_eval!("3 + (2 + 2 * 2) == 9");535 r#""{\n\"a\": 3,\n\"b\": 4,\n\"c\": 6\n}""#
428 }536 );
429537 }
430 #[test]538
431 fn string_concat() {539 #[test]
432 assert_eval!("'Hello' + 'World' == 'HelloWorld'");540 fn test() {
433 assert_eval!("'Hello' * 3 == 'HelloHelloHello'");541 assert_json!(
434 assert_eval!("'Hello' + 'World' * 3 == 'HelloWorldWorldWorld'");542 r#"[[a, b] for a in [1,2,3] for b in [4,5,6]]"#,
435 }543 "[[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]]"
436544 );
437 #[test]545 }
438 fn local() {546
439 assert_eval!("local a = 2; local b = 3; a + b == 5");547 #[test]
440 assert_eval!("local a = 1, b = a + 1; a + b == 3");548 fn sjsonnet() {
441 assert_eval!("local a = 1; local a = 2; a == 2");549 eval!(
442 }550 r#"
443551 local x0 = {k: 1};
444 #[test]552 local x1 = {k: x0.k + x0.k};
445 fn object_lazyness() {553 local x2 = {k: x1.k + x1.k};
446 assert_json!("local a = {a:error 'test'}; {}", r#"{}"#);554 local x3 = {k: x2.k + x2.k};
447 }555 local x4 = {k: x3.k + x3.k};
448556 local x5 = {k: x4.k + x4.k};
449 #[test]557 local x6 = {k: x5.k + x5.k};
450 fn object_inheritance() {558 local x7 = {k: x6.k + x6.k};
451 assert_json!("{a: self.b} + {b:3}", r#"{"a":3,"b":3}"#);559 local x8 = {k: x7.k + x7.k};
452 }560 local x9 = {k: x8.k + x8.k};
453561 local x10 = {k: x9.k + x9.k};
454 #[test]562 local x11 = {k: x10.k + x10.k};
455 fn test_object() {563 local x12 = {k: x11.k + x11.k};
456 assert_json!("{a:2}", r#"{"a":2}"#);564 local x13 = {k: x12.k + x12.k};
457 assert_json!("{a:2+2}", r#"{"a":4}"#);565 local x14 = {k: x13.k + x13.k};
458 assert_json!("{a:2}+{b:2}", r#"{"a":2,"b":2}"#);566 local x15 = {k: x14.k + x14.k};
459 assert_json!("{b:3}+{b:2}", r#"{"b":2}"#);567 local x16 = {k: x15.k + x15.k};
460 assert_json!("{b:3}+{b+:2}", r#"{"b":5}"#);568 local x17 = {k: x16.k + x16.k};
461 assert_json!("local test='a'; {[test]:2}", r#"{"a":2}"#);569 local x18 = {k: x17.k + x17.k};
462 assert_json!(570 local x19 = {k: x18.k + x18.k};
463 r#"571 local x20 = {k: x19.k + x19.k};
464 {572 local x21 = {k: x20.k + x20.k};
465 name: "Alice",573 x21.k
466 welcome: "Hello " + self.name + "!",574 "#
467 }575 );
468 "#,576 }
469 r#"{"name":"Alice","welcome":"Hello Alice!"}"#
470 );
471 assert_json!(
472 r#"
473 {
474 name: "Alice",
475 welcome: "Hello " + self.name + "!",
476 } + {
477 name: "Bob"
478 }
479 "#,
480 r#"{"name":"Bob","welcome":"Hello Bob!"}"#
481 );
482 }
483
484 #[test]
485 fn functions() {
486 assert_json!(r#"local a = function(b, c = 2) b + c; a(2)"#, "4");
487 assert_json!(
488 r#"local a = function(b, c = "Dear") b + c + d, d = "World"; a("Hello")"#,
489 r#""HelloDearWorld""#
490 );
491 }
492
493 #[test]
494 fn local_methods() {
495 assert_json!(r#"local a(b, c = 2) = b + c; a(2)"#, "4");
496 assert_json!(
497 r#"local a(b, c = "Dear") = b + c + d, d = "World"; a("Hello")"#,
498 r#""HelloDearWorld""#
499 );
500 }
501
502 #[test]
503 fn object_locals() {
504 assert_json!(r#"{local a = 3, b: a}"#, r#"{"b":3}"#);
505 assert_json!(r#"{local a = 3, local c = a, b: c}"#, r#"{"b":3}"#);
506 assert_json!(
507 r#"{local a = function (b) {[b]:4}, test: a("test")}"#,
508 r#"{"test":{"test":4}}"#
509 );
510 }
511
512 #[test]
513 fn direct_self() {
514 println!(
515 "{:#?}",
516 eval!(
517 r#"
518 {
519 local me = self,
520 a: 3,
521 b(): me.a,
522 }
523 "#
524 )
525 );
526 }
527
528 #[test]
529 fn indirect_self() {
530 // `self` assigned to `me` was lost when being
531 // referenced from field
532 eval_stdlib!(
533 r#"{
534 local me = self,
535 a: 3,
536 b: me.a,
537 }.b"#
538 );
539 }
540
541 // We can't trust other tests (And official jsonnet testsuite), if assert is not working correctly
542 #[test]
543 fn std_assert_ok() {
544 eval_stdlib!("std.assertEqual(4.5 << 2, 16)");
545 }
546
547 #[test]
548 #[should_panic]
549 fn std_assert_failure() {
550 eval_stdlib!("std.assertEqual(4.5 << 2, 15)");
551 }
552
553 #[test]
554 fn string_is_string() {
555 assert_eq!(
556 eval_stdlib!("local arr = 'hello'; (!std.isArray(arr)) && (!std.isString(arr))"),
557 Val::Bool(false)
558 );
559 }
560
561 #[test]
562 fn base64_works() {
563 assert_json_stdlib!(r#"std.base64("test")"#, r#""dGVzdA==""#);
564 }
565
566 #[test]
567 fn utf8_chars() {
568 assert_json_stdlib!(
569 r#"local c="😎";{c:std.codepoint(c),l:std.length(c)}"#,
570 r#"{"c":128526,"l":1}"#
571 )
572 }
573
574 #[test]
575 fn json() {
576 assert_json_stdlib!(
577 r#"std.manifestJsonEx({a:3, b:4, c:6},"")"#,
578 r#""{\n"a": 3,\n"b": 4,\n"c": 6\n}""#
579 );
580 }
581
582 #[test]
583 fn test() {
584 assert_json_stdlib!(
585 r#"[[a, b] for a in [1,2,3] for b in [4,5,6]]"#,
586 "[[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]]"
587 );
588 }
589
590 #[test]
591 fn sjsonnet() {
592 eval!(
593 r#"
594 local x0 = {k: 1};
595 local x1 = {k: x0.k + x0.k};
596 local x2 = {k: x1.k + x1.k};
597 local x3 = {k: x2.k + x2.k};
598 local x4 = {k: x3.k + x3.k};
599 local x5 = {k: x4.k + x4.k};
600 local x6 = {k: x5.k + x5.k};
601 local x7 = {k: x6.k + x6.k};
602 local x8 = {k: x7.k + x7.k};
603 local x9 = {k: x8.k + x8.k};
604 local x10 = {k: x9.k + x9.k};
605 local x11 = {k: x10.k + x10.k};
606 local x12 = {k: x11.k + x11.k};
607 local x13 = {k: x12.k + x12.k};
608 local x14 = {k: x13.k + x13.k};
609 local x15 = {k: x14.k + x14.k};
610 local x16 = {k: x15.k + x15.k};
611 local x17 = {k: x16.k + x16.k};
612 local x18 = {k: x17.k + x17.k};
613 local x19 = {k: x18.k + x18.k};
614 local x20 = {k: x19.k + x19.k};
615 local x21 = {k: x20.k + x20.k};
616 x21.k
617 "#
618 );
619 }
620 */
621}577}
622578
addedcrates/jsonnet-evaluator/src/map.rsdiffbeforeafterboth
--- /dev/null
+++ b/crates/jsonnet-evaluator/src/map.rs
@@ -0,0 +1,46 @@
+use std::{borrow::Borrow, collections::HashMap, hash::Hash, rc::Rc};
+
+#[derive(Default, Debug)]
+struct LayeredHashMapInternals<K: Hash, V> {
+	parent: Option<LayeredHashMap<K, V>>,
+	current: HashMap<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 {
+		let super_map = self.clone();
+		LayeredHashMap(Rc::new(LayeredHashMapInternals {
+			parent: Some(super_map),
+			current: new_layer,
+		}))
+	}
+
+	pub fn get<Q: ?Sized>(&self, key: &Q) -> Option<&V>
+	where
+		K: Borrow<Q>,
+		Q: Hash + Eq,
+	{
+		(self.0)
+			.current
+			.get(&key)
+			.or_else(|| self.0.parent.as_ref().and_then(|p| p.get(key)))
+	}
+}
+
+impl<K: Hash, V> Clone for LayeredHashMap<K, V> {
+	fn clone(&self) -> Self {
+		LayeredHashMap(self.0.clone())
+	}
+}
+
+impl<K: Hash + Eq, V> Default for LayeredHashMap<K, V> {
+	fn default() -> Self {
+		LayeredHashMap(Rc::new(LayeredHashMapInternals {
+			parent: None,
+			current: HashMap::new(),
+		}))
+	}
+}
modifiedcrates/jsonnet-evaluator/src/val.rsdiffbeforeafterboth
--- a/crates/jsonnet-evaluator/src/val.rs
+++ b/crates/jsonnet-evaluator/src/val.rs
@@ -1,11 +1,9 @@
 use crate::{
-	create_error, Context, Error, FunctionDefault, FunctionRhs, LazyBinding, ObjValue, Result,
+	create_error, evaluate, function::inline_parse_function_call, Context, Error, ObjValue, Result,
 };
-use closure::closure;
-use jsonnet_parser::{Param, ParamsDesc};
+use jsonnet_parser::{ArgsDesc, LocExpr, ParamsDesc};
 use std::{
 	cell::RefCell,
-	collections::HashMap,
 	fmt::{Debug, Display},
 	rc::Rc,
 };
@@ -73,47 +71,21 @@
 pub struct FuncDesc {
 	pub ctx: Context,
 	pub params: ParamsDesc,
-	pub eval_rhs: FunctionRhs,
-	pub eval_default: FunctionDefault,
+	pub body: LocExpr,
 }
 impl FuncDesc {
 	// TODO: Check for unset variables
 	/// This function is always inlined to make tailstrict work
 	#[inline(always)]
-	pub fn evaluate(&self, args: Vec<(Option<String>, Val)>) -> Result<Val> {
-		let mut new_bindings: HashMap<String, LazyBinding> = HashMap::new();
-		let future_ctx = Context::new_future();
-
-		for Param(name, default) in self.params.with_defaults() {
-			let default = default.unwrap();
-			let eval_default = self.eval_default.clone();
-			new_bindings.insert(
-				name,
-				LazyBinding::Bound(lazy_val!(
-					closure!(clone future_ctx, clone eval_default, clone default, || (eval_default.clone()).0
-					(future_ctx.clone().unwrap(), default.clone()))
-				)),
-			);
-		}
-		for (name, val) in args.clone().into_iter().filter(|e| e.0.is_some()) {
-			new_bindings.insert(
-				name.as_ref().unwrap().clone(),
-				LazyBinding::Bound(resolved_lazy_val!(val.clone())),
-			);
-		}
-		for (i, param) in self.params.0.iter().enumerate() {
-			if let Some((None, val)) = args.get(i) {
-				new_bindings.insert(
-					param.0.clone(),
-					LazyBinding::Bound(resolved_lazy_val!(val.clone())),
-				);
-			}
-		}
-		let ctx = self
-			.ctx
-			.extend(new_bindings, None, None, None)?
-			.into_future(future_ctx);
-		self.eval_rhs.0(ctx)
+	pub fn evaluate(&self, call_ctx: Context, args: &ArgsDesc, tailstrict: bool) -> Result<Val> {
+		let ctx = inline_parse_function_call(
+			call_ctx,
+			Some(self.ctx.clone()),
+			&self.params,
+			args,
+			tailstrict,
+		)?;
+		evaluate(ctx, &self.body)
 	}
 }