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, }, ], }