Add ast parsing for for loops

Signed-off-by: Marcel Müller <neikos@neikos.email>
This commit is contained in:
Marcel Müller 2026-03-11 14:00:45 +01:00
parent a099c74b1b
commit e64256b65f
3 changed files with 165 additions and 0 deletions

View file

@ -230,6 +230,18 @@ pub enum TemplateAstExpr<'input> {
ConditionalChain {
chain: Vec<TemplateAstExpr<'input>>,
},
ForChain {
for_block: Box<TemplateAstExpr<'input>>,
content: Vec<TemplateAstExpr<'input>>,
else_block: Option<Box<TemplateAstExpr<'input>>>,
else_content: Option<Vec<TemplateAstExpr<'input>>>,
end_block: Box<TemplateAstExpr<'input>>,
},
For {
value_ident: TemplateToken,
value_expression: Box<TemplateAstExpr<'input>>,
},
ForElse,
VariableAccess(TemplateToken),
IfConditional {
expression: Box<TemplateAstExpr<'input>>,
@ -316,6 +328,7 @@ fn parse_action<'input>(input: &mut Input<'input>) -> Result<TemplateAstExpr<'in
"action",
alt((
parse_conditional_chain,
parse_for_chain,
(parse_block(
cut_err(not(repeat_till(
0..,
@ -336,6 +349,67 @@ fn parse_action<'input>(input: &mut Input<'input>) -> Result<TemplateAstExpr<'in
.parse_next(input)
}
fn parse_for_chain<'input>(input: &mut Input<'input>) -> Result<TemplateAstExpr<'input>, AstError> {
trace("for_loop", |input: &mut Input<'input>| {
let for_block = parse_for_loop.map(Box::new).parse_next(input)?;
let loop_end = (
opt((
parse_loop_else.map(Box::new),
repeat_till(0.., parse_ast, peek(parse_end)).map(|(val, _)| val),
)),
parse_end.map(Box::new),
);
let (content, taken) = resume_after_cut(
repeat_till(0.., parse_ast, loop_end),
repeat_till(0.., any, parse_end).map(|((), _)| ()),
)
.with_taken()
.parse_next(input)?;
let Some((content, (else_stuff, end_block))) = content else {
return Ok(TemplateAstExpr::Invalid(taken));
};
let (else_block, else_content) = else_stuff.unzip();
Ok(TemplateAstExpr::ForChain {
for_block,
content,
else_block,
else_content,
end_block,
})
})
.parse_next(input)
}
fn parse_for_loop<'input>(input: &mut Input<'input>) -> Result<TemplateAstExpr<'input>, AstError> {
trace(
"for_loop_inner",
parse_block(
(
ws,
TokenKind::For,
ws,
TokenKind::Ident,
ws,
TokenKind::In,
ws,
parse_value_expression.map(Box::new),
)
.map(|(_, _for, _, value_ident, _, _in, _, value_expression)| {
TemplateAstExpr::For {
value_ident,
value_expression,
}
}),
),
)
.parse_next(input)
}
fn parse_conditional_chain<'input>(
input: &mut Input<'input>,
) -> Result<TemplateAstExpr<'input>, AstError> {
@ -447,6 +521,18 @@ fn parse_conditional_else<'input>(
.parse_next(input)
}
fn parse_loop_else<'input>(input: &mut Input<'input>) -> Result<TemplateAstExpr<'input>, AstError> {
trace(
"end",
parse_block(
TokenKind::ConditionalElse
.value(TemplateAstExpr::ForElse)
.context(AstError::ctx().msg("Expected an else block here")),
),
)
.parse_next(input)
}
fn parse_end<'input>(input: &mut Input<'input>) -> Result<TemplateAstExpr<'input>, AstError> {
trace(
"end",
@ -815,4 +901,15 @@ mod tests {
insta::assert_debug_snapshot!(ast);
}
#[test]
fn check_for_loop() {
let input = "{{ for value in array }} Hi: {{= value }} {{ else }} No Content :C {{ end }}";
let parsed = crate::parser::parse(input.into()).unwrap();
let ast = panic_pretty(input, parse(parsed.tokens()));
insta::assert_debug_snapshot!(ast);
}
}

View file

@ -0,0 +1,61 @@
---
source: src/ast/mod.rs
expression: ast
---
TemplateAst {
root: [
ForChain {
for_block: Block {
prev_whitespace_content: None,
expression: For {
value_ident: [Ident]"value" (7..12),
value_expression: VariableAccess(
[Ident]"array" (16..21),
),
},
post_whitespace_content: Some(
[Whitespace]" " (24..25),
),
},
content: [
StaticContent(
[Content]"Hi:" (25..28),
),
Interpolation {
prev_whitespace_content: Some(
[Whitespace]" " (28..29),
),
expression: VariableAccess(
[Ident]"value" (33..38),
),
post_whitespace_content: Some(
[Whitespace]" " (41..42),
),
},
],
else_block: Some(
Block {
prev_whitespace_content: None,
expression: ForElse,
post_whitespace_content: Some(
[Whitespace]" " (52..53),
),
},
),
else_content: Some(
[
StaticContent(
[Content]"No Content :C" (53..66),
),
],
),
end_block: Block {
prev_whitespace_content: Some(
[Whitespace]" " (66..67),
),
expression: EndBlock,
post_whitespace_content: None,
},
},
],
}

View file

@ -205,6 +205,9 @@ fn emit_ast_expr(
| TemplateAstExpr::IfConditional { .. }
| TemplateAstExpr::ConditionalContent { .. }
| TemplateAstExpr::ElseConditional { .. }
| TemplateAstExpr::ForChain { .. }
| TemplateAstExpr::For { .. }
| TemplateAstExpr::ForElse
| TemplateAstExpr::Invalid { .. }
| TemplateAstExpr::VariableAccess { .. } => eval.push(Instruction::Abort),
}
@ -231,6 +234,10 @@ fn emit_expr_load(
TemplateAstExpr::ElseConditional { .. } => todo!(),
TemplateAstExpr::EndBlock => todo!(),
TemplateAstExpr::Block { .. } => todo!(),
TemplateAstExpr::ForChain { .. } => todo!(),
TemplateAstExpr::For { .. } => todo!(),
TemplateAstExpr::ForElse => todo!(),
TemplateAstExpr::IfConditional { .. } => todo!(),
TemplateAstExpr::ConditionalContent { .. } => todo!(),
}