This commit is contained in:
2025-08-30 00:37:36 +10:00
parent 73f2dccf4f
commit 3522b1acc2
4 changed files with 266 additions and 36 deletions

92
src/evaluator.rs Normal file
View File

@@ -0,0 +1,92 @@
use crate::parser::*;
use crate::tokenizer::Literal;
use std::fmt;
#[derive(Debug, PartialEq)]
pub enum Eval {
Literal(Literal),
Expr(Expr),
}
impl fmt::Display for Eval {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Eval::Literal(lit) => write!(f, "{lit:?}"),
Eval::Expr(expr) => write!(f, "({expr})"),
}
}
}
pub fn _evaluate(expr: &mut Expr) -> Result<Eval, String> {
let res = match expr {
Expr::Literal(lit) => Eval::Literal(lit.clone()),
Expr::Infix { op, lhs, rhs } => {
let lval = _evaluate(lhs)?;
let rval = _evaluate(rhs)?;
match op {
InfixOp::ADD => eval_add(&lval, &rval)?,
InfixOp::SUB => eval_sub(&lval, &rval)?,
InfixOp::MUL => eval_mul(&lval, &rval)?,
InfixOp::DIV => eval_div(&lval, &rval)?,
_ => return Err(format!("Evaluation error: Unsupported operator {:?}", op)),
}
}
it => return Err(format!("Evaluation error: Unsupported expression {:?}", it)),
};
Ok(res)
}
fn eval_add(lval: &Eval, rval: &Eval) -> Result<Eval, String> {
match (lval, rval) {
(Eval::Literal(a), Eval::Literal(b)) => {
if let Some(res) = eval_numeric_infix(a, b, |x, y| x + y, |x, y| x + y) {
return Ok(Eval::Literal(res));
}
// Try string concatenation
if let (Literal::String(x), Literal::String(y)) = (a, b) {
let mut res = x.to_owned();
res.push_str(y);
return Ok(Eval::Literal(Literal::String(res)));
}
Err("Evaluation error: expected string or numeric types for ADD function.".to_string())
}
_ => return Err("Evalutation error: expected literals for ADD function.".to_string()),
}
}
fn eval_sub(lval: &Eval, rval: &Eval) -> Result<Eval, String> {
Err("Todo.".to_string())
}
fn eval_mul(lval: &Eval, rval: &Eval) -> Result<Eval, String> {
Err("Todo.".to_string())
}
fn eval_div(lval: &Eval, rval: &Eval) -> Result<Eval, String> {
Err("Todo.".to_string())
}
pub fn eval_numeric_infix<FInt, FDouble>(
lhs: &Literal,
rhs: &Literal,
int_op: FInt,
double_op: FDouble,
) -> Option<Literal>
where
FInt: Fn(i64, i64) -> i64,
FDouble: Fn(f64, f64) -> f64,
{
match (lhs, rhs) {
(Literal::Integer(a), Literal::Integer(b)) => Some(Literal::Integer(int_op(*a, *b))),
(Literal::Double(a), Literal::Double(b)) => Some(Literal::Double(double_op(*a, *b))),
(Literal::Integer(a), Literal::Double(b)) => {
Some(Literal::Double(double_op(*a as f64, *b)))
}
(Literal::Double(a), Literal::Integer(b)) => {
Some(Literal::Double(double_op(*a, *b as f64)))
}
_ => None,
}
}

View File

@@ -1,16 +1,14 @@
mod evaluator;
mod parser; mod parser;
mod tokenizer; mod tokenizer;
use crate::parser::*;
use crate::tokenizer::*;
use std::io; use std::io;
fn main() { fn main() {
let mut input = String::new(); let mut input = String::new();
io::stdin().read_line(&mut input).expect("Expected input."); io::stdin().read_line(&mut input).expect("Expected input.");
let mut t = Tokenizer::new(&input).unwrap(); let mut ast = parser::parse(&input).unwrap();
println!("{:?}\n", t.tokens);
let ast = parser::parse(&mut t).unwrap();
println!("{}", ast.pretty()); println!("{}", ast.pretty());
println!("{}", evaluator::_evaluate(&mut ast).unwrap());
} }

View File

@@ -2,10 +2,15 @@ use crate::tokenizer::*;
use std::fmt; use std::fmt;
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum PrefixOp {} pub enum PrefixOp {
POS,
NEG,
NOT,
}
pub trait Precedence { #[derive(Debug, PartialEq)]
fn prec(&self) -> u8; pub enum PostfixOp {
PERCENT,
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
@@ -14,25 +19,27 @@ pub enum InfixOp {
DIV, DIV,
ADD, ADD,
SUB, SUB,
} AND,
OR,
impl Precedence for InfixOp {
fn prec(&self) -> u8 {
match self {
InfixOp::MUL | InfixOp::DIV => 2,
InfixOp::ADD | InfixOp::SUB => 1,
}
}
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum Expr { pub enum Expr {
Literal(Literal), Literal(Literal),
CellRef(String),
Function {
name: String,
args: Vec<Expr>,
},
Group(Box<Expr>), Group(Box<Expr>),
Prefix { Prefix {
op: PrefixOp, op: PrefixOp,
expr: Box<Expr>, expr: Box<Expr>,
}, },
Postfix {
op: PostfixOp,
expr: Box<Expr>,
},
Infix { Infix {
op: InfixOp, op: InfixOp,
lhs: Box<Expr>, lhs: Box<Expr>,
@@ -40,13 +47,46 @@ pub enum Expr {
}, },
} }
// Ref: https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html
// We have left and right precedence as to allow associative operators
// to parse as you would more expect and to break ties in a predictable manner
pub trait Precedence {
fn prec(&self) -> (u8, u8);
}
impl Precedence for InfixOp {
fn prec(&self) -> (u8, u8) {
match self {
InfixOp::MUL | InfixOp::DIV | InfixOp::AND => (3, 4),
InfixOp::ADD | InfixOp::SUB | InfixOp::OR => (1, 2),
}
}
}
impl Precedence for PrefixOp {
fn prec(&self) -> (u8, u8) {
match self {
_it => (0, 5),
}
}
}
impl Precedence for PostfixOp {
fn prec(&self) -> (u8, u8) {
match self {
_it => (6, 0),
}
}
}
impl fmt::Display for Expr { impl fmt::Display for Expr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self {
Expr::Literal(lit) => write!(f, "{lit:?}"), Expr::Literal(lit) => write!(f, "{lit:?}"),
Expr::Group(expr) => write!(f, "({expr})"), Expr::Group(expr) => write!(f, "({expr})"),
Expr::Prefix { op, expr } => write!(f, "({op:?} {expr})"), Expr::Prefix { op, expr } => write!(f, "({op:?} {expr})"),
Expr::Postfix { op, expr } => write!(f, "({op:?} {expr})"),
Expr::Infix { op, lhs, rhs } => write!(f, "({lhs} {op:?} {rhs})"), Expr::Infix { op, lhs, rhs } => write!(f, "({lhs} {op:?} {rhs})"),
Expr::Function { name, args } => write!(f, "{name}({args:?})"),
Expr::CellRef(it) => write!(f, "CellRef({it})"),
} }
} }
} }
@@ -70,16 +110,25 @@ impl Expr {
match self { match self {
Expr::Literal(_) => {} Expr::Literal(_) => {}
Expr::CellRef(_) => {}
Expr::Group(expr) => { Expr::Group(expr) => {
result.push_str(&expr.pretty_branch(&new_prefix, true)); result.push_str(&expr.pretty_branch(&new_prefix, true));
} }
Expr::Prefix { expr, .. } => { Expr::Prefix { expr, .. } => {
result.push_str(&expr.pretty_branch(&new_prefix, true)); result.push_str(&expr.pretty_branch(&new_prefix, true));
} }
Expr::Postfix { expr, .. } => {
result.push_str(&expr.pretty_branch(&new_prefix, true));
}
Expr::Infix { lhs, rhs, .. } => { Expr::Infix { lhs, rhs, .. } => {
result.push_str(&lhs.pretty_branch(&new_prefix, false)); result.push_str(&lhs.pretty_branch(&new_prefix, false));
result.push_str(&rhs.pretty_branch(&new_prefix, true)); result.push_str(&rhs.pretty_branch(&new_prefix, true));
} }
Expr::Function { args, .. } => {
for (idx, arg) in args.iter().enumerate() {
result.push_str(&arg.pretty_branch(&new_prefix, idx == args.len() - 1));
}
}
} }
result result
} }
@@ -97,46 +146,133 @@ impl Expr {
Expr::Literal(lit) => format!("Literal({:?})", lit), Expr::Literal(lit) => format!("Literal({:?})", lit),
Expr::Group(_) => "Group".to_string(), Expr::Group(_) => "Group".to_string(),
Expr::Prefix { op, .. } => format!("Prefix({:?})", op), Expr::Prefix { op, .. } => format!("Prefix({:?})", op),
Expr::Postfix { op, .. } => format!("Postfix({:?})", op),
Expr::Infix { op, .. } => format!("Infix({:?})", op), Expr::Infix { op, .. } => format!("Infix({:?})", op),
Expr::Function { name, .. } => format!("Function({:?})", name),
Expr::CellRef(it) => format!("CellRef({:?})", it),
} }
} }
} }
pub fn parse(input: &mut Tokenizer) -> Result<Expr, String> { pub fn parse(input: &str) -> Result<Expr, String> {
_parse(input, 0) let mut tokenizer = Tokenizer::new(input)?;
// println!("{:?}", tokenizer.tokens);
_parse(&mut tokenizer, 0)
} }
pub fn _parse(input: &mut Tokenizer, min_prec: u8) -> Result<Expr, String> { pub fn _parse(input: &mut Tokenizer, min_prec: u8) -> Result<Expr, String> {
let mut lhs = match input.next() { let mut lhs = match input.next() {
Token::Literal(it) => Expr::Literal(it), Token::Literal(it) => Expr::Literal(it),
Token::Identifier(id) if id == "true" => Expr::Literal(Literal::Boolean(true)),
Token::Identifier(id) if id == "false" => Expr::Literal(Literal::Boolean(false)),
Token::Paren('(') => {
let lhs = _parse(input, 0)?;
if input.next() != Token::Paren(')') {
return Err(format!("Parse error: expected closing paren."));
}
Expr::Group(Box::new(lhs))
}
Token::Operator(op) => {
let prefix_op = match op {
'+' => PrefixOp::POS,
'-' => PrefixOp::NEG,
'!' => PrefixOp::NOT,
it => return Err(format!("Parse error: unknown prefix operator {:?}.", it)),
};
let rhs = _parse(input, prefix_op.prec().1)?;
Expr::Prefix {
op: prefix_op,
expr: Box::new(rhs),
}
}
Token::Identifier(id) => match input.peek() {
Token::Paren('(') => {
input.next();
let mut args: Vec<Expr> = Vec::new();
loop {
let nxt = input.peek();
if nxt == Token::Paren(')') {
input.next();
break;
} else if nxt != Token::Comma && args.len() != 0 {
return Err(format!(
"Parse error: expected comma while parsing argument of function {:?}.",
id
));
}
if args.len() != 0 {
input.next(); // Skip comma
}
let arg = _parse(input, 0)?;
args.push(arg);
}
Expr::Function {
name: id,
args: args,
}
}
_ => Expr::CellRef(id),
},
it => return Err(format!("Parse error: did not expect token {:?}.", it)), it => return Err(format!("Parse error: did not expect token {:?}.", it)),
}; };
loop { // In the reference article this is a loop with match
let op = match input.peek() { // statement that breaks on Eof and closing paren but this is simpler and works as expected
Token::Eof => break, while let Token::Operator(op) = input.peek() {
Token::Operator(op) => match op { if "+-*/&|".contains(op) {
let infix_op = match op {
'+' => InfixOp::ADD, '+' => InfixOp::ADD,
'-' => InfixOp::SUB, '-' => InfixOp::SUB,
'*' => InfixOp::MUL, '*' => InfixOp::MUL,
'/' => InfixOp::DIV, '/' => InfixOp::DIV,
it => return Err(format!("Parse error: do not know operator {:?}.", it)), '&' => InfixOp::AND,
}, '|' => InfixOp::OR,
it => return Err(format!("Parse error: did not expect token {:?}.", it)), it => {
return Err(format!("Parse error: do not know infix operator {:?}.", it));
}
}; };
if op.prec() < min_prec { let (l_prec, r_prec) = infix_op.prec();
if l_prec < min_prec {
break; break;
} }
input.next(); input.next();
lhs = { let rhs = _parse(input, r_prec)?;
let rhs = _parse(input, op.prec()).unwrap(); lhs = Expr::Infix {
Expr::Infix { op: infix_op,
op: op,
lhs: Box::new(lhs), lhs: Box::new(lhs),
rhs: Box::new(rhs), rhs: Box::new(rhs),
};
} else if "%".contains(op) {
let postfix_op = match op {
'%' => PostfixOp::PERCENT,
it => {
return Err(format!(
"Parse error: do not know postfix operator {:?}.",
it
));
} }
};
let (l_prec, _) = postfix_op.prec();
if l_prec < min_prec {
break;
}
input.next();
lhs = Expr::Postfix {
op: postfix_op,
expr: Box::new(lhs),
};
} }
} }

View File

@@ -12,6 +12,7 @@ pub enum Token {
Literal(Literal), Literal(Literal),
Operator(char), Operator(char),
Paren(char), Paren(char),
Comma,
Eof, Eof,
} }
@@ -84,12 +85,15 @@ impl Tokenizer {
string.push(ch); string.push(ch);
} }
tokens.push(Token::Literal(Literal::String(string))); tokens.push(Token::Literal(Literal::String(string)));
} else if "+-*/^".contains(c) { } else if "+-*/^!%&|".contains(c) {
tokens.push(Token::Operator(c)); tokens.push(Token::Operator(c));
chars.next(); chars.next();
} else if "()".contains(c) { } else if "()".contains(c) {
tokens.push(Token::Paren(c)); tokens.push(Token::Paren(c));
chars.next(); chars.next();
} else if c == ',' {
tokens.push(Token::Comma);
chars.next();
} else { } else {
return Err(format!("Encountered unknown token char: {c}")); return Err(format!("Encountered unknown token char: {c}"));
} }