2024-12-06 13:57:31 +13:00
|
|
|
|
// TODO optimise size of CompilationError
|
|
|
|
|
#![allow(clippy::result_large_err)]
|
|
|
|
|
|
2024-12-05 19:51:06 -08:00
|
|
|
|
use super::CompilationError;
|
2023-11-03 13:30:19 -05:00
|
|
|
|
use crate::{
|
2024-12-03 16:39:51 +13:00
|
|
|
|
SourceRange,
|
2025-06-26 17:02:54 -05:00
|
|
|
|
parsing::ast::types::{BinaryExpression, BinaryOperator, BinaryPart, Node},
|
2023-11-03 13:30:19 -05:00
|
|
|
|
};
|
2023-10-31 14:16:18 -05:00
|
|
|
|
|
|
|
|
|
/// Parses a list of tokens (in infix order, i.e. as the user typed them)
|
|
|
|
|
/// into a binary expression tree.
|
2024-12-06 13:57:31 +13:00
|
|
|
|
pub fn parse(infix_tokens: Vec<BinaryExpressionToken>) -> Result<Node<BinaryExpression>, CompilationError> {
|
2023-10-31 14:16:18 -05:00
|
|
|
|
let rpn = postfix(infix_tokens);
|
|
|
|
|
evaluate(rpn)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Parses a list of tokens (in postfix order) into a binary expression tree.
|
2024-12-06 13:57:31 +13:00
|
|
|
|
fn evaluate(rpn: Vec<BinaryExpressionToken>) -> Result<Node<BinaryExpression>, CompilationError> {
|
|
|
|
|
let source_range = source_range(&rpn);
|
2023-11-03 13:30:19 -05:00
|
|
|
|
let mut operand_stack: Vec<BinaryPart> = Vec::new();
|
2024-12-06 13:57:31 +13:00
|
|
|
|
let e = CompilationError::fatal(source_range, "error parsing binary math expressions");
|
2023-10-31 14:16:18 -05:00
|
|
|
|
for item in rpn {
|
|
|
|
|
let expr = match item {
|
|
|
|
|
BinaryExpressionToken::Operator(operator) => {
|
2023-11-03 13:30:19 -05:00
|
|
|
|
let Some(right) = operand_stack.pop() else {
|
|
|
|
|
return Err(e);
|
|
|
|
|
};
|
|
|
|
|
let Some(left) = operand_stack.pop() else {
|
|
|
|
|
return Err(e);
|
|
|
|
|
};
|
2024-10-30 16:52:17 -04:00
|
|
|
|
let start = left.start();
|
|
|
|
|
let end = right.end();
|
2024-11-07 11:23:41 -05:00
|
|
|
|
let module_id = left.module_id();
|
2024-10-30 16:52:17 -04:00
|
|
|
|
|
|
|
|
|
BinaryPart::BinaryExpression(Node::boxed(
|
|
|
|
|
BinaryExpression {
|
|
|
|
|
operator,
|
|
|
|
|
left,
|
|
|
|
|
right,
|
|
|
|
|
digest: None,
|
|
|
|
|
},
|
|
|
|
|
start,
|
|
|
|
|
end,
|
2024-11-07 11:23:41 -05:00
|
|
|
|
module_id,
|
2024-10-30 16:52:17 -04:00
|
|
|
|
))
|
2023-10-31 14:16:18 -05:00
|
|
|
|
}
|
|
|
|
|
BinaryExpressionToken::Operand(o) => o,
|
|
|
|
|
};
|
|
|
|
|
operand_stack.push(expr)
|
|
|
|
|
}
|
2023-11-03 13:30:19 -05:00
|
|
|
|
if let Some(BinaryPart::BinaryExpression(expr)) = operand_stack.pop() {
|
|
|
|
|
Ok(*expr)
|
2023-10-31 14:16:18 -05:00
|
|
|
|
} else {
|
2023-11-03 13:30:19 -05:00
|
|
|
|
// If this branch is used, the evaluation algorithm has a bug and must be fixed.
|
|
|
|
|
// This is a programmer error, not a user error.
|
|
|
|
|
Err(e)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-06 13:57:31 +13:00
|
|
|
|
fn source_range(tokens: &[BinaryExpressionToken]) -> SourceRange {
|
2023-11-03 13:30:19 -05:00
|
|
|
|
let sources: Vec<_> = tokens
|
|
|
|
|
.iter()
|
|
|
|
|
.filter_map(|op| match op {
|
|
|
|
|
BinaryExpressionToken::Operator(_) => None,
|
2024-11-07 11:23:41 -05:00
|
|
|
|
BinaryExpressionToken::Operand(o) => Some((o.start(), o.end(), o.module_id())),
|
2023-11-03 13:30:19 -05:00
|
|
|
|
})
|
|
|
|
|
.collect();
|
|
|
|
|
match (sources.first(), sources.last()) {
|
2024-12-06 13:57:31 +13:00
|
|
|
|
(Some((start, _, module_id)), Some((_, end, _))) => SourceRange::new(*start, *end, *module_id),
|
|
|
|
|
_ => panic!(),
|
2023-10-31 14:16:18 -05:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Reorders tokens from infix order to postfix order.
|
|
|
|
|
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) => {
|
|
|
|
|
// From https://en.wikipedia.org/wiki/Shunting_yard_algorithm:
|
|
|
|
|
// while (
|
|
|
|
|
// there is an operator o2 at the top of the operator stack which is not a left parenthesis,
|
|
|
|
|
// and (o2 has greater precedence than o1 or (o1 and o2 have the same precedence and o1 is left-associative))
|
|
|
|
|
// )
|
2023-11-03 13:30:19 -05:00
|
|
|
|
// pop o2 from the operator stack into the output queue
|
|
|
|
|
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;
|
|
|
|
|
}
|
2023-10-31 14:16:18 -05:00
|
|
|
|
}
|
|
|
|
|
operator_stack.push(o1);
|
|
|
|
|
}
|
|
|
|
|
o @ BinaryExpressionToken::Operand(_) => output.push(o),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// After the while loop, pop the remaining items from the operator stack into the output queue.
|
|
|
|
|
output.extend(operator_stack.into_iter().rev().map(BinaryExpressionToken::Operator));
|
|
|
|
|
output
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Expressions are made up of operators and operands.
|
|
|
|
|
#[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::*;
|
2025-01-22 08:29:30 +13:00
|
|
|
|
use crate::{
|
2025-06-26 17:02:54 -05:00
|
|
|
|
ModuleId,
|
2025-01-22 08:29:30 +13:00
|
|
|
|
parsing::{
|
|
|
|
|
ast::types::{Literal, LiteralValue},
|
|
|
|
|
token::NumericSuffix,
|
|
|
|
|
},
|
|
|
|
|
};
|
2023-10-31 14:16:18 -05:00
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn parse_and_evaluate() {
|
|
|
|
|
/// Make a literal
|
|
|
|
|
fn lit(n: u8) -> BinaryPart {
|
2024-10-30 16:52:17 -04:00
|
|
|
|
BinaryPart::Literal(Box::new(Node::new(
|
|
|
|
|
Literal {
|
2025-01-22 08:29:30 +13:00
|
|
|
|
value: LiteralValue::Number {
|
|
|
|
|
value: n as f64,
|
|
|
|
|
suffix: NumericSuffix::None,
|
|
|
|
|
},
|
2024-10-30 16:52:17 -04:00
|
|
|
|
raw: n.to_string(),
|
|
|
|
|
digest: None,
|
|
|
|
|
},
|
|
|
|
|
0,
|
|
|
|
|
0,
|
2024-11-07 11:23:41 -05:00
|
|
|
|
ModuleId::default(),
|
2024-10-30 16:52:17 -04:00
|
|
|
|
)))
|
2023-10-31 14:16:18 -05:00
|
|
|
|
}
|
|
|
|
|
let tests: Vec<Vec<BinaryExpressionToken>> = vec![
|
|
|
|
|
// 3 + 4 × 2 ÷ ( 1 − 5 ) ^ 2 ^ 3
|
|
|
|
|
vec![
|
|
|
|
|
lit(3).into(),
|
|
|
|
|
BinaryOperator::Add.into(),
|
|
|
|
|
lit(4).into(),
|
|
|
|
|
BinaryOperator::Mul.into(),
|
|
|
|
|
lit(2).into(),
|
|
|
|
|
BinaryOperator::Div.into(),
|
2024-10-30 16:52:17 -04:00
|
|
|
|
BinaryPart::BinaryExpression(Node::boxed(
|
|
|
|
|
BinaryExpression {
|
|
|
|
|
operator: BinaryOperator::Sub,
|
|
|
|
|
left: lit(1),
|
|
|
|
|
right: lit(5),
|
|
|
|
|
digest: None,
|
|
|
|
|
},
|
|
|
|
|
0,
|
|
|
|
|
0,
|
2024-11-07 11:23:41 -05:00
|
|
|
|
ModuleId::default(),
|
2024-10-30 16:52:17 -04:00
|
|
|
|
))
|
2023-10-31 14:16:18 -05:00
|
|
|
|
.into(),
|
|
|
|
|
BinaryOperator::Pow.into(),
|
|
|
|
|
lit(2).into(),
|
|
|
|
|
BinaryOperator::Pow.into(),
|
|
|
|
|
lit(3).into(),
|
|
|
|
|
],
|
|
|
|
|
];
|
|
|
|
|
for infix_input in tests {
|
|
|
|
|
let rpn = postfix(infix_input);
|
2023-11-03 13:30:19 -05:00
|
|
|
|
let _tree = evaluate(rpn).unwrap();
|
2023-10-31 14:16:18 -05:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|