--- a/cmds/jrsonnet/Cargo.toml +++ b/cmds/jrsonnet/Cargo.toml @@ -31,7 +31,7 @@ ] # Destructuring of locals exp-destruct = ["jrsonnet-evaluator/exp-destruct"] -# Iteration over objects yields [key, value] elements +# Iteration over objects using [key]: value syntax exp-object-iteration = ["jrsonnet-evaluator/exp-object-iteration"] # Bigint type exp-bigint = ["jrsonnet-evaluator/exp-bigint", "jrsonnet-cli/exp-bigint"] --- a/crates/jrsonnet-evaluator/Cargo.toml +++ b/crates/jrsonnet-evaluator/Cargo.toml @@ -40,8 +40,12 @@ "jrsonnet-peg-parser?/exp-destruct", "jrsonnet-ir-parser?/exp-destruct", ] -# Iteration over objects yields [key, value] elements -exp-object-iteration = [] +# Iteration over objects using [key]: value syntax +exp-object-iteration = [ + "jrsonnet-ir/exp-object-iteration", + "jrsonnet-peg-parser?/exp-object-iteration", + "jrsonnet-ir-parser?/exp-object-iteration", +] # Bigint type exp-bigint = ["num-bigint", "jrsonnet-types/exp-bigint"] # obj?.field, obj?.['field'] --- a/crates/jrsonnet-evaluator/src/analyze.rs +++ b/crates/jrsonnet-evaluator/src/analyze.rs @@ -1862,6 +1862,8 @@ ); (r, rest) } + #[cfg(feature = "exp-object-iteration")] + CompSpec::ForObjSpec(_) => todo!(), } } let outer_depth = stack.depth; --- a/crates/jrsonnet-ir-parser/Cargo.toml +++ b/crates/jrsonnet-ir-parser/Cargo.toml @@ -11,9 +11,10 @@ [features] default = [] -experimental = ["exp-null-coaelse", "exp-destruct"] +experimental = ["exp-null-coaelse", "exp-destruct", "exp-object-iteration"] exp-null-coaelse = ["jrsonnet-ir/exp-null-coaelse"] exp-destruct = ["jrsonnet-ir/exp-destruct"] +exp-object-iteration = ["jrsonnet-ir/exp-object-iteration"] [dependencies] insta.workspace = true --- a/crates/jrsonnet-ir-parser/src/lib.rs +++ b/crates/jrsonnet-ir-parser/src/lib.rs @@ -66,6 +66,13 @@ !self.at_eof() && self.peek() == kind } + #[allow(dead_code)] + fn nth(&self, n: usize) -> SyntaxKind { + self.lexemes + .get(self.offset + n) + .map_or(SyntaxKind::EOF, |l| l.kind) + } + fn eat_any(&mut self) { self.offset += 1; } @@ -561,20 +568,36 @@ } } -fn for_spec(p: &mut Parser<'_>) -> Result { +fn for_spec(p: &mut Parser<'_>) -> Result { p.eat(T![for])?; + #[cfg(feature = "exp-object-iteration")] + if p.at(T!['[']) && p.nth(1) == SyntaxKind::IDENT && p.nth(2) == T![']'] && p.nth(3) == T![:] { + p.eat(T!['['])?; + let key = ident(p)?; + p.eat(T![']'])?; + let visibility = visibility(p)?; + let value = destruct(p)?; + p.eat(T![in])?; + let over = expr(p)?; + return Ok(CompSpec::ForObjSpec(jrsonnet_ir::ForObjSpecData { + key, + visibility, + value, + over, + })); + } let d = destruct(p)?; p.eat(T![in])?; let over = expr(p)?; - Ok(ForSpecData { destruct: d, over }) + Ok(CompSpec::ForSpec(ForSpecData { destruct: d, over })) } fn compspecs(p: &mut Parser<'_>) -> Result> { let mut specs = Vec::new(); - specs.push(CompSpec::ForSpec(for_spec(p)?)); + specs.push(for_spec(p)?); loop { if p.at(T![for]) { - specs.push(CompSpec::ForSpec(for_spec(p)?)); + specs.push(for_spec(p)?); } else if p.at(T![if]) { let isd = if_spec_data(p)?; specs.push(CompSpec::IfSpec(isd)); --- a/crates/jrsonnet-ir/Cargo.toml +++ b/crates/jrsonnet-ir/Cargo.toml @@ -12,9 +12,10 @@ [features] default = [] -experimental = ["exp-destruct", "exp-null-coaelse"] +experimental = ["exp-destruct", "exp-null-coaelse", "exp-object-iteration"] exp-destruct = [] exp-null-coaelse = [] +exp-object-iteration = [] [dependencies] jrsonnet-interner.workspace = true --- a/crates/jrsonnet-ir/src/expr.rs +++ b/crates/jrsonnet-ir/src/expr.rs @@ -314,10 +314,21 @@ pub over: Expr, } +#[cfg(feature = "exp-object-iteration")] #[derive(Debug, PartialEq, Acyclic)] +pub struct ForObjSpecData { + pub key: IStr, + pub visibility: Visibility, + pub value: Destruct, + pub over: Expr, +} + +#[derive(Debug, PartialEq, Acyclic)] pub enum CompSpec { IfSpec(IfSpecData), ForSpec(ForSpecData), + #[cfg(feature = "exp-object-iteration")] + ForObjSpec(ForObjSpecData), } #[derive(Debug, PartialEq, Acyclic)] --- a/crates/jrsonnet-ir/src/visit.rs +++ b/crates/jrsonnet-ir/src/visit.rs @@ -1,5 +1,7 @@ use jrsonnet_interner::IStr; +#[cfg(feature = "exp-object-iteration")] +use crate::ForObjSpecData; use crate::{ ArgsDesc, AssertExpr, AssertStmt, BinaryOp, BindSpec, CompSpec, Destruct, Expr, ExprParam, ExprParams, FieldMember, FieldName, ForSpecData, IfElse, IfSpecData, ImportKind, IndexPart, @@ -69,6 +71,17 @@ visit_destruct(v, destruct); v.visit_expr(over); } + #[cfg(feature = "exp-object-iteration")] + CompSpec::ForObjSpec(for_obj_spec_data) => { + let ForObjSpecData { + key: _, + visibility: _, + value, + over, + } = for_obj_spec_data; + visit_destruct(v, value); + v.visit_expr(over); + } } } pub fn visit_params(v: &mut V, par: &ExprParams) { --- a/crates/jrsonnet-peg-parser/Cargo.toml +++ b/crates/jrsonnet-peg-parser/Cargo.toml @@ -22,6 +22,7 @@ [features] default = [] -experimental = ["exp-destruct", "exp-null-coaelse"] +experimental = ["exp-destruct", "exp-null-coaelse", "exp-object-iteration"] exp-destruct = ["jrsonnet-ir/exp-destruct"] exp-null-coaelse = ["jrsonnet-ir/exp-null-coaelse"] +exp-object-iteration = ["jrsonnet-ir/exp-object-iteration"] --- a/crates/jrsonnet-peg-parser/src/lib.rs +++ b/crates/jrsonnet-peg-parser/src/lib.rs @@ -241,11 +241,21 @@ = i:spanned(, s) _ cond:expr(s) {IfSpecData { span: i.span, cond }} pub rule forspec(s: &ParserSettings) -> ForSpecData = keyword("for") _ destruct:destruct(s) _ keyword("in") _ over:expr(s) { ForSpecData { destruct, over } } + rule ensure_object_iteration() + = "" {? + #[cfg(not(feature = "exp-object-iteration"))] return Err("!!!experimental object iteration was not enabled"); + #[cfg(feature = "exp-object-iteration")] Ok(()) + } + pub rule forobjspec(s: &ParserSettings) -> CompSpec + = ensure_object_iteration() keyword("for") _ "[" _ key:id() _ "]" _ vis:visibility() _ value:destruct(s) _ keyword("in") _ over:expr(s) { + #[cfg(feature = "exp-object-iteration")] return CompSpec::ForObjSpec(jrsonnet_ir::ForObjSpecData { key, visibility: vis, value, over }); + #[cfg(not(feature = "exp-object-iteration"))] unreachable!("ensure_object_iteration will fail if feature is not enabled") + } rule compspec(s: &ParserSettings) -> CompSpec - = i:ifspec(s) { CompSpec::IfSpec(i) } / f:forspec(s) {CompSpec::ForSpec(f)} + = i:ifspec(s) { CompSpec::IfSpec(i) } / f:forobjspec(s) { f } / f:forspec(s) {CompSpec::ForSpec(f)} pub rule compspecs(s: &ParserSettings) -> Vec = specs:compspec(s) ++ _ {? - if !matches!(specs[0], CompSpec::ForSpec(_)) { + if matches!(specs[0], CompSpec::IfSpec(_)) { return Err("") } Ok(specs)