From 145d305c94c234782e0b13b53267d1f119fe739c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sun, 15 Mar 2026 13:36:34 +0100 Subject: [PATCH 1/4] Add parsing of '.' (dot) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- src/lexer/mod.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/lexer/mod.rs b/src/lexer/mod.rs index 6fb66a2..277f3e3 100644 --- a/src/lexer/mod.rs +++ b/src/lexer/mod.rs @@ -239,6 +239,7 @@ pub enum TokenOperator { Lesser, LesserOrEqual, QuestionMark, + Dot, } #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -569,6 +570,7 @@ fn parse_operator<'input>(input: &mut Input<'input>) -> PResult<'input, Template cut_err(fail), )), '?' => empty.value(TokenOperator::QuestionMark), + '.' => empty.value(TokenOperator::Dot), _ => fail, }, ) @@ -930,4 +932,28 @@ mod tests { ) "#); } + + #[test] + fn parse_dot() { + let input = "{{= foo?.bar }}"; + let output = parse(input.into()); + + insta::assert_debug_snapshot!(output, @r#" + Ok( + ParsedTemplate { + tokens: [ + [LeftDelim]"{{" (0..2), + [WantsOutput]"=" (2..3), + [Whitespace]" " (3..4), + [Ident]"foo" (4..7), + [Operator(QuestionMark)]"?" (7..8), + [Operator(Dot)]"." (8..9), + [Ident]"bar" (9..12), + [Whitespace]" " (12..13), + [RightDelim]"}}" (13..15), + ], + }, + ) + "#); + } } From a590839b219c80980b325926a8bfb7340eb72349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sun, 15 Mar 2026 13:39:08 +0100 Subject: [PATCH 2/4] Add parsing of dot operator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- src/parser/mod.rs | 12 +++++++++++ ..._parser__tests__check_access_operator.snap | 21 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 src/parser/snapshots/nomo__parser__tests__check_access_operator.snap diff --git a/src/parser/mod.rs b/src/parser/mod.rs index d45eea7..94fba56 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -710,6 +710,7 @@ fn parse_expression<'input>( Left GreaterOrEqual => 15, Left Lesser => 15, Left LesserOrEqual => 15, + Left Dot => 23, ] }).postfix(dispatch! { surrounded(ws, parse_operator); TokenOperator::QuestionMark => Postfix(22, |input, rhs| { @@ -1071,4 +1072,15 @@ mod tests { insta::assert_debug_snapshot!(ast); } + + #[test] + fn check_access_operator() { + let input = "{{= foo?.bar }}"; + + let parsed = crate::lexer::parse(input.into()).unwrap(); + + let ast = panic_pretty(input, parse(parsed.tokens())); + + insta::assert_debug_snapshot!(ast); + } } diff --git a/src/parser/snapshots/nomo__parser__tests__check_access_operator.snap b/src/parser/snapshots/nomo__parser__tests__check_access_operator.snap new file mode 100644 index 0000000..3fcf602 --- /dev/null +++ b/src/parser/snapshots/nomo__parser__tests__check_access_operator.snap @@ -0,0 +1,21 @@ +--- +source: src/parser/mod.rs +expression: ast +--- +TemplateAst { + root: [ + Interpolation { + prev_whitespace_content: None, + expression: Operation { + op: Dot, + lhs: ConditionalAccess( + [Ident]"foo" (4..7), + ), + rhs: VariableAccess( + [Ident]"bar" (9..12), + ), + }, + post_whitespace_content: None, + }, + ], +} From 4f770c1f24daefe3f4eafb3784248650e89463ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sun, 15 Mar 2026 13:48:51 +0100 Subject: [PATCH 3/4] Abstract infix macro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- src/compiler/mod.rs | 6 ++- src/parser/mod.rs | 37 +++++++++++-------- ..._parser__tests__check_access_operator.snap | 2 +- ...o__parser__tests__check_function_call.snap | 4 +- ...rser__tests__check_logical_expression.snap | 10 ++--- ..._parser__tests__check_math_expression.snap | 6 +-- tests/cases/maths.2-ast.snap | 12 +++--- 7 files changed, 42 insertions(+), 35 deletions(-) diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index 98e350e..00fe68e 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -424,8 +424,9 @@ fn emit_ast_expr( | TemplateAstExpr::Invalid { .. } | TemplateAstExpr::Literal { .. } | TemplateAstExpr::FunctionCall { .. } - | TemplateAstExpr::Operation { .. } + | TemplateAstExpr::MathOperation { .. } | TemplateAstExpr::ConditionalAccess { .. } + | TemplateAstExpr::AccessOperation { .. } | TemplateAstExpr::VariableAccess { .. } => eval.push(Instruction::Abort), } } @@ -458,7 +459,7 @@ fn emit_expr_load( slot: emit_slot, }); } - TemplateAstExpr::Operation { op, lhs, rhs } => { + TemplateAstExpr::MathOperation { op, lhs, rhs } => { let left_slot = machine.reserve_slot(); emit_expr_load(machine, eval, left_slot, lhs); let right_slot = machine.reserve_slot(); @@ -491,6 +492,7 @@ fn emit_expr_load( } TemplateAstExpr::ConditionalChain { .. } => todo!(), TemplateAstExpr::ElseConditional { .. } => todo!(), + TemplateAstExpr::AccessOperation { .. } => todo!(), TemplateAstExpr::EndBlock => todo!(), TemplateAstExpr::Block { .. } => todo!(), TemplateAstExpr::ForChain { .. } => todo!(), diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 94fba56..d7e1271 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -262,7 +262,12 @@ pub enum TemplateAstExpr<'input> { expression: Option>>, }, Invalid(&'input [TemplateToken]), - Operation { + MathOperation { + op: TokenOperator, + lhs: Box>, + rhs: Box>, + }, + AccessOperation { op: TokenOperator, lhs: Box>, rhs: Box>, @@ -681,10 +686,10 @@ fn parse_expression<'input>( input: &mut Input<'input>, ) -> Result, AstError> { macro_rules! infix { - ($parser:expr => [ $($side:tt $val:tt => $prec:expr),* $(,)? ]) => { + ($parser:expr => [ $($side:tt $op:tt $val:tt => $prec:expr),* $(,)? ]) => { dispatch! { surrounded(ws, parse_operator); $( - TokenOperator::$val => $side($prec, |_, lhs, rhs| Ok(TemplateAstExpr::Operation { + TokenOperator::$val => $side($prec, |_, lhs, rhs| Ok(TemplateAstExpr::$op { op: TokenOperator::$val, lhs: Box::new(lhs), rhs: Box::new(rhs) @@ -698,19 +703,19 @@ fn parse_expression<'input>( "expression", expression(surrounded(ws, parse_operand)).infix(infix! { surrounded(ws, parse_operator) => [ - Left Plus => 18, - Left Minus => 18, - Left Times => 20, - Left Divide => 20, - Left And => 10, - Left Or => 7, - Left Equal => 12, - Left NotEqual => 12, - Left Greater => 15, - Left GreaterOrEqual => 15, - Left Lesser => 15, - Left LesserOrEqual => 15, - Left Dot => 23, + Left MathOperation Plus => 18, + Left MathOperation Minus => 18, + Left MathOperation Times => 20, + Left MathOperation Divide => 20, + Left MathOperation And => 10, + Left MathOperation Or => 7, + Left MathOperation Equal => 12, + Left MathOperation NotEqual => 12, + Left MathOperation Greater => 15, + Left MathOperation GreaterOrEqual => 15, + Left MathOperation Lesser => 15, + Left MathOperation LesserOrEqual => 15, + Left AccessOperation Dot => 23, ] }).postfix(dispatch! { surrounded(ws, parse_operator); TokenOperator::QuestionMark => Postfix(22, |input, rhs| { diff --git a/src/parser/snapshots/nomo__parser__tests__check_access_operator.snap b/src/parser/snapshots/nomo__parser__tests__check_access_operator.snap index 3fcf602..fd84622 100644 --- a/src/parser/snapshots/nomo__parser__tests__check_access_operator.snap +++ b/src/parser/snapshots/nomo__parser__tests__check_access_operator.snap @@ -6,7 +6,7 @@ TemplateAst { root: [ Interpolation { prev_whitespace_content: None, - expression: Operation { + expression: AccessOperation { op: Dot, lhs: ConditionalAccess( [Ident]"foo" (4..7), diff --git a/src/parser/snapshots/nomo__parser__tests__check_function_call.snap b/src/parser/snapshots/nomo__parser__tests__check_function_call.snap index 40e920c..b85eeee 100644 --- a/src/parser/snapshots/nomo__parser__tests__check_function_call.snap +++ b/src/parser/snapshots/nomo__parser__tests__check_function_call.snap @@ -9,7 +9,7 @@ TemplateAst { expression: FunctionCall { name: [Ident]"foo" (4..7), args: [ - Operation { + MathOperation { op: Times, lhs: Literal { source: [Literal(Integer(2))]"2" (8..9), @@ -27,7 +27,7 @@ TemplateAst { FunctionCall { name: [Ident]"bar" (15..18), args: [ - Operation { + MathOperation { op: Plus, lhs: Literal { source: [Literal(Integer(2))]"2" (19..20), diff --git a/src/parser/snapshots/nomo__parser__tests__check_logical_expression.snap b/src/parser/snapshots/nomo__parser__tests__check_logical_expression.snap index aca365a..19b5f84 100644 --- a/src/parser/snapshots/nomo__parser__tests__check_logical_expression.snap +++ b/src/parser/snapshots/nomo__parser__tests__check_logical_expression.snap @@ -6,9 +6,9 @@ TemplateAst { root: [ Interpolation { prev_whitespace_content: None, - expression: Operation { + expression: MathOperation { op: Or, - lhs: Operation { + lhs: MathOperation { op: And, lhs: Literal { source: [Literal(Bool(true))]"true" (4..8), @@ -23,9 +23,9 @@ TemplateAst { }, }, }, - rhs: Operation { + rhs: MathOperation { op: And, - lhs: Operation { + lhs: MathOperation { op: GreaterOrEqual, lhs: Literal { source: [Literal(Integer(3))]"3" (21..22), @@ -40,7 +40,7 @@ TemplateAst { }, }, }, - rhs: Operation { + rhs: MathOperation { op: Equal, lhs: Literal { source: [Literal(Integer(5))]"5" (31..32), diff --git a/src/parser/snapshots/nomo__parser__tests__check_math_expression.snap b/src/parser/snapshots/nomo__parser__tests__check_math_expression.snap index 7ba6415..5b5a0e3 100644 --- a/src/parser/snapshots/nomo__parser__tests__check_math_expression.snap +++ b/src/parser/snapshots/nomo__parser__tests__check_math_expression.snap @@ -6,9 +6,9 @@ TemplateAst { root: [ Interpolation { prev_whitespace_content: None, - expression: Operation { + expression: MathOperation { op: Plus, - lhs: Operation { + lhs: MathOperation { op: Times, lhs: Literal { source: [Literal(Integer(5))]"5" (4..5), @@ -23,7 +23,7 @@ TemplateAst { }, }, }, - rhs: Operation { + rhs: MathOperation { op: Divide, lhs: Literal { source: [Literal(Integer(2))]"2" (12..13), diff --git a/tests/cases/maths.2-ast.snap b/tests/cases/maths.2-ast.snap index 45be132..4639215 100644 --- a/tests/cases/maths.2-ast.snap +++ b/tests/cases/maths.2-ast.snap @@ -9,7 +9,7 @@ TemplateAst { root: [ Interpolation { prev_whitespace_content: None, - expression: Operation { + expression: MathOperation { op: Times, lhs: Literal { source: [Literal(Integer(5))]"5" (4..5), @@ -30,9 +30,9 @@ TemplateAst { }, Interpolation { prev_whitespace_content: None, - expression: Operation { + expression: MathOperation { op: Plus, - lhs: Operation { + lhs: MathOperation { op: Times, lhs: Literal { source: [Literal(Integer(2))]"2" (17..18), @@ -47,7 +47,7 @@ TemplateAst { }, }, }, - rhs: Operation { + rhs: MathOperation { op: Times, lhs: Literal { source: [Literal(Integer(4))]"4" (25..26), @@ -69,9 +69,9 @@ TemplateAst { }, Interpolation { prev_whitespace_content: None, - expression: Operation { + expression: MathOperation { op: Plus, - lhs: Operation { + lhs: MathOperation { op: Divide, lhs: Literal { source: [Literal(Integer(3))]"3" (38..39), From ffd9baf90f56fac6530dfed1274443a3e4039a6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sun, 15 Mar 2026 14:31:46 +0100 Subject: [PATCH 4/4] Add deep access MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- src/compiler/mod.rs | 72 ++++- src/eval/mod.rs | 42 ++- src/parser/mod.rs | 4 +- src/value.rs | 11 +- tests/cases/deep_access.1-parsed.snap | 74 +++++ tests/cases/deep_access.2-ast.snap | 131 ++++++++ tests/cases/deep_access.3-instructions.snap | 313 ++++++++++++++++++++ tests/cases/deep_access.4-output.snap | 11 + tests/cases/deep_access.nomo | 16 + 9 files changed, 669 insertions(+), 5 deletions(-) create mode 100644 tests/cases/deep_access.1-parsed.snap create mode 100644 tests/cases/deep_access.2-ast.snap create mode 100644 tests/cases/deep_access.3-instructions.snap create mode 100644 tests/cases/deep_access.4-output.snap create mode 100644 tests/cases/deep_access.nomo diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index 00fe68e..4fc3644 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -108,6 +108,20 @@ pub enum Instruction { args: Vec, slot: VariableSlot, }, + LoadFromSlotToSlot { + from_slot: VariableSlot, + to_slot: VariableSlot, + }, + JumpIfUndefined { + slot: VariableSlot, + jump: LabelSlot, + }, + IndexSlotToSlot { + name: NomoInput, + from_slot: VariableSlot, + to_slot: VariableSlot, + fail_on_not_found: bool, + }, } #[derive(Debug, Clone)] @@ -452,6 +466,63 @@ fn emit_expr_load( fail_on_not_found: false, }); } + TemplateAstExpr::AccessOperation { op, lhs, rhs } => { + assert_eq!( + op, + &TokenOperator::Dot, + "Only dot can be used to access variables, this is a bug" + ); + fn inner_access_op( + eval: &mut Vec, + end_label: LabelSlot, + current_slot: VariableSlot, + next: &TemplateAstExpr<'_>, + ) { + match next { + TemplateAstExpr::ConditionalAccess(template_token) + | TemplateAstExpr::VariableAccess(template_token) => { + eval.push(Instruction::IndexSlotToSlot { + name: template_token.source().clone(), + from_slot: current_slot, + to_slot: current_slot, + fail_on_not_found: matches!( + next, + TemplateAstExpr::VariableAccess { .. } + ), + }); + eval.push(Instruction::JumpIfUndefined { + slot: current_slot, + jump: end_label, + }); + } + TemplateAstExpr::AccessOperation { op, lhs, rhs } => { + assert_eq!( + op, + &TokenOperator::Dot, + "Only dot can be used to access variables, this is a bug" + ); + + inner_access_op(eval, end_label, current_slot, lhs); + inner_access_op(eval, end_label, current_slot, rhs); + } + _ => unreachable!(), + } + } + + let end_label = machine.reserve_label(); + let start_slot = machine.reserve_slot(); + emit_expr_load(machine, eval, start_slot, lhs); + eval.push(Instruction::JumpIfUndefined { + slot: start_slot, + jump: end_label, + }); + inner_access_op(eval, end_label, start_slot, rhs); + machine.assign_label(end_label, eval.len()); + eval.push(Instruction::LoadFromSlotToSlot { + from_slot: start_slot, + to_slot: emit_slot, + }); + } TemplateAstExpr::Literal { source, value } => { eval.push(Instruction::LoadLiteralToSlot { source: source.clone(), @@ -492,7 +563,6 @@ fn emit_expr_load( } TemplateAstExpr::ConditionalChain { .. } => todo!(), TemplateAstExpr::ElseConditional { .. } => todo!(), - TemplateAstExpr::AccessOperation { .. } => todo!(), TemplateAstExpr::EndBlock => todo!(), TemplateAstExpr::Block { .. } => todo!(), TemplateAstExpr::ForChain { .. } => todo!(), diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 62a013f..283432a 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -119,7 +119,7 @@ pub fn execute( } else if matches!(value, NomoValue::Undefined) { String::new() } else { - panic!("Unknown variable"); + panic!("Could not print out value {value:?}"); }; output.push_str(&value); @@ -243,6 +243,46 @@ pub fn execute( scopes.insert_into_slot(*slot, value); } + Instruction::LoadFromSlotToSlot { from_slot, to_slot } => { + let value = scopes.get(from_slot); + scopes.insert_into_slot(*to_slot, value.clone()); + } + Instruction::JumpIfUndefined { slot, jump } => { + let is_undefined = matches!(scopes.get(slot), NomoValue::Undefined); + if is_undefined { + let Some(new_ip) = vm.labels.get(jump) else { + return Err(EvaluationError::LabelNotFound); + }; + + ip = *new_ip; + continue; + } + } + Instruction::IndexSlotToSlot { + name, + from_slot, + to_slot, + fail_on_not_found, + } => { + let value = scopes.get(from_slot); + + match value { + NomoValue::Object { value } => { + let value = value.get(name.as_str()); + + let value = if let Some(value) = value { + value.clone() + } else if *fail_on_not_found { + panic!("Could not index"); + } else { + NomoValue::Undefined + }; + + scopes.insert_into_slot(dbg!(*to_slot), dbg!(value)); + } + _ => panic!("Invalid indexing"), + } + } } ip += 1; diff --git a/src/parser/mod.rs b/src/parser/mod.rs index d7e1271..43481a2 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -715,10 +715,10 @@ fn parse_expression<'input>( Left MathOperation GreaterOrEqual => 15, Left MathOperation Lesser => 15, Left MathOperation LesserOrEqual => 15, - Left AccessOperation Dot => 23, + Left AccessOperation Dot => 22, ] }).postfix(dispatch! { surrounded(ws, parse_operator); - TokenOperator::QuestionMark => Postfix(22, |input, rhs| { + TokenOperator::QuestionMark => Postfix(23, |input, rhs| { match rhs { TemplateAstExpr::VariableAccess(access) => Ok(TemplateAstExpr::ConditionalAccess(access)), _ => Err(AstError::from_input(input)), diff --git a/src/value.rs b/src/value.rs index c587f82..ab317bb 100644 --- a/src/value.rs +++ b/src/value.rs @@ -54,6 +54,8 @@ impl NomoValue { pub fn as_bool(&self) -> Option { if let Self::Bool { value } = self { Some(*value) + } else if let Self::Undefined = self { + Some(false) } else { None } @@ -438,7 +440,14 @@ impl TryFrom for NomoValue { .map(TryInto::try_into) .collect::>()?, }), - serde_json::Value::Object(_map) => todo!(), + serde_json::Value::Object(map) => { + let value = map + .into_iter() + .map(|(key, value)| Ok((key, NomoValue::try_from(value)?))) + .collect::>()?; + + Ok(NomoValue::Object { value }) + } } } } diff --git a/tests/cases/deep_access.1-parsed.snap b/tests/cases/deep_access.1-parsed.snap new file mode 100644 index 0000000..ecc2284 --- /dev/null +++ b/tests/cases/deep_access.1-parsed.snap @@ -0,0 +1,74 @@ +--- +source: tests/file_tests.rs +expression: parsed +info: + input: "{{ if world?.active }}\nHello {{= world.name }}\n{{ end }}\n\n{{ if world?.foo?.bar?.active }}\n Weird!\n{{ else }}\n Deep access is working :)\n{{ end }}" + context: + world: + active: true + name: World! +--- +ParsedTemplate { + tokens: [ + [LeftDelim]"{{" (0..2), + [Whitespace]" " (2..3), + [ConditionalIf]"if" (3..5), + [Whitespace]" " (5..6), + [Ident]"world" (6..11), + [Operator(QuestionMark)]"?" (11..12), + [Operator(Dot)]"." (12..13), + [Ident]"active" (13..19), + [Whitespace]" " (19..20), + [RightDelim]"}}" (20..22), + [Whitespace]"\n" (22..23), + [Content]"Hello" (23..28), + [Whitespace]" " (28..29), + [LeftDelim]"{{" (29..31), + [WantsOutput]"=" (31..32), + [Whitespace]" " (32..33), + [Ident]"world" (33..38), + [Operator(Dot)]"." (38..39), + [Ident]"name" (39..43), + [Whitespace]" " (43..44), + [RightDelim]"}}" (44..46), + [Whitespace]"\n" (46..47), + [LeftDelim]"{{" (47..49), + [Whitespace]" " (49..50), + [End]"end" (50..53), + [Whitespace]" " (53..54), + [RightDelim]"}}" (54..56), + [Whitespace]"\n\n" (56..58), + [LeftDelim]"{{" (58..60), + [Whitespace]" " (60..61), + [ConditionalIf]"if" (61..63), + [Whitespace]" " (63..64), + [Ident]"world" (64..69), + [Operator(QuestionMark)]"?" (69..70), + [Operator(Dot)]"." (70..71), + [Ident]"foo" (71..74), + [Operator(QuestionMark)]"?" (74..75), + [Operator(Dot)]"." (75..76), + [Ident]"bar" (76..79), + [Operator(QuestionMark)]"?" (79..80), + [Operator(Dot)]"." (80..81), + [Ident]"active" (81..87), + [Whitespace]" " (87..88), + [RightDelim]"}}" (88..90), + [Whitespace]"\n " (90..95), + [Content]"Weird!" (95..101), + [Whitespace]"\n" (101..102), + [LeftDelim]"{{" (102..104), + [Whitespace]" " (104..105), + [ConditionalElse]"else" (105..109), + [Whitespace]" " (109..110), + [RightDelim]"}}" (110..112), + [Whitespace]"\n " (112..117), + [Content]"Deep access is working :)" (117..142), + [Whitespace]"\n" (142..143), + [LeftDelim]"{{" (143..145), + [Whitespace]" " (145..146), + [End]"end" (146..149), + [Whitespace]" " (149..150), + [RightDelim]"}}" (150..152), + ], +} diff --git a/tests/cases/deep_access.2-ast.snap b/tests/cases/deep_access.2-ast.snap new file mode 100644 index 0000000..bd3439e --- /dev/null +++ b/tests/cases/deep_access.2-ast.snap @@ -0,0 +1,131 @@ +--- +source: tests/file_tests.rs +expression: ast +info: + input: "{{ if world?.active }}\nHello {{= world.name }}\n{{ end }}\n\n{{ if world?.foo?.bar?.active }}\n Weird!\n{{ else }}\n Deep access is working :)\n{{ end }}" + context: + world: + active: true + name: World! +--- +TemplateAst { + root: [ + ConditionalChain { + chain: [ + Block { + prev_whitespace_content: None, + expression: IfConditional { + expression: AccessOperation { + op: Dot, + lhs: ConditionalAccess( + [Ident]"world" (6..11), + ), + rhs: VariableAccess( + [Ident]"active" (13..19), + ), + }, + }, + post_whitespace_content: Some( + [Whitespace]"\n" (22..23), + ), + }, + ConditionalContent { + content: [ + StaticContent( + [Content]"Hello" (23..28), + ), + Interpolation { + prev_whitespace_content: Some( + [Whitespace]" " (28..29), + ), + expression: AccessOperation { + op: Dot, + lhs: VariableAccess( + [Ident]"world" (33..38), + ), + rhs: VariableAccess( + [Ident]"name" (39..43), + ), + }, + post_whitespace_content: Some( + [Whitespace]"\n" (46..47), + ), + }, + ], + }, + Block { + prev_whitespace_content: None, + expression: EndBlock, + post_whitespace_content: Some( + [Whitespace]"\n\n" (56..58), + ), + }, + ], + }, + ConditionalChain { + chain: [ + Block { + prev_whitespace_content: None, + expression: IfConditional { + expression: AccessOperation { + op: Dot, + lhs: AccessOperation { + op: Dot, + lhs: AccessOperation { + op: Dot, + lhs: ConditionalAccess( + [Ident]"world" (64..69), + ), + rhs: ConditionalAccess( + [Ident]"foo" (71..74), + ), + }, + rhs: ConditionalAccess( + [Ident]"bar" (76..79), + ), + }, + rhs: VariableAccess( + [Ident]"active" (81..87), + ), + }, + }, + post_whitespace_content: Some( + [Whitespace]"\n " (90..95), + ), + }, + ConditionalContent { + content: [ + StaticContent( + [Content]"Weird!" (95..101), + ), + ], + }, + Block { + prev_whitespace_content: Some( + [Whitespace]"\n" (101..102), + ), + expression: ElseConditional { + expression: None, + }, + post_whitespace_content: Some( + [Whitespace]"\n " (112..117), + ), + }, + ConditionalContent { + content: [ + StaticContent( + [Content]"Deep access is working :)" (117..142), + ), + ], + }, + Block { + prev_whitespace_content: Some( + [Whitespace]"\n" (142..143), + ), + expression: EndBlock, + post_whitespace_content: None, + }, + ], + }, + ], +} diff --git a/tests/cases/deep_access.3-instructions.snap b/tests/cases/deep_access.3-instructions.snap new file mode 100644 index 0000000..dd12a27 --- /dev/null +++ b/tests/cases/deep_access.3-instructions.snap @@ -0,0 +1,313 @@ +--- +source: tests/file_tests.rs +expression: emit +info: + input: "{{ if world?.active }}\nHello {{= world.name }}\n{{ end }}\n\n{{ if world?.foo?.bar?.active }}\n Weird!\n{{ else }}\n Deep access is working :)\n{{ end }}" + context: + world: + active: true + name: World! +--- +VMInstructions { + labels: { + LabelSlot { + index: 0, + }: 18, + LabelSlot { + index: 2, + }: 4, + LabelSlot { + index: 4, + }: 18, + LabelSlot { + index: 6, + }: 13, + LabelSlot { + index: 8, + }: 43, + LabelSlot { + index: 10, + }: 31, + LabelSlot { + index: 12, + }: 27, + LabelSlot { + index: 14, + }: 23, + LabelSlot { + index: 16, + }: 38, + }, + instructions: [ + LoadFromContextToSlot { + name: "world" (6..11), + slot: VariableSlot { + index: 3, + }, + fail_on_not_found: false, + }, + JumpIfUndefined { + slot: VariableSlot { + index: 3, + }, + jump: LabelSlot { + index: 2, + }, + }, + IndexSlotToSlot { + name: "active" (13..19), + from_slot: VariableSlot { + index: 3, + }, + to_slot: VariableSlot { + index: 3, + }, + fail_on_not_found: true, + }, + JumpIfUndefined { + slot: VariableSlot { + index: 3, + }, + jump: LabelSlot { + index: 2, + }, + }, + LoadFromSlotToSlot { + from_slot: VariableSlot { + index: 3, + }, + to_slot: VariableSlot { + index: 1, + }, + }, + JumpIfNotTrue { + emit_slot: VariableSlot { + index: 1, + }, + jump: LabelSlot { + index: 4, + }, + }, + AppendContent { + content: "\n" (22..23), + }, + AppendContent { + content: "Hello" (23..28), + }, + AppendContent { + content: " " (28..29), + }, + LoadFromContextToSlot { + name: "world" (33..38), + slot: VariableSlot { + index: 7, + }, + fail_on_not_found: true, + }, + JumpIfUndefined { + slot: VariableSlot { + index: 7, + }, + jump: LabelSlot { + index: 6, + }, + }, + IndexSlotToSlot { + name: "name" (39..43), + from_slot: VariableSlot { + index: 7, + }, + to_slot: VariableSlot { + index: 7, + }, + fail_on_not_found: true, + }, + JumpIfUndefined { + slot: VariableSlot { + index: 7, + }, + jump: LabelSlot { + index: 6, + }, + }, + LoadFromSlotToSlot { + from_slot: VariableSlot { + index: 7, + }, + to_slot: VariableSlot { + index: 5, + }, + }, + EmitFromSlot { + slot: VariableSlot { + index: 5, + }, + }, + AppendContent { + content: "\n" (46..47), + }, + Jump { + jump: LabelSlot { + index: 0, + }, + }, + AppendContent { + content: "\n" (22..23), + }, + AppendContent { + content: "\n\n" (56..58), + }, + LoadFromContextToSlot { + name: "world" (64..69), + slot: VariableSlot { + index: 15, + }, + fail_on_not_found: false, + }, + JumpIfUndefined { + slot: VariableSlot { + index: 15, + }, + jump: LabelSlot { + index: 14, + }, + }, + IndexSlotToSlot { + name: "foo" (71..74), + from_slot: VariableSlot { + index: 15, + }, + to_slot: VariableSlot { + index: 15, + }, + fail_on_not_found: false, + }, + JumpIfUndefined { + slot: VariableSlot { + index: 15, + }, + jump: LabelSlot { + index: 14, + }, + }, + LoadFromSlotToSlot { + from_slot: VariableSlot { + index: 15, + }, + to_slot: VariableSlot { + index: 13, + }, + }, + JumpIfUndefined { + slot: VariableSlot { + index: 13, + }, + jump: LabelSlot { + index: 12, + }, + }, + IndexSlotToSlot { + name: "bar" (76..79), + from_slot: VariableSlot { + index: 13, + }, + to_slot: VariableSlot { + index: 13, + }, + fail_on_not_found: false, + }, + JumpIfUndefined { + slot: VariableSlot { + index: 13, + }, + jump: LabelSlot { + index: 12, + }, + }, + LoadFromSlotToSlot { + from_slot: VariableSlot { + index: 13, + }, + to_slot: VariableSlot { + index: 11, + }, + }, + JumpIfUndefined { + slot: VariableSlot { + index: 11, + }, + jump: LabelSlot { + index: 10, + }, + }, + IndexSlotToSlot { + name: "active" (81..87), + from_slot: VariableSlot { + index: 11, + }, + to_slot: VariableSlot { + index: 11, + }, + fail_on_not_found: true, + }, + JumpIfUndefined { + slot: VariableSlot { + index: 11, + }, + jump: LabelSlot { + index: 10, + }, + }, + LoadFromSlotToSlot { + from_slot: VariableSlot { + index: 11, + }, + to_slot: VariableSlot { + index: 9, + }, + }, + JumpIfNotTrue { + emit_slot: VariableSlot { + index: 9, + }, + jump: LabelSlot { + index: 16, + }, + }, + AppendContent { + content: "\n " (90..95), + }, + AppendContent { + content: "Weird!" (95..101), + }, + AppendContent { + content: "\n" (101..102), + }, + Jump { + jump: LabelSlot { + index: 8, + }, + }, + AppendContent { + content: "\n " (90..95), + }, + AppendContent { + content: "\n " (112..117), + }, + AppendContent { + content: "Deep access is working :)" (117..142), + }, + AppendContent { + content: "\n" (142..143), + }, + Jump { + jump: LabelSlot { + index: 8, + }, + }, + AppendContent { + content: "\n " (112..117), + }, + NoOp, + ], +} diff --git a/tests/cases/deep_access.4-output.snap b/tests/cases/deep_access.4-output.snap new file mode 100644 index 0000000..a6d57e5 --- /dev/null +++ b/tests/cases/deep_access.4-output.snap @@ -0,0 +1,11 @@ +--- +source: tests/file_tests.rs +expression: output +info: + input: "{{ if world?.active }}\nHello {{= world.name }}\n{{ end }}\n\n{{ if world?.foo?.bar?.active }}\n Weird!\n{{ else }}\n Deep access is working :)\n{{ end }}" + context: + world: + active: true + name: World! +--- +"\nHello World!\n\n\n\n Deep access is working :)\n" diff --git a/tests/cases/deep_access.nomo b/tests/cases/deep_access.nomo new file mode 100644 index 0000000..cde34cb --- /dev/null +++ b/tests/cases/deep_access.nomo @@ -0,0 +1,16 @@ +{ + "world": { + "active": true, + "name": "World!" + } +} +--- +{{ if world?.active }} +Hello {{= world.name }} +{{ end }} + +{{ if world?.foo?.bar?.active }} + Weird! +{{ else }} + Deep access is working :) +{{ end }} \ No newline at end of file