diff --git a/src/ast/mod.rs b/src/ast/mod.rs index fa78ccc..1d483a7 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -1,9 +1,12 @@ use thiserror::Error; use winnow::Parser; use winnow::RecoverableParser; +use winnow::combinator::Infix::Left; use winnow::combinator::alt; use winnow::combinator::cut_err; use winnow::combinator::delimited; +use winnow::combinator::dispatch; +use winnow::combinator::expression; use winnow::combinator::not; use winnow::combinator::opt; use winnow::combinator::peek; @@ -24,7 +27,9 @@ use winnow::token::any; use crate::SourceSpan; use crate::parser::TemplateToken; use crate::parser::TokenKind; +use crate::parser::TokenOperator; use crate::resume_after_cut; +use crate::value::NomoValue; #[derive(Debug, Clone)] pub struct TemplateAst<'input> { @@ -253,7 +258,16 @@ pub enum TemplateAstExpr<'input> { expression: Option>>, }, Invalid(&'input [TemplateToken]), + Operation { + op: TokenOperator, + lhs: Box>, + rhs: Box>, + }, EndBlock, + Literal { + source: TemplateToken, + value: NomoValue, + }, } fn parse_asts<'input>(input: &mut Input<'input>) -> Result>, AstError> { @@ -275,7 +289,7 @@ fn parse_interpolation<'input>( input: &mut Input<'input>, ) -> Result, AstError> { let expr_parser = resume_after_cut( - parse_value_expression, + parse_expression, repeat_till( 0.., any, @@ -397,7 +411,7 @@ fn parse_for_loop<'input>(input: &mut Input<'input>) -> Result( preceded( TokenKind::ConditionalIf, cut_err( - surrounded(ws, parse_value_expression) + surrounded(ws, parse_expression) .map(Box::new) .context(AstError::ctx().msg("Expected an expression after 'if'")), ), @@ -510,7 +524,7 @@ fn parse_conditional_else<'input>( surrounded(ws, TokenKind::ConditionalElse), opt(preceded( TokenKind::ConditionalIf, - cut_err(surrounded(ws, parse_value_expression)).map(Box::new), + cut_err(surrounded(ws, parse_expression)).map(Box::new), )), ) .map(|else_expr| TemplateAstExpr::ElseConditional { @@ -545,12 +559,6 @@ fn parse_end<'input>(input: &mut Input<'input>) -> Result( - 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> { @@ -611,6 +619,55 @@ where ) } +fn parse_operand<'input>(input: &mut Input<'input>) -> Result, AstError> { + trace("operand", alt((parse_variable_access, parse_literal))).parse_next(input) +} + +fn parse_literal<'input>(input: &mut Input<'input>) -> Result, AstError> { + trace( + "literal", + any.verify_map(|token: &TemplateToken| match token.kind() { + TokenKind::Literal(literal) => Some(TemplateAstExpr::Literal { + source: token.clone(), + value: match literal { + crate::parser::TokenLiteral::Bool(bool) => NomoValue::Bool { value: bool }, + crate::parser::TokenLiteral::Integer(int) => NomoValue::Integer { value: int }, + }, + }), + _ => None, + }), + ) + .parse_next(input) +} + +fn parse_operator<'input>(input: &mut Input<'input>) -> Result { + trace( + "operator", + any.verify_map(|t: &'input TemplateToken| match t.kind() { + TokenKind::Operator(op) => Some(op), + _ => None, + }), + ) + .parse_next(input) +} + +fn parse_expression<'input>( + input: &mut Input<'input>, +) -> Result, AstError> { + trace( + "expression", + expression(surrounded(ws, parse_operand)).infix(dispatch! { surrounded(ws, parse_operator); + TokenOperator::Plus => Left(5, |_, lhs, rhs| Ok(TemplateAstExpr::Operation { op: TokenOperator::Plus, lhs: Box::new(lhs), rhs: Box::new(rhs) })), + TokenOperator::Minus => Left(5, |_, lhs, rhs| Ok(TemplateAstExpr::Operation { op: TokenOperator::Minus, lhs: Box::new(lhs), rhs: Box::new(rhs) })), + TokenOperator::Times => Left(7, |_, lhs, rhs| Ok(TemplateAstExpr::Operation { op: TokenOperator::Times, lhs: Box::new(lhs), rhs: Box::new(rhs) })), + TokenOperator::Divide => Left(7, |_, lhs, rhs| Ok(TemplateAstExpr::Operation { op: TokenOperator::Divide, lhs: Box::new(lhs), rhs: Box::new(rhs) })), + TokenOperator::And => Left(7, |_, lhs, rhs| Ok(TemplateAstExpr::Operation { op: TokenOperator::And, lhs: Box::new(lhs), rhs: Box::new(rhs) })), + TokenOperator::Or => Left(5, |_, lhs, rhs| Ok(TemplateAstExpr::Operation { op: TokenOperator::Or, lhs: Box::new(lhs), rhs: Box::new(rhs) })), + }), + ) + .parse_next(input) +} + fn ws<'input>(input: &mut Input<'input>) -> Result<(), AstError> { repeat(.., TokenKind::Whitespace).parse_next(input) } @@ -912,4 +969,15 @@ mod tests { insta::assert_debug_snapshot!(ast); } + + #[test] + fn check_math_expression() { + let input = "{{= 5 * 3 + 2 / 3 }}"; + + 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_math_expression.snap b/src/ast/snapshots/nomo__ast__tests__check_math_expression.snap new file mode 100644 index 0000000..7265e0a --- /dev/null +++ b/src/ast/snapshots/nomo__ast__tests__check_math_expression.snap @@ -0,0 +1,45 @@ +--- +source: src/ast/mod.rs +expression: ast +--- +TemplateAst { + root: [ + Interpolation { + prev_whitespace_content: None, + expression: Operation { + op: Plus, + lhs: Operation { + op: Times, + lhs: Literal { + source: [Literal(Integer(5))]"5" (4..5), + value: Integer { + value: 5, + }, + }, + rhs: Literal { + source: [Literal(Integer(3))]"3" (8..9), + value: Integer { + value: 3, + }, + }, + }, + rhs: Operation { + op: Divide, + lhs: Literal { + source: [Literal(Integer(2))]"2" (12..13), + value: Integer { + value: 2, + }, + }, + rhs: Literal { + source: [Literal(Integer(3))]"3" (16..17), + value: Integer { + value: 3, + }, + }, + }, + }, + post_whitespace_content: None, + }, + ], +} diff --git a/src/emit/mod.rs b/src/emit/mod.rs index b34d4cf..afaa4cd 100644 --- a/src/emit/mod.rs +++ b/src/emit/mod.rs @@ -402,6 +402,8 @@ fn emit_ast_expr( | TemplateAstExpr::For { .. } | TemplateAstExpr::ForElse | TemplateAstExpr::Invalid { .. } + | TemplateAstExpr::Literal { .. } + | TemplateAstExpr::Operation { .. } | TemplateAstExpr::VariableAccess { .. } => eval.push(Instruction::Abort), } } @@ -430,6 +432,8 @@ fn emit_expr_load( TemplateAstExpr::ForChain { .. } => todo!(), TemplateAstExpr::For { .. } => todo!(), TemplateAstExpr::ForElse => todo!(), + TemplateAstExpr::Operation { .. } => todo!(), + TemplateAstExpr::Literal { .. } => todo!(), TemplateAstExpr::IfConditional { .. } => todo!(), TemplateAstExpr::ConditionalContent { .. } => todo!(), diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 96d5e32..a5474c4 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -33,6 +33,7 @@ use winnow::stream::Location; use winnow::stream::Recoverable; use winnow::stream::Stream; use winnow::token::any; +use winnow::token::literal; use winnow::token::one_of; use winnow::token::rest; use winnow::token::take_until; @@ -535,7 +536,7 @@ fn ident<'input>(input: &mut Input<'input>) -> PResult<'input, NomoInput> { .context(ParseError::ctx().msg("Expected an ident, but found a literal instead")) .parse_next(input)?; - let literal_start = alpha1; + let literal_start = alt((alpha1, "_")); ( literal_start, diff --git a/tests/cases/1-parsed@identifiers.snap b/tests/cases/1-parsed@identifiers.snap index 66a2da0..31760f1 100644 --- a/tests/cases/1-parsed@identifiers.snap +++ b/tests/cases/1-parsed@identifiers.snap @@ -1,6 +1,15 @@ --- source: tests/file_tests.rs expression: parsed +info: + input: "{{= _name }}\n{{= a_name }}\n{{= name }}\n{{= _name1 }}\n{{= _namE }}\n{{= name1 }}" + context: + _namE: Foo + name1: Foo + name: Foo + _name: Foo + a_name: Foo + _name1: Foo input_file: tests/cases/identifiers.nomo --- ParsedTemplate { @@ -22,29 +31,29 @@ ParsedTemplate { [LeftDelim]"{{" (27..29), [WantsOutput]"=" (29..30), [Whitespace]" " (30..31), - [Ident]"1name" (31..36), - [Whitespace]" " (36..37), - [RightDelim]"}}" (37..39), - [Whitespace]"\n" (39..40), - [LeftDelim]"{{" (40..42), - [WantsOutput]"=" (42..43), - [Whitespace]" " (43..44), - [Ident]"_name1" (44..50), - [Whitespace]" " (50..51), - [RightDelim]"}}" (51..53), - [Whitespace]"\n" (53..54), - [LeftDelim]"{{" (54..56), - [WantsOutput]"=" (56..57), - [Whitespace]" " (57..58), - [Ident]"_namE" (58..63), - [Whitespace]" " (63..64), - [RightDelim]"}}" (64..66), - [Whitespace]"\n" (66..67), - [LeftDelim]"{{" (67..69), - [WantsOutput]"=" (69..70), - [Whitespace]" " (70..71), - [Ident]"name1" (71..76), - [Whitespace]" " (76..77), - [RightDelim]"}}" (77..79), + [Ident]"name" (31..35), + [Whitespace]" " (35..36), + [RightDelim]"}}" (36..38), + [Whitespace]"\n" (38..39), + [LeftDelim]"{{" (39..41), + [WantsOutput]"=" (41..42), + [Whitespace]" " (42..43), + [Ident]"_name1" (43..49), + [Whitespace]" " (49..50), + [RightDelim]"}}" (50..52), + [Whitespace]"\n" (52..53), + [LeftDelim]"{{" (53..55), + [WantsOutput]"=" (55..56), + [Whitespace]" " (56..57), + [Ident]"_namE" (57..62), + [Whitespace]" " (62..63), + [RightDelim]"}}" (63..65), + [Whitespace]"\n" (65..66), + [LeftDelim]"{{" (66..68), + [WantsOutput]"=" (68..69), + [Whitespace]" " (69..70), + [Ident]"name1" (70..75), + [Whitespace]" " (75..76), + [RightDelim]"}}" (76..78), ], } diff --git a/tests/cases/2-ast@identifiers.snap b/tests/cases/2-ast@identifiers.snap index ddcc251..4333d9d 100644 --- a/tests/cases/2-ast@identifiers.snap +++ b/tests/cases/2-ast@identifiers.snap @@ -1,6 +1,15 @@ --- source: tests/file_tests.rs expression: ast +info: + input: "{{= _name }}\n{{= a_name }}\n{{= name }}\n{{= _name1 }}\n{{= _namE }}\n{{= name1 }}" + context: + _namE: Foo + name1: Foo + name: Foo + _name: Foo + a_name: Foo + _name1: Foo input_file: tests/cases/identifiers.nomo --- TemplateAst { @@ -26,34 +35,34 @@ TemplateAst { Interpolation { prev_whitespace_content: None, expression: VariableAccess( - [Ident]"1name" (31..36), + [Ident]"name" (31..35), ), post_whitespace_content: Some( - [Whitespace]"\n" (39..40), + [Whitespace]"\n" (38..39), ), }, Interpolation { prev_whitespace_content: None, expression: VariableAccess( - [Ident]"_name1" (44..50), + [Ident]"_name1" (43..49), ), post_whitespace_content: Some( - [Whitespace]"\n" (53..54), + [Whitespace]"\n" (52..53), ), }, Interpolation { prev_whitespace_content: None, expression: VariableAccess( - [Ident]"_namE" (58..63), + [Ident]"_namE" (57..62), ), post_whitespace_content: Some( - [Whitespace]"\n" (66..67), + [Whitespace]"\n" (65..66), ), }, Interpolation { prev_whitespace_content: None, expression: VariableAccess( - [Ident]"name1" (71..76), + [Ident]"name1" (70..75), ), post_whitespace_content: None, }, diff --git a/tests/cases/3-instructions@identifiers.snap b/tests/cases/3-instructions@identifiers.snap index 01ec3a8..9556d23 100644 --- a/tests/cases/3-instructions@identifiers.snap +++ b/tests/cases/3-instructions@identifiers.snap @@ -1,6 +1,15 @@ --- source: tests/file_tests.rs expression: emit +info: + input: "{{= _name }}\n{{= a_name }}\n{{= name }}\n{{= _name1 }}\n{{= _namE }}\n{{= name1 }}" + context: + _namE: Foo + name1: Foo + name: Foo + _name: Foo + a_name: Foo + _name1: Foo input_file: tests/cases/identifiers.nomo --- VMInstructions { @@ -35,7 +44,7 @@ VMInstructions { content: "\n" (26..27), }, LoadFromContextToSlot { - name: "1name" (31..36), + name: "name" (31..35), slot: VariableSlot { index: 2, }, @@ -46,10 +55,10 @@ VMInstructions { }, }, AppendContent { - content: "\n" (39..40), + content: "\n" (38..39), }, LoadFromContextToSlot { - name: "_name1" (44..50), + name: "_name1" (43..49), slot: VariableSlot { index: 3, }, @@ -60,10 +69,10 @@ VMInstructions { }, }, AppendContent { - content: "\n" (53..54), + content: "\n" (52..53), }, LoadFromContextToSlot { - name: "_namE" (58..63), + name: "_namE" (57..62), slot: VariableSlot { index: 4, }, @@ -74,10 +83,10 @@ VMInstructions { }, }, AppendContent { - content: "\n" (66..67), + content: "\n" (65..66), }, LoadFromContextToSlot { - name: "name1" (71..76), + name: "name1" (70..75), slot: VariableSlot { index: 5, }, diff --git a/tests/cases/identifiers.nomo b/tests/cases/identifiers.nomo index 570a7b2..da49414 100644 --- a/tests/cases/identifiers.nomo +++ b/tests/cases/identifiers.nomo @@ -1,7 +1,7 @@ { "_name": "Foo", "a_name": "Foo", - "1name": "Foo", + "name": "Foo", "_name1": "Foo", "_namE": "Foo", "name1": "Foo" @@ -9,7 +9,7 @@ --- {{= _name }} {{= a_name }} -{{= 1name }} +{{= name }} {{= _name1 }} {{= _namE }} {{= name1 }} \ No newline at end of file