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);
}
}