diff --git a/src/ast/mod.rs b/src/ast/mod.rs
index e5d9d82..5405f53 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -4,7 +4,9 @@ use winnow::RecoverableParser;
use winnow::combinator::alt;
use winnow::combinator::cut_err;
use winnow::combinator::delimited;
+use winnow::combinator::not;
use winnow::combinator::opt;
+use winnow::combinator::preceded;
use winnow::combinator::repeat;
use winnow::combinator::repeat_till;
use winnow::error::AddContext;
@@ -140,7 +142,7 @@ impl<'input> Parser, TemplateToken, AstError> for TokenKind {
}
pub fn parse(input: &[TemplateToken]) -> Result, AstFailure> {
- let (_remaining, val, errors) = parse_ast.recoverable_parse(TokenSlice::new(input));
+ let (_remaining, val, errors) = parse_asts.recoverable_parse(TokenSlice::new(input));
if errors.is_empty()
&& let Some(val) = val
@@ -155,23 +157,46 @@ pub fn parse(input: &[TemplateToken]) -> Result, AstFailure> {
pub enum TemplateAstExpr<'input> {
StaticContent(TemplateToken),
Interpolation {
- prev_whitespace: Option,
- wants_output: Option,
+ prev_whitespace_content: Option,
+ wants_output: TemplateToken,
expression: Box>,
- post_whitespace: Option,
+ post_whitespace_content: Option,
+ },
+ Action {
+ prev_whitespace_content: Option,
+ expression: Box>,
+ post_whitespace_content: Option,
},
VariableAccess(TemplateToken),
+ ConditionalChain {
+ chain: Vec>,
+ },
+ IfConditional {
+ expression: Box>,
+ content: Vec>,
+ end_block: Box>,
+ },
+ ElseConditional {
+ expression: Vec>,
+ },
Invalid(&'input [TemplateToken]),
+ EndBlock,
+ Block {
+ prev_whitespace_content: Option,
+ expression: Box>,
+ post_whitespace_content: Option,
+ },
}
-fn parse_ast<'input>(input: &mut Input<'input>) -> Result>, AstError> {
- repeat(
- 0..,
- alt((
- TokenKind::Content.map(TemplateAstExpr::StaticContent),
- parse_interpolation,
- )),
- )
+fn parse_asts<'input>(input: &mut Input<'input>) -> Result>, AstError> {
+ repeat(0.., parse_ast).parse_next(input)
+}
+fn parse_ast<'input>(input: &mut Input<'input>) -> Result, AstError> {
+ alt((
+ TokenKind::Content.map(TemplateAstExpr::StaticContent),
+ parse_interpolation,
+ parse_action,
+ ))
.parse_next(input)
}
@@ -179,16 +204,16 @@ fn parse_interpolation<'input>(
input: &mut Input<'input>,
) -> Result, AstError> {
let expr_parser = resume_after_cut(
- alt((parse_variable_access,)),
+ parse_value_expression,
repeat_till(1.., any, TokenKind::RightDelim).map(|((), _)| ()),
)
.with_taken()
.map(|(expr, taken)| expr.unwrap_or(TemplateAstExpr::Invalid(taken)));
- let (prev_whitespace, _left, (wants_output, expression, _right, post_whitespace)) = (
+ let (prev_whitespace, _left, wants_output, (expression, _right, post_whitespace)) = (
opt(TokenKind::Whitespace),
TokenKind::LeftDelim,
+ TokenKind::WantsOutput,
cut_err((
- opt(TokenKind::WantsOutput),
delimited(ignore_ws, expr_parser, ignore_ws).map(Box::new),
TokenKind::RightDelim,
opt(TokenKind::Whitespace),
@@ -197,13 +222,89 @@ fn parse_interpolation<'input>(
.parse_next(input)?;
Ok(TemplateAstExpr::Interpolation {
- prev_whitespace,
+ prev_whitespace_content: prev_whitespace,
wants_output,
expression,
- post_whitespace,
+ post_whitespace_content: post_whitespace,
})
}
+fn parse_value_expression<'input>(
+ input: &mut Input<'input>,
+) -> Result, AstError> {
+ alt((parse_variable_access,)).parse_next(input)
+}
+
+fn parse_action<'input>(input: &mut Input<'input>) -> Result, AstError> {
+ alt((parse_conditional_chain,)).parse_next(input)
+}
+
+fn parse_conditional_chain<'input>(
+ input: &mut Input<'input>,
+) -> Result, AstError> {
+ let if_expression = parse_conditional.parse_next(input)?;
+ let mut chain = vec![];
+
+ let (content, end_block): (Vec<_>, _) =
+ repeat_till(1.., parse_ast, parse_end).parse_next(input)?;
+
+ chain.push(TemplateAstExpr::IfConditional {
+ expression: Box::new(if_expression),
+ content,
+ end_block: Box::new(end_block),
+ });
+
+ Ok(TemplateAstExpr::ConditionalChain { chain })
+}
+
+fn parse_conditional<'input>(
+ input: &mut Input<'input>,
+) -> Result, AstError> {
+ parse_block(preceded(
+ TokenKind::ConditionalIf,
+ surrounded(ignore_ws, parse_value_expression),
+ ))
+ .parse_next(input)
+}
+
+fn parse_end<'input>(input: &mut Input<'input>) -> Result, AstError> {
+ parse_block(TokenKind::End.value(TemplateAstExpr::EndBlock)).parse_next(input)
+}
+
+fn parse_block<'input, ParseNext>(
+ parser: ParseNext,
+) -> impl Parser, TemplateAstExpr<'input>, AstError>
+where
+ ParseNext: Parser, TemplateAstExpr<'input>, AstError>,
+{
+ let expr_parser = resume_after_cut(
+ parser,
+ repeat_till(1.., any, TokenKind::RightDelim).map(|((), _)| ()),
+ )
+ .with_taken()
+ .map(|(expr, taken)| expr.unwrap_or(TemplateAstExpr::Invalid(taken)));
+
+ (
+ opt(TokenKind::Whitespace),
+ TokenKind::LeftDelim,
+ not(TokenKind::WantsOutput),
+ cut_err((
+ delimited(ignore_ws, expr_parser.map(Box::new), ignore_ws),
+ TokenKind::RightDelim,
+ opt(TokenKind::Whitespace),
+ )),
+ )
+ .map(
+ |(prev_whitespace, _left, _not_token, (expression, _right, post_whitespace))| {
+ TemplateAstExpr::Block {
+ prev_whitespace_content: prev_whitespace,
+ expression,
+ post_whitespace_content: post_whitespace,
+ }
+ },
+ )
+}
+
fn parse_variable_access<'input>(
input: &mut Input<'input>,
) -> Result, AstError> {
@@ -216,6 +317,20 @@ fn ignore_ws<'input>(input: &mut Input<'input>) -> Result<(), AstError> {
repeat(.., TokenKind::Whitespace).parse_next(input)
}
+fn surrounded(
+ ignored: IgnoredParser,
+ parser: ParseNext,
+) -> impl Parser
+where
+ Input: Stream,
+ Error: ParserError,
+ IgnoredParser: Parser,
+ IgnoredParser: Clone,
+ ParseNext: Parser,
+{
+ delimited(ignored.clone(), parser, ignored)
+}
+
#[cfg(test)]
mod tests {
use crate::ast::parse;
@@ -260,28 +375,96 @@ mod tests {
},
),
Interpolation {
- prev_whitespace: Some(
+ prev_whitespace_content: Some(
TemplateToken {
kind: Whitespace,
source: " ",
},
),
- wants_output: Some(
- TemplateToken {
- kind: WantsOutput,
- source: "=",
- },
- ),
+ wants_output: TemplateToken {
+ kind: WantsOutput,
+ source: "=",
+ },
expression: VariableAccess(
TemplateToken {
kind: Ident,
source: "world",
},
),
- post_whitespace: None,
+ post_whitespace_content: None,
},
],
}
"#);
}
+
+ #[test]
+ fn check_simple_if() {
+ let input = "{{ if foo }} Hiii {{ end }}";
+
+ let parsed = crate::parser::parse(input.into()).unwrap();
+
+ let ast = parse(parsed.tokens()).unwrap();
+
+ insta::assert_debug_snapshot!(ast, @r#"
+ TemplateAst {
+ root: [
+ ConditionalChain {
+ chain: [
+ IfConditional {
+ expression: Block {
+ prev_whitespace_content: None,
+ expression: VariableAccess(
+ TemplateToken {
+ kind: Ident,
+ source: "foo",
+ },
+ ),
+ post_whitespace_content: Some(
+ TemplateToken {
+ kind: Whitespace,
+ source: " ",
+ },
+ ),
+ },
+ content: [
+ StaticContent(
+ TemplateToken {
+ kind: Content,
+ source: "Hiii",
+ },
+ ),
+ ],
+ end_block: Block {
+ prev_whitespace_content: Some(
+ TemplateToken {
+ kind: Whitespace,
+ source: " ",
+ },
+ ),
+ expression: EndBlock,
+ post_whitespace_content: None,
+ },
+ },
+ ],
+ },
+ ],
+ }
+ "#);
+ }
+
+ #[test]
+ fn check_nested_simple_if() {
+ let input = r#"{{ if foo }}
+ {{ if bar }}
+ Hiii
+ {{ end }}
+ {{ end }}"#;
+
+ let parsed = crate::parser::parse(input.into()).unwrap();
+
+ let ast = parse(parsed.tokens()).unwrap();
+
+ insta::assert_debug_snapshot!(ast);
+ }
}
diff --git a/src/ast/snapshots/nomo__ast__tests__check_nested_simple_if.snap b/src/ast/snapshots/nomo__ast__tests__check_nested_simple_if.snap
new file mode 100644
index 0000000..8e58393
--- /dev/null
+++ b/src/ast/snapshots/nomo__ast__tests__check_nested_simple_if.snap
@@ -0,0 +1,84 @@
+---
+source: src/ast/mod.rs
+expression: ast
+---
+TemplateAst {
+ root: [
+ ConditionalChain {
+ chain: [
+ IfConditional {
+ expression: Block {
+ prev_whitespace_content: None,
+ expression: VariableAccess(
+ TemplateToken {
+ kind: Ident,
+ source: "foo",
+ },
+ ),
+ post_whitespace_content: Some(
+ TemplateToken {
+ kind: Whitespace,
+ source: "
+ ",
+ },
+ ),
+ },
+ content: [
+ ConditionalChain {
+ chain: [
+ IfConditional {
+ expression: Block {
+ prev_whitespace_content: None,
+ expression: VariableAccess(
+ TemplateToken {
+ kind: Ident,
+ source: "bar",
+ },
+ ),
+ post_whitespace_content: Some(
+ TemplateToken {
+ kind: Whitespace,
+ source: "
+ ",
+ },
+ ),
+ },
+ content: [
+ StaticContent(
+ TemplateToken {
+ kind: Content,
+ source: "Hiii",
+ },
+ ),
+ ],
+ end_block: Block {
+ prev_whitespace_content: Some(
+ TemplateToken {
+ kind: Whitespace,
+ source: "
+ ",
+ },
+ ),
+ expression: EndBlock,
+ post_whitespace_content: Some(
+ TemplateToken {
+ kind: Whitespace,
+ source: "
+ ",
+ },
+ ),
+ },
+ },
+ ],
+ },
+ ],
+ end_block: Block {
+ prev_whitespace_content: None,
+ expression: EndBlock,
+ post_whitespace_content: None,
+ },
+ },
+ ],
+ },
+ ],
+}
diff --git a/src/emit/mod.rs b/src/emit/mod.rs
index c3fb6a8..be15eec 100644
--- a/src/emit/mod.rs
+++ b/src/emit/mod.rs
@@ -24,19 +24,10 @@ pub struct VariableSlot {
#[derive(Debug)]
pub enum Instruction {
- AppendContent {
- content: NomoInput,
- },
- LoadFromContextToSlot {
- name: NomoInput,
- slot: VariableSlot,
- },
- EmitFromSlot {
- slot: VariableSlot,
- },
- PushScope {
- inherit_parent: bool,
- },
+ AppendContent { content: NomoInput },
+ LoadFromContextToSlot { name: NomoInput, slot: VariableSlot },
+ EmitFromSlot { slot: VariableSlot },
+ PushScope { inherit_parent: bool },
Abort,
}
@@ -64,10 +55,10 @@ fn emit_ast_expr(
});
}
TemplateAstExpr::Interpolation {
- prev_whitespace,
+ prev_whitespace_content: prev_whitespace,
wants_output,
expression,
- post_whitespace,
+ post_whitespace_content: post_whitespace,
} => {
if let Some(ws) = prev_whitespace {
eval.push(Instruction::AppendContent {
@@ -77,10 +68,7 @@ fn emit_ast_expr(
let emit_slot = machine.reserve_slot();
emit_expr(machine, eval, emit_slot, expression);
-
- if wants_output.is_some() {
- eval.push(Instruction::EmitFromSlot { slot: emit_slot });
- }
+ eval.push(Instruction::EmitFromSlot { slot: emit_slot });
if let Some(ws) = post_whitespace {
eval.push(Instruction::AppendContent {
@@ -91,6 +79,24 @@ fn emit_ast_expr(
TemplateAstExpr::Invalid { .. } | TemplateAstExpr::VariableAccess { .. } => {
eval.push(Instruction::Abort)
}
+ TemplateAstExpr::ConditionalChain { chain } => todo!(),
+ TemplateAstExpr::ElseConditional { expression } => todo!(),
+ TemplateAstExpr::Action {
+ prev_whitespace_content,
+ expression,
+ post_whitespace_content,
+ } => todo!(),
+ TemplateAstExpr::EndBlock => todo!(),
+ TemplateAstExpr::Block {
+ prev_whitespace_content,
+ expression,
+ post_whitespace_content,
+ } => todo!(),
+ TemplateAstExpr::IfConditional {
+ expression,
+ content,
+ end_block,
+ } => todo!(),
}
}
@@ -111,6 +117,24 @@ fn emit_expr(
TemplateAstExpr::StaticContent { .. } | TemplateAstExpr::Interpolation { .. } => {
unreachable!("Invalid AST here")
}
+ TemplateAstExpr::ConditionalChain { chain } => todo!(),
+ TemplateAstExpr::ElseConditional { expression } => todo!(),
+ TemplateAstExpr::Action {
+ prev_whitespace_content,
+ expression,
+ post_whitespace_content,
+ } => todo!(),
+ TemplateAstExpr::EndBlock => todo!(),
+ TemplateAstExpr::Block {
+ prev_whitespace_content,
+ expression,
+ post_whitespace_content,
+ } => todo!(),
+ TemplateAstExpr::IfConditional {
+ expression,
+ content,
+ end_block,
+ } => todo!(),
}
}
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index f8089a3..ebe854e 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -420,7 +420,7 @@ fn parse_ident<'input>(input: &mut Input<'input>) -> PResult<'input, TemplateTok
}
fn ident<'input>(input: &mut Input<'input>) -> PResult<'input, NomoInput> {
- peek(not(parse_literal))
+ peek(not(alt((parse_literal, parse_condition, parse_end))))
.context(ParseError::ctx().msg("Expected an ident, but found a literal instead"))
.parse_next(input)?;
@@ -564,7 +564,7 @@ mod tests {
source: " ",
},
TemplateToken {
- kind: Ident,
+ kind: ConditionalIf,
source: "if",
},
TemplateToken {
@@ -608,7 +608,7 @@ mod tests {
source: " ",
},
TemplateToken {
- kind: Ident,
+ kind: End,
source: "end",
},
TemplateToken {
diff --git a/tests/cases/2-ast@identifiers.snap b/tests/cases/2-ast@identifiers.snap
index 6a2ca03..11dc899 100644
--- a/tests/cases/2-ast@identifiers.snap
+++ b/tests/cases/2-ast@identifiers.snap
@@ -6,20 +6,18 @@ input_file: tests/cases/identifiers.nomo
TemplateAst {
root: [
Interpolation {
- prev_whitespace: None,
- wants_output: Some(
- TemplateToken {
- kind: WantsOutput,
- source: "=",
- },
- ),
+ prev_whitespace_content: None,
+ wants_output: TemplateToken {
+ kind: WantsOutput,
+ source: "=",
+ },
expression: VariableAccess(
TemplateToken {
kind: Ident,
source: "_name",
},
),
- post_whitespace: Some(
+ post_whitespace_content: Some(
TemplateToken {
kind: Whitespace,
source: "
@@ -28,20 +26,18 @@ TemplateAst {
),
},
Interpolation {
- prev_whitespace: None,
- wants_output: Some(
- TemplateToken {
- kind: WantsOutput,
- source: "=",
- },
- ),
+ prev_whitespace_content: None,
+ wants_output: TemplateToken {
+ kind: WantsOutput,
+ source: "=",
+ },
expression: VariableAccess(
TemplateToken {
kind: Ident,
source: "a_name",
},
),
- post_whitespace: Some(
+ post_whitespace_content: Some(
TemplateToken {
kind: Whitespace,
source: "
@@ -50,20 +46,18 @@ TemplateAst {
),
},
Interpolation {
- prev_whitespace: None,
- wants_output: Some(
- TemplateToken {
- kind: WantsOutput,
- source: "=",
- },
- ),
+ prev_whitespace_content: None,
+ wants_output: TemplateToken {
+ kind: WantsOutput,
+ source: "=",
+ },
expression: VariableAccess(
TemplateToken {
kind: Ident,
source: "1name",
},
),
- post_whitespace: Some(
+ post_whitespace_content: Some(
TemplateToken {
kind: Whitespace,
source: "
@@ -72,20 +66,18 @@ TemplateAst {
),
},
Interpolation {
- prev_whitespace: None,
- wants_output: Some(
- TemplateToken {
- kind: WantsOutput,
- source: "=",
- },
- ),
+ prev_whitespace_content: None,
+ wants_output: TemplateToken {
+ kind: WantsOutput,
+ source: "=",
+ },
expression: VariableAccess(
TemplateToken {
kind: Ident,
source: "_name1",
},
),
- post_whitespace: Some(
+ post_whitespace_content: Some(
TemplateToken {
kind: Whitespace,
source: "
@@ -94,20 +86,18 @@ TemplateAst {
),
},
Interpolation {
- prev_whitespace: None,
- wants_output: Some(
- TemplateToken {
- kind: WantsOutput,
- source: "=",
- },
- ),
+ prev_whitespace_content: None,
+ wants_output: TemplateToken {
+ kind: WantsOutput,
+ source: "=",
+ },
expression: VariableAccess(
TemplateToken {
kind: Ident,
source: "_namE",
},
),
- post_whitespace: Some(
+ post_whitespace_content: Some(
TemplateToken {
kind: Whitespace,
source: "
@@ -116,20 +106,18 @@ TemplateAst {
),
},
Interpolation {
- prev_whitespace: None,
- wants_output: Some(
- TemplateToken {
- kind: WantsOutput,
- source: "=",
- },
- ),
+ prev_whitespace_content: None,
+ wants_output: TemplateToken {
+ kind: WantsOutput,
+ source: "=",
+ },
expression: VariableAccess(
TemplateToken {
kind: Ident,
source: "name1",
},
),
- post_whitespace: None,
+ post_whitespace_content: None,
},
],
}
diff --git a/tests/cases/2-ast@interpolation.snap b/tests/cases/2-ast@interpolation.snap
index 78da4c0..5871ea9 100644
--- a/tests/cases/2-ast@interpolation.snap
+++ b/tests/cases/2-ast@interpolation.snap
@@ -12,25 +12,23 @@ TemplateAst {
},
),
Interpolation {
- prev_whitespace: Some(
+ prev_whitespace_content: Some(
TemplateToken {
kind: Whitespace,
source: " ",
},
),
- wants_output: Some(
- TemplateToken {
- kind: WantsOutput,
- source: "=",
- },
- ),
+ wants_output: TemplateToken {
+ kind: WantsOutput,
+ source: "=",
+ },
expression: VariableAccess(
TemplateToken {
kind: Ident,
source: "name",
},
),
- post_whitespace: None,
+ post_whitespace_content: None,
},
],
}
diff --git a/tests/cases/2-ast@multiple.snap b/tests/cases/2-ast@multiple.snap
index b2b9471..15f14c2 100644
--- a/tests/cases/2-ast@multiple.snap
+++ b/tests/cases/2-ast@multiple.snap
@@ -12,25 +12,23 @@ TemplateAst {
},
),
Interpolation {
- prev_whitespace: Some(
+ prev_whitespace_content: Some(
TemplateToken {
kind: Whitespace,
source: " ",
},
),
- wants_output: Some(
- TemplateToken {
- kind: WantsOutput,
- source: "=",
- },
- ),
+ wants_output: TemplateToken {
+ kind: WantsOutput,
+ source: "=",
+ },
expression: VariableAccess(
TemplateToken {
kind: Ident,
source: "name",
},
),
- post_whitespace: Some(
+ post_whitespace_content: Some(
TemplateToken {
kind: Whitespace,
source: " ",
@@ -38,20 +36,18 @@ TemplateAst {
),
},
Interpolation {
- prev_whitespace: None,
- wants_output: Some(
- TemplateToken {
- kind: WantsOutput,
- source: "=",
- },
- ),
+ prev_whitespace_content: None,
+ wants_output: TemplateToken {
+ kind: WantsOutput,
+ source: "=",
+ },
expression: VariableAccess(
TemplateToken {
kind: Ident,
source: "lastname",
},
),
- post_whitespace: None,
+ post_whitespace_content: None,
},
],
}