🙃
This commit is contained in:
92
src/evaluator.rs
Normal file
92
src/evaluator.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|||||||
188
src/parser.rs
188
src/parser.rs
@@ -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),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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}"));
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user