You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
306 lines
9.3 KiB
306 lines
9.3 KiB
use crate::board::Board;
|
|
use crate::lexer::{lexer, DecimalToken, Token};
|
|
use crate::parser;
|
|
use crate::parser::{Expression, Expressions};
|
|
use crate::position::Position2d;
|
|
use crate::tile::{Operator, Tile};
|
|
|
|
/// Evaluates a single expression syntax tree.
|
|
pub fn calculate(expr: &Expression) -> f64 {
|
|
match expr {
|
|
Expression::Digit(value) => *value as f64,
|
|
Expression::Parentheses(expr) => calculate(expr),
|
|
Expression::Unary(operator, expr) => {
|
|
let value = calculate(expr);
|
|
match operator {
|
|
Operator::Subtract => -value,
|
|
_ => panic!("Invalid unary operator"),
|
|
}
|
|
}
|
|
Expression::Binary(operator, left, right) => {
|
|
let left = calculate(left);
|
|
let right = calculate(right);
|
|
match operator {
|
|
Operator::Add => left + right,
|
|
Operator::Subtract => left - right,
|
|
Operator::Multiply => left * right,
|
|
Operator::Divide => left / right,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Evaluates a vector of expression syntax trees.
|
|
pub fn are_valid_expressions(expr: &Expressions) -> bool {
|
|
let mut res: Option<f64> = None;
|
|
for expr in expr {
|
|
let value = calculate(expr);
|
|
if let Some(res) = res {
|
|
if res != value {
|
|
return false;
|
|
}
|
|
} else {
|
|
res = Some(value);
|
|
}
|
|
}
|
|
res.is_some()
|
|
}
|
|
|
|
/// Determines if the given tokens are a valid equation.
|
|
pub fn is_valid_guess_of_tokens(tokens: &[Token]) -> bool {
|
|
let mut res: Option<f64> = None;
|
|
for part in tokens.split(|token| matches!(token, Token::Equals)) {
|
|
let rpn = shunting_yard(part);
|
|
if let Ok(rpn) = rpn {
|
|
let value = evaluate_rpn(&rpn);
|
|
if let Ok(value) = value {
|
|
if let Some(res) = res {
|
|
if res != value {
|
|
return false;
|
|
}
|
|
} else {
|
|
res = Some(value);
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
res.is_some()
|
|
}
|
|
|
|
pub fn is_valid_guess(board: &Board, positions: &[Position2d]) -> Result<bool, ()> {
|
|
let tiles = positions
|
|
.iter()
|
|
.map(|&pos| board.get(pos.x, pos.y))
|
|
.collect::<Option<Vec<Tile>>>()
|
|
.ok_or(())?;
|
|
|
|
let tokens = lexer(&tiles);
|
|
let expressions = parser::parse(&tokens)?;
|
|
if expressions.len() < 2 {
|
|
Ok(false)
|
|
} else {
|
|
Ok(are_valid_expressions(&expressions))
|
|
}
|
|
}
|
|
|
|
/// Convert an infix expression to a postfix expression.
|
|
fn shunting_yard(tokens: &[Token]) -> Result<Vec<DecimalToken>, ()> {
|
|
let mut operator_stack: Vec<DecimalToken> = Vec::with_capacity(tokens.len());
|
|
let mut output: Vec<DecimalToken> = Vec::with_capacity(tokens.len());
|
|
|
|
for token in tokens {
|
|
match token {
|
|
Token::NumberLiteral(num) => output.push(DecimalToken::NumberLiteral(*num as f64)),
|
|
Token::Operator(op) => {
|
|
while let Some(DecimalToken::Operator(top_op)) = operator_stack.last() {
|
|
if top_op.precedence() >= op.precedence() {
|
|
output.push(operator_stack.pop().unwrap());
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
operator_stack.push(DecimalToken::Operator(*op));
|
|
}
|
|
Token::LeftParen => operator_stack.push(DecimalToken::LeftParen),
|
|
Token::RightParen => {
|
|
while let Some(top_op) = operator_stack.pop() {
|
|
if let DecimalToken::LeftParen = top_op {
|
|
break;
|
|
} else {
|
|
output.push(top_op);
|
|
}
|
|
}
|
|
}
|
|
_ => panic!("Unexpected token: {token:?}"),
|
|
}
|
|
}
|
|
|
|
while let Some(top_op) = operator_stack.pop() {
|
|
output.push(top_op);
|
|
}
|
|
|
|
Ok(output)
|
|
}
|
|
|
|
/// Evaluate a postfix expression.
|
|
fn evaluate_rpn(tokens: &[DecimalToken]) -> Result<f64, ()> {
|
|
let mut stack = Vec::new();
|
|
for token in tokens {
|
|
match token {
|
|
DecimalToken::NumberLiteral(num) => stack.push(*num),
|
|
DecimalToken::Operator(op) => {
|
|
let right = stack.pop().ok_or(())?;
|
|
let left = stack.pop().ok_or(())?;
|
|
let result = match op {
|
|
Operator::Add => left + right,
|
|
Operator::Subtract => left - right,
|
|
Operator::Multiply => left * right,
|
|
Operator::Divide => left / right,
|
|
};
|
|
stack.push(result);
|
|
}
|
|
_ => panic!("Unexpected token: {token:?}"),
|
|
}
|
|
}
|
|
|
|
stack.pop().ok_or(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_calculate() {
|
|
let expr = Expression::Binary(
|
|
Operator::Add,
|
|
Box::new(Expression::Digit(1)),
|
|
Box::new(Expression::Digit(2)),
|
|
);
|
|
assert_eq!(calculate(&expr), 3.0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_are_valid_expressions() {
|
|
let expr = vec![
|
|
Expression::Binary(
|
|
Operator::Add,
|
|
Box::new(Expression::Digit(3)),
|
|
Box::new(Expression::Digit(4)),
|
|
),
|
|
Expression::Binary(
|
|
Operator::Add,
|
|
Box::new(Expression::Digit(6)),
|
|
Box::new(Expression::Digit(1)),
|
|
),
|
|
];
|
|
assert!(are_valid_expressions(&expr));
|
|
|
|
let expr = vec![
|
|
Expression::Digit(9),
|
|
Expression::Binary(
|
|
Operator::Add,
|
|
Box::new(Expression::Digit(7)),
|
|
Box::new(Expression::Digit(1)),
|
|
),
|
|
];
|
|
assert!(!are_valid_expressions(&expr));
|
|
}
|
|
|
|
#[test]
|
|
fn test_are_valid_expressions_negative() {
|
|
let expr = vec![
|
|
Expression::Binary(
|
|
Operator::Add,
|
|
Box::new(Expression::Digit(2)),
|
|
Box::new(Expression::Digit(-5)),
|
|
),
|
|
Expression::Binary(
|
|
Operator::Subtract,
|
|
Box::new(Expression::Digit(-4)),
|
|
Box::new(Expression::Digit(-1)),
|
|
),
|
|
];
|
|
assert!(are_valid_expressions(&expr));
|
|
}
|
|
|
|
#[test]
|
|
fn test_are_valid_expressions_unary_negative() {
|
|
let expr = vec![
|
|
Expression::Unary(Operator::Subtract, Box::new(Expression::Digit(8))),
|
|
Expression::Digit(-8),
|
|
];
|
|
assert!(are_valid_expressions(&expr));
|
|
}
|
|
|
|
#[test]
|
|
fn shunting_yard_sample() {
|
|
let tokens = vec![
|
|
Token::NumberLiteral(5),
|
|
Token::Operator(Operator::Multiply),
|
|
Token::NumberLiteral(9),
|
|
Token::Operator(Operator::Subtract),
|
|
Token::NumberLiteral(2),
|
|
];
|
|
let res = shunting_yard(&tokens).expect("Failed to evaluate");
|
|
assert_eq!(
|
|
res,
|
|
vec![
|
|
DecimalToken::NumberLiteral(5.),
|
|
DecimalToken::NumberLiteral(9.),
|
|
DecimalToken::Operator(Operator::Multiply),
|
|
DecimalToken::NumberLiteral(2.),
|
|
DecimalToken::Operator(Operator::Subtract),
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn shunting_yard_precedence() {
|
|
let tokens = vec![
|
|
Token::NumberLiteral(1),
|
|
Token::Operator(Operator::Add),
|
|
Token::NumberLiteral(2),
|
|
Token::Operator(Operator::Multiply),
|
|
Token::NumberLiteral(3),
|
|
Token::Operator(Operator::Add),
|
|
Token::NumberLiteral(4),
|
|
];
|
|
let res = shunting_yard(&tokens).expect("Failed to evaluate");
|
|
assert_eq!(
|
|
res,
|
|
vec![
|
|
DecimalToken::NumberLiteral(1.),
|
|
DecimalToken::NumberLiteral(2.),
|
|
DecimalToken::NumberLiteral(3.),
|
|
DecimalToken::Operator(Operator::Multiply),
|
|
DecimalToken::Operator(Operator::Add),
|
|
DecimalToken::NumberLiteral(4.),
|
|
DecimalToken::Operator(Operator::Add),
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn is_valid_guess_of_tiles_equals() {
|
|
let tokens = vec![
|
|
Token::NumberLiteral(4),
|
|
Token::Operator(Operator::Subtract),
|
|
Token::NumberLiteral(1),
|
|
Token::Equals,
|
|
Token::NumberLiteral(3),
|
|
];
|
|
assert!(is_valid_guess_of_tokens(&tokens));
|
|
}
|
|
|
|
#[test]
|
|
fn is_valid_guess_of_tiles_not_equals() {
|
|
let tokens = vec![
|
|
Token::NumberLiteral(8),
|
|
Token::Operator(Operator::Divide),
|
|
Token::NumberLiteral(4),
|
|
Token::Equals,
|
|
Token::NumberLiteral(5),
|
|
];
|
|
assert!(!is_valid_guess_of_tokens(&tokens));
|
|
}
|
|
|
|
#[test]
|
|
fn is_valid_guess_of_tiles_precedence() {
|
|
let tokens = vec![
|
|
Token::NumberLiteral(5),
|
|
Token::Operator(Operator::Add),
|
|
Token::NumberLiteral(-2),
|
|
Token::Operator(Operator::Multiply),
|
|
Token::NumberLiteral(3),
|
|
Token::Equals,
|
|
Token::NumberLiteral(-1),
|
|
];
|
|
assert!(is_valid_guess_of_tokens(&tokens));
|
|
}
|
|
}
|