--- a/crates/jsonnet-evaluator/src/error.rs +++ b/crates/jsonnet-evaluator/src/error.rs @@ -14,6 +14,8 @@ UndefinedExternalVariable(String), + FieldMustBeStringGot(ValType), + RuntimeError(String), StackOverflow, FractionalIndex, --- a/crates/jsonnet-evaluator/src/evaluate.rs +++ b/crates/jsonnet-evaluator/src/evaluate.rs @@ -1,6 +1,6 @@ use crate::{ context_creator, create_error, future_wrapper, lazy_val, push, with_state, Context, - ContextCreator, FuncDesc, LazyBinding, LazyVal, ObjMember, ObjValue, Result, Val, + ContextCreator, Error, FuncDesc, LazyBinding, LazyVal, ObjMember, ObjValue, Result, Val, }; use closure::closure; use jsonnet_parser::{ @@ -167,13 +167,14 @@ future_wrapper!(HashMap, FutureNewBindings); future_wrapper!(ObjValue, FutureObjValue); -pub fn evaluate_comp( +#[inline(always)] +pub fn evaluate_comp( context: Context, - value: &LocExpr, + value: &impl Fn(Context) -> Result, specs: &[CompSpec], -) -> Result>> { +) -> Result>> { Ok(match specs.get(0) { - None => Some(vec![evaluate(context, &value)?]), + None => Some(vec![value(context)?]), Some(CompSpec::IfSpec(IfSpecData(cond))) => { if evaluate(context.clone(), &cond)?.try_cast_bool("if spec")? { evaluate_comp(context, value, &specs[1..])? @@ -193,7 +194,7 @@ &specs[1..], )?); } - Some(out.iter().flatten().flatten().cloned().collect()) + Some(out.into_iter().flatten().flatten().collect()) } _ => panic!("for expression evaluated to non-iterable value"), } @@ -208,7 +209,7 @@ let new_bindings = FutureNewBindings::new(); let future_this = FutureObjValue::new(); let context_creator = context_creator!( - closure!(clone context, clone new_bindings, clone future_this, |this: Option, super_obj: Option| { + closure!(clone context, clone new_bindings, |this: Option, super_obj: Option| { Ok(context.clone().extend_unbound( new_bindings.clone().unwrap(), context.clone().dollar().clone().or_else(||this.clone()), @@ -301,7 +302,70 @@ } future_this.fill(ObjValue::new(None, Rc::new(new_members))) } - _ => todo!(), + ObjBody::ObjComp { + pre_locals, + key, + value, + post_locals, + compspecs, + } => { + let future_this = FutureObjValue::new(); + let mut new_members = BTreeMap::new(); + for (k, v) in evaluate_comp( + context.clone(), + &|ctx| { + let new_bindings = FutureNewBindings::new(); + let context_creator = context_creator!( + closure!(clone context, clone new_bindings, |this: Option, super_obj: Option| { + Ok(context.clone().extend_unbound( + new_bindings.clone().unwrap(), + context.clone().dollar().clone().or_else(||this.clone()), + None, + super_obj + )?) + }) + ); + let mut bindings: HashMap = HashMap::new(); + for (n, b) in pre_locals + .iter() + .chain(post_locals.iter()) + .map(|b| evaluate_binding(b, context_creator.clone())) + { + bindings.insert(n, b); + } + let bindings = new_bindings.fill(bindings); + let ctx = ctx.extend_unbound(bindings, None, None, None)?; + let key = evaluate(ctx.clone(), &key)?; + let value = LazyBinding::Bindable(Rc::new( + closure!(clone ctx, clone value, |this, _super_obj| { + Ok(LazyVal::new_resolved(evaluate(ctx.extend(HashMap::new(), None, this, None)?, &value)?)) + }), + )); + + Ok((key, value)) + }, + &compspecs, + )? + .unwrap() + { + match k { + Val::Null => {} + Val::Str(n) => { + new_members.insert( + n, + ObjMember { + add: false, + visibility: Visibility::Normal, + invoke: v, + }, + ); + } + v => create_error(Error::FieldMustBeStringGot(v.value_type()?))?, + } + } + + future_this.fill(ObjValue::new(None, Rc::new(new_members))) + } }) } @@ -405,7 +469,7 @@ } ArrComp(expr, compspecs) => Val::Arr( // First compspec should be forspec, so no "None" possible here - evaluate_comp(context, expr, compspecs)?.unwrap(), + evaluate_comp(context, &|ctx| evaluate(ctx, expr), compspecs)?.unwrap(), ), Obj(body) => Val::Obj(evaluate_object(context, body.clone())?), ObjExtend(s, t) => evaluate_add_op( --- a/crates/jsonnet-evaluator/src/lib.rs +++ b/crates/jsonnet-evaluator/src/lib.rs @@ -493,6 +493,14 @@ } #[test] + fn object_comp() { + assert_json!( + r#"{local t = "a", ["h"+i+"_"+z]: if "h"+(i-1)+"_"+z in self then t+1 else 0+t for i in [1,2,3] for z in [2,3,4] if z != i}"#, + "{\"h1_2\": \"0a\",\"h1_3\": \"0a\",\"h1_4\": \"0a\",\"h2_3\": \"a1\",\"h2_4\": \"a1\",\"h3_2\": \"0a\",\"h3_4\": \"a1\"}" + ) + } + + #[test] fn direct_self() { println!( "{:#?}", --- a/crates/jsonnet-parser/src/expr.rs +++ b/crates/jsonnet-parser/src/expr.rs @@ -120,7 +120,7 @@ key: LocExpr, value: LocExpr, post_locals: Vec, - rest: Vec, + compspecs: Vec, }, } --- a/crates/jsonnet-parser/src/lib.rs +++ b/crates/jsonnet-parser/src/lib.rs @@ -138,7 +138,7 @@ key, value, post_locals, - rest: [vec![CompSpec::ForSpec(forspec)], others.unwrap_or_default()].concat(), + compspecs: [vec![CompSpec::ForSpec(forspec)], others.unwrap_or_default()].concat(), } } / members:(member(s) ** comma()) comma()? {expr::ObjBody::MemberList(members)}