From 8afc2d1bde10ef81a851e0c2ea0c84b62a95ad61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20M=C3=BCller?= Date: Sun, 8 Mar 2026 15:06:29 +0100 Subject: [PATCH] Add parsing for conditionals (cont.) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Marcel MΓΌller --- flake.lock | 6 +- rust-toolchain.toml | 2 +- src/ast/mod.rs | 345 +++++++++++++----- ...omo__ast__tests__check_invalid_action.snap | 30 ++ ...o__ast__tests__check_nested_simple_if.snap | 224 ++++++++---- .../nomo__ast__tests__simple_if_ast.snap | 70 ++++ .../nomo__ast__tests__simple_if_tokens.snap | 45 +++ src/emit/mod.rs | 161 +++++--- src/eval/mod.rs | 31 +- src/input.rs | 29 +- src/lib.rs | 10 +- src/parser/mod.rs | 137 ++----- tests/cases/1-parsed@condition.snap | 31 ++ tests/cases/1-parsed@identifiers.snap | 210 +++-------- tests/cases/1-parsed@interpolation.snap | 40 +- tests/cases/1-parsed@multiple.snap | 75 +--- tests/cases/1-parsed@simple.snap | 5 +- tests/cases/2-ast@condition.snap | 45 +++ tests/cases/2-ast@identifiers.snap | 84 +---- tests/cases/2-ast@interpolation.snap | 19 +- tests/cases/2-ast@multiple.snap | 33 +- tests/cases/2-ast@simple.snap | 5 +- tests/cases/3-instructions@condition.snap | 42 +++ tests/cases/3-instructions@identifiers.snap | 27 +- tests/cases/3-instructions@interpolation.snap | 6 +- tests/cases/3-instructions@multiple.snap | 10 +- tests/cases/3-instructions@simple.snap | 2 +- tests/cases/4-output@condition.snap | 6 + tests/cases/condition.nomo | 10 + 29 files changed, 994 insertions(+), 746 deletions(-) create mode 100644 src/ast/snapshots/nomo__ast__tests__check_invalid_action.snap create mode 100644 src/ast/snapshots/nomo__ast__tests__simple_if_ast.snap create mode 100644 src/ast/snapshots/nomo__ast__tests__simple_if_tokens.snap create mode 100644 tests/cases/1-parsed@condition.snap create mode 100644 tests/cases/2-ast@condition.snap create mode 100644 tests/cases/3-instructions@condition.snap create mode 100644 tests/cases/4-output@condition.snap create mode 100644 tests/cases/condition.nomo diff --git a/flake.lock b/flake.lock index 9930d69..9eef645 100644 --- a/flake.lock +++ b/flake.lock @@ -63,11 +63,11 @@ ] }, "locked": { - "lastModified": 1772593411, - "narHash": "sha256-47WOnCSyOL6AghZiMIJaTLWM359DHe3be9R1cNCdGUE=", + "lastModified": 1772939270, + "narHash": "sha256-HbxD5DJAKxzo0G8on5wdY+OZNiUWt3FTvGmXmVEmg7g=", "owner": "oxalica", "repo": "rust-overlay", - "rev": "a741b36b77440f5db15fcf2ab6d7d592d2f9ee8f", + "rev": "bb93f191a07c0165992ed6d0b4197ee5c7e6e641", "type": "github" }, "original": { diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 5355133..76a06e6 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.93.1" +channel = "1.94.0" diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 5405f53..29b2333 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -6,18 +6,22 @@ use winnow::combinator::cut_err; use winnow::combinator::delimited; use winnow::combinator::not; use winnow::combinator::opt; +use winnow::combinator::peek; use winnow::combinator::preceded; use winnow::combinator::repeat; use winnow::combinator::repeat_till; +use winnow::combinator::trace; use winnow::error::AddContext; use winnow::error::FromRecoverableError; use winnow::error::ModalError; use winnow::error::ParserError; +use winnow::stream::Offset; use winnow::stream::Recoverable; use winnow::stream::Stream; use winnow::stream::TokenSlice; use winnow::token::any; +use crate::SourceSpan; use crate::parser::TemplateToken; use crate::parser::TokenKind; use crate::resume_after_cut; @@ -83,6 +87,29 @@ impl FromRecoverableError, AstError> for AstError { input: &Input, mut e: AstError, ) -> Self { + e.span = e.span.or_else(|| { + let offset = input.offset_from(token_start); + + let mut tokens = input + .previous_tokens() + .take(offset) + .filter(|t| t.kind() != TokenKind::Whitespace); + let last = tokens.next(); + let first = tokens.last(); + match (last, first) { + (None, None) => None, + (None, Some(single)) | (Some(single), None) => Some(SourceSpan { + range: single.source().get_range(), + }), + (Some(last), Some(first)) => { + let start = first.source().get_range().start; + let end = last.source().get_range().end; + + Some(SourceSpan { range: start..end }) + } + } + }); + e } } @@ -117,7 +144,9 @@ impl ParserError> for AstError { } #[derive(Debug, Error)] -pub struct AstFailure {} +pub struct AstFailure { + errors: Vec, +} impl std::fmt::Display for AstFailure { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -126,8 +155,40 @@ impl std::fmt::Display for AstFailure { } impl AstFailure { - fn from_errors(_errors: Vec, _input: &[TemplateToken]) -> AstFailure { - AstFailure {} + fn from_errors(errors: Vec) -> AstFailure { + AstFailure { errors } + } + + pub fn to_report(&self, source: &str) -> String { + let reports = self + .errors + .iter() + .map(|error| { + annotate_snippets::Level::ERROR + .primary_title( + error + .message + .as_deref() + .unwrap_or("An error occurred while producing an Ast"), + ) + .element( + annotate_snippets::Snippet::source(source).annotation( + annotate_snippets::AnnotationKind::Primary + .span(error.span.clone().map(|s| s.range).unwrap_or_else(|| 0..0)), + ), + ) + .elements( + error + .help + .as_ref() + .map(|help| annotate_snippets::Level::HELP.message(help)), + ) + }) + .collect::>(); + + let renderer = annotate_snippets::Renderer::styled() + .decor_style(annotate_snippets::renderer::DecorStyle::Unicode); + renderer.render(&reports) } } @@ -149,7 +210,7 @@ pub fn parse(input: &[TemplateToken]) -> Result, AstFailure> { { Ok(TemplateAst { root: val }) } else { - Err(AstFailure::from_errors(errors, input)) + Err(AstFailure::from_errors(errors)) } } @@ -157,12 +218,6 @@ pub fn parse(input: &[TemplateToken]) -> Result, AstFailure> { pub enum TemplateAstExpr<'input> { StaticContent(TemplateToken), Interpolation { - prev_whitespace_content: Option, - wants_output: TemplateToken, - expression: Box>, - post_whitespace_content: Option, - }, - Action { prev_whitespace_content: Option, expression: Box>, post_whitespace_content: Option, @@ -172,7 +227,7 @@ pub enum TemplateAstExpr<'input> { chain: Vec>, }, IfConditional { - expression: Box>, + if_block: Box>, content: Vec>, end_block: Box>, }, @@ -193,8 +248,11 @@ fn parse_asts<'input>(input: &mut Input<'input>) -> Result(input: &mut Input<'input>) -> Result, AstError> { alt(( - TokenKind::Content.map(TemplateAstExpr::StaticContent), - parse_interpolation, + trace( + "content", + TokenKind::Content.map(TemplateAstExpr::StaticContent), + ), + trace("interpolation", parse_interpolation), parse_action, )) .parse_next(input) @@ -205,16 +263,16 @@ fn parse_interpolation<'input>( ) -> Result, AstError> { let expr_parser = resume_after_cut( parse_value_expression, - repeat_till(1.., any, TokenKind::RightDelim).map(|((), _)| ()), + repeat_till(0.., any, peek(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(( - delimited(ignore_ws, expr_parser, ignore_ws).map(Box::new), + surrounded(ws, expr_parser).map(Box::new), TokenKind::RightDelim, opt(TokenKind::Whitespace), )), @@ -223,52 +281,99 @@ fn parse_interpolation<'input>( Ok(TemplateAstExpr::Interpolation { prev_whitespace_content: prev_whitespace, - wants_output, expression, 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) + trace( + "action", + alt(( + parse_conditional_chain, + (parse_block( + cut_err(not(repeat_till( + 0.., + any, + peek((ws, TokenKind::RightDelim)), + ) + .map(|((), _)| ()))) + .context( + AstError::ctx() + .msg("Standlone action block") + .help("If you want to output this expression, add a '=' to the block"), + ) + .take() + .map(TemplateAstExpr::Invalid), + )), + )), + ) + .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![]; + trace("conditional_chain", |input: &mut Input<'input>| { + let if_block = parse_conditional.map(Box::new).parse_next(input)?; + let mut chain = vec![]; - let (content, end_block): (Vec<_>, _) = - repeat_till(1.., parse_ast, parse_end).parse_next(input)?; + let (content, end_block): (Vec<_>, _) = + repeat_till(0.., parse_ast, parse_end.map(Box::new)).parse_next(input)?; - chain.push(TemplateAstExpr::IfConditional { - expression: Box::new(if_expression), - content, - end_block: Box::new(end_block), - }); + chain.push(TemplateAstExpr::IfConditional { + if_block, + content, + end_block, + }); - Ok(TemplateAstExpr::ConditionalChain { chain }) + Ok(TemplateAstExpr::ConditionalChain { chain }) + }) + .parse_next(input) } fn parse_conditional<'input>( input: &mut Input<'input>, ) -> Result, AstError> { - parse_block(preceded( - TokenKind::ConditionalIf, - surrounded(ignore_ws, parse_value_expression), - )) + trace( + "conditional", + parse_block(preceded( + TokenKind::ConditionalIf, + cut_err( + surrounded(ws, parse_value_expression) + .context(AstError::ctx().msg("Expected an expression after 'if'")), + ), + )), + ) .parse_next(input) } fn parse_end<'input>(input: &mut Input<'input>) -> Result, AstError> { - parse_block(TokenKind::End.value(TemplateAstExpr::EndBlock)).parse_next(input) + trace( + "end", + parse_block( + TokenKind::End + .value(TemplateAstExpr::EndBlock) + .context(AstError::ctx().msg("Expected an end block here")), + ), + ) + .parse_next(input) +} + +fn parse_value_expression<'input>( + input: &mut Input<'input>, +) -> Result, AstError> { + trace("value_expression", alt((parse_variable_access,))).parse_next(input) +} + +fn parse_variable_access<'input>( + input: &mut Input<'input>, +) -> Result, AstError> { + trace( + "variable_access", + TokenKind::Ident.map(TemplateAstExpr::VariableAccess), + ) + .parse_next(input) } fn parse_block<'input, ParseNext>( @@ -279,7 +384,7 @@ where { let expr_parser = resume_after_cut( parser, - repeat_till(1.., any, TokenKind::RightDelim).map(|((), _)| ()), + repeat_till(0.., any, peek(TokenKind::RightDelim)).map(|((), _)| ()), ) .with_taken() .map(|(expr, taken)| expr.unwrap_or(TemplateAstExpr::Invalid(taken))); @@ -288,11 +393,11 @@ where opt(TokenKind::Whitespace), TokenKind::LeftDelim, not(TokenKind::WantsOutput), - cut_err(( - delimited(ignore_ws, expr_parser.map(Box::new), ignore_ws), + ( + surrounded(ws, expr_parser.map(Box::new)), TokenKind::RightDelim, opt(TokenKind::Whitespace), - )), + ), ) .map( |(prev_whitespace, _left, _not_token, (expression, _right, post_whitespace))| { @@ -305,15 +410,7 @@ where ) } -fn parse_variable_access<'input>( - input: &mut Input<'input>, -) -> Result, AstError> { - TokenKind::Ident - .map(TemplateAstExpr::VariableAccess) - .parse_next(input) -} - -fn ignore_ws<'input>(input: &mut Input<'input>) -> Result<(), AstError> { +fn ws<'input>(input: &mut Input<'input>) -> Result<(), AstError> { repeat(.., TokenKind::Whitespace).parse_next(input) } @@ -333,7 +430,19 @@ where #[cfg(test)] mod tests { + use winnow::Parser; + use winnow::combinator::alt; + use winnow::combinator::fail; + use winnow::stream::TokenSlice; + + use crate::ast::AstError; + use crate::ast::AstFailure; + use crate::ast::TemplateAst; + use crate::ast::TemplateAstExpr; use crate::ast::parse; + use crate::ast::parse_block; + use crate::ast::parse_end; + use crate::parser::TokenKind; #[test] fn check_only_content() { @@ -347,10 +456,7 @@ mod tests { TemplateAst { root: [ StaticContent( - TemplateToken { - kind: Content, - source: "Hello World", - }, + "Hello World" (0..11), ), ], } @@ -369,27 +475,14 @@ mod tests { TemplateAst { root: [ StaticContent( - TemplateToken { - kind: Content, - source: "Hello", - }, + "Hello" (0..5), ), Interpolation { prev_whitespace_content: Some( - TemplateToken { - kind: Whitespace, - source: " ", - }, + " " (5..6), ), - wants_output: TemplateToken { - kind: WantsOutput, - source: "=", - }, expression: VariableAccess( - TemplateToken { - kind: Ident, - source: "world", - }, + "world" (10..15), ), post_whitespace_content: None, }, @@ -412,35 +505,23 @@ mod tests { ConditionalChain { chain: [ IfConditional { - expression: Block { + if_block: Block { prev_whitespace_content: None, expression: VariableAccess( - TemplateToken { - kind: Ident, - source: "foo", - }, + "foo" (6..9), ), post_whitespace_content: Some( - TemplateToken { - kind: Whitespace, - source: " ", - }, + " " (12..13), ), }, content: [ StaticContent( - TemplateToken { - kind: Content, - source: "Hiii", - }, + "Hiii" (13..17), ), ], end_block: Block { prev_whitespace_content: Some( - TemplateToken { - kind: Whitespace, - source: " ", - }, + " " (17..18), ), expression: EndBlock, post_whitespace_content: None, @@ -453,18 +534,98 @@ mod tests { "#); } + fn panic_pretty<'a>( + input: &'_ str, + tokens: Result, AstFailure>, + ) -> TemplateAst<'a> { + match tokens { + Ok(ast) => ast, + Err(failure) => { + panic!("{}", failure.to_report(input)); + } + } + } + + #[test] + fn check_invalid_action() { + let input = r#"{{ value }} + {{ value }} + {{ value }} + {{ value }} + {{ value }}"#; + + let parsed = crate::parser::parse(input.into()).unwrap(); + + let ast = parse(parsed.tokens()).unwrap_err(); + + insta::assert_snapshot!(ast.to_report(input)); + } + #[test] fn check_nested_simple_if() { let input = r#"{{ if foo }} {{ if bar }} Hiii {{ end }} - {{ end }}"#; + {{ end }} + + {{= value }} + "#; let parsed = crate::parser::parse(input.into()).unwrap(); - let ast = parse(parsed.tokens()).unwrap(); + insta::assert_debug_snapshot!("simple_if_tokens", parsed); - insta::assert_debug_snapshot!(ast); + let ast = panic_pretty(input, parse(parsed.tokens())); + + insta::assert_debug_snapshot!("simple_if_ast", ast); + } + + #[test] + fn check_parsing_block() { + use winnow::RecoverableParser; + + let input = "{{ foo }}"; + + let parsed = crate::parser::parse(input.into()).unwrap(); + + let result = alt(( + parse_end, + parse_block( + (TokenKind::Ident.void(), fail::<_, (), _>) + .void() + .context(AstError::ctx().msg("No ident allowed")) + .take() + .map(TemplateAstExpr::Invalid), + ), + )) + .recoverable_parse(TokenSlice::new(parsed.tokens())); + + insta::assert_debug_snapshot!(result, @r#" + ( + [ + "{{" (0..2), + " " (2..3), + "foo" (3..6), + " " (6..7), + "}}" (7..9), + ], + None, + [ + AstError { + message: Some( + "No ident allowed", + ), + help: None, + span: Some( + SourceSpan { + range: 0..6, + }, + ), + is_fatal: false, + }, + ], + ) + "#); } } diff --git a/src/ast/snapshots/nomo__ast__tests__check_invalid_action.snap b/src/ast/snapshots/nomo__ast__tests__check_invalid_action.snap new file mode 100644 index 0000000..c8f8f50 --- /dev/null +++ b/src/ast/snapshots/nomo__ast__tests__check_invalid_action.snap @@ -0,0 +1,30 @@ +--- +source: src/ast/mod.rs +expression: ast.to_report(input) +--- +error: Standlone action block +  β•­β–Έ  +1 β”‚ {{ value }} + β”‚ ━━━━━ + β”‚ + β•° help: If you want to output this expression, add a '=' to the block +error: Standlone action block +  β•­β–Έ  +2 β”‚ {{ value }} + β”‚ ━━━━━ + β•° help: If you want to output this expression, add a '=' to the block +error: Standlone action block +  β•­β–Έ  +3 β”‚ {{ value }} + β”‚ ━━━━━ + β•° help: If you want to output this expression, add a '=' to the block +error: Standlone action block +  β•­β–Έ  +4 β”‚ {{ value }} + β”‚ ━━━━━ + β•° help: If you want to output this expression, add a '=' to the block +error: Standlone action block +  β•­β–Έ  +5 β”‚ {{ value }} + β”‚ ━━━━━ + β•° help: If you want to output this expression, add a '=' to the block 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 index 8e58393..27bbdad 100644 --- a/src/ast/snapshots/nomo__ast__tests__check_nested_simple_if.snap +++ b/src/ast/snapshots/nomo__ast__tests__check_nested_simple_if.snap @@ -1,84 +1,152 @@ --- source: src/ast/mod.rs -expression: ast +expression: parsed --- -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, - }, - }, - ], +ParsedTemplate { + tokens: [ + TemplateToken { + kind: LeftDelim, + source: "{{" (0..2), + }, + TemplateToken { + kind: Whitespace, + source: " " (2..3), + }, + TemplateToken { + kind: ConditionalIf, + source: "if" (3..5), + }, + TemplateToken { + kind: Whitespace, + source: " " (5..6), + }, + TemplateToken { + kind: Ident, + source: "foo" (6..9), + }, + TemplateToken { + kind: Whitespace, + source: " " (9..10), + }, + TemplateToken { + kind: RightDelim, + source: "}}" (10..12), + }, + TemplateToken { + kind: Whitespace, + source: "\n " (12..25), + }, + TemplateToken { + kind: LeftDelim, + source: "{{" (25..27), + }, + TemplateToken { + kind: Whitespace, + source: " " (27..28), + }, + TemplateToken { + kind: ConditionalIf, + source: "if" (28..30), + }, + TemplateToken { + kind: Whitespace, + source: " " (30..31), + }, + TemplateToken { + kind: Ident, + source: "bar" (31..34), + }, + TemplateToken { + kind: Whitespace, + source: " " (34..35), + }, + TemplateToken { + kind: RightDelim, + source: "}}" (35..37), + }, + TemplateToken { + kind: Whitespace, + source: "\n " (37..54), + }, + TemplateToken { + kind: Content, + source: "Hiii" (54..58), + }, + TemplateToken { + kind: Whitespace, + source: "\n " (58..71), + }, + TemplateToken { + kind: LeftDelim, + source: "{{" (71..73), + }, + TemplateToken { + kind: Whitespace, + source: " " (73..74), + }, + TemplateToken { + kind: End, + source: "end" (74..77), + }, + TemplateToken { + kind: Whitespace, + source: " " (77..78), + }, + TemplateToken { + kind: RightDelim, + source: "}}" (78..80), + }, + TemplateToken { + kind: Whitespace, + source: "\n " (80..89), + }, + TemplateToken { + kind: LeftDelim, + source: "{{" (89..91), + }, + TemplateToken { + kind: Whitespace, + source: " " (91..92), + }, + TemplateToken { + kind: End, + source: "end" (92..95), + }, + TemplateToken { + kind: Whitespace, + source: " " (95..96), + }, + TemplateToken { + kind: RightDelim, + source: "}}" (96..98), + }, + TemplateToken { + kind: Whitespace, + source: "\n\n " (98..108), + }, + TemplateToken { + kind: LeftDelim, + source: "{{" (108..110), + }, + TemplateToken { + kind: Whitespace, + source: " " (110..111), + }, + TemplateToken { + kind: Ident, + source: "value" (111..116), + }, + TemplateToken { + kind: Whitespace, + source: " " (116..117), + }, + TemplateToken { + kind: RightDelim, + source: "}}" (117..119), + }, + TemplateToken { + kind: Whitespace, + source: "\n " (119..128), }, ], } diff --git a/src/ast/snapshots/nomo__ast__tests__simple_if_ast.snap b/src/ast/snapshots/nomo__ast__tests__simple_if_ast.snap new file mode 100644 index 0000000..0cdc6a6 --- /dev/null +++ b/src/ast/snapshots/nomo__ast__tests__simple_if_ast.snap @@ -0,0 +1,70 @@ +--- +source: src/ast/mod.rs +expression: ast +--- +TemplateAst { + root: [ + ConditionalChain { + chain: [ + IfConditional { + if_block: Block { + prev_whitespace_content: None, + expression: VariableAccess( + "foo" (6..9), + ), + post_whitespace_content: Some( + "\n " (12..25), + ), + }, + content: [ + ConditionalChain { + chain: [ + IfConditional { + if_block: Block { + prev_whitespace_content: None, + expression: VariableAccess( + "bar" (31..34), + ), + post_whitespace_content: Some( + "\n " (37..54), + ), + }, + content: [ + StaticContent( + "Hiii" (54..58), + ), + ], + end_block: Block { + prev_whitespace_content: Some( + "\n " (58..71), + ), + expression: EndBlock, + post_whitespace_content: Some( + "\n " (80..89), + ), + }, + }, + ], + }, + ], + end_block: Block { + prev_whitespace_content: None, + expression: EndBlock, + post_whitespace_content: Some( + "\n\n " (98..108), + ), + }, + }, + ], + }, + Interpolation { + prev_whitespace_content: None, + expression: VariableAccess( + "value" (112..117), + ), + post_whitespace_content: Some( + "\n " (120..129), + ), + }, + ], +} diff --git a/src/ast/snapshots/nomo__ast__tests__simple_if_tokens.snap b/src/ast/snapshots/nomo__ast__tests__simple_if_tokens.snap new file mode 100644 index 0000000..db5ba26 --- /dev/null +++ b/src/ast/snapshots/nomo__ast__tests__simple_if_tokens.snap @@ -0,0 +1,45 @@ +--- +source: src/ast/mod.rs +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), + ], +} diff --git a/src/emit/mod.rs b/src/emit/mod.rs index be15eec..b7aa055 100644 --- a/src/emit/mod.rs +++ b/src/emit/mod.rs @@ -24,11 +24,24 @@ 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, + JumpIfNotTrue { + emit_slot: VariableSlot, + jump: isize, + }, } pub fn emit_machine(input: crate::ast::TemplateAst<'_>) -> Vec { @@ -55,53 +68,110 @@ fn emit_ast_expr( }); } TemplateAstExpr::Interpolation { - prev_whitespace_content: prev_whitespace, - wants_output, + prev_whitespace_content, expression, - post_whitespace_content: post_whitespace, + post_whitespace_content, } => { - if let Some(ws) = prev_whitespace { + if let Some(ws) = prev_whitespace_content { eval.push(Instruction::AppendContent { content: ws.source().clone(), }); } let emit_slot = machine.reserve_slot(); - emit_expr(machine, eval, emit_slot, expression); + emit_expr_load(machine, eval, emit_slot, expression); eval.push(Instruction::EmitFromSlot { slot: emit_slot }); - if let Some(ws) = post_whitespace { + if let Some(ws) = post_whitespace_content { eval.push(Instruction::AppendContent { content: ws.source().clone(), }); } } - TemplateAstExpr::Invalid { .. } | TemplateAstExpr::VariableAccess { .. } => { - eval.push(Instruction::Abort) + TemplateAstExpr::ConditionalChain { chain } => { + let mut chain = chain.iter(); + let Some(TemplateAstExpr::IfConditional { + if_block: expression, + content, + end_block, + }) = chain.next() + else { + unreachable!("First element in conditional chain should be an IfConditional"); + }; + + let TemplateAstExpr::Block { + prev_whitespace_content, + expression, + post_whitespace_content, + } = expression.as_ref() + else { + unreachable!("The end of an IfConditional must be a Block"); + }; + + if let Some(ws) = prev_whitespace_content { + eval.push(Instruction::AppendContent { + content: ws.source().clone(), + }); + } + + let emit_slot = machine.reserve_slot(); + emit_expr_load(machine, eval, emit_slot, expression); + + let index = eval.len(); + eval.push(Instruction::JumpIfNotTrue { + emit_slot, + jump: isize::MAX, + }); + + if let Some(ws) = post_whitespace_content { + eval.push(Instruction::AppendContent { + content: ws.source().clone(), + }); + } + + for ast in content { + emit_ast_expr(machine, eval, ast); + } + + let TemplateAstExpr::Block { + prev_whitespace_content, + post_whitespace_content, + .. + } = end_block.as_ref() + else { + unreachable!("The end of an IfConditional must be a Block"); + }; + + if let Some(ws) = prev_whitespace_content { + eval.push(Instruction::AppendContent { + content: ws.source().clone(), + }); + } + + let jump = eval.len() - index - 1; + eval[index] = Instruction::JumpIfNotTrue { + emit_slot, + jump: jump as isize, + }; + + if let Some(ws) = post_whitespace_content { + eval.push(Instruction::AppendContent { + content: ws.source().clone(), + }); + } } - 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!(), + + TemplateAstExpr::Block { .. } + | TemplateAstExpr::EndBlock + | TemplateAstExpr::IfConditional { .. } + | TemplateAstExpr::ElseConditional { .. } + | TemplateAstExpr::Invalid { .. } + | TemplateAstExpr::VariableAccess { .. } => eval.push(Instruction::Abort), } } -fn emit_expr( - machine: &mut EmitMachine, +fn emit_expr_load( + _machine: &mut EmitMachine, eval: &mut Vec, emit_slot: VariableSlot, expression: &TemplateAstExpr<'_>, @@ -117,24 +187,11 @@ 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::ConditionalChain { .. } => todo!(), + TemplateAstExpr::ElseConditional { .. } => todo!(), TemplateAstExpr::EndBlock => todo!(), - TemplateAstExpr::Block { - prev_whitespace_content, - expression, - post_whitespace_content, - } => todo!(), - TemplateAstExpr::IfConditional { - expression, - content, - end_block, - } => todo!(), + TemplateAstExpr::Block { .. } => todo!(), + TemplateAstExpr::IfConditional { .. } => todo!(), } } @@ -155,13 +212,13 @@ mod tests { insta::assert_debug_snapshot!(emit, @r#" [ AppendContent { - content: "Hello", + content: "Hello" (0..5), }, AppendContent { - content: " ", + content: " " (5..6), }, LoadFromContextToSlot { - name: "world", + name: "world" (10..15), slot: VariableSlot { index: 0, }, diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 579891e..f94ed8c 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -13,6 +13,11 @@ pub enum EvaluationError { UnknownVariable(NomoInput), /// An explicit abort was requested ExplicitAbort, + /** The instruction pointer overflowed + ** + ** This is an internal error and is a bug that should be reported + */ + InstructionPointerOverflow, } pub fn execute( @@ -23,7 +28,14 @@ pub fn execute( let mut scopes: HashMap = HashMap::new(); - for instr in instructions { + let mut ip = 0; + loop { + if ip >= instructions.len() { + break; + } + + let instr = instructions.get(ip).unwrap(); + match instr { Instruction::AppendContent { content } => output.push_str(content), Instruction::LoadFromContextToSlot { name, slot } => { @@ -40,7 +52,24 @@ pub fn execute( } Instruction::PushScope { inherit_parent: _ } => todo!(), Instruction::Abort => return Err(EvaluationError::ExplicitAbort), + Instruction::JumpIfNotTrue { emit_slot, jump } => { + let dont_jump = scopes.get(emit_slot).unwrap().as_bool().unwrap(); + if dont_jump { + // We are done + } else { + let (new_ip, overflow) = ip.overflowing_add_signed(*jump); + + if overflow { + return Err(EvaluationError::InstructionPointerOverflow); + } else { + ip = new_ip; + continue; + } + } + } } + + ip += 1; } Ok(output) diff --git a/src/input.rs b/src/input.rs index 0f0290f..ba404eb 100644 --- a/src/input.rs +++ b/src/input.rs @@ -22,11 +22,20 @@ impl NomoInput { pub fn into_parts(self) -> (Arc, Range) { (self.backing, self.range) } + + pub fn get_range(&self) -> Range { + self.range.clone() + } +} + +#[derive(Debug, Clone)] +pub struct NomoInputCheckpoint { + range: Range, } impl std::fmt::Debug for NomoInput { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "\"{}\"", self.as_str()) + write!(f, "{:?} ({:?})", self.as_str(), self.range) } } @@ -74,6 +83,18 @@ impl NomoInput { } } +impl Offset for NomoInputCheckpoint { + fn offset_from(&self, start: &Self) -> usize { + self.range.start - start.range.start + } +} + +impl Offset for NomoInput { + fn offset_from(&self, start: &NomoInputCheckpoint) -> usize { + self.range.start - start.range.start + } +} + impl Offset for NomoInput { fn offset_from(&self, start: &Self) -> usize { self.as_str().offset_from(&start.as_str()) @@ -116,7 +137,7 @@ impl Stream for NomoInput { type IterOffsets = NomoInputIter; - type Checkpoint = NomoInput; + type Checkpoint = NomoInputCheckpoint; fn iter_offsets(&self) -> Self::IterOffsets { NomoInputIter { @@ -167,7 +188,9 @@ impl Stream for NomoInput { } fn checkpoint(&self) -> Self::Checkpoint { - self.clone() + NomoInputCheckpoint { + range: self.get_range(), + } } fn reset(&mut self, checkpoint: &Self::Checkpoint) { diff --git a/src/lib.rs b/src/lib.rs index 5bf0c60..2679358 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -65,13 +65,8 @@ impl Nomo { let instructions = emit::emit_machine(ast); - self.templates.insert( - name.into(), - Template { - source, - instructions, - }, - ); + self.templates + .insert(name.into(), Template { instructions }); Ok(()) } @@ -89,7 +84,6 @@ impl Nomo { } struct Template { - source: NomoInput, instructions: Vec, } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index ebe854e..14d9a41 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -245,12 +245,28 @@ impl winnow::stream::ContainsToken<&'_ TemplateToken> for [Tok } } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq)] pub struct TemplateToken { kind: TokenKind, source: NomoInput, } +impl std::fmt::Debug for TemplateToken { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.source()) + } +} + +impl Location for TemplateToken { + fn previous_token_end(&self) -> usize { + NomoInput::get_range(&self.source).start + } + + fn current_token_start(&self) -> usize { + NomoInput::get_range(&self.source).start + } +} + macro_rules! impl_token_kind_builders { ($($name:ident => $kind:expr),+ $(,)?) => { $( @@ -459,10 +475,7 @@ mod tests { Ok( ParsedTemplate { tokens: [ - TemplateToken { - kind: Content, - source: "Hello There", - }, + "Hello There" (0..11), ], }, ) @@ -478,34 +491,13 @@ mod tests { Ok( ParsedTemplate { tokens: [ - TemplateToken { - kind: Content, - source: "Hello", - }, - TemplateToken { - kind: Whitespace, - source: " ", - }, - TemplateToken { - kind: LeftDelim, - source: "{{", - }, - TemplateToken { - kind: Whitespace, - source: " ", - }, - TemplateToken { - kind: Ident, - source: "there", - }, - TemplateToken { - kind: Whitespace, - source: " ", - }, - TemplateToken { - kind: RightDelim, - source: "}}", - }, + "Hello" (0..5), + " " (5..6), + "{{" (6..8), + " " (8..9), + "there" (9..14), + " " (14..15), + "}}" (15..17), ], }, ) @@ -555,70 +547,21 @@ mod tests { Ok( ParsedTemplate { tokens: [ - TemplateToken { - kind: LeftDelim, - source: "{{", - }, - TemplateToken { - kind: Whitespace, - source: " ", - }, - TemplateToken { - kind: ConditionalIf, - source: "if", - }, - TemplateToken { - kind: Whitespace, - source: " ", - }, - TemplateToken { - kind: Literal( - Bool( - true, - ), - ), - source: "true", - }, - TemplateToken { - kind: Whitespace, - source: " ", - }, - TemplateToken { - kind: RightDelim, - source: "}}", - }, - TemplateToken { - kind: Whitespace, - source: " ", - }, - TemplateToken { - kind: Content, - source: "Hello!", - }, - TemplateToken { - kind: Whitespace, - source: " ", - }, - TemplateToken { - kind: LeftDelim, - source: "{{", - }, - TemplateToken { - kind: Whitespace, - source: " ", - }, - TemplateToken { - kind: End, - source: "end", - }, - TemplateToken { - kind: Whitespace, - source: " ", - }, - TemplateToken { - kind: RightDelim, - source: "}}", - }, + "{{" (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), ], }, ) diff --git a/tests/cases/1-parsed@condition.snap b/tests/cases/1-parsed@condition.snap new file mode 100644 index 0000000..5d3000b --- /dev/null +++ b/tests/cases/1-parsed@condition.snap @@ -0,0 +1,31 @@ +--- +source: tests/file_tests.rs +expression: parsed +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), + ], +} diff --git a/tests/cases/1-parsed@identifiers.snap b/tests/cases/1-parsed@identifiers.snap index 935c9ff..da59d40 100644 --- a/tests/cases/1-parsed@identifiers.snap +++ b/tests/cases/1-parsed@identifiers.snap @@ -5,174 +5,46 @@ input_file: tests/cases/identifiers.nomo --- ParsedTemplate { tokens: [ - TemplateToken { - kind: LeftDelim, - source: "{{", - }, - TemplateToken { - kind: WantsOutput, - source: "=", - }, - TemplateToken { - kind: Whitespace, - source: " ", - }, - TemplateToken { - kind: Ident, - source: "_name", - }, - TemplateToken { - kind: Whitespace, - source: " ", - }, - TemplateToken { - kind: RightDelim, - source: "}}", - }, - TemplateToken { - kind: Whitespace, - source: " - ", - }, - TemplateToken { - kind: LeftDelim, - source: "{{", - }, - TemplateToken { - kind: WantsOutput, - source: "=", - }, - TemplateToken { - kind: Whitespace, - source: " ", - }, - TemplateToken { - kind: Ident, - source: "a_name", - }, - TemplateToken { - kind: Whitespace, - source: " ", - }, - TemplateToken { - kind: RightDelim, - source: "}}", - }, - TemplateToken { - kind: Whitespace, - source: " - ", - }, - TemplateToken { - kind: LeftDelim, - source: "{{", - }, - TemplateToken { - kind: WantsOutput, - source: "=", - }, - TemplateToken { - kind: Whitespace, - source: " ", - }, - TemplateToken { - kind: Ident, - source: "1name", - }, - TemplateToken { - kind: Whitespace, - source: " ", - }, - TemplateToken { - kind: RightDelim, - source: "}}", - }, - TemplateToken { - kind: Whitespace, - source: " - ", - }, - TemplateToken { - kind: LeftDelim, - source: "{{", - }, - TemplateToken { - kind: WantsOutput, - source: "=", - }, - TemplateToken { - kind: Whitespace, - source: " ", - }, - TemplateToken { - kind: Ident, - source: "_name1", - }, - TemplateToken { - kind: Whitespace, - source: " ", - }, - TemplateToken { - kind: RightDelim, - source: "}}", - }, - TemplateToken { - kind: Whitespace, - source: " - ", - }, - TemplateToken { - kind: LeftDelim, - source: "{{", - }, - TemplateToken { - kind: WantsOutput, - source: "=", - }, - TemplateToken { - kind: Whitespace, - source: " ", - }, - TemplateToken { - kind: Ident, - source: "_namE", - }, - TemplateToken { - kind: Whitespace, - source: " ", - }, - TemplateToken { - kind: RightDelim, - source: "}}", - }, - TemplateToken { - kind: Whitespace, - source: " - ", - }, - TemplateToken { - kind: LeftDelim, - source: "{{", - }, - TemplateToken { - kind: WantsOutput, - source: "=", - }, - TemplateToken { - kind: Whitespace, - source: " ", - }, - TemplateToken { - kind: Ident, - source: "name1", - }, - TemplateToken { - kind: Whitespace, - source: " ", - }, - TemplateToken { - kind: RightDelim, - source: "}}", - }, + "{{" (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), ], } diff --git a/tests/cases/1-parsed@interpolation.snap b/tests/cases/1-parsed@interpolation.snap index 9aa8976..3b5f8a8 100644 --- a/tests/cases/1-parsed@interpolation.snap +++ b/tests/cases/1-parsed@interpolation.snap @@ -5,37 +5,13 @@ input_file: tests/cases/interpolation.nomo --- ParsedTemplate { tokens: [ - TemplateToken { - kind: Content, - source: "Hello! I'm", - }, - TemplateToken { - kind: Whitespace, - source: " ", - }, - TemplateToken { - kind: LeftDelim, - source: "{{", - }, - TemplateToken { - kind: WantsOutput, - source: "=", - }, - TemplateToken { - kind: Whitespace, - source: " ", - }, - TemplateToken { - kind: Ident, - source: "name", - }, - TemplateToken { - kind: Whitespace, - source: " ", - }, - TemplateToken { - kind: RightDelim, - source: "}}", - }, + "Hello! I'm" (0..10), + " " (10..11), + "{{" (11..13), + "=" (13..14), + " " (14..15), + "name" (15..19), + " " (19..20), + "}}" (20..22), ], } diff --git a/tests/cases/1-parsed@multiple.snap b/tests/cases/1-parsed@multiple.snap index ad8715b..2bc80df 100644 --- a/tests/cases/1-parsed@multiple.snap +++ b/tests/cases/1-parsed@multiple.snap @@ -5,65 +5,20 @@ input_file: tests/cases/multiple.nomo --- ParsedTemplate { tokens: [ - TemplateToken { - kind: Content, - source: "Hi there! My name is", - }, - TemplateToken { - kind: Whitespace, - source: " ", - }, - TemplateToken { - kind: LeftDelim, - source: "{{", - }, - TemplateToken { - kind: WantsOutput, - source: "=", - }, - TemplateToken { - kind: Whitespace, - source: " ", - }, - TemplateToken { - kind: Ident, - source: "name", - }, - TemplateToken { - kind: Whitespace, - source: " ", - }, - TemplateToken { - kind: RightDelim, - source: "}}", - }, - TemplateToken { - kind: Whitespace, - source: " ", - }, - TemplateToken { - kind: LeftDelim, - source: "{{", - }, - TemplateToken { - kind: WantsOutput, - source: "=", - }, - TemplateToken { - kind: Whitespace, - source: " ", - }, - TemplateToken { - kind: Ident, - source: "lastname", - }, - TemplateToken { - kind: Whitespace, - source: " ", - }, - TemplateToken { - kind: RightDelim, - source: "}}", - }, + "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), ], } diff --git a/tests/cases/1-parsed@simple.snap b/tests/cases/1-parsed@simple.snap index 2114563..6da0af7 100644 --- a/tests/cases/1-parsed@simple.snap +++ b/tests/cases/1-parsed@simple.snap @@ -5,9 +5,6 @@ input_file: tests/cases/simple.nomo --- ParsedTemplate { tokens: [ - TemplateToken { - kind: Content, - source: "Hello World!", - }, + "Hello World!" (0..12), ], } diff --git a/tests/cases/2-ast@condition.snap b/tests/cases/2-ast@condition.snap new file mode 100644 index 0000000..4a68493 --- /dev/null +++ b/tests/cases/2-ast@condition.snap @@ -0,0 +1,45 @@ +--- +source: tests/file_tests.rs +expression: ast +input_file: tests/cases/condition.nomo +--- +TemplateAst { + root: [ + ConditionalChain { + chain: [ + IfConditional { + if_block: Block { + prev_whitespace_content: None, + expression: VariableAccess( + "test" (6..10), + ), + post_whitespace_content: Some( + "\n " (13..18), + ), + }, + content: [ + StaticContent( + "Hello World!" (18..30), + ), + ], + end_block: Block { + prev_whitespace_content: Some( + "\n" (30..31), + ), + expression: EndBlock, + post_whitespace_content: Some( + "\n\n" (40..42), + ), + }, + }, + ], + }, + Interpolation { + prev_whitespace_content: None, + expression: VariableAccess( + "stuff" (46..51), + ), + post_whitespace_content: None, + }, + ], +} diff --git a/tests/cases/2-ast@identifiers.snap b/tests/cases/2-ast@identifiers.snap index 11dc899..a383d5c 100644 --- a/tests/cases/2-ast@identifiers.snap +++ b/tests/cases/2-ast@identifiers.snap @@ -7,115 +7,53 @@ TemplateAst { root: [ Interpolation { prev_whitespace_content: None, - wants_output: TemplateToken { - kind: WantsOutput, - source: "=", - }, expression: VariableAccess( - TemplateToken { - kind: Ident, - source: "_name", - }, + "_name" (4..9), ), post_whitespace_content: Some( - TemplateToken { - kind: Whitespace, - source: " - ", - }, + "\n" (12..13), ), }, Interpolation { prev_whitespace_content: None, - wants_output: TemplateToken { - kind: WantsOutput, - source: "=", - }, expression: VariableAccess( - TemplateToken { - kind: Ident, - source: "a_name", - }, + "a_name" (17..23), ), post_whitespace_content: Some( - TemplateToken { - kind: Whitespace, - source: " - ", - }, + "\n" (26..27), ), }, Interpolation { prev_whitespace_content: None, - wants_output: TemplateToken { - kind: WantsOutput, - source: "=", - }, expression: VariableAccess( - TemplateToken { - kind: Ident, - source: "1name", - }, + "1name" (31..36), ), post_whitespace_content: Some( - TemplateToken { - kind: Whitespace, - source: " - ", - }, + "\n" (39..40), ), }, Interpolation { prev_whitespace_content: None, - wants_output: TemplateToken { - kind: WantsOutput, - source: "=", - }, expression: VariableAccess( - TemplateToken { - kind: Ident, - source: "_name1", - }, + "_name1" (44..50), ), post_whitespace_content: Some( - TemplateToken { - kind: Whitespace, - source: " - ", - }, + "\n" (53..54), ), }, Interpolation { prev_whitespace_content: None, - wants_output: TemplateToken { - kind: WantsOutput, - source: "=", - }, expression: VariableAccess( - TemplateToken { - kind: Ident, - source: "_namE", - }, + "_namE" (58..63), ), post_whitespace_content: Some( - TemplateToken { - kind: Whitespace, - source: " - ", - }, + "\n" (66..67), ), }, Interpolation { prev_whitespace_content: None, - wants_output: TemplateToken { - kind: WantsOutput, - source: "=", - }, expression: VariableAccess( - TemplateToken { - kind: Ident, - source: "name1", - }, + "name1" (71..76), ), post_whitespace_content: None, }, diff --git a/tests/cases/2-ast@interpolation.snap b/tests/cases/2-ast@interpolation.snap index 5871ea9..57a4a5b 100644 --- a/tests/cases/2-ast@interpolation.snap +++ b/tests/cases/2-ast@interpolation.snap @@ -6,27 +6,14 @@ input_file: tests/cases/interpolation.nomo TemplateAst { root: [ StaticContent( - TemplateToken { - kind: Content, - source: "Hello! I'm", - }, + "Hello! I'm" (0..10), ), Interpolation { prev_whitespace_content: Some( - TemplateToken { - kind: Whitespace, - source: " ", - }, + " " (10..11), ), - wants_output: TemplateToken { - kind: WantsOutput, - source: "=", - }, expression: VariableAccess( - TemplateToken { - kind: Ident, - source: "name", - }, + "name" (15..19), ), post_whitespace_content: None, }, diff --git a/tests/cases/2-ast@multiple.snap b/tests/cases/2-ast@multiple.snap index 15f14c2..5fdacd6 100644 --- a/tests/cases/2-ast@multiple.snap +++ b/tests/cases/2-ast@multiple.snap @@ -6,46 +6,23 @@ input_file: tests/cases/multiple.nomo TemplateAst { root: [ StaticContent( - TemplateToken { - kind: Content, - source: "Hi there! My name is", - }, + "Hi there! My name is" (0..20), ), Interpolation { prev_whitespace_content: Some( - TemplateToken { - kind: Whitespace, - source: " ", - }, + " " (20..21), ), - wants_output: TemplateToken { - kind: WantsOutput, - source: "=", - }, expression: VariableAccess( - TemplateToken { - kind: Ident, - source: "name", - }, + "name" (25..29), ), post_whitespace_content: Some( - TemplateToken { - kind: Whitespace, - source: " ", - }, + " " (32..33), ), }, Interpolation { prev_whitespace_content: None, - wants_output: TemplateToken { - kind: WantsOutput, - source: "=", - }, expression: VariableAccess( - TemplateToken { - kind: Ident, - source: "lastname", - }, + "lastname" (37..45), ), post_whitespace_content: None, }, diff --git a/tests/cases/2-ast@simple.snap b/tests/cases/2-ast@simple.snap index cffaa71..ff6bdba 100644 --- a/tests/cases/2-ast@simple.snap +++ b/tests/cases/2-ast@simple.snap @@ -6,10 +6,7 @@ input_file: tests/cases/simple.nomo TemplateAst { root: [ StaticContent( - TemplateToken { - kind: Content, - source: "Hello World!", - }, + "Hello World!" (0..12), ), ], } diff --git a/tests/cases/3-instructions@condition.snap b/tests/cases/3-instructions@condition.snap new file mode 100644 index 0000000..f79800d --- /dev/null +++ b/tests/cases/3-instructions@condition.snap @@ -0,0 +1,42 @@ +--- +source: tests/file_tests.rs +expression: emit +input_file: tests/cases/condition.nomo +--- +[ + LoadFromContextToSlot { + name: "test" (6..10), + slot: VariableSlot { + index: 0, + }, + }, + JumpIfNotTrue { + emit_slot: VariableSlot { + index: 0, + }, + jump: 3, + }, + AppendContent { + content: "\n " (13..18), + }, + AppendContent { + content: "Hello World!" (18..30), + }, + AppendContent { + content: "\n" (30..31), + }, + AppendContent { + content: "\n\n" (40..42), + }, + LoadFromContextToSlot { + name: "stuff" (46..51), + slot: VariableSlot { + index: 1, + }, + }, + EmitFromSlot { + slot: VariableSlot { + index: 1, + }, + }, +] diff --git a/tests/cases/3-instructions@identifiers.snap b/tests/cases/3-instructions@identifiers.snap index bd18fe5..36bc082 100644 --- a/tests/cases/3-instructions@identifiers.snap +++ b/tests/cases/3-instructions@identifiers.snap @@ -5,7 +5,7 @@ input_file: tests/cases/identifiers.nomo --- [ LoadFromContextToSlot { - name: "_name", + name: "_name" (4..9), slot: VariableSlot { index: 0, }, @@ -16,11 +16,10 @@ input_file: tests/cases/identifiers.nomo }, }, AppendContent { - content: " - ", + content: "\n" (12..13), }, LoadFromContextToSlot { - name: "a_name", + name: "a_name" (17..23), slot: VariableSlot { index: 1, }, @@ -31,11 +30,10 @@ input_file: tests/cases/identifiers.nomo }, }, AppendContent { - content: " - ", + content: "\n" (26..27), }, LoadFromContextToSlot { - name: "1name", + name: "1name" (31..36), slot: VariableSlot { index: 2, }, @@ -46,11 +44,10 @@ input_file: tests/cases/identifiers.nomo }, }, AppendContent { - content: " - ", + content: "\n" (39..40), }, LoadFromContextToSlot { - name: "_name1", + name: "_name1" (44..50), slot: VariableSlot { index: 3, }, @@ -61,11 +58,10 @@ input_file: tests/cases/identifiers.nomo }, }, AppendContent { - content: " - ", + content: "\n" (53..54), }, LoadFromContextToSlot { - name: "_namE", + name: "_namE" (58..63), slot: VariableSlot { index: 4, }, @@ -76,11 +72,10 @@ input_file: tests/cases/identifiers.nomo }, }, AppendContent { - content: " - ", + content: "\n" (66..67), }, LoadFromContextToSlot { - name: "name1", + name: "name1" (71..76), slot: VariableSlot { index: 5, }, diff --git a/tests/cases/3-instructions@interpolation.snap b/tests/cases/3-instructions@interpolation.snap index d2acc12..f4873f3 100644 --- a/tests/cases/3-instructions@interpolation.snap +++ b/tests/cases/3-instructions@interpolation.snap @@ -5,13 +5,13 @@ input_file: tests/cases/interpolation.nomo --- [ AppendContent { - content: "Hello! I'm", + content: "Hello! I'm" (0..10), }, AppendContent { - content: " ", + content: " " (10..11), }, LoadFromContextToSlot { - name: "name", + name: "name" (15..19), slot: VariableSlot { index: 0, }, diff --git a/tests/cases/3-instructions@multiple.snap b/tests/cases/3-instructions@multiple.snap index a65b11a..99ccf8f 100644 --- a/tests/cases/3-instructions@multiple.snap +++ b/tests/cases/3-instructions@multiple.snap @@ -5,13 +5,13 @@ input_file: tests/cases/multiple.nomo --- [ AppendContent { - content: "Hi there! My name is", + content: "Hi there! My name is" (0..20), }, AppendContent { - content: " ", + content: " " (20..21), }, LoadFromContextToSlot { - name: "name", + name: "name" (25..29), slot: VariableSlot { index: 0, }, @@ -22,10 +22,10 @@ input_file: tests/cases/multiple.nomo }, }, AppendContent { - content: " ", + content: " " (32..33), }, LoadFromContextToSlot { - name: "lastname", + name: "lastname" (37..45), slot: VariableSlot { index: 1, }, diff --git a/tests/cases/3-instructions@simple.snap b/tests/cases/3-instructions@simple.snap index 976141e..0f495a6 100644 --- a/tests/cases/3-instructions@simple.snap +++ b/tests/cases/3-instructions@simple.snap @@ -5,6 +5,6 @@ input_file: tests/cases/simple.nomo --- [ AppendContent { - content: "Hello World!", + content: "Hello World!" (0..12), }, ] diff --git a/tests/cases/4-output@condition.snap b/tests/cases/4-output@condition.snap new file mode 100644 index 0000000..1d5af6f --- /dev/null +++ b/tests/cases/4-output@condition.snap @@ -0,0 +1,6 @@ +--- +source: tests/file_tests.rs +expression: output +input_file: tests/cases/condition.nomo +--- +"\n Hello World!\n\n\nmore" diff --git a/tests/cases/condition.nomo b/tests/cases/condition.nomo new file mode 100644 index 0000000..df80917 --- /dev/null +++ b/tests/cases/condition.nomo @@ -0,0 +1,10 @@ +{ + "test": true, + "stuff": "more" +} +--- +{{ if test }} + Hello World! +{{ end }} + +{{= stuff }} \ No newline at end of file