From 4aecc221ffa52f19aaca13c212bbd95fae721764 Mon Sep 17 00:00:00 2001 From: Лач Date: Wed, 03 Jun 2020 10:53:11 +0000 Subject: [PATCH] feat(evaluator): ArrComp support --- --- 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, --- 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, FutureNewBindings); future_wrapper!(ObjValue, FutureObjValue); +pub fn evaluate_comp( + context: Context, + eval_state: EvaluationState, + value: &LocExpr, + specs: &[CompSpec], +) -> Option> { + 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, --- 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] --- 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) } --- 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() --- a/crates/jsonnet-parser/src/expr.rs +++ b/crates/jsonnet-parser/src/expr.rs @@ -165,7 +165,7 @@ /// ] /// ], /// ``` - ArrComp(LocExpr, ForSpecData, Vec), + ArrComp(LocExpr, Vec), /// Object: {a: 2} Obj(ObjBody), --- a/crates/jsonnet-parser/src/lib.rs +++ b/crates/jsonnet-parser/src/lib.rs @@ -125,7 +125,7 @@ pub rule string_expr(s: &ParserSettings) -> LocExpr = l(s, ) pub rule obj_expr(s: &ParserSettings) -> LocExpr = l(s,<"{" _ body:objinside(s) _ "}" {Expr::Obj(body)}>) pub rule array_expr(s: &ParserSettings) -> LocExpr = l(s,<"[" _ elems:(expr(s) ** comma()) _ comma()? "]" {Expr::Arr(elems)}>) - 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())}>) + 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())}>) pub rule number_expr(s: &ParserSettings) -> LocExpr = l(s,) pub rule var_expr(s: &ParserSettings) -> LocExpr = l(s,) pub rule if_then_else_expr(s: &ParserSettings) -> LocExpr = l(s, { + LocExpr(std::rc::Rc::new($expr), None) + }; +} + #[cfg(test)] pub mod tests { use super::{expr::*, parse}; use crate::ParserSettings; - macro_rules! el { - ($expr:expr) => { - LocExpr(std::rc::Rc::new($expr), None) - }; - } + macro_rules! parse { ($s:expr) => { parse( @@ -390,8 +393,10 @@ )), ArgsDesc(vec![Arg(None, el!(Var("x".to_owned())))]) )), - ForSpecData("x".to_owned(), el!(Var("arr".to_owned()))), - vec![] + vec![CompSpec::ForSpec(ForSpecData( + "x".to_owned(), + el!(Var("arr".to_owned())) + ))] )), ) } @@ -403,24 +408,26 @@ parse!("[k for k in std.objectFields(patch) if patch[k] == null]"), el!(ArrComp( el!(Var("k".to_owned())), - ForSpecData( - "k".to_owned(), - el!(Apply( + vec![ + CompSpec::ForSpec(ForSpecData( + "k".to_owned(), + el!(Apply( + el!(Index( + el!(Var("std".to_owned())), + el!(Str("objectFields".to_owned())) + )), + ArgsDesc(vec![Arg(None, el!(Var("patch".to_owned())))]) + )) + )), + CompSpec::IfSpec(IfSpecData(el!(BinaryOp( el!(Index( - el!(Var("std".to_owned())), - el!(Str("objectFields".to_owned())) + el!(Var("patch".to_owned())), + el!(Var("k".to_owned())) )), - ArgsDesc(vec![Arg(None, el!(Var("patch".to_owned())))]) - )) - ), - vec![CompSpec::IfSpec(IfSpecData(el!(BinaryOp( - el!(Index( - el!(Var("patch".to_owned())), - el!(Var("k".to_owned())) - )), - BinaryOpType::Eq, - el!(Literal(LiteralType::Null)) - ))))] + BinaryOpType::Eq, + el!(Literal(LiteralType::Null)) + )))) + ] )) ); } -- gitstuff