use gazelle::Precedence;
use gazelle_macros::gazelle;
gazelle! {
grammar Expr {
start expr;
terminals {
NUM: Num,
LPAREN, RPAREN, COLON,
MINUS, prec OP: Op
}
expr: Expr = term @eval_term
| expr OP expr @eval_binop;
term: Term = NUM @eval_num
| LPAREN expr RPAREN @eval_paren
| MINUS term @eval_neg;
}
}
struct Eval;
impl ExprTypes for Eval {
type Num = i64;
type Op = char;
type Term = i64;
type Expr = i64;
}
impl ExprActions for Eval {
fn eval_num(&mut self, n: i64) -> Result<i64, gazelle::ParseError> { Ok(n) }
fn eval_paren(&mut self, e: i64) -> Result<i64, gazelle::ParseError> { Ok(e) }
fn eval_neg(&mut self, e: i64) -> Result<i64, gazelle::ParseError> { Ok(-e) }
fn eval_term(&mut self, t: i64) -> Result<i64, gazelle::ParseError> { Ok(t) }
fn eval_binop(&mut self, l: i64, op: char, r: i64) -> Result<i64, gazelle::ParseError> {
Ok(match op {
'?' => unreachable!("ternary handled specially"),
'|' => if l != 0 || r != 0 { 1 } else { 0 }, '&' => if l != 0 && r != 0 { 1 } else { 0 }, '=' => if l == r { 1 } else { 0 }, '!' => if l != r { 1 } else { 0 }, '<' => if l < r { 1 } else { 0 },
'>' => if l > r { 1 } else { 0 },
'L' => if l <= r { 1 } else { 0 }, 'G' => if l >= r { 1 } else { 0 }, '+' => l + r,
'-' => l - r,
'*' => l * r,
'/' => l / r,
'%' => l % r,
_ => panic!("unknown op: {}", op),
})
}
}
fn eval(input: &str) -> Result<i64, String> {
let mut parser = ExprParser::<Eval>::new();
let mut actions = Eval;
let tokens = lex(input)?;
for tok in tokens {
parser.push(tok, &mut actions).map_err(|e| format!("{:?}", e))?;
}
parser.finish(&mut actions).map_err(|(p, e)| p.format_error(&e))
}
fn lex(input: &str) -> Result<Vec<ExprTerminal<Eval>>, String> {
let mut tokens = Vec::new();
let mut chars = input.chars().peekable();
while let Some(&c) = chars.peek() {
match c {
' ' | '\t' | '\n' => { chars.next(); }
'0'..='9' => {
let mut num = 0i64;
while let Some(&c) = chars.peek() {
if c.is_ascii_digit() {
num = num * 10 + (c as i64 - '0' as i64);
chars.next();
} else {
break;
}
}
tokens.push(ExprTerminal::NUM(num));
}
'(' => { chars.next(); tokens.push(ExprTerminal::LPAREN); }
')' => { chars.next(); tokens.push(ExprTerminal::RPAREN); }
':' => { chars.next(); tokens.push(ExprTerminal::COLON); }
'+' => {
chars.next();
tokens.push(ExprTerminal::OP('+', Precedence::Left(6)));
}
'-' => {
chars.next();
let is_unary = tokens.last().map(|t| matches!(t,
ExprTerminal::OP(_, _) | ExprTerminal::LPAREN | ExprTerminal::MINUS
)).unwrap_or(true);
if is_unary {
tokens.push(ExprTerminal::MINUS);
} else {
tokens.push(ExprTerminal::OP('-', Precedence::Left(6)));
}
}
'*' => { chars.next(); tokens.push(ExprTerminal::OP('*', Precedence::Left(7))); }
'/' => { chars.next(); tokens.push(ExprTerminal::OP('/', Precedence::Left(7))); }
'%' => { chars.next(); tokens.push(ExprTerminal::OP('%', Precedence::Left(7))); }
'|' => {
chars.next();
if chars.peek() == Some(&'|') {
chars.next();
tokens.push(ExprTerminal::OP('|', Precedence::Left(2)));
} else {
return Err("Expected ||".into());
}
}
'&' => {
chars.next();
if chars.peek() == Some(&'&') {
chars.next();
tokens.push(ExprTerminal::OP('&', Precedence::Left(3)));
} else {
return Err("Expected &&".into());
}
}
'=' => {
chars.next();
if chars.peek() == Some(&'=') {
chars.next();
tokens.push(ExprTerminal::OP('=', Precedence::Left(4)));
} else {
return Err("Expected ==".into());
}
}
'!' => {
chars.next();
if chars.peek() == Some(&'=') {
chars.next();
tokens.push(ExprTerminal::OP('!', Precedence::Left(4)));
} else {
return Err("Expected !=".into());
}
}
'<' => {
chars.next();
if chars.peek() == Some(&'=') {
chars.next();
tokens.push(ExprTerminal::OP('L', Precedence::Left(5))); } else {
tokens.push(ExprTerminal::OP('<', Precedence::Left(5)));
}
}
'>' => {
chars.next();
if chars.peek() == Some(&'=') {
chars.next();
tokens.push(ExprTerminal::OP('G', Precedence::Left(5))); } else {
tokens.push(ExprTerminal::OP('>', Precedence::Left(5)));
}
}
_ => return Err(format!("Unexpected char: {}", c)),
}
}
Ok(tokens)
}
fn main() {
println!("Expression Evaluator - Dynamic Precedence Test");
println!();
let tests = [
("1 + 2 * 3", 7), ("2 * 3 + 1", 7), ("(1 + 2) * 3", 9), ("10 - 3 - 2", 5), ("2 * 3 * 4", 24), ("1 + 2 + 3 * 4", 15), ("1 < 2", 1), ("2 < 1", 0),
("1 + 1 == 2", 1), ("1 == 1 && 2 == 2", 1), ("1 || 0 && 0", 1), ("0 || 0 && 1", 0), ("-5", -5), ("--5", 5), ("2 * -3", -6), ("1 - 2 - 3", -4), ("100 / 10 / 2", 5), ];
let mut passed = 0;
let mut failed = 0;
for (expr, expected) in tests {
match eval(expr) {
Ok(result) if result == expected => {
println!("PASS: {} = {}", expr, result);
passed += 1;
}
Ok(result) => {
println!("FAIL: {} = {} (expected {})", expr, result, expected);
failed += 1;
}
Err(e) => {
println!("ERROR: {} -> {}", expr, e);
failed += 1;
}
}
}
println!();
println!("{} passed, {} failed", passed, failed);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_precedence() {
assert_eq!(eval("1 + 2 * 3").unwrap(), 7); assert_eq!(eval("2 * 3 + 1").unwrap(), 7);
assert_eq!(eval("(1 + 2) * 3").unwrap(), 9);
}
#[test]
fn test_associativity() {
assert_eq!(eval("10 - 3 - 2").unwrap(), 5); assert_eq!(eval("2 * 3 * 4").unwrap(), 24);
assert_eq!(eval("1 - 2 - 3").unwrap(), -4); assert_eq!(eval("100 / 10 / 2").unwrap(), 5); }
#[test]
fn test_comparison_precedence() {
assert_eq!(eval("1 + 1 == 2").unwrap(), 1); assert_eq!(eval("1 == 1 && 2 == 2").unwrap(), 1); assert_eq!(eval("1 || 0 && 0").unwrap(), 1); }
#[test]
fn test_unary() {
assert_eq!(eval("-5").unwrap(), -5);
assert_eq!(eval("--5").unwrap(), 5);
assert_eq!(eval("2 * -3").unwrap(), -6);
}
}