#![allow(clippy::result_large_err)]
use super::CompilationIssue;
use crate::SourceRange;
use crate::parsing::ast::types::BinaryExpression;
use crate::parsing::ast::types::BinaryOperator;
use crate::parsing::ast::types::BinaryPart;
use crate::parsing::ast::types::Node;
pub fn parse(infix_tokens: Vec<BinaryExpressionToken>) -> Result<Node<BinaryExpression>, CompilationIssue> {
let rpn = postfix(infix_tokens);
evaluate(rpn)
}
fn evaluate(rpn: Vec<BinaryExpressionToken>) -> Result<Node<BinaryExpression>, CompilationIssue> {
let source_range = source_range(&rpn);
let mut operand_stack: Vec<BinaryPart> = Vec::new();
let e = CompilationIssue::fatal(source_range, "error parsing binary math expressions");
for item in rpn {
let expr = match item {
BinaryExpressionToken::Operator(operator) => {
let Some(right) = operand_stack.pop() else {
return Err(e);
};
let Some(left) = operand_stack.pop() else {
return Err(e);
};
BinaryPart::BinaryExpression(Node::boxed(
left.start(),
right.end(),
left.module_id(),
BinaryExpression {
operator,
left,
right,
digest: None,
},
))
}
BinaryExpressionToken::Operand(o) => o,
};
operand_stack.push(expr)
}
if let Some(BinaryPart::BinaryExpression(expr)) = operand_stack.pop() {
Ok(*expr)
} else {
Err(e)
}
}
fn source_range(tokens: &[BinaryExpressionToken]) -> SourceRange {
let sources: Vec<_> = tokens
.iter()
.filter_map(|op| match op {
BinaryExpressionToken::Operator(_) => None,
BinaryExpressionToken::Operand(o) => Some((o.start(), o.end(), o.module_id())),
})
.collect();
match (sources.first(), sources.last()) {
(Some((start, _, module_id)), Some((_, end, _))) => SourceRange::new(*start, *end, *module_id),
_ => panic!(),
}
}
fn postfix(infix: Vec<BinaryExpressionToken>) -> Vec<BinaryExpressionToken> {
let mut operator_stack: Vec<BinaryOperator> = Vec::with_capacity(infix.len());
let mut output = Vec::with_capacity(infix.len());
for token in infix {
match token {
BinaryExpressionToken::Operator(o1) => {
while let Some(o2) = operator_stack.pop() {
if (o2.precedence() > o1.precedence())
|| o1.precedence() == o2.precedence() && o1.associativity().is_left()
{
output.push(BinaryExpressionToken::Operator(o2));
} else {
operator_stack.push(o2);
break;
}
}
operator_stack.push(o1);
}
o @ BinaryExpressionToken::Operand(_) => output.push(o),
}
}
output.extend(operator_stack.into_iter().rev().map(BinaryExpressionToken::Operator));
output
}
#[derive(PartialEq, Debug)]
pub enum BinaryExpressionToken {
Operator(BinaryOperator),
Operand(BinaryPart),
}
impl From<BinaryPart> for BinaryExpressionToken {
fn from(value: BinaryPart) -> Self {
Self::Operand(value)
}
}
impl From<BinaryOperator> for BinaryExpressionToken {
fn from(value: BinaryOperator) -> Self {
Self::Operator(value)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ModuleId;
use crate::parsing::ast::types::Literal;
use crate::parsing::ast::types::LiteralValue;
use crate::parsing::token::NumericSuffix;
#[test]
fn parse_and_evaluate() {
fn lit(n: u8) -> BinaryPart {
BinaryPart::Literal(Box::new(Node::new(
Literal {
value: LiteralValue::Number {
value: n as f64,
suffix: NumericSuffix::None,
},
raw: n.to_string(),
digest: None,
},
0,
0,
ModuleId::default(),
)))
}
let tests: Vec<Vec<BinaryExpressionToken>> = vec![
vec![
lit(3).into(),
BinaryOperator::Add.into(),
lit(4).into(),
BinaryOperator::Mul.into(),
lit(2).into(),
BinaryOperator::Div.into(),
BinaryPart::BinaryExpression(Node::boxed(
0,
0,
ModuleId::default(),
BinaryExpression {
operator: BinaryOperator::Sub,
left: lit(1),
right: lit(5),
digest: None,
},
))
.into(),
BinaryOperator::Pow.into(),
lit(2).into(),
BinaryOperator::Pow.into(),
lit(3).into(),
],
];
for infix_input in tests {
let rpn = postfix(infix_input);
let _tree = evaluate(rpn).unwrap();
}
}
}