From c9314a3d9bdf2bcbda547b9347457f0250272cc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sun, 8 Mar 2026 21:22:57 +0100 Subject: [PATCH] Split up if chains more 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 | 98 +++++++++++++++---- .../nomo__ast__tests__simple_if_ast.snap | 4 + src/emit/mod.rs | 7 +- tests/cases/2-ast@condition.snap | 2 + 4 files changed, 90 insertions(+), 21 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 46e1add..8e2a1a7 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -228,10 +228,12 @@ pub enum TemplateAstExpr<'input> { }, IfConditional { if_block: Box>, + }, + ConditionalContent { content: Vec>, }, ElseConditional { - expression: Vec>, + expression: Option>>, }, Invalid(&'input [TemplateToken]), EndBlock, @@ -314,21 +316,45 @@ fn parse_conditional_chain<'input>( input: &mut Input<'input>, ) -> Result, AstError> { trace("conditional_chain", |input: &mut Input<'input>| { - let if_block = parse_conditional.map(Box::new).parse_next(input)?; + let if_block = parse_conditional_if.map(Box::new).parse_next(input)?; let mut chain = vec![]; - let (content, end_block): (Vec<_>, _) = - repeat_till(0.., parse_ast, parse_end).parse_next(input)?; + chain.push(TemplateAstExpr::IfConditional { if_block }); - chain.push(TemplateAstExpr::IfConditional { if_block, content }); - chain.push(end_block); + loop { + let (content, end_block): (Vec<_>, _) = repeat_till( + 0.., + parse_ast, + trace( + "conditional_chain_else/end", + alt((parse_end, parse_conditional_else)), + ), + ) + .parse_next(input)?; + + chain.push(TemplateAstExpr::ConditionalContent { content }); + + let is_end = if let TemplateAstExpr::Block { ref expression, .. } = end_block + && let TemplateAstExpr::EndBlock = &**expression + { + true + } else { + false + }; + + chain.push(end_block); + + if dbg!(is_end) { + break; + } + } Ok(TemplateAstExpr::ConditionalChain { chain }) }) .parse_next(input) } -fn parse_conditional<'input>( +fn parse_conditional_if<'input>( input: &mut Input<'input>, ) -> Result, AstError> { trace( @@ -344,6 +370,27 @@ fn parse_conditional<'input>( .parse_next(input) } +fn parse_conditional_else<'input>( + input: &mut Input<'input>, +) -> Result, AstError> { + trace( + "conditional_else", + parse_block( + preceded( + surrounded(ws, TokenKind::ConditionalElse), + opt(preceded( + TokenKind::ConditionalIf, + cut_err(surrounded(ws, parse_value_expression)).map(Box::new), + )), + ) + .map(|else_expr| TemplateAstExpr::ElseConditional { + expression: else_expr, + }), + ), + ) + .parse_next(input) +} + fn parse_end<'input>(input: &mut Input<'input>) -> Result, AstError> { trace( "end", @@ -440,6 +487,18 @@ mod tests { use crate::ast::parse_end; use crate::parser::TokenKind; + fn panic_pretty<'a>( + input: &'_ str, + tokens: Result, AstFailure>, + ) -> TemplateAst<'a> { + match tokens { + Ok(ast) => ast, + Err(failure) => { + panic!("{}", failure.to_report(input)); + } + } + } + #[test] fn check_only_content() { let input = "Hello World"; @@ -493,7 +552,7 @@ mod tests { let parsed = crate::parser::parse(input.into()).unwrap(); - let ast = parse(parsed.tokens()).unwrap(); + let ast = panic_pretty(input, parse(parsed.tokens())); insta::assert_debug_snapshot!(ast, @r#" TemplateAst { @@ -510,6 +569,8 @@ mod tests { [Whitespace]" " (12..13), ), }, + }, + ConditionalContent { content: [ StaticContent( [Content]"Hiii" (13..17), @@ -530,18 +591,6 @@ mod tests { "#); } - fn panic_pretty<'a>( - input: &'_ str, - tokens: Result, AstFailure>, - ) -> TemplateAst<'a> { - match tokens { - Ok(ast) => ast, - Err(failure) => { - panic!("{}", failure.to_report(input)); - } - } - } - #[test] fn check_invalid_action() { let input = r#"{{ value }} @@ -624,4 +673,13 @@ mod tests { ) "#); } + + #[test] + fn check_empty_output() { + let input = "{{ if foo }}{{ end }}"; + + let parsed = crate::parser::parse(input.into()).unwrap(); + + panic_pretty(input, parse(parsed.tokens())); + } } 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 54438bf..59d5426 100644 --- a/src/ast/snapshots/nomo__ast__tests__simple_if_ast.snap +++ b/src/ast/snapshots/nomo__ast__tests__simple_if_ast.snap @@ -16,6 +16,8 @@ TemplateAst { [Whitespace]"\n " (12..25), ), }, + }, + ConditionalContent { content: [ ConditionalChain { chain: [ @@ -29,6 +31,8 @@ TemplateAst { [Whitespace]"\n " (37..54), ), }, + }, + ConditionalContent { content: [ StaticContent( [Content]"Hiii" (54..58), diff --git a/src/emit/mod.rs b/src/emit/mod.rs index 6928917..6f38a91 100644 --- a/src/emit/mod.rs +++ b/src/emit/mod.rs @@ -92,7 +92,6 @@ fn emit_ast_expr( let mut chain = chain.iter(); let Some(TemplateAstExpr::IfConditional { if_block: expression, - content, }) = chain.next() else { unreachable!("First element in conditional chain should be an IfConditional"); @@ -107,6 +106,10 @@ fn emit_ast_expr( unreachable!("The end of an IfConditional must be a Block"); }; + let Some(TemplateAstExpr::ConditionalContent { content }) = chain.next() else { + unreachable!("The end of an IfConditional must be a Block"); + }; + if let Some(ws) = prev_whitespace_content { eval.push(Instruction::AppendContent { content: ws.source().clone(), @@ -163,6 +166,7 @@ fn emit_ast_expr( TemplateAstExpr::Block { .. } | TemplateAstExpr::EndBlock | TemplateAstExpr::IfConditional { .. } + | TemplateAstExpr::ConditionalContent { .. } | TemplateAstExpr::ElseConditional { .. } | TemplateAstExpr::Invalid { .. } | TemplateAstExpr::VariableAccess { .. } => eval.push(Instruction::Abort), @@ -191,6 +195,7 @@ fn emit_expr_load( TemplateAstExpr::EndBlock => todo!(), TemplateAstExpr::Block { .. } => todo!(), TemplateAstExpr::IfConditional { .. } => todo!(), + TemplateAstExpr::ConditionalContent { .. } => todo!(), } } diff --git a/tests/cases/2-ast@condition.snap b/tests/cases/2-ast@condition.snap index 90c0914..22d6b93 100644 --- a/tests/cases/2-ast@condition.snap +++ b/tests/cases/2-ast@condition.snap @@ -17,6 +17,8 @@ TemplateAst { [Whitespace]"\n " (13..18), ), }, + }, + ConditionalContent { content: [ StaticContent( [Content]"Hello World!" (18..30),