From ef02e94591c89ba7d0d5a59df8573ccf8f221d7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Mon, 9 Mar 2026 11:40:12 +0100 Subject: [PATCH 1/4] Add tests for if/else if/else/if MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- src/ast/mod.rs | 54 ++++++++++++++++- .../nomo__ast__tests__check_if_else.snap | 55 +++++++++++++++++ .../nomo__ast__tests__check_if_else_if.snap | 59 +++++++++++++++++++ 3 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 src/ast/snapshots/nomo__ast__tests__check_if_else.snap create mode 100644 src/ast/snapshots/nomo__ast__tests__check_if_else_if.snap diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 8e2a1a7..ba8122f 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -675,11 +675,61 @@ mod tests { } #[test] - fn check_empty_output() { + fn check_empty_if_output() { let input = "{{ if foo }}{{ end }}"; let parsed = crate::parser::parse(input.into()).unwrap(); - panic_pretty(input, parse(parsed.tokens())); + let ast = panic_pretty(input, parse(parsed.tokens())); + + insta::assert_debug_snapshot!(ast, @r#" + TemplateAst { + root: [ + ConditionalChain { + chain: [ + IfConditional { + if_block: Block { + prev_whitespace_content: None, + expression: VariableAccess( + [Ident]"foo" (6..9), + ), + post_whitespace_content: None, + }, + }, + ConditionalContent { + content: [], + }, + Block { + prev_whitespace_content: None, + expression: EndBlock, + post_whitespace_content: None, + }, + ], + }, + ], + } + "#); + } + + #[test] + fn check_if_else() { + let input = "{{ if foo }} foo {{ else }} bar {{ end }}"; + + let parsed = crate::parser::parse(input.into()).unwrap(); + + let ast = panic_pretty(input, parse(parsed.tokens())); + + insta::assert_debug_snapshot!(ast); + } + + #[test] + fn check_if_else_if() { + let input = "{{ if foo }} foo {{ else if bar }} bar {{ end }}"; + + let parsed = crate::parser::parse(input.into()).unwrap(); + + let ast = panic_pretty(input, parse(parsed.tokens())); + + insta::assert_debug_snapshot!(ast); } } diff --git a/src/ast/snapshots/nomo__ast__tests__check_if_else.snap b/src/ast/snapshots/nomo__ast__tests__check_if_else.snap new file mode 100644 index 0000000..07e4955 --- /dev/null +++ b/src/ast/snapshots/nomo__ast__tests__check_if_else.snap @@ -0,0 +1,55 @@ +--- +source: src/ast/mod.rs +expression: ast +--- +TemplateAst { + root: [ + ConditionalChain { + chain: [ + IfConditional { + if_block: Block { + prev_whitespace_content: None, + expression: VariableAccess( + [Ident]"foo" (6..9), + ), + post_whitespace_content: Some( + [Whitespace]" " (12..13), + ), + }, + }, + ConditionalContent { + content: [ + StaticContent( + [Content]"foo" (13..16), + ), + ], + }, + Block { + prev_whitespace_content: Some( + [Whitespace]" " (16..17), + ), + expression: ElseConditional { + expression: None, + }, + post_whitespace_content: Some( + [Whitespace]" " (27..28), + ), + }, + ConditionalContent { + content: [ + StaticContent( + [Content]"bar" (28..31), + ), + ], + }, + Block { + prev_whitespace_content: Some( + [Whitespace]" " (31..32), + ), + expression: EndBlock, + post_whitespace_content: None, + }, + ], + }, + ], +} diff --git a/src/ast/snapshots/nomo__ast__tests__check_if_else_if.snap b/src/ast/snapshots/nomo__ast__tests__check_if_else_if.snap new file mode 100644 index 0000000..2690597 --- /dev/null +++ b/src/ast/snapshots/nomo__ast__tests__check_if_else_if.snap @@ -0,0 +1,59 @@ +--- +source: src/ast/mod.rs +expression: ast +--- +TemplateAst { + root: [ + ConditionalChain { + chain: [ + IfConditional { + if_block: Block { + prev_whitespace_content: None, + expression: VariableAccess( + [Ident]"foo" (6..9), + ), + post_whitespace_content: Some( + [Whitespace]" " (12..13), + ), + }, + }, + ConditionalContent { + content: [ + StaticContent( + [Content]"foo" (13..16), + ), + ], + }, + Block { + prev_whitespace_content: Some( + [Whitespace]" " (16..17), + ), + expression: ElseConditional { + expression: Some( + VariableAccess( + [Ident]"bar" (28..31), + ), + ), + }, + post_whitespace_content: Some( + [Whitespace]" " (34..35), + ), + }, + ConditionalContent { + content: [ + StaticContent( + [Content]"bar" (35..38), + ), + ], + }, + Block { + prev_whitespace_content: Some( + [Whitespace]" " (38..39), + ), + expression: EndBlock, + post_whitespace_content: None, + }, + ], + }, + ], +} From ff308649b9dd90f63d3e353b96fba5b788b38101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Mon, 9 Mar 2026 12:51:49 +0100 Subject: [PATCH 2/4] Add if else if chains MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- src/ast/mod.rs | 46 +++--- .../nomo__ast__tests__check_if_else.snap | 12 +- .../nomo__ast__tests__check_if_else_if.snap | 12 +- .../nomo__ast__tests__simple_if_ast.snap | 24 +-- src/emit/mod.rs | 155 +++++++++++------- .../nomo__emit__tests__check_if_else_if.snap | 75 +++++++++ src/eval/mod.rs | 10 ++ tests/cases/2-ast@condition.snap | 12 +- tests/cases/3-instructions@condition.snap | 8 +- tests/cases/4-output@condition.snap | 2 +- 10 files changed, 241 insertions(+), 115 deletions(-) create mode 100644 src/emit/snapshots/nomo__emit__tests__check_if_else_if.snap diff --git a/src/ast/mod.rs b/src/ast/mod.rs index ba8122f..9f18f45 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -227,7 +227,7 @@ pub enum TemplateAstExpr<'input> { chain: Vec>, }, IfConditional { - if_block: Box>, + expression: Box>, }, ConditionalContent { content: Vec>, @@ -316,10 +316,10 @@ fn parse_conditional_chain<'input>( input: &mut Input<'input>, ) -> Result, AstError> { trace("conditional_chain", |input: &mut Input<'input>| { - let if_block = parse_conditional_if.map(Box::new).parse_next(input)?; + let if_block = parse_conditional_if.parse_next(input)?; let mut chain = vec![]; - chain.push(TemplateAstExpr::IfConditional { if_block }); + chain.push(if_block); loop { let (content, end_block): (Vec<_>, _) = repeat_till( @@ -344,7 +344,7 @@ fn parse_conditional_chain<'input>( chain.push(end_block); - if dbg!(is_end) { + if is_end { break; } } @@ -359,13 +359,17 @@ fn parse_conditional_if<'input>( ) -> Result, AstError> { trace( "conditional", - parse_block(preceded( - TokenKind::ConditionalIf, - cut_err( - surrounded(ws, parse_value_expression) - .context(AstError::ctx().msg("Expected an expression after 'if'")), - ), - )), + parse_block( + preceded( + TokenKind::ConditionalIf, + cut_err( + surrounded(ws, parse_value_expression) + .map(Box::new) + .context(AstError::ctx().msg("Expected an expression after 'if'")), + ), + ) + .map(|expression| TemplateAstExpr::IfConditional { expression }), + ), ) .parse_next(input) } @@ -559,16 +563,16 @@ mod tests { root: [ ConditionalChain { chain: [ - IfConditional { - if_block: Block { - prev_whitespace_content: None, + Block { + prev_whitespace_content: None, + expression: IfConditional { expression: VariableAccess( [Ident]"foo" (6..9), ), - post_whitespace_content: Some( - [Whitespace]" " (12..13), - ), }, + post_whitespace_content: Some( + [Whitespace]" " (12..13), + ), }, ConditionalContent { content: [ @@ -687,14 +691,14 @@ mod tests { root: [ ConditionalChain { chain: [ - IfConditional { - if_block: Block { - prev_whitespace_content: None, + Block { + prev_whitespace_content: None, + expression: IfConditional { expression: VariableAccess( [Ident]"foo" (6..9), ), - post_whitespace_content: None, }, + post_whitespace_content: None, }, ConditionalContent { content: [], diff --git a/src/ast/snapshots/nomo__ast__tests__check_if_else.snap b/src/ast/snapshots/nomo__ast__tests__check_if_else.snap index 07e4955..0a3b739 100644 --- a/src/ast/snapshots/nomo__ast__tests__check_if_else.snap +++ b/src/ast/snapshots/nomo__ast__tests__check_if_else.snap @@ -6,16 +6,16 @@ TemplateAst { root: [ ConditionalChain { chain: [ - IfConditional { - if_block: Block { - prev_whitespace_content: None, + Block { + prev_whitespace_content: None, + expression: IfConditional { expression: VariableAccess( [Ident]"foo" (6..9), ), - post_whitespace_content: Some( - [Whitespace]" " (12..13), - ), }, + post_whitespace_content: Some( + [Whitespace]" " (12..13), + ), }, ConditionalContent { content: [ diff --git a/src/ast/snapshots/nomo__ast__tests__check_if_else_if.snap b/src/ast/snapshots/nomo__ast__tests__check_if_else_if.snap index 2690597..3912e60 100644 --- a/src/ast/snapshots/nomo__ast__tests__check_if_else_if.snap +++ b/src/ast/snapshots/nomo__ast__tests__check_if_else_if.snap @@ -6,16 +6,16 @@ TemplateAst { root: [ ConditionalChain { chain: [ - IfConditional { - if_block: Block { - prev_whitespace_content: None, + Block { + prev_whitespace_content: None, + expression: IfConditional { expression: VariableAccess( [Ident]"foo" (6..9), ), - post_whitespace_content: Some( - [Whitespace]" " (12..13), - ), }, + post_whitespace_content: Some( + [Whitespace]" " (12..13), + ), }, ConditionalContent { content: [ diff --git a/src/ast/snapshots/nomo__ast__tests__simple_if_ast.snap b/src/ast/snapshots/nomo__ast__tests__simple_if_ast.snap index 59d5426..3701cc2 100644 --- a/src/ast/snapshots/nomo__ast__tests__simple_if_ast.snap +++ b/src/ast/snapshots/nomo__ast__tests__simple_if_ast.snap @@ -6,31 +6,31 @@ TemplateAst { root: [ ConditionalChain { chain: [ - IfConditional { - if_block: Block { - prev_whitespace_content: None, + Block { + prev_whitespace_content: None, + expression: IfConditional { expression: VariableAccess( [Ident]"foo" (6..9), ), - post_whitespace_content: Some( - [Whitespace]"\n " (12..25), - ), }, + post_whitespace_content: Some( + [Whitespace]"\n " (12..25), + ), }, ConditionalContent { content: [ ConditionalChain { chain: [ - IfConditional { - if_block: Block { - prev_whitespace_content: None, + Block { + prev_whitespace_content: None, + expression: IfConditional { expression: VariableAccess( [Ident]"bar" (31..34), ), - post_whitespace_content: Some( - [Whitespace]"\n " (37..54), - ), }, + post_whitespace_content: Some( + [Whitespace]"\n " (37..54), + ), }, ConditionalContent { content: [ diff --git a/src/emit/mod.rs b/src/emit/mod.rs index 6f38a91..2c1a105 100644 --- a/src/emit/mod.rs +++ b/src/emit/mod.rs @@ -42,6 +42,9 @@ pub enum Instruction { emit_slot: VariableSlot, jump: isize, }, + Jump { + jump: isize, + }, } pub fn emit_machine(input: crate::ast::TemplateAst<'_>) -> Vec { @@ -90,76 +93,91 @@ fn emit_ast_expr( } TemplateAstExpr::ConditionalChain { chain } => { let mut chain = chain.iter(); - let Some(TemplateAstExpr::IfConditional { - if_block: expression, - }) = chain.next() - else { - unreachable!("First element in conditional chain should be an IfConditional"); - }; - let TemplateAstExpr::Block { - prev_whitespace_content, - expression, - post_whitespace_content, - } = expression.as_ref() - else { - unreachable!("The end of an IfConditional must be a Block"); - }; + let mut end_indices = vec![]; - let Some(TemplateAstExpr::ConditionalContent { content }) = chain.next() else { - unreachable!("The end of an IfConditional must be a Block"); - }; + let mut previous_post_whitespace_content: &Option = &None; + let mut previous_jump: Option = None; - if let Some(ws) = prev_whitespace_content { + loop { + let next = chain.next().unwrap(); + if let Some(ws) = previous_post_whitespace_content { + eval.push(Instruction::AppendContent { + content: ws.source().clone(), + }); + } + + if let TemplateAstExpr::ConditionalContent { content } = &next { + for ast in content { + emit_ast_expr(machine, eval, ast); + } + + end_indices.push(eval.len()); + eval.push(Instruction::Jump { jump: isize::MAX }); + } else if let TemplateAstExpr::Block { + prev_whitespace_content, + post_whitespace_content, + expression, + } = &next + { + previous_post_whitespace_content = post_whitespace_content; + if let Some(ws) = prev_whitespace_content { + eval.push(Instruction::AppendContent { + content: ws.source().clone(), + }); + } + + if let TemplateAstExpr::IfConditional { expression } = &**expression { + let emit_slot = machine.reserve_slot(); + emit_expr_load(machine, eval, emit_slot, expression); + + previous_jump = Some(eval.len()); + eval.push(Instruction::JumpIfNotTrue { + emit_slot, + jump: isize::MAX, + }); + } else if let TemplateAstExpr::ElseConditional { expression } = &**expression { + if let Some(previous_jump) = previous_jump.take() { + let new_jump = eval.len() - previous_jump - 1; + let Instruction::JumpIfNotTrue { jump, .. } = &mut eval[previous_jump] + else { + panic!("Jump slot had something that is not a jump?!"); + }; + + *jump = new_jump as isize; + } else { + panic!("Got an else without a previous if?"); + }; + + if let Some(expression) = expression { + let emit_slot = machine.reserve_slot(); + emit_expr_load(machine, eval, emit_slot, expression); + + previous_jump = Some(eval.len()); + eval.push(Instruction::JumpIfNotTrue { + emit_slot, + jump: isize::MAX, + }); + } else { + // We don't have to do anything in the else case + } + } else if let TemplateAstExpr::EndBlock = &**expression { + break; + } + } + } + + if let Some(ws) = previous_post_whitespace_content { eval.push(Instruction::AppendContent { content: ws.source().clone(), }); } - let emit_slot = machine.reserve_slot(); - emit_expr_load(machine, eval, emit_slot, expression); - - let index = eval.len(); - eval.push(Instruction::JumpIfNotTrue { - emit_slot, - jump: isize::MAX, - }); - - if let Some(ws) = post_whitespace_content { - eval.push(Instruction::AppendContent { - content: ws.source().clone(), - }); - } - - for ast in content { - emit_ast_expr(machine, eval, ast); - } - - let Some(TemplateAstExpr::Block { - prev_whitespace_content, - post_whitespace_content, - .. - }) = chain.last() - else { - unreachable!("The end of an IfConditional must be a End Block"); - }; - - if let Some(ws) = prev_whitespace_content { - eval.push(Instruction::AppendContent { - content: ws.source().clone(), - }); - } - - let jump = eval.len() - index - 1; - eval[index] = Instruction::JumpIfNotTrue { - emit_slot, - jump: jump as isize, - }; - - if let Some(ws) = post_whitespace_content { - eval.push(Instruction::AppendContent { - content: ws.source().clone(), - }); + for index in end_indices { + let jump = eval.len() - index - 1; + eval[index] = Instruction::Jump { + jump: jump as isize, + }; } } @@ -235,4 +253,17 @@ mod tests { ] "#); } + + #[test] + fn check_if_else_if() { + let input = "{{ if foo }} foo {{ else if bar }} bar {{ else }} foobar {{ end }}"; + + let parsed = crate::parser::parse(input.into()).unwrap(); + + let ast = crate::ast::parse(parsed.tokens()).unwrap(); + + let emit = emit_machine(ast); + + insta::assert_debug_snapshot!(emit); + } } diff --git a/src/emit/snapshots/nomo__emit__tests__check_if_else_if.snap b/src/emit/snapshots/nomo__emit__tests__check_if_else_if.snap new file mode 100644 index 0000000..14b290f --- /dev/null +++ b/src/emit/snapshots/nomo__emit__tests__check_if_else_if.snap @@ -0,0 +1,75 @@ +--- +source: src/emit/mod.rs +expression: emit +--- +[ + LoadFromContextToSlot { + name: "foo" (6..9), + slot: VariableSlot { + index: 0, + }, + }, + JumpIfNotTrue { + emit_slot: VariableSlot { + index: 0, + }, + jump: 5, + }, + AppendContent { + content: " " (12..13), + }, + AppendContent { + content: "foo" (13..16), + }, + Jump { + jump: 14, + }, + AppendContent { + content: " " (12..13), + }, + AppendContent { + content: " " (16..17), + }, + LoadFromContextToSlot { + name: "bar" (28..31), + slot: VariableSlot { + index: 1, + }, + }, + JumpIfNotTrue { + emit_slot: VariableSlot { + index: 1, + }, + jump: 5, + }, + AppendContent { + content: " " (34..35), + }, + AppendContent { + content: "bar" (35..38), + }, + Jump { + jump: 7, + }, + AppendContent { + content: " " (34..35), + }, + AppendContent { + content: " " (38..39), + }, + AppendContent { + content: " " (49..50), + }, + AppendContent { + content: "foobar" (50..56), + }, + Jump { + jump: 2, + }, + AppendContent { + content: " " (49..50), + }, + AppendContent { + content: " " (56..57), + }, +] diff --git a/src/eval/mod.rs b/src/eval/mod.rs index f94ed8c..0b0b6f9 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -67,6 +67,16 @@ pub fn execute( } } } + Instruction::Jump { jump } => { + let (new_ip, overflow) = ip.overflowing_add_signed(*jump); + + if overflow { + return Err(EvaluationError::InstructionPointerOverflow); + } else { + ip = new_ip; + continue; + } + } } ip += 1; diff --git a/tests/cases/2-ast@condition.snap b/tests/cases/2-ast@condition.snap index 22d6b93..ae3a62e 100644 --- a/tests/cases/2-ast@condition.snap +++ b/tests/cases/2-ast@condition.snap @@ -7,16 +7,16 @@ TemplateAst { root: [ ConditionalChain { chain: [ - IfConditional { - if_block: Block { - prev_whitespace_content: None, + Block { + prev_whitespace_content: None, + expression: IfConditional { expression: VariableAccess( [Ident]"test" (6..10), ), - post_whitespace_content: Some( - [Whitespace]"\n " (13..18), - ), }, + post_whitespace_content: Some( + [Whitespace]"\n " (13..18), + ), }, ConditionalContent { content: [ diff --git a/tests/cases/3-instructions@condition.snap b/tests/cases/3-instructions@condition.snap index f79800d..56d187e 100644 --- a/tests/cases/3-instructions@condition.snap +++ b/tests/cases/3-instructions@condition.snap @@ -14,7 +14,7 @@ input_file: tests/cases/condition.nomo emit_slot: VariableSlot { index: 0, }, - jump: 3, + jump: 9223372036854775807, }, AppendContent { content: "\n " (13..18), @@ -22,6 +22,12 @@ input_file: tests/cases/condition.nomo AppendContent { content: "Hello World!" (18..30), }, + Jump { + jump: 3, + }, + AppendContent { + content: "\n " (13..18), + }, AppendContent { content: "\n" (30..31), }, diff --git a/tests/cases/4-output@condition.snap b/tests/cases/4-output@condition.snap index 1d5af6f..78c70f2 100644 --- a/tests/cases/4-output@condition.snap +++ b/tests/cases/4-output@condition.snap @@ -3,4 +3,4 @@ source: tests/file_tests.rs expression: output input_file: tests/cases/condition.nomo --- -"\n Hello World!\n\n\nmore" +"\n Hello World!\n\nmore" From ae379df9dbb0916bc15a5725532b9bd9a41653de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Mon, 9 Mar 2026 13:04:16 +0100 Subject: [PATCH 3/4] Also fix the jump if its the last one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- src/emit/mod.rs | 11 +++- tests/cases/1-parsed@if_else_if.snap | 36 +++++++++++++ tests/cases/2-ast@if_else_if.snap | 60 +++++++++++++++++++++ tests/cases/3-instructions@condition.snap | 2 +- tests/cases/3-instructions@if_else_if.snap | 61 ++++++++++++++++++++++ tests/cases/4-output@if_else_if.snap | 6 +++ tests/cases/if_else_if.nomo | 11 ++++ 7 files changed, 185 insertions(+), 2 deletions(-) create mode 100644 tests/cases/1-parsed@if_else_if.snap create mode 100644 tests/cases/2-ast@if_else_if.snap create mode 100644 tests/cases/3-instructions@if_else_if.snap create mode 100644 tests/cases/4-output@if_else_if.snap create mode 100644 tests/cases/if_else_if.nomo diff --git a/src/emit/mod.rs b/src/emit/mod.rs index 2c1a105..d052418 100644 --- a/src/emit/mod.rs +++ b/src/emit/mod.rs @@ -147,7 +147,7 @@ fn emit_ast_expr( *jump = new_jump as isize; } else { panic!("Got an else without a previous if?"); - }; + } if let Some(expression) = expression { let emit_slot = machine.reserve_slot(); @@ -167,6 +167,15 @@ fn emit_ast_expr( } } + if let Some(previous_jump) = previous_jump.take() { + let new_jump = eval.len() - previous_jump - 1; + let Instruction::JumpIfNotTrue { jump, .. } = &mut eval[previous_jump] else { + panic!("Jump slot had something that is not a jump?!"); + }; + + *jump = new_jump as isize; + } + if let Some(ws) = previous_post_whitespace_content { eval.push(Instruction::AppendContent { content: ws.source().clone(), diff --git a/tests/cases/1-parsed@if_else_if.snap b/tests/cases/1-parsed@if_else_if.snap new file mode 100644 index 0000000..287fce6 --- /dev/null +++ b/tests/cases/1-parsed@if_else_if.snap @@ -0,0 +1,36 @@ +--- +source: tests/file_tests.rs +expression: parsed +input_file: tests/cases/if_else_if.nomo +--- +ParsedTemplate { + tokens: [ + [LeftDelim]"{{" (0..2), + [Whitespace]" " (2..3), + [ConditionalIf]"if" (3..5), + [Whitespace]" " (5..6), + [Ident]"test" (6..10), + [Whitespace]" " (10..11), + [RightDelim]"}}" (11..13), + [Whitespace]"\n " (13..18), + [Content]"Not Hello World! :C" (18..37), + [Whitespace]"\n" (37..38), + [LeftDelim]"{{" (38..40), + [Whitespace]" " (40..41), + [ConditionalElse]"else" (41..45), + [Whitespace]" " (45..46), + [ConditionalIf]"if" (46..48), + [Whitespace]" " (48..49), + [Ident]"another_test" (49..61), + [Whitespace]" " (61..62), + [RightDelim]"}}" (62..64), + [Whitespace]"\n " (64..69), + [Content]"Hello World!" (69..81), + [Whitespace]"\n" (81..82), + [LeftDelim]"{{" (82..84), + [Whitespace]" " (84..85), + [End]"end" (85..88), + [Whitespace]" " (88..89), + [RightDelim]"}}" (89..91), + ], +} diff --git a/tests/cases/2-ast@if_else_if.snap b/tests/cases/2-ast@if_else_if.snap new file mode 100644 index 0000000..3bc850b --- /dev/null +++ b/tests/cases/2-ast@if_else_if.snap @@ -0,0 +1,60 @@ +--- +source: tests/file_tests.rs +expression: ast +input_file: tests/cases/if_else_if.nomo +--- +TemplateAst { + root: [ + ConditionalChain { + chain: [ + Block { + prev_whitespace_content: None, + expression: IfConditional { + expression: VariableAccess( + [Ident]"test" (6..10), + ), + }, + post_whitespace_content: Some( + [Whitespace]"\n " (13..18), + ), + }, + ConditionalContent { + content: [ + StaticContent( + [Content]"Not Hello World! :C" (18..37), + ), + ], + }, + Block { + prev_whitespace_content: Some( + [Whitespace]"\n" (37..38), + ), + expression: ElseConditional { + expression: Some( + VariableAccess( + [Ident]"another_test" (49..61), + ), + ), + }, + post_whitespace_content: Some( + [Whitespace]"\n " (64..69), + ), + }, + ConditionalContent { + content: [ + StaticContent( + [Content]"Hello World!" (69..81), + ), + ], + }, + Block { + prev_whitespace_content: Some( + [Whitespace]"\n" (81..82), + ), + expression: EndBlock, + post_whitespace_content: None, + }, + ], + }, + ], +} diff --git a/tests/cases/3-instructions@condition.snap b/tests/cases/3-instructions@condition.snap index 56d187e..943c455 100644 --- a/tests/cases/3-instructions@condition.snap +++ b/tests/cases/3-instructions@condition.snap @@ -14,7 +14,7 @@ input_file: tests/cases/condition.nomo emit_slot: VariableSlot { index: 0, }, - jump: 9223372036854775807, + jump: 5, }, AppendContent { content: "\n " (13..18), diff --git a/tests/cases/3-instructions@if_else_if.snap b/tests/cases/3-instructions@if_else_if.snap new file mode 100644 index 0000000..14b47e7 --- /dev/null +++ b/tests/cases/3-instructions@if_else_if.snap @@ -0,0 +1,61 @@ +--- +source: tests/file_tests.rs +expression: emit +input_file: tests/cases/if_else_if.nomo +--- +[ + LoadFromContextToSlot { + name: "test" (6..10), + slot: VariableSlot { + index: 0, + }, + }, + JumpIfNotTrue { + emit_slot: VariableSlot { + index: 0, + }, + jump: 5, + }, + AppendContent { + content: "\n " (13..18), + }, + AppendContent { + content: "Not Hello World! :C" (18..37), + }, + Jump { + jump: 9, + }, + AppendContent { + content: "\n " (13..18), + }, + AppendContent { + content: "\n" (37..38), + }, + LoadFromContextToSlot { + name: "another_test" (49..61), + slot: VariableSlot { + index: 1, + }, + }, + JumpIfNotTrue { + emit_slot: VariableSlot { + index: 1, + }, + jump: 5, + }, + AppendContent { + content: "\n " (64..69), + }, + AppendContent { + content: "Hello World!" (69..81), + }, + Jump { + jump: 2, + }, + AppendContent { + content: "\n " (64..69), + }, + AppendContent { + content: "\n" (81..82), + }, +] diff --git a/tests/cases/4-output@if_else_if.snap b/tests/cases/4-output@if_else_if.snap new file mode 100644 index 0000000..bee69fc --- /dev/null +++ b/tests/cases/4-output@if_else_if.snap @@ -0,0 +1,6 @@ +--- +source: tests/file_tests.rs +expression: output +input_file: tests/cases/if_else_if.nomo +--- +"\n\n Hello World!\n" diff --git a/tests/cases/if_else_if.nomo b/tests/cases/if_else_if.nomo new file mode 100644 index 0000000..befa0fe --- /dev/null +++ b/tests/cases/if_else_if.nomo @@ -0,0 +1,11 @@ +{ + "test": false, + "another_test": true, + "stuff": "more" +} +--- +{{ if test }} + Not Hello World! :C +{{ else if another_test }} + Hello World! +{{ end }} \ No newline at end of file From 6a233e978fcfdfb37c75597dc3c9ca3fd59550d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Mon, 9 Mar 2026 13:08:15 +0100 Subject: [PATCH 4/4] Patch in content of previous block if it exists MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel Müller --- src/emit/mod.rs | 11 ++++++--- .../nomo__emit__tests__check_if_else_if.snap | 24 +++++++++---------- tests/cases/3-instructions@condition.snap | 8 +++---- tests/cases/3-instructions@if_else_if.snap | 16 ++++++------- tests/cases/4-output@condition.snap | 2 +- tests/cases/4-output@if_else_if.snap | 2 +- 6 files changed, 34 insertions(+), 29 deletions(-) diff --git a/src/emit/mod.rs b/src/emit/mod.rs index d052418..95a92e6 100644 --- a/src/emit/mod.rs +++ b/src/emit/mod.rs @@ -122,9 +122,14 @@ fn emit_ast_expr( { previous_post_whitespace_content = post_whitespace_content; if let Some(ws) = prev_whitespace_content { - eval.push(Instruction::AppendContent { - content: ws.source().clone(), - }); + eval.insert( + eval.len() - 2, + Instruction::AppendContent { + content: ws.source().clone(), + }, + ); + let index_index = end_indices.len() - 1; + end_indices[index_index] += 1; } if let TemplateAstExpr::IfConditional { expression } = &**expression { diff --git a/src/emit/snapshots/nomo__emit__tests__check_if_else_if.snap b/src/emit/snapshots/nomo__emit__tests__check_if_else_if.snap index 14b290f..ee52b87 100644 --- a/src/emit/snapshots/nomo__emit__tests__check_if_else_if.snap +++ b/src/emit/snapshots/nomo__emit__tests__check_if_else_if.snap @@ -21,15 +21,15 @@ expression: emit AppendContent { content: "foo" (13..16), }, + AppendContent { + content: " " (16..17), + }, Jump { - jump: 14, + jump: 13, }, AppendContent { content: " " (12..13), }, - AppendContent { - content: " " (16..17), - }, LoadFromContextToSlot { name: "bar" (28..31), slot: VariableSlot { @@ -48,28 +48,28 @@ expression: emit AppendContent { content: "bar" (35..38), }, + AppendContent { + content: " " (38..39), + }, Jump { - jump: 7, + jump: 6, }, AppendContent { content: " " (34..35), }, - AppendContent { - content: " " (38..39), - }, AppendContent { content: " " (49..50), }, AppendContent { content: "foobar" (50..56), }, + AppendContent { + content: " " (56..57), + }, Jump { - jump: 2, + jump: 1, }, AppendContent { content: " " (49..50), }, - AppendContent { - content: " " (56..57), - }, ] diff --git a/tests/cases/3-instructions@condition.snap b/tests/cases/3-instructions@condition.snap index 943c455..c2901f7 100644 --- a/tests/cases/3-instructions@condition.snap +++ b/tests/cases/3-instructions@condition.snap @@ -22,15 +22,15 @@ input_file: tests/cases/condition.nomo AppendContent { content: "Hello World!" (18..30), }, + AppendContent { + content: "\n" (30..31), + }, Jump { - jump: 3, + jump: 2, }, AppendContent { content: "\n " (13..18), }, - AppendContent { - content: "\n" (30..31), - }, AppendContent { content: "\n\n" (40..42), }, diff --git a/tests/cases/3-instructions@if_else_if.snap b/tests/cases/3-instructions@if_else_if.snap index 14b47e7..d19a4ed 100644 --- a/tests/cases/3-instructions@if_else_if.snap +++ b/tests/cases/3-instructions@if_else_if.snap @@ -22,15 +22,15 @@ input_file: tests/cases/if_else_if.nomo AppendContent { content: "Not Hello World! :C" (18..37), }, + AppendContent { + content: "\n" (37..38), + }, Jump { - jump: 9, + jump: 8, }, AppendContent { content: "\n " (13..18), }, - AppendContent { - content: "\n" (37..38), - }, LoadFromContextToSlot { name: "another_test" (49..61), slot: VariableSlot { @@ -49,13 +49,13 @@ input_file: tests/cases/if_else_if.nomo AppendContent { content: "Hello World!" (69..81), }, + AppendContent { + content: "\n" (81..82), + }, Jump { - jump: 2, + jump: 1, }, AppendContent { content: "\n " (64..69), }, - AppendContent { - content: "\n" (81..82), - }, ] diff --git a/tests/cases/4-output@condition.snap b/tests/cases/4-output@condition.snap index 78c70f2..1d5af6f 100644 --- a/tests/cases/4-output@condition.snap +++ b/tests/cases/4-output@condition.snap @@ -3,4 +3,4 @@ source: tests/file_tests.rs expression: output input_file: tests/cases/condition.nomo --- -"\n Hello World!\n\nmore" +"\n Hello World!\n\n\nmore" diff --git a/tests/cases/4-output@if_else_if.snap b/tests/cases/4-output@if_else_if.snap index bee69fc..efb547b 100644 --- a/tests/cases/4-output@if_else_if.snap +++ b/tests/cases/4-output@if_else_if.snap @@ -3,4 +3,4 @@ source: tests/file_tests.rs expression: output input_file: tests/cases/if_else_if.nomo --- -"\n\n Hello World!\n" +"\n \n Hello World!\n\n "