Parse and ast math expressions
Signed-off-by: Marcel Müller <neikos@neikos.email>
This commit is contained in:
parent
8cc8488de4
commit
8c02dbd672
8 changed files with 196 additions and 51 deletions
|
|
@ -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<Box<TemplateAstExpr<'input>>>,
|
||||
},
|
||||
Invalid(&'input [TemplateToken]),
|
||||
Operation {
|
||||
op: TokenOperator,
|
||||
lhs: Box<TemplateAstExpr<'input>>,
|
||||
rhs: Box<TemplateAstExpr<'input>>,
|
||||
},
|
||||
EndBlock,
|
||||
Literal {
|
||||
source: TemplateToken,
|
||||
value: NomoValue,
|
||||
},
|
||||
}
|
||||
|
||||
fn parse_asts<'input>(input: &mut Input<'input>) -> Result<Vec<TemplateAstExpr<'input>>, AstError> {
|
||||
|
|
@ -275,7 +289,7 @@ fn parse_interpolation<'input>(
|
|||
input: &mut Input<'input>,
|
||||
) -> Result<TemplateAstExpr<'input>, 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<TemplateAstExpr<'
|
|||
ws,
|
||||
TokenKind::In,
|
||||
ws,
|
||||
parse_value_expression.map(Box::new),
|
||||
parse_expression.map(Box::new),
|
||||
)
|
||||
.map(|(_, _for, _, value_ident, _, _in, _, value_expression)| {
|
||||
TemplateAstExpr::For {
|
||||
|
|
@ -489,7 +503,7 @@ fn parse_conditional_if<'input>(
|
|||
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<TemplateAstExpr<'input
|
|||
.parse_next(input)
|
||||
}
|
||||
|
||||
fn parse_value_expression<'input>(
|
||||
input: &mut Input<'input>,
|
||||
) -> Result<TemplateAstExpr<'input>, AstError> {
|
||||
trace("value_expression", alt((parse_variable_access,))).parse_next(input)
|
||||
}
|
||||
|
||||
fn parse_variable_access<'input>(
|
||||
input: &mut Input<'input>,
|
||||
) -> Result<TemplateAstExpr<'input>, AstError> {
|
||||
|
|
@ -611,6 +619,55 @@ where
|
|||
)
|
||||
}
|
||||
|
||||
fn parse_operand<'input>(input: &mut Input<'input>) -> Result<TemplateAstExpr<'input>, AstError> {
|
||||
trace("operand", alt((parse_variable_access, parse_literal))).parse_next(input)
|
||||
}
|
||||
|
||||
fn parse_literal<'input>(input: &mut Input<'input>) -> Result<TemplateAstExpr<'input>, 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<TokenOperator, AstError> {
|
||||
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<TemplateAstExpr<'input>, 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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
@ -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!(),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue