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/src/parser/mod.rs b/src/parser/mod.rs index 6c4f857..20f2a14 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -16,6 +16,7 @@ use winnow::combinator::eof; use winnow::combinator::not; use winnow::combinator::opt; use winnow::combinator::peek; +use winnow::combinator::preceded; use winnow::combinator::repeat_till; use winnow::combinator::terminated; use winnow::combinator::trace; @@ -201,6 +202,7 @@ pub enum TokenKind { Content, LeftDelim, RightDelim, + TrimWhitespace, WantsOutput, Ident, Whitespace, @@ -286,6 +288,7 @@ impl TemplateToken { content => TokenKind::Content, left_delim => TokenKind::LeftDelim, right_delim => TokenKind::RightDelim, + trim_whitespace => TokenKind::TrimWhitespace, wants_output => TokenKind::WantsOutput, ident => TokenKind::Ident, whitespace => TokenKind::Whitespace, @@ -344,9 +347,10 @@ fn parse_content<'input>(input: &mut Input<'input>) -> PResult<'input, Vec(input: &mut Input<'input>) -> PResult<'input, Vec> { let prev_whitespace = opt(parse_whitespace).parse_next(input)?; let left_delim = "{{".map(TemplateToken::left_delim).parse_next(input)?; + let left_trim = opt("-".map(TemplateToken::trim_whitespace)).parse_next(input)?; let wants_output = opt("=".map(TemplateToken::wants_output)).parse_next(input)?; - let get_tokens = repeat_till(1.., parse_block_token, peek("}}")); + let get_tokens = repeat_till(1.., parse_block_token, peek(preceded(opt("-"), "}}"))); let recover = take_until(0.., "}}").void(); let (inside_tokens, _): (Vec<_>, _) = get_tokens @@ -357,14 +361,17 @@ fn parse_interpolate<'input>(input: &mut Input<'input>) -> PResult<'input, Vec