git.delta.rocks / jrsonnet / refs/commits / 4aecc221ffa5

difftreelog

feat(evaluator) ArrComp support

Лач2020-06-03parent: #56fe309.patch.diff
in: master

7 files changed

modifiedcrates/jsonnet-evaluator/src/ctx.rsdiffbeforeafterboth
--- a/crates/jsonnet-evaluator/src/ctx.rs
+++ b/crates/jsonnet-evaluator/src/ctx.rs
@@ -1,4 +1,7 @@
-use crate::{future_wrapper, rc_fn_helper, LazyBinding, LazyVal, ObjValue};
+use crate::{
+	future_wrapper, lazy_binding, lazy_val, rc_fn_helper, LazyBinding, LazyVal, ObjValue, Val,
+};
+use closure::closure;
 use std::{cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc};
 
 rc_fn_helper!(
@@ -62,6 +65,17 @@
 		ctx.unwrap()
 	}
 
+	pub fn with_var(&self, name: String, value: Val) -> Context {
+		let mut new_bindings: HashMap<_, LazyBinding> = HashMap::new();
+		new_bindings.insert(
+			name,
+			lazy_binding!(
+				closure!(clone value, |_t, _s|lazy_val!(closure!(clone value, ||value.clone())))
+			),
+		);
+		self.extend(new_bindings, None, None, None)
+	}
+
 	pub fn extend(
 		&self,
 		new_bindings: HashMap<String, LazyBinding>,
modifiedcrates/jsonnet-evaluator/src/evaluate.rsdiffbeforeafterboth
--- a/crates/jsonnet-evaluator/src/evaluate.rs
+++ b/crates/jsonnet-evaluator/src/evaluate.rs
@@ -5,8 +5,9 @@
 };
 use closure::closure;
 use jsonnet_parser::{
-	ArgsDesc, BinaryOpType, BindSpec, Expr, FieldMember, 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},
@@ -94,29 +95,64 @@
 	}
 }
 
-pub fn evaluate_binary_op(a: &Val, op: BinaryOpType, b: &Val) -> Val {
+pub fn evaluate_add_op(a: &Val, b: &Val) -> Val {
+	match (a, b) {
+		(Val::Str(v1), Val::Str(v2)) => Val::Str(v1.to_owned() + &v2),
+		(Val::Str(v1), Val::Num(v2)) => Val::Str(format!("{}{}", v1, v2)),
+		(Val::Num(v1), Val::Str(v2)) => Val::Str(format!("{}{}", v1, v2)),
+		(Val::Obj(v1), Val::Obj(v2)) => Val::Obj(v2.with_super(v1.clone())),
+		(Val::Arr(a), Val::Arr(b)) => Val::Arr([&a[..], &b[..]].concat()),
+		(Val::Num(v1), Val::Num(v2)) => Val::Num(v1 + v2),
+		_ => panic!("can't add: {:?} and {:?}", a, b),
+	}
+}
+
+pub fn evaluate_binary_op(
+	context: Context,
+	eval_state: EvaluationState,
+	a: &Val,
+	op: BinaryOpType,
+	b: &Val,
+) -> Val {
 	match (a, op, b) {
-		(Val::Lazy(a), o, b) => evaluate_binary_op(&a.evaluate(), o, b),
-		(a, o, Val::Lazy(b)) => evaluate_binary_op(a, o, &b.evaluate()),
+		(Val::Lazy(a), o, b) => evaluate_binary_op(context, eval_state, &a.evaluate(), o, b),
+		(a, o, Val::Lazy(b)) => evaluate_binary_op(context, eval_state, a, o, &b.evaluate()),
+
+		(a, BinaryOpType::Add, b) => evaluate_add_op(a, b),
 
-		(Val::Str(v1), BinaryOpType::Add, Val::Str(v2)) => Val::Str(v1.to_owned() + &v2),
 		(Val::Str(v1), BinaryOpType::Ne, Val::Str(v2)) => bool_val(v1 != v2),
 
-		(Val::Str(v1), BinaryOpType::Add, Val::Num(v2)) => Val::Str(format!("{}{}", v1, v2)),
 		(Val::Str(v1), BinaryOpType::Mul, Val::Num(v2)) => Val::Str(v1.repeat(*v2 as usize)),
+		(Val::Str(format), BinaryOpType::Mod, args) => evaluate(
+			context
+				.with_var("__tmp__format__".to_owned(), Val::Str(format.to_owned()))
+				.with_var(
+					"__tmp__args__".to_owned(),
+					match args {
+						Val::Arr(v) => Val::Arr(v.clone()),
+						v => Val::Arr(vec![v.clone()]),
+					},
+				),
+			eval_state,
+			&el!(Expr::Apply(
+				el!(Expr::Index(
+					el!(Expr::Var("std".to_owned())),
+					el!(Expr::Str("format".to_owned()))
+				)),
+				ArgsDesc(vec![
+					Arg(None, el!(Expr::Var("__tmp__format__".to_owned()))),
+					Arg(None, el!(Expr::Var("__tmp__args__".to_owned())))
+				])
+			)),
+		),
 
 		(Val::Bool(a), BinaryOpType::And, Val::Bool(b)) => Val::Bool(*a && *b),
 		(Val::Bool(a), BinaryOpType::Or, Val::Bool(b)) => Val::Bool(*a || *b),
 
-		(Val::Obj(v1), BinaryOpType::Add, Val::Obj(v2)) => Val::Obj(v2.with_super(v1.clone())),
-
-		(Val::Arr(a), BinaryOpType::Add, Val::Arr(b)) => Val::Arr([&a[..], &b[..]].concat()),
-
 		(Val::Num(v1), BinaryOpType::Mul, Val::Num(v2)) => Val::Num(v1 * v2),
 		(Val::Num(v1), BinaryOpType::Div, Val::Num(v2)) => Val::Num(v1 / v2),
 		(Val::Num(v1), BinaryOpType::Mod, Val::Num(v2)) => Val::Num(v1 % v2),
 
-		(Val::Num(v1), BinaryOpType::Add, Val::Num(v2)) => Val::Num(v1 + v2),
 		(Val::Num(v1), BinaryOpType::Sub, Val::Num(v2)) => Val::Num(v1 - v2),
 
 		(Val::Num(v1), BinaryOpType::Lhs, Val::Num(v2)) => {
@@ -152,6 +188,42 @@
 future_wrapper!(HashMap<String, LazyBinding>, FutureNewBindings);
 future_wrapper!(ObjValue, FutureObjValue);
 
+pub fn evaluate_comp(
+	context: Context,
+	eval_state: EvaluationState,
+	value: &LocExpr,
+	specs: &[CompSpec],
+) -> Option<Vec<Val>> {
+	match specs.get(0) {
+		None => Some(vec![evaluate(context, eval_state, &value)]),
+		Some(CompSpec::IfSpec(IfSpecData(cond))) => {
+			match evaluate(context.clone(), eval_state.clone(), &cond).unwrap_if_lazy() {
+				Val::Bool(false) => None,
+				Val::Bool(true) => evaluate_comp(context, eval_state, value, &specs[1..]),
+				_ => panic!("if expression evaluated to non-boolean value"),
+			}
+		}
+		Some(CompSpec::ForSpec(ForSpecData(var, expr))) => {
+			match evaluate(context.clone(), eval_state.clone(), &expr).unwrap_if_lazy() {
+				Val::Arr(list) => {
+					let mut out = Vec::new();
+					for item in list {
+						let item = item.clone();
+						out.push(evaluate_comp(
+							context.with_var(var.clone(), item),
+							eval_state.clone(),
+							value,
+							&specs[1..],
+						));
+					}
+					Some(out.iter().flatten().flatten().cloned().collect())
+				}
+				_ => panic!("for expression evaluated to non-iterable value"),
+			}
+		}
+	}
+}
+
 // TODO: Asserts
 pub fn evaluate_object(context: Context, eval_state: EvaluationState, object: ObjBody) -> ObjValue {
 	match object {
@@ -272,11 +344,18 @@
 			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),
-			),
+			BinaryOp(v1, o, v2) => {
+				let a = evaluate(context.clone(), eval_state.clone(), v1).unwrap_if_lazy();
+				let op = *o;
+				let b = evaluate(context.clone(), eval_state.clone(), v2).unwrap_if_lazy();
+				evaluate_binary_op(
+					context,
+					eval_state,
+					&a,
+					op,
+					&b,
+				)
+			},
 			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) => {
@@ -286,7 +365,7 @@
 				) {
 					(Val::Obj(v), Val::Str(s)) => v
 						.get(&s)
-						.unwrap_or_else(closure!(clone context, || {
+						.unwrap_or_else(closure!(clone context, clone eval_state, || {
 							if let Some(n) = v.get("__intristic_namespace__") {
 								if let Val::Str(n) = n.unwrap_if_lazy() {
 									Val::Intristic(n, s)
@@ -335,12 +414,16 @@
 				}
 				Val::Arr(out)
 			}
+			ArrComp(expr, compspecs) => {
+				Val::Arr(evaluate_comp(context, eval_state, expr, compspecs).unwrap())
+			}
 			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) {
+						// arr/string/function
 						("std", "length") => {
 							assert_eq!(args.len(), 1);
 							let expr = &args.get(0).unwrap().1;
@@ -350,11 +433,13 @@
 								v => panic!("can't get length of {:?}", v),
 							}
 						}
+						// any
 						("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())
 						}
+						// length, idx=>any
 						("std", "makeArray") => {
 							assert_eq!(args.len(), 2);
 							if let (Val::Num(v), Val::Func(d)) = (
@@ -371,6 +456,7 @@
 								panic!("bad makeArray call");
 							}
 						}
+						// string
 						("std", "codepoint") => {
 							assert_eq!(args.len(), 1);
 							if let Val::Str(s) = evaluate(context, eval_state, &args[0].1) {
@@ -383,6 +469,19 @@
 								panic!("bad codepoint call");
 							}
 						}
+						// object, includeHidden
+						("std", "objectFieldsEx") => {
+							assert_eq!(args.len(), 2);
+							if let (Val::Obj(body), Val::Bool(_include_hidden)) = (
+								evaluate(context.clone(), eval_state.clone(), &args[0].1),
+								evaluate(context, eval_state, &args[1].1),
+							) {
+								// TODO: handle visibility (_include_hidden)
+								Val::Arr(body.fields().into_iter().map(Val::Str).collect())
+							} else {
+								panic!("bad objectFieldsEx call");
+							}
+						}
 						(ns, name) => panic!("Intristic not found: {}.{}", ns, name),
 					},
 					Val::Func(f) => f.evaluate(
@@ -402,6 +501,17 @@
 				}
 			}
 			Function(params, body) => evaluate_method(context, eval_state, body, params.clone()),
+			AssertExpr(AssertStmt(value, msg), returned) => {
+				if evaluate(context.clone(), eval_state.clone(), &value).try_cast_bool() {
+					evaluate(context, eval_state, returned)
+				}else {
+					if let Some(msg) = msg {
+						panic!("assertion failed ({:?}): {}", value, evaluate(context, eval_state, msg).try_cast_str());
+					} else {
+						panic!("assertion failed ({:?}): no message", value);
+					}
+				}
+			},
 			Error(e) => panic!("error: {}", evaluate(context, eval_state, e)),
 			IfElse {
 				cond,
modifiedcrates/jsonnet-evaluator/src/lib.rsdiffbeforeafterboth
--- a/crates/jsonnet-evaluator/src/lib.rs
+++ b/crates/jsonnet-evaluator/src/lib.rs
@@ -359,7 +359,12 @@
 
 	#[test]
 	fn json() {
-		println!("{:?}", eval_stdlib!(r#"std.manifestJson({a:3, b:4, c:6})"#));
+		println!("{:?}", eval_stdlib!(r#"std.manifestJsonEx({a:3, b:4, c:6},"")"#));
+	}
+
+	#[test]
+	fn test() {
+		assert_json_stdlib!(r#"[[a, b] for a in [1,2,3] for b in [4,5,6]]"#, "");
 	}
 
 	#[test]
modifiedcrates/jsonnet-evaluator/src/obj.rsdiffbeforeafterboth
--- a/crates/jsonnet-evaluator/src/obj.rs
+++ b/crates/jsonnet-evaluator/src/obj.rs
@@ -1,5 +1,5 @@
-use crate::{evaluate_binary_op, Binding, Val};
-use jsonnet_parser::{BinaryOpType, Visibility};
+use crate::{evaluate_add_op, Binding, Val};
+use jsonnet_parser::Visibility;
 use std::{
 	cell::RefCell,
 	collections::{BTreeMap, BTreeSet, HashMap},
@@ -90,9 +90,8 @@
 			(Some(k), Some(s)) => {
 				let our = k.invoke.0(Some(real_this.clone()), self.0.super_obj.clone());
 				if k.add {
-					s.get_raw(key, real_this).map_or(Some(our.clone()), |v| {
-						Some(evaluate_binary_op(&v, BinaryOpType::Add, &our))
-					})
+					s.get_raw(key, real_this)
+						.map_or(Some(our.clone()), |v| Some(evaluate_add_op(&v, &our)))
 				} else {
 					Some(our)
 				}
modifiedcrates/jsonnet-evaluator/src/val.rsdiffbeforeafterboth
--- a/crates/jsonnet-evaluator/src/val.rs
+++ b/crates/jsonnet-evaluator/src/val.rs
@@ -117,6 +117,18 @@
 	Intristic(String, String),
 }
 impl Val {
+	pub fn try_cast_bool(self) -> bool {
+		match self.unwrap_if_lazy() {
+			Val::Bool(v) => v,
+			v => panic!("expected bool, got {:?}", v),
+		}
+	}
+	pub fn try_cast_str(self) -> String {
+		match self.unwrap_if_lazy() {
+			Val::Str(v) => v,
+			v => panic!("expected bool, got {:?}", v),
+		}
+	}
 	pub fn unwrap_if_lazy(self) -> Self {
 		if let Val::Lazy(v) = self {
 			v.evaluate().unwrap_if_lazy()
modifiedcrates/jsonnet-parser/src/expr.rsdiffbeforeafterboth
--- a/crates/jsonnet-parser/src/expr.rs
+++ b/crates/jsonnet-parser/src/expr.rs
@@ -165,7 +165,7 @@
 	///    ]
 	///  ],
 	/// ```
-	ArrComp(LocExpr, ForSpecData, Vec<CompSpec>),
+	ArrComp(LocExpr, Vec<CompSpec>),
 
 	/// Object: {a: 2}
 	Obj(ObjBody),
modifiedcrates/jsonnet-parser/src/lib.rsdiffbeforeafterboth
125 pub rule string_expr(s: &ParserSettings) -> LocExpr = l(s, <s:string() {Expr::Str(s)}>)125 pub rule string_expr(s: &ParserSettings) -> LocExpr = l(s, <s:string() {Expr::Str(s)}>)
126 pub rule obj_expr(s: &ParserSettings) -> LocExpr = l(s,<"{" _ body:objinside(s) _ "}" {Expr::Obj(body)}>)126 pub rule obj_expr(s: &ParserSettings) -> LocExpr = l(s,<"{" _ body:objinside(s) _ "}" {Expr::Obj(body)}>)
127 pub rule array_expr(s: &ParserSettings) -> LocExpr = l(s,<"[" _ elems:(expr(s) ** comma()) _ comma()? "]" {Expr::Arr(elems)}>)127 pub rule array_expr(s: &ParserSettings) -> LocExpr = l(s,<"[" _ elems:(expr(s) ** comma()) _ comma()? "]" {Expr::Arr(elems)}>)
128 pub rule array_comp_expr(s: &ParserSettings) -> LocExpr = l(s,<"[" _ expr:expr(s) _ comma()? _ forspec:forspec(s) _ others:(others: compspec(s) _ {others})? "]" {Expr::ArrComp(expr, forspec, others.unwrap_or_default())}>)128 pub rule array_comp_expr(s: &ParserSettings) -> LocExpr = l(s,<"[" _ expr:expr(s) _ comma()? _ forspec:forspec(s) _ others:(others: compspec(s) _ {others})? "]" {Expr::ArrComp(expr, [vec![CompSpec::ForSpec(forspec)], others.unwrap_or_default()].concat())}>)
129 pub rule number_expr(s: &ParserSettings) -> LocExpr = l(s,<n:number() { expr::Expr::Num(n) }>)129 pub rule number_expr(s: &ParserSettings) -> LocExpr = l(s,<n:number() { expr::Expr::Num(n) }>)
130 pub rule var_expr(s: &ParserSettings) -> LocExpr = l(s,<n:id() { expr::Expr::Var(n) }>)130 pub rule var_expr(s: &ParserSettings) -> LocExpr = l(s,<n:id() { expr::Expr::Var(n) }>)
131 pub rule if_then_else_expr(s: &ParserSettings) -> LocExpr = l(s,<cond:ifspec(s) _ keyword("then") _ cond_then:expr(s) cond_else:(_ keyword("else") _ e:expr(s) {e})? {Expr::IfElse{131 pub rule if_then_else_expr(s: &ParserSettings) -> LocExpr = l(s,<cond:ifspec(s) _ keyword("then") _ cond_then:expr(s) cond_else:(_ keyword("else") _ e:expr(s) {e})? {Expr::IfElse{
252 jsonnet_parser::jsonnet(str, settings)252 jsonnet_parser::jsonnet(str, settings)
253}253}
254
255#[macro_export]
256macro_rules! el {
257 ($expr:expr) => {
258 LocExpr(std::rc::Rc::new($expr), None)
259 };
260}
254261
255#[cfg(test)]262#[cfg(test)]
256pub mod tests {263pub mod tests {
257 use super::{expr::*, parse};264 use super::{expr::*, parse};
258 use crate::ParserSettings;265 use crate::ParserSettings;
259 macro_rules! el {266
260 ($expr:expr) => {
261 LocExpr(std::rc::Rc::new($expr), None)
262 };
263 }
264 macro_rules! parse {267 macro_rules! parse {
265 ($s:expr) => {268 ($s:expr) => {
266 parse(269 parse(
390 )),393 )),
391 ArgsDesc(vec![Arg(None, el!(Var("x".to_owned())))])394 ArgsDesc(vec![Arg(None, el!(Var("x".to_owned())))])
392 )),395 )),
393 ForSpecData("x".to_owned(), el!(Var("arr".to_owned()))),396 vec![CompSpec::ForSpec(ForSpecData(
394 vec![]397 "x".to_owned(),
398 el!(Var("arr".to_owned()))
399 ))]
395 )),400 )),
396 )401 )
397 }402 }
403 parse!("[k for k in std.objectFields(patch) if patch[k] == null]"),408 parse!("[k for k in std.objectFields(patch) if patch[k] == null]"),
404 el!(ArrComp(409 el!(ArrComp(
405 el!(Var("k".to_owned())),410 el!(Var("k".to_owned())),
411 vec![
406 ForSpecData(412 CompSpec::ForSpec(ForSpecData(
407 "k".to_owned(),413 "k".to_owned(),
408 el!(Apply(414 el!(Apply(
409 el!(Index(415 el!(Index(
412 )),418 )),
413 ArgsDesc(vec![Arg(None, el!(Var("patch".to_owned())))])419 ArgsDesc(vec![Arg(None, el!(Var("patch".to_owned())))])
414 ))420 ))
415 ),421 )),
416 vec![CompSpec::IfSpec(IfSpecData(el!(BinaryOp(422 CompSpec::IfSpec(IfSpecData(el!(BinaryOp(
417 el!(Index(423 el!(Index(
418 el!(Var("patch".to_owned())),424 el!(Var("patch".to_owned())),
419 el!(Var("k".to_owned()))425 el!(Var("k".to_owned()))
420 )),426 )),
421 BinaryOpType::Eq,427 BinaryOpType::Eq,
422 el!(Literal(LiteralType::Null))428 el!(Literal(LiteralType::Null))
423 ))))]429 ))))
430 ]
424 ))431 ))
425 );432 );
426 }433 }