--- a/cmds/jrsonnet/Cargo.toml +++ b/cmds/jrsonnet/Cargo.toml @@ -17,6 +17,9 @@ ] # Destructuring of locals exp-destruct = ["jrsonnet-evaluator/exp-destruct"] +# Iteration over objects yields [key, value] elements +exp-object-iteration = ["jrsonnet-evaluator/exp-object-iteration"] + # std.thisFile support legacy-this-file = ["jrsonnet-cli/legacy-this-file"] --- a/crates/jrsonnet-evaluator/Cargo.toml +++ b/crates/jrsonnet-evaluator/Cargo.toml @@ -20,6 +20,9 @@ exp-preserve-order = [] # Implements field destructuring exp-destruct = ["jrsonnet-parser/exp-destruct"] +# Iteration over objects yields [key, value] elements +exp-object-iteration = [] + # Improves performance, and implements some useful things using nightly-only features nightly = [] --- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs @@ -66,7 +66,7 @@ Val::Arr(list) => { for item in list.iter_lazy() { let fctx = Pending::new(); - let mut new_bindings = GcHashMap::new(); + let mut new_bindings = GcHashMap::with_capacity(var.capacity_hint()); destruct(var, item, fctx.clone(), &mut new_bindings)?; let ctx = ctx .clone() @@ -76,6 +76,46 @@ evaluate_comp(ctx, &specs[1..], callback)?; } } + #[cfg(feature = "exp-object-iteration")] + Val::Obj(obj) => { + for field in obj.fields( + // TODO: Should there be ability to preserve iteration order? + #[cfg(feature = "exp-preserve-order")] + false, + ) { + #[derive(Trace)] + struct ObjectFieldThunk { + obj: ObjValue, + field: IStr, + } + impl ThunkValue for ObjectFieldThunk { + type Output = Val; + + fn get(self: Box) -> Result { + self.obj.get(self.field).transpose().expect( + "field exists, as field name was obtained from object.fields()", + ) + } + } + + let fctx = Pending::new(); + let mut new_bindings = GcHashMap::with_capacity(var.capacity_hint()); + let value = Thunk::evaluated(Val::Arr(ArrValue::Lazy(Cc::new(vec![ + Thunk::evaluated(Val::Str(field.clone())), + Thunk::new(tb!(ObjectFieldThunk { + field: field.clone(), + obj: obj.clone(), + })), + ])))); + destruct(var, value, fctx.clone(), &mut new_bindings)?; + let ctx = ctx + .clone() + .extend(new_bindings, None, None, None) + .into_future(fctx); + + evaluate_comp(ctx, &specs[1..], callback)?; + } + } _ => throw!(InComprehensionCanOnlyIterateOverArray), }, } @@ -99,7 +139,8 @@ fn bind(&self, sup: Option, this: Option) -> Result { let fctx = Context::new_future(); - let mut new_bindings = GcHashMap::new(); + let mut new_bindings = + GcHashMap::with_capacity(self.locals.iter().map(BindSpec::capacity_hint).sum()); for b in self.locals.iter() { evaluate_dest(b, fctx.clone(), &mut new_bindings)?; } @@ -446,7 +487,7 @@ }, LocalExpr(bindings, returned) => { let mut new_bindings: GcHashMap> = - GcHashMap::with_capacity(bindings.len()); + GcHashMap::with_capacity(bindings.iter().map(BindSpec::capacity_hint).sum()); let fctx = Context::new_future(); for b in bindings { evaluate_dest(b, fctx.clone(), &mut new_bindings)?;