pub mod flatten;
mod types;
use flatten::flatten_expr;
pub use types::*;
use crate::evaluator_rules::RuleSet;
use crate::grammar::*;
use crate::utils::{hash, normalize};
use std::collections::HashSet;
use std::error::Error;
use std::rc::Rc;
pub fn evaluate(expr: Stmt, ctxt: EvaluatorContext) -> Result<Expr, Box<dyn Error>> {
let mut rule_set = RuleSet::default();
for rule in ctxt.rule_blacklist {
rule_set.remove(rule)
}
let built_rules = rule_set.build()?;
let mut simplified_expr: Rc<Expr> = match expr {
Stmt::Expr(expr) => expr.into(),
_ => todo!("Evaluation currently only handles expressions"),
};
let mut expr_hash = hash(&simplified_expr);
let mut seen: HashSet<u64> = HashSet::new();
if ctxt.always_flatten {
simplified_expr = flatten_expr(&simplified_expr);
}
while seen.insert(expr_hash) {
for rule in &built_rules {
simplified_expr = rule.transform(simplified_expr);
}
expr_hash = hash(&simplified_expr);
}
Ok(normalize(simplified_expr).as_ref().clone())
}
#[cfg(test)]
mod tests {
use super::evaluate;
use crate::evaluator_rules::RuleName;
use crate::grammar::*;
use crate::{parse_expression, scan, EvaluatorContext};
fn parse(program: &str) -> Stmt {
let tokens = scan(program).tokens;
let (parsed, _) = parse_expression(tokens);
parsed
}
macro_rules! partial_evaluator_tests {
($($name:ident: $program:expr => $result:expr)*) => {
$(
#[test]
fn $name() {
let parsed = parse($program);
let evaluated = evaluate(parsed.clone(), EvaluatorContext::default()).unwrap();
assert_eq!(evaluated.to_string(), $result.to_string());
}
)*
}
}
partial_evaluator_tests! {
add: "1 + 2" => "3"
add_nested_left: "1 + 2 + a" => "a + 3"
add_nested_right: "a + 1 + 2" => "a + 3"
add_nested_with_reorder: "1 + a + 2" => "a + 3"
sub: "1 - 2" => "-1"
sub_nested_left: "1 - 2 - a" => "-1 - a"
mult: "2 * 3" => "6"
mult_nested_left: "2 * 3 * a" => "a * 6"
div: "6 / 2" => "3"
div_nested_left: "6 / 2 / a" => "3 / a"
div_associated: "6 / 2 / 3" => "1"
modulo: "6 % 4" => "2"
modulo_nested_left: "6 % 4 % a" => "2 % a"
modulo_associated: "9 % 5 % 5" => "4"
exp: "2 ^ 3" => "8"
exp_nested_left: "2 ^ 3 ^ a" => "2 ^ 3 ^ a"
exp_associated: "2 ^ 3 ^ 2" => "512"
posate: "+1" => "1"
posate_nested: "+(b + c)" => "b + c"
posate_nested_right: "a + +(b + c)" => "a + b + c"
posate_nested_prec: "a * +(b + c)" => "a * (b + c)"
negate: "-1" => "-1"
negate_nested: "1 + -2" => "-1"
additive_identity_var: "a + 0" => "a"
additive_identity_const: "1 + 0" => "1"
additive_identity_any: "(a * b) + 0" => "a * b"
additive_identity_nested: "(a + 0) + 0" => "a"
additive_identity_with_reorder: "0 + a + 0" => "a"
additive_inverse_var: "a - a" => "0"
additive_inverse_const: "1 - 1" => "0"
additive_inverse_any: "(a * b) - (a * b)" => "0"
additive_inverse_nested: "(a + 0) - a" => "0"
additive_inverse_with_reorder: "a + 0 - a" => "0"
subtractive_identity_var: "a - 0" => "a"
subtractive_identity_const: "1 - 0" => "1"
subtractive_identity_any: "(a * b) - 0" => "a * b"
subtractive_identity_nested: "(a + 0) - 0" => "a"
subtractive_identity_with_reorder: "0 - a - 0" => "-a"
reorder_constants: "1 + a" => "a + 1"
reorder_constants_nested: "1 + a + 2" => "a + 3"
reorder_constants_nested_left: "a + 1 + 2" => "a + 3"
reorder_constants_nested_right: "1 + 2 + a" => "a + 3"
distribute_negation: "-(a - b)" => "b - a"
distribute_negation_nested: "1 + -(a - b)" => "b + 1 - a"
distribute_negation_with_eval: "1 + -(2 - 3)" => "2"
unwrap_parens_const: "(1)" => "1"
unwrap_parens_var: "(a)" => "a"
unwrap_parens_nested: "(a) + (1)" => "a + 1"
unwrap_brackets_const: "[1]" => "1"
unwrap_brackets_var: "[a]" => "a"
unwrap_brackets_nested: "[a] + [1]" => "a + 1"
flattened_addition: "1 + 2 - b + 3 - b" => "6 - b - b"
issue_92: "a + 1 - 1" => "a"
}
#[test]
fn remove_rule() {
let parsed = parse("1 - 2 + 3 * 4");
let ctxt = EvaluatorContext::default()
.with_blacklist([RuleName::Add].to_vec())
.always_flatten(false);
let evaluated = evaluate(parsed, ctxt).unwrap();
assert_eq!(evaluated.to_string(), "-1 + 12".to_string());
}
}