diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 9f18f45..da68d0b 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -264,16 +264,32 @@ fn parse_interpolation<'input>( ) -> Result, AstError> { let expr_parser = resume_after_cut( parse_value_expression, - repeat_till(0.., any, peek(TokenKind::RightDelim)).map(|((), _)| ()), + repeat_till( + 0.., + any, + peek(preceded( + opt(TokenKind::TrimWhitespace), + 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, + left_trim, + _wants_output, + (expression, right_trim, _right, post_whitespace), + ) = ( opt(TokenKind::Whitespace), TokenKind::LeftDelim, + opt(TokenKind::TrimWhitespace), TokenKind::WantsOutput, cut_err(( surrounded(ws, expr_parser).map(Box::new), + opt(TokenKind::TrimWhitespace), TokenKind::RightDelim, opt(TokenKind::Whitespace), )), @@ -281,9 +297,17 @@ fn parse_interpolation<'input>( .parse_next(input)?; Ok(TemplateAstExpr::Interpolation { - prev_whitespace_content: prev_whitespace, + prev_whitespace_content: if left_trim.is_some() { + None + } else { + prev_whitespace + }, expression, - post_whitespace_content: post_whitespace, + post_whitespace_content: if right_trim.is_some() { + None + } else { + post_whitespace + }, }) } @@ -439,19 +463,35 @@ where ( opt(TokenKind::Whitespace), TokenKind::LeftDelim, + opt(TokenKind::TrimWhitespace), not(TokenKind::WantsOutput), ( surrounded(ws, expr_parser.map(Box::new)), + opt(TokenKind::TrimWhitespace), TokenKind::RightDelim, opt(TokenKind::Whitespace), ), ) .map( - |(prev_whitespace, _left, _not_token, (expression, _right, post_whitespace))| { + |( + prev_whitespace, + _left, + left_trim, + _not_token, + (expression, right_trim, _right, post_whitespace), + )| { TemplateAstExpr::Block { - prev_whitespace_content: prev_whitespace, + prev_whitespace_content: if left_trim.is_some() { + None + } else { + prev_whitespace + }, expression, - post_whitespace_content: post_whitespace, + post_whitespace_content: if right_trim.is_some() { + None + } else { + post_whitespace + }, } }, ) @@ -736,4 +776,15 @@ mod tests { insta::assert_debug_snapshot!(ast); } + + #[test] + fn check_trim_whitespace() { + let input = "{{ if foo -}} foo {{- else if bar -}} bar {{- end }}"; + + let parsed = crate::parser::parse(input.into()).unwrap(); + + let ast = panic_pretty(input, parse(parsed.tokens())); + + insta::assert_debug_snapshot!(ast); + } } diff --git a/src/ast/snapshots/nomo__ast__tests__check_trim_whitespace.snap b/src/ast/snapshots/nomo__ast__tests__check_trim_whitespace.snap new file mode 100644 index 0000000..bce8aa2 --- /dev/null +++ b/src/ast/snapshots/nomo__ast__tests__check_trim_whitespace.snap @@ -0,0 +1,51 @@ +--- +source: src/ast/mod.rs +expression: ast +--- +TemplateAst { + root: [ + ConditionalChain { + chain: [ + Block { + prev_whitespace_content: None, + expression: IfConditional { + expression: VariableAccess( + [Ident]"foo" (6..9), + ), + }, + post_whitespace_content: None, + }, + ConditionalContent { + content: [ + StaticContent( + [Content]"foo" (14..17), + ), + ], + }, + Block { + prev_whitespace_content: None, + expression: ElseConditional { + expression: Some( + VariableAccess( + [Ident]"bar" (30..33), + ), + ), + }, + post_whitespace_content: None, + }, + ConditionalContent { + content: [ + StaticContent( + [Content]"bar" (38..41), + ), + ], + }, + Block { + prev_whitespace_content: None, + expression: EndBlock, + post_whitespace_content: None, + }, + ], + }, + ], +} diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 0b0b6f9..b4202f1 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -53,11 +53,12 @@ pub fn execute( Instruction::PushScope { inherit_parent: _ } => todo!(), Instruction::Abort => return Err(EvaluationError::ExplicitAbort), Instruction::JumpIfNotTrue { emit_slot, jump } => { + let jump = if *jump == 0 { 1 } else { *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); + let (new_ip, overflow) = ip.overflowing_add_signed(jump); if overflow { return Err(EvaluationError::InstructionPointerOverflow); @@ -68,7 +69,8 @@ pub fn execute( } } Instruction::Jump { jump } => { - let (new_ip, overflow) = ip.overflowing_add_signed(*jump); + let jump = if *jump == 0 { 1 } else { *jump }; + let (new_ip, overflow) = ip.overflowing_add_signed(jump); if overflow { return Err(EvaluationError::InstructionPointerOverflow); diff --git a/tests/cases/1-parsed@trim_whitespace.snap b/tests/cases/1-parsed@trim_whitespace.snap new file mode 100644 index 0000000..b95d570 --- /dev/null +++ b/tests/cases/1-parsed@trim_whitespace.snap @@ -0,0 +1,34 @@ +--- +source: tests/file_tests.rs +expression: parsed +input_file: tests/cases/trim_whitespace.nomo +--- +ParsedTemplate { + tokens: [ + [LeftDelim]"{{" (0..2), + [Whitespace]" " (2..3), + [ConditionalIf]"if" (3..5), + [Whitespace]" " (5..6), + [Ident]"test" (6..10), + [Whitespace]" " (10..11), + [TrimWhitespace]"-" (11..12), + [RightDelim]"}}" (12..14), + [Whitespace]"\n " (14..19), + [Content]"Hello" (19..24), + [Whitespace]" " (24..25), + [LeftDelim]"{{" (25..27), + [WantsOutput]"=" (27..28), + [Whitespace]" " (28..29), + [Ident]"stuff" (29..34), + [Whitespace]" " (34..35), + [TrimWhitespace]"-" (35..36), + [RightDelim]"}}" (36..38), + [Whitespace]"\n" (38..39), + [LeftDelim]"{{" (39..41), + [TrimWhitespace]"-" (41..42), + [Whitespace]" " (42..43), + [End]"end" (43..46), + [Whitespace]" " (46..47), + [RightDelim]"}}" (47..49), + ], +} diff --git a/tests/cases/2-ast@trim_whitespace.snap b/tests/cases/2-ast@trim_whitespace.snap new file mode 100644 index 0000000..0e20a97 --- /dev/null +++ b/tests/cases/2-ast@trim_whitespace.snap @@ -0,0 +1,43 @@ +--- +source: tests/file_tests.rs +expression: ast +input_file: tests/cases/trim_whitespace.nomo +--- +TemplateAst { + root: [ + ConditionalChain { + chain: [ + Block { + prev_whitespace_content: None, + expression: IfConditional { + expression: VariableAccess( + [Ident]"test" (6..10), + ), + }, + post_whitespace_content: None, + }, + ConditionalContent { + content: [ + StaticContent( + [Content]"Hello" (19..24), + ), + Interpolation { + prev_whitespace_content: Some( + [Whitespace]" " (24..25), + ), + expression: VariableAccess( + [Ident]"stuff" (29..34), + ), + post_whitespace_content: None, + }, + ], + }, + Block { + prev_whitespace_content: None, + expression: EndBlock, + post_whitespace_content: None, + }, + ], + }, + ], +} diff --git a/tests/cases/3-instructions@trim_whitespace.snap b/tests/cases/3-instructions@trim_whitespace.snap new file mode 100644 index 0000000..09c074c --- /dev/null +++ b/tests/cases/3-instructions@trim_whitespace.snap @@ -0,0 +1,39 @@ +--- +source: tests/file_tests.rs +expression: emit +input_file: tests/cases/trim_whitespace.nomo +--- +[ + LoadFromContextToSlot { + name: "test" (6..10), + slot: VariableSlot { + index: 0, + }, + }, + JumpIfNotTrue { + emit_slot: VariableSlot { + index: 0, + }, + jump: 5, + }, + AppendContent { + content: "Hello" (19..24), + }, + AppendContent { + content: " " (24..25), + }, + LoadFromContextToSlot { + name: "stuff" (29..34), + slot: VariableSlot { + index: 1, + }, + }, + EmitFromSlot { + slot: VariableSlot { + index: 1, + }, + }, + Jump { + jump: 0, + }, +] diff --git a/tests/cases/4-output@trim_whitespace.snap b/tests/cases/4-output@trim_whitespace.snap new file mode 100644 index 0000000..03e2039 --- /dev/null +++ b/tests/cases/4-output@trim_whitespace.snap @@ -0,0 +1,6 @@ +--- +source: tests/file_tests.rs +expression: output +input_file: tests/cases/trim_whitespace.nomo +--- +"Hello Hemera" diff --git a/tests/cases/trim_whitespace.nomo b/tests/cases/trim_whitespace.nomo new file mode 100644 index 0000000..a8a4eaa --- /dev/null +++ b/tests/cases/trim_whitespace.nomo @@ -0,0 +1,8 @@ +{ + "test": true, + "stuff": "Hemera" +} +--- +{{ if test -}} + Hello {{= stuff -}} +{{- end }} \ No newline at end of file