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)
+---
+[1m[91merror[0m[1m: Standlone action block[0m
+ [1m[94m ╭▸ [0m
+[1m[94m1[0m [1m[94m│[0m {{ value }}
+ [1m[94m│[0m [1m[91m━━━━━[0m
+ [1m[94m│[0m
+ [1m[94m╰ [0m[1mhelp[0m: If you want to output this expression, add a '=' to the block
+[1m[91merror[0m[1m: Standlone action block[0m
+ [1m[94m ╭▸ [0m
+[1m[94m2[0m [1m[94m│[0m {{ value }}
+ [1m[94m│[0m [1m[91m━━━━━[0m
+ [1m[94m╰ [0m[1mhelp[0m: If you want to output this expression, add a '=' to the block
+[1m[91merror[0m[1m: Standlone action block[0m
+ [1m[94m ╭▸ [0m
+[1m[94m3[0m [1m[94m│[0m {{ value }}
+ [1m[94m│[0m [1m[91m━━━━━[0m
+ [1m[94m╰ [0m[1mhelp[0m: If you want to output this expression, add a '=' to the block
+[1m[91merror[0m[1m: Standlone action block[0m
+ [1m[94m ╭▸ [0m
+[1m[94m4[0m [1m[94m│[0m {{ value }}
+ [1m[94m│[0m [1m[91m━━━━━[0m
+ [1m[94m╰ [0m[1mhelp[0m: If you want to output this expression, add a '=' to the block
+[1m[91merror[0m[1m: Standlone action block[0m
+ [1m[94m ╭▸ [0m
+[1m[94m5[0m [1m[94m│[0m {{ value }}
+ [1m[94m│[0m [1m[91m━━━━━[0m
+ [1m[94m╰ [0m[1mhelp[0m: 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