Compare commits

...

3 commits

Author SHA1 Message Date
383f543119 Move EndBlock to own element
Signed-off-by: Marcel Müller <neikos@neikos.email>
2026-03-08 20:10:33 +01:00
08b480705b Also emit TokenKind in debug
Signed-off-by: Marcel Müller <neikos@neikos.email>
2026-03-08 20:05:54 +01:00
13eb4ca1d0 Add else parsing
Signed-off-by: Marcel Müller <neikos@neikos.email>
2026-03-08 20:02:33 +01:00
15 changed files with 269 additions and 246 deletions

View file

@ -229,7 +229,6 @@ pub enum TemplateAstExpr<'input> {
IfConditional {
if_block: Box<TemplateAstExpr<'input>>,
content: Vec<TemplateAstExpr<'input>>,
end_block: Box<TemplateAstExpr<'input>>,
},
ElseConditional {
expression: Vec<TemplateAstExpr<'input>>,
@ -319,13 +318,10 @@ fn parse_conditional_chain<'input>(
let mut chain = vec![];
let (content, end_block): (Vec<_>, _) =
repeat_till(0.., parse_ast, parse_end.map(Box::new)).parse_next(input)?;
repeat_till(0.., parse_ast, parse_end).parse_next(input)?;
chain.push(TemplateAstExpr::IfConditional {
if_block,
content,
end_block,
});
chain.push(TemplateAstExpr::IfConditional { if_block, content });
chain.push(end_block);
Ok(TemplateAstExpr::ConditionalChain { chain })
})
@ -456,7 +452,7 @@ mod tests {
TemplateAst {
root: [
StaticContent(
"Hello World" (0..11),
[Content]"Hello World" (0..11),
),
],
}
@ -475,14 +471,14 @@ mod tests {
TemplateAst {
root: [
StaticContent(
"Hello" (0..5),
[Content]"Hello" (0..5),
),
Interpolation {
prev_whitespace_content: Some(
" " (5..6),
[Whitespace]" " (5..6),
),
expression: VariableAccess(
"world" (10..15),
[Ident]"world" (10..15),
),
post_whitespace_content: None,
},
@ -508,24 +504,24 @@ mod tests {
if_block: Block {
prev_whitespace_content: None,
expression: VariableAccess(
"foo" (6..9),
[Ident]"foo" (6..9),
),
post_whitespace_content: Some(
" " (12..13),
[Whitespace]" " (12..13),
),
},
content: [
StaticContent(
"Hiii" (13..17),
[Content]"Hiii" (13..17),
),
],
end_block: Block {
prev_whitespace_content: Some(
" " (17..18),
),
expression: EndBlock,
post_whitespace_content: None,
},
},
Block {
prev_whitespace_content: Some(
[Whitespace]" " (17..18),
),
expression: EndBlock,
post_whitespace_content: None,
},
],
},
@ -604,11 +600,11 @@ mod tests {
insta::assert_debug_snapshot!(result, @r#"
(
[
"{{" (0..2),
" " (2..3),
"foo" (3..6),
" " (6..7),
"}}" (7..9),
[LeftDelim]"{{" (0..2),
[Whitespace]" " (2..3),
[Ident]"foo" (3..6),
[Whitespace]" " (6..7),
[RightDelim]"}}" (7..9),
],
None,
[

View file

@ -10,10 +10,10 @@ TemplateAst {
if_block: Block {
prev_whitespace_content: None,
expression: VariableAccess(
"foo" (6..9),
[Ident]"foo" (6..9),
),
post_whitespace_content: Some(
"\n " (12..25),
[Whitespace]"\n " (12..25),
),
},
content: [
@ -23,47 +23,47 @@ TemplateAst {
if_block: Block {
prev_whitespace_content: None,
expression: VariableAccess(
"bar" (31..34),
[Ident]"bar" (31..34),
),
post_whitespace_content: Some(
"\n " (37..54),
[Whitespace]"\n " (37..54),
),
},
content: [
StaticContent(
"Hiii" (54..58),
[Content]"Hiii" (54..58),
),
],
end_block: Block {
prev_whitespace_content: Some(
"\n " (58..71),
),
expression: EndBlock,
post_whitespace_content: Some(
"\n " (80..89),
),
},
},
Block {
prev_whitespace_content: Some(
[Whitespace]"\n " (58..71),
),
expression: EndBlock,
post_whitespace_content: Some(
[Whitespace]"\n " (80..89),
),
},
],
},
],
end_block: Block {
prev_whitespace_content: None,
expression: EndBlock,
post_whitespace_content: Some(
"\n\n " (98..108),
),
},
},
Block {
prev_whitespace_content: None,
expression: EndBlock,
post_whitespace_content: Some(
[Whitespace]"\n\n " (98..108),
),
},
],
},
Interpolation {
prev_whitespace_content: None,
expression: VariableAccess(
"value" (112..117),
[Ident]"value" (112..117),
),
post_whitespace_content: Some(
"\n " (120..129),
[Whitespace]"\n " (120..129),
),
},
],

View file

@ -4,42 +4,42 @@ expression: parsed
---
ParsedTemplate {
tokens: [
"{{" (0..2),
" " (2..3),
"if" (3..5),
" " (5..6),
"foo" (6..9),
" " (9..10),
"}}" (10..12),
"\n " (12..25),
"{{" (25..27),
" " (27..28),
"if" (28..30),
" " (30..31),
"bar" (31..34),
" " (34..35),
"}}" (35..37),
"\n " (37..54),
"Hiii" (54..58),
"\n " (58..71),
"{{" (71..73),
" " (73..74),
"end" (74..77),
" " (77..78),
"}}" (78..80),
"\n " (80..89),
"{{" (89..91),
" " (91..92),
"end" (92..95),
" " (95..96),
"}}" (96..98),
"\n\n " (98..108),
"{{" (108..110),
"=" (110..111),
" " (111..112),
"value" (112..117),
" " (117..118),
"}}" (118..120),
"\n " (120..129),
[LeftDelim]"{{" (0..2),
[Whitespace]" " (2..3),
[ConditionalIf]"if" (3..5),
[Whitespace]" " (5..6),
[Ident]"foo" (6..9),
[Whitespace]" " (9..10),
[RightDelim]"}}" (10..12),
[Whitespace]"\n " (12..25),
[LeftDelim]"{{" (25..27),
[Whitespace]" " (27..28),
[ConditionalIf]"if" (28..30),
[Whitespace]" " (30..31),
[Ident]"bar" (31..34),
[Whitespace]" " (34..35),
[RightDelim]"}}" (35..37),
[Whitespace]"\n " (37..54),
[Content]"Hiii" (54..58),
[Whitespace]"\n " (58..71),
[LeftDelim]"{{" (71..73),
[Whitespace]" " (73..74),
[End]"end" (74..77),
[Whitespace]" " (77..78),
[RightDelim]"}}" (78..80),
[Whitespace]"\n " (80..89),
[LeftDelim]"{{" (89..91),
[Whitespace]" " (91..92),
[End]"end" (92..95),
[Whitespace]" " (95..96),
[RightDelim]"}}" (96..98),
[Whitespace]"\n\n " (98..108),
[LeftDelim]"{{" (108..110),
[WantsOutput]"=" (110..111),
[Whitespace]" " (111..112),
[Ident]"value" (112..117),
[Whitespace]" " (117..118),
[RightDelim]"}}" (118..120),
[Whitespace]"\n " (120..129),
],
}

View file

@ -93,7 +93,6 @@ fn emit_ast_expr(
let Some(TemplateAstExpr::IfConditional {
if_block: expression,
content,
end_block,
}) = chain.next()
else {
unreachable!("First element in conditional chain should be an IfConditional");
@ -133,13 +132,13 @@ fn emit_ast_expr(
emit_ast_expr(machine, eval, ast);
}
let TemplateAstExpr::Block {
let Some(TemplateAstExpr::Block {
prev_whitespace_content,
post_whitespace_content,
..
} = end_block.as_ref()
}) = chain.last()
else {
unreachable!("The end of an IfConditional must be a Block");
unreachable!("The end of an IfConditional must be a End Block");
};
if let Some(ws) = prev_whitespace_content {

View file

@ -206,6 +206,7 @@ pub enum TokenKind {
Whitespace,
Invalid,
ConditionalIf,
ConditionalElse,
End,
Literal(TokenLiteral),
}
@ -253,7 +254,7 @@ pub struct TemplateToken {
impl std::fmt::Debug for TemplateToken {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self.source())
write!(f, "[{:?}]{:?}", self.kind(), self.source())
}
}
@ -290,6 +291,7 @@ impl TemplateToken {
whitespace => TokenKind::Whitespace,
invalid => TokenKind::Invalid,
conditional_if => TokenKind::ConditionalIf,
conditional_else => TokenKind::ConditionalElse,
end => TokenKind::End,
}
@ -344,7 +346,7 @@ fn parse_interpolate<'input>(input: &mut Input<'input>) -> PResult<'input, Vec<T
let left_delim = "{{".map(TemplateToken::left_delim).parse_next(input)?;
let wants_output = opt("=".map(TemplateToken::wants_output)).parse_next(input)?;
let get_tokens = repeat_till(1.., parse_interpolate_token, peek("}}"));
let get_tokens = repeat_till(1.., parse_block_token, peek("}}"));
let recover = take_until(0.., "}}").void();
let (inside_tokens, _): (Vec<_>, _) = get_tokens
@ -369,13 +371,14 @@ fn parse_interpolate<'input>(input: &mut Input<'input>) -> PResult<'input, Vec<T
Ok(tokens)
}
fn parse_interpolate_token<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> {
fn parse_block_token<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> {
trace(
"parse_interpolate_token",
"parse_block_token",
alt((
parse_ident,
parse_literal,
parse_condition,
parse_condition_if,
parse_condition_else,
parse_end,
parse_whitespace,
)),
@ -385,7 +388,7 @@ fn parse_interpolate_token<'input>(input: &mut Input<'input>) -> PResult<'input,
fn parse_literal<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> {
trace(
"parse_condition",
"parse_literal",
alt((parse_boolean,))
.with_taken()
.map(|(lit, span)| TemplateToken::literal(lit, span)),
@ -401,12 +404,24 @@ fn parse_boolean<'input>(input: &mut Input<'input>) -> PResult<'input, TokenLite
.parse_next(input)
}
fn parse_condition<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> {
trace("parse_condition", "if".map(TemplateToken::conditional_if)).parse_next(input)
fn parse_condition_if<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> {
trace(
"parse_condition_if",
"if".map(TemplateToken::conditional_if),
)
.parse_next(input)
}
fn parse_condition_else<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> {
trace(
"parse_condition_else",
"else".map(TemplateToken::conditional_else),
)
.parse_next(input)
}
fn parse_end<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> {
trace("parse_condition", "end".map(TemplateToken::end)).parse_next(input)
trace("parse_end", "end".map(TemplateToken::end)).parse_next(input)
}
fn parse_whitespace<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateToken> {
@ -436,9 +451,14 @@ fn parse_ident<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateTok
}
fn ident<'input>(input: &mut Input<'input>) -> PResult<'input, NomoInput> {
peek(not(alt((parse_literal, parse_condition, parse_end))))
.context(ParseError::ctx().msg("Expected an ident, but found a literal instead"))
.parse_next(input)?;
peek(not(alt((
parse_literal,
parse_condition_if,
parse_condition_else,
parse_end,
))))
.context(ParseError::ctx().msg("Expected an ident, but found a literal instead"))
.parse_next(input)?;
take_while(1.., |c: char| c.is_alphanumeric() || "_".contains(c)).parse_next(input)
}
@ -475,7 +495,7 @@ mod tests {
Ok(
ParsedTemplate {
tokens: [
"Hello There" (0..11),
[Content]"Hello There" (0..11),
],
},
)
@ -491,13 +511,13 @@ mod tests {
Ok(
ParsedTemplate {
tokens: [
"Hello" (0..5),
" " (5..6),
"{{" (6..8),
" " (8..9),
"there" (9..14),
" " (14..15),
"}}" (15..17),
[Content]"Hello" (0..5),
[Whitespace]" " (5..6),
[LeftDelim]"{{" (6..8),
[Whitespace]" " (8..9),
[Ident]"there" (9..14),
[Whitespace]" " (14..15),
[RightDelim]"}}" (15..17),
],
},
)
@ -540,28 +560,36 @@ mod tests {
#[test]
fn parse_simple_condition() {
let input = "{{ if true }} Hello! {{ end }}";
let input = "{{ if true }} Hello! {{ else }} Bye {{ end }}";
let output = parse(input.into());
insta::assert_debug_snapshot!(output, @r#"
Ok(
ParsedTemplate {
tokens: [
"{{" (0..2),
" " (2..3),
"if" (3..5),
" " (5..6),
"true" (6..10),
" " (10..11),
"}}" (11..13),
" " (13..14),
"Hello!" (14..20),
" " (20..21),
"{{" (21..23),
" " (23..24),
"end" (24..27),
" " (27..28),
"}}" (28..30),
[LeftDelim]"{{" (0..2),
[Whitespace]" " (2..3),
[ConditionalIf]"if" (3..5),
[Whitespace]" " (5..6),
[Literal(Bool(true))]"true" (6..10),
[Whitespace]" " (10..11),
[RightDelim]"}}" (11..13),
[Whitespace]" " (13..14),
[Content]"Hello!" (14..20),
[Whitespace]" " (20..21),
[LeftDelim]"{{" (21..23),
[Whitespace]" " (23..24),
[ConditionalElse]"else" (24..28),
[Whitespace]" " (28..29),
[RightDelim]"}}" (29..31),
[Whitespace]" " (31..32),
[Content]"Bye" (32..35),
[Whitespace]" " (35..36),
[LeftDelim]"{{" (36..38),
[Whitespace]" " (38..39),
[End]"end" (39..42),
[Whitespace]" " (42..43),
[RightDelim]"}}" (43..45),
],
},
)

View file

@ -5,27 +5,27 @@ input_file: tests/cases/condition.nomo
---
ParsedTemplate {
tokens: [
"{{" (0..2),
" " (2..3),
"if" (3..5),
" " (5..6),
"test" (6..10),
" " (10..11),
"}}" (11..13),
"\n " (13..18),
"Hello World!" (18..30),
"\n" (30..31),
"{{" (31..33),
" " (33..34),
"end" (34..37),
" " (37..38),
"}}" (38..40),
"\n\n" (40..42),
"{{" (42..44),
"=" (44..45),
" " (45..46),
"stuff" (46..51),
" " (51..52),
"}}" (52..54),
[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]"Hello World!" (18..30),
[Whitespace]"\n" (30..31),
[LeftDelim]"{{" (31..33),
[Whitespace]" " (33..34),
[End]"end" (34..37),
[Whitespace]" " (37..38),
[RightDelim]"}}" (38..40),
[Whitespace]"\n\n" (40..42),
[LeftDelim]"{{" (42..44),
[WantsOutput]"=" (44..45),
[Whitespace]" " (45..46),
[Ident]"stuff" (46..51),
[Whitespace]" " (51..52),
[RightDelim]"}}" (52..54),
],
}

View file

@ -5,46 +5,46 @@ input_file: tests/cases/identifiers.nomo
---
ParsedTemplate {
tokens: [
"{{" (0..2),
"=" (2..3),
" " (3..4),
"_name" (4..9),
" " (9..10),
"}}" (10..12),
"\n" (12..13),
"{{" (13..15),
"=" (15..16),
" " (16..17),
"a_name" (17..23),
" " (23..24),
"}}" (24..26),
"\n" (26..27),
"{{" (27..29),
"=" (29..30),
" " (30..31),
"1name" (31..36),
" " (36..37),
"}}" (37..39),
"\n" (39..40),
"{{" (40..42),
"=" (42..43),
" " (43..44),
"_name1" (44..50),
" " (50..51),
"}}" (51..53),
"\n" (53..54),
"{{" (54..56),
"=" (56..57),
" " (57..58),
"_namE" (58..63),
" " (63..64),
"}}" (64..66),
"\n" (66..67),
"{{" (67..69),
"=" (69..70),
" " (70..71),
"name1" (71..76),
" " (76..77),
"}}" (77..79),
[LeftDelim]"{{" (0..2),
[WantsOutput]"=" (2..3),
[Whitespace]" " (3..4),
[Ident]"_name" (4..9),
[Whitespace]" " (9..10),
[RightDelim]"}}" (10..12),
[Whitespace]"\n" (12..13),
[LeftDelim]"{{" (13..15),
[WantsOutput]"=" (15..16),
[Whitespace]" " (16..17),
[Ident]"a_name" (17..23),
[Whitespace]" " (23..24),
[RightDelim]"}}" (24..26),
[Whitespace]"\n" (26..27),
[LeftDelim]"{{" (27..29),
[WantsOutput]"=" (29..30),
[Whitespace]" " (30..31),
[Ident]"1name" (31..36),
[Whitespace]" " (36..37),
[RightDelim]"}}" (37..39),
[Whitespace]"\n" (39..40),
[LeftDelim]"{{" (40..42),
[WantsOutput]"=" (42..43),
[Whitespace]" " (43..44),
[Ident]"_name1" (44..50),
[Whitespace]" " (50..51),
[RightDelim]"}}" (51..53),
[Whitespace]"\n" (53..54),
[LeftDelim]"{{" (54..56),
[WantsOutput]"=" (56..57),
[Whitespace]" " (57..58),
[Ident]"_namE" (58..63),
[Whitespace]" " (63..64),
[RightDelim]"}}" (64..66),
[Whitespace]"\n" (66..67),
[LeftDelim]"{{" (67..69),
[WantsOutput]"=" (69..70),
[Whitespace]" " (70..71),
[Ident]"name1" (71..76),
[Whitespace]" " (76..77),
[RightDelim]"}}" (77..79),
],
}

View file

@ -5,13 +5,13 @@ input_file: tests/cases/interpolation.nomo
---
ParsedTemplate {
tokens: [
"Hello! I'm" (0..10),
" " (10..11),
"{{" (11..13),
"=" (13..14),
" " (14..15),
"name" (15..19),
" " (19..20),
"}}" (20..22),
[Content]"Hello! I'm" (0..10),
[Whitespace]" " (10..11),
[LeftDelim]"{{" (11..13),
[WantsOutput]"=" (13..14),
[Whitespace]" " (14..15),
[Ident]"name" (15..19),
[Whitespace]" " (19..20),
[RightDelim]"}}" (20..22),
],
}

View file

@ -5,20 +5,20 @@ input_file: tests/cases/multiple.nomo
---
ParsedTemplate {
tokens: [
"Hi there! My name is" (0..20),
" " (20..21),
"{{" (21..23),
"=" (23..24),
" " (24..25),
"name" (25..29),
" " (29..30),
"}}" (30..32),
" " (32..33),
"{{" (33..35),
"=" (35..36),
" " (36..37),
"lastname" (37..45),
" " (45..46),
"}}" (46..48),
[Content]"Hi there! My name is" (0..20),
[Whitespace]" " (20..21),
[LeftDelim]"{{" (21..23),
[WantsOutput]"=" (23..24),
[Whitespace]" " (24..25),
[Ident]"name" (25..29),
[Whitespace]" " (29..30),
[RightDelim]"}}" (30..32),
[Whitespace]" " (32..33),
[LeftDelim]"{{" (33..35),
[WantsOutput]"=" (35..36),
[Whitespace]" " (36..37),
[Ident]"lastname" (37..45),
[Whitespace]" " (45..46),
[RightDelim]"}}" (46..48),
],
}

View file

@ -5,6 +5,6 @@ input_file: tests/cases/simple.nomo
---
ParsedTemplate {
tokens: [
"Hello World!" (0..12),
[Content]"Hello World!" (0..12),
],
}

View file

@ -11,33 +11,33 @@ TemplateAst {
if_block: Block {
prev_whitespace_content: None,
expression: VariableAccess(
"test" (6..10),
[Ident]"test" (6..10),
),
post_whitespace_content: Some(
"\n " (13..18),
[Whitespace]"\n " (13..18),
),
},
content: [
StaticContent(
"Hello World!" (18..30),
[Content]"Hello World!" (18..30),
),
],
end_block: Block {
prev_whitespace_content: Some(
"\n" (30..31),
),
expression: EndBlock,
post_whitespace_content: Some(
"\n\n" (40..42),
),
},
},
Block {
prev_whitespace_content: Some(
[Whitespace]"\n" (30..31),
),
expression: EndBlock,
post_whitespace_content: Some(
[Whitespace]"\n\n" (40..42),
),
},
],
},
Interpolation {
prev_whitespace_content: None,
expression: VariableAccess(
"stuff" (46..51),
[Ident]"stuff" (46..51),
),
post_whitespace_content: None,
},

View file

@ -8,52 +8,52 @@ TemplateAst {
Interpolation {
prev_whitespace_content: None,
expression: VariableAccess(
"_name" (4..9),
[Ident]"_name" (4..9),
),
post_whitespace_content: Some(
"\n" (12..13),
[Whitespace]"\n" (12..13),
),
},
Interpolation {
prev_whitespace_content: None,
expression: VariableAccess(
"a_name" (17..23),
[Ident]"a_name" (17..23),
),
post_whitespace_content: Some(
"\n" (26..27),
[Whitespace]"\n" (26..27),
),
},
Interpolation {
prev_whitespace_content: None,
expression: VariableAccess(
"1name" (31..36),
[Ident]"1name" (31..36),
),
post_whitespace_content: Some(
"\n" (39..40),
[Whitespace]"\n" (39..40),
),
},
Interpolation {
prev_whitespace_content: None,
expression: VariableAccess(
"_name1" (44..50),
[Ident]"_name1" (44..50),
),
post_whitespace_content: Some(
"\n" (53..54),
[Whitespace]"\n" (53..54),
),
},
Interpolation {
prev_whitespace_content: None,
expression: VariableAccess(
"_namE" (58..63),
[Ident]"_namE" (58..63),
),
post_whitespace_content: Some(
"\n" (66..67),
[Whitespace]"\n" (66..67),
),
},
Interpolation {
prev_whitespace_content: None,
expression: VariableAccess(
"name1" (71..76),
[Ident]"name1" (71..76),
),
post_whitespace_content: None,
},

View file

@ -6,14 +6,14 @@ input_file: tests/cases/interpolation.nomo
TemplateAst {
root: [
StaticContent(
"Hello! I'm" (0..10),
[Content]"Hello! I'm" (0..10),
),
Interpolation {
prev_whitespace_content: Some(
" " (10..11),
[Whitespace]" " (10..11),
),
expression: VariableAccess(
"name" (15..19),
[Ident]"name" (15..19),
),
post_whitespace_content: None,
},

View file

@ -6,23 +6,23 @@ input_file: tests/cases/multiple.nomo
TemplateAst {
root: [
StaticContent(
"Hi there! My name is" (0..20),
[Content]"Hi there! My name is" (0..20),
),
Interpolation {
prev_whitespace_content: Some(
" " (20..21),
[Whitespace]" " (20..21),
),
expression: VariableAccess(
"name" (25..29),
[Ident]"name" (25..29),
),
post_whitespace_content: Some(
" " (32..33),
[Whitespace]" " (32..33),
),
},
Interpolation {
prev_whitespace_content: None,
expression: VariableAccess(
"lastname" (37..45),
[Ident]"lastname" (37..45),
),
post_whitespace_content: None,
},

View file

@ -6,7 +6,7 @@ input_file: tests/cases/simple.nomo
TemplateAst {
root: [
StaticContent(
"Hello World!" (0..12),
[Content]"Hello World!" (0..12),
),
],
}