🙃
This commit is contained in:
99
backend/src/cell.rs
Normal file
99
backend/src/cell.rs
Normal file
@@ -0,0 +1,99 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::evaluator::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Cell {
|
||||
eval: Eval,
|
||||
raw: String,
|
||||
i_dep: HashSet<CellRef>,
|
||||
they_dep: HashSet<CellRef>,
|
||||
}
|
||||
|
||||
impl Cell {
|
||||
pub fn new(eval: Eval, raw: String) -> Self {
|
||||
Self {
|
||||
eval,
|
||||
raw,
|
||||
i_dep: HashSet::new(),
|
||||
they_dep: HashSet::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn raw(&self) -> String {
|
||||
self.raw.clone()
|
||||
}
|
||||
|
||||
pub fn eval(&self) -> Eval {
|
||||
self.eval.clone()
|
||||
}
|
||||
|
||||
pub fn add_i_dep(&mut self, dep: CellRef) {
|
||||
self.i_dep.insert(dep);
|
||||
}
|
||||
|
||||
pub fn add_they_dep(&mut self, dep: CellRef) {
|
||||
self.they_dep.insert(dep);
|
||||
}
|
||||
|
||||
pub fn clear_i_dep(&mut self) {
|
||||
self.i_dep.clear();
|
||||
}
|
||||
|
||||
pub fn clear_they_dep(&mut self) {
|
||||
self.they_dep.clear();
|
||||
}
|
||||
|
||||
pub fn set_eval(&mut self, eval: Eval) {
|
||||
self.eval = eval;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub struct CellRef {
|
||||
pub row: i64,
|
||||
pub col: i64,
|
||||
}
|
||||
|
||||
impl CellRef {
|
||||
pub fn new(s: String) -> Result<CellRef, String> {
|
||||
let s = s.trim();
|
||||
let mut col: i64 = 0;
|
||||
let mut i = 0;
|
||||
|
||||
// consume leading letters for the column
|
||||
for (idx, ch) in s.char_indices() {
|
||||
if ch.is_ascii_alphabetic() {
|
||||
let u = ch.to_ascii_uppercase() as u8;
|
||||
let val = (u - b'A' + 1) as i64; // A->1 ... Z->26
|
||||
col = col * 26 + val;
|
||||
i = idx + ch.len_utf8();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if col <= 0 {
|
||||
return Err(format!(
|
||||
"Parse error: missing column letters in cell ref: {s}"
|
||||
));
|
||||
}
|
||||
|
||||
let row_part = &s[i..];
|
||||
if row_part.is_empty() {
|
||||
return Err(format!(
|
||||
"Parse error: missing column letters in cell ref: {s}"
|
||||
));
|
||||
} else if !row_part.chars().all(|c| c.is_ascii_digit()) {
|
||||
return Err(format!(
|
||||
"Parse error: row part must be numeric in cell ref: {s}"
|
||||
));
|
||||
}
|
||||
|
||||
if let Ok(row) = row_part.parse::<i64>() {
|
||||
Ok(CellRef { row, col })
|
||||
} else {
|
||||
Err(format!("Parse error: invalid row number."))
|
||||
}
|
||||
}
|
||||
}
|
||||
168
backend/src/evaluator.rs
Normal file
168
backend/src/evaluator.rs
Normal file
@@ -0,0 +1,168 @@
|
||||
use crate::cell::{Cell, CellRef};
|
||||
use crate::parser::*;
|
||||
use crate::tokenizer::Literal;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Eval {
|
||||
Literal(Literal),
|
||||
}
|
||||
|
||||
impl fmt::Display for Eval {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Eval::Literal(lit) => write!(f, "{lit:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Evaluator {
|
||||
cells: HashMap<CellRef, Cell>,
|
||||
}
|
||||
|
||||
impl Evaluator {
|
||||
pub fn new() -> Evaluator {
|
||||
return Evaluator {
|
||||
cells: HashMap::new(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn set_cell(&mut self, cell_ref: CellRef, raw_val: String) -> Result<(), String> {
|
||||
if self.cells.contains_key(&cell_ref) && self.cells[&cell_ref].raw() == raw_val {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let eval: Eval;
|
||||
|
||||
if let Some(c) = raw_val.chars().nth(0)
|
||||
&& c == '='
|
||||
{
|
||||
eval = self.evaluate(raw_val[1..].to_owned())?;
|
||||
} else {
|
||||
match self.evaluate(raw_val.to_owned()) {
|
||||
Ok(e) => {
|
||||
eval = e;
|
||||
}
|
||||
Err(_) => eval = Eval::Literal(Literal::String(raw_val.to_owned())),
|
||||
}
|
||||
}
|
||||
|
||||
self.cells.insert(cell_ref, Cell::new(eval, raw_val));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_cell(&mut self, cell_ref: CellRef) -> Result<(String, Eval), String> {
|
||||
if !self.cells.contains_key(&cell_ref) {
|
||||
return Err(format!("Cell at {:?} not found.", cell_ref));
|
||||
}
|
||||
|
||||
let cell = &self.cells[&cell_ref];
|
||||
|
||||
Ok((cell.raw(), cell.eval()))
|
||||
}
|
||||
|
||||
pub fn evaluate(&mut self, str: String) -> Result<Eval, String> {
|
||||
let (mut expr, mut deps) = parse(&str)?;
|
||||
|
||||
self.evaluate_expr(&mut expr)
|
||||
}
|
||||
|
||||
fn evaluate_expr(&mut self, expr: &mut Expr) -> Result<Eval, String> {
|
||||
let res = match expr {
|
||||
Expr::Literal(lit) => Eval::Literal(lit.clone()),
|
||||
Expr::CellRef(re) => self.get_cell(re.to_owned())?.1,
|
||||
Expr::Infix { op, lhs, rhs } => {
|
||||
let lval = self.evaluate_expr(lhs)?;
|
||||
let rval = self.evaluate_expr(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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn eval_sub(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));
|
||||
}
|
||||
|
||||
Err("Evaluation error: expected string or numeric types for SUB function.".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
fn eval_mul(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));
|
||||
}
|
||||
|
||||
Err("Evaluation error: expected string or numeric types for MUL function.".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
fn eval_div(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));
|
||||
}
|
||||
|
||||
Err("Evaluation error: expected string or numeric types for DIV function.".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
69
backend/src/main.rs
Normal file
69
backend/src/main.rs
Normal file
@@ -0,0 +1,69 @@
|
||||
mod cell;
|
||||
mod evaluator;
|
||||
mod parser;
|
||||
mod tokenizer;
|
||||
|
||||
use std::io;
|
||||
|
||||
use websocket::server::WsServer;
|
||||
|
||||
use crate::{cell::CellRef, evaluator::Evaluator};
|
||||
|
||||
fn main() {
|
||||
// let mut input = String::new();
|
||||
// io::stdin().read_line(&mut input).expect("Expected input.");
|
||||
|
||||
// let mut ast = parser::parse(&input).unwrap();
|
||||
// println!("{}", ast.pretty());
|
||||
let mut evaluator = Evaluator::new();
|
||||
// // println!("{}", evaluator.evaluate(input).unwrap());
|
||||
// let a1 = CellRef { row: 1, col: 2 };
|
||||
// evaluator.set_cell(a1, input).unwrap();
|
||||
// println!("{:?}", evaluator.get_cell(a1).unwrap());
|
||||
|
||||
println!("CMDS : set <cell_ref>, get <cell_ref>");
|
||||
loop {
|
||||
let mut input = String::new();
|
||||
io::stdin().read_line(&mut input).expect("Expected input.");
|
||||
|
||||
let cmds = ["set", "get"];
|
||||
let cmd = &input[0..3];
|
||||
if !cmds.iter().any(|c| c == &cmd) {
|
||||
println!("{} is an invalid command!", cmd);
|
||||
println!("CMDS : set <cell_ref>, get <cell_ref>");
|
||||
continue;
|
||||
}
|
||||
|
||||
let rest = &input[4..];
|
||||
let mut parts = rest.splitn(2, char::is_whitespace);
|
||||
|
||||
let raw_ref = parts.next().unwrap_or("").trim(); // cell reference
|
||||
let raw_str = parts.next().unwrap_or("").trim(); // rest of the string (value)
|
||||
// println!("{} {}", raw_ref, raw_str);
|
||||
|
||||
if let Ok(cell_ref) = CellRef::new(raw_ref.to_owned()) {
|
||||
match cmd {
|
||||
"set" => match evaluator.set_cell(cell_ref, raw_str.to_owned()) {
|
||||
Ok(_) => println!("Successfully set cell {} to {}.", raw_ref, raw_str),
|
||||
Err(e) => println!("{}", e),
|
||||
},
|
||||
"get" => match evaluator.get_cell(cell_ref) {
|
||||
Ok(res) => println!("{:?}", res),
|
||||
Err(e) => println!("{}", e),
|
||||
},
|
||||
_ => {
|
||||
panic!("Impossible.");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
println!("{} is an invalid cell reference!", raw_ref);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let rt = Runtime::new().unwrap();
|
||||
let handle = rt.handle().clone();
|
||||
let addr = "127.0.0.1:7050";
|
||||
|
||||
let socket = WsServer::bind(addr, handle);
|
||||
}
|
||||
291
backend/src/parser.rs
Normal file
291
backend/src/parser.rs
Normal file
@@ -0,0 +1,291 @@
|
||||
use crate::{cell::CellRef, tokenizer::*};
|
||||
use std::{collections::HashSet, fmt};
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum PrefixOp {
|
||||
POS,
|
||||
NEG,
|
||||
NOT,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum PostfixOp {
|
||||
PERCENT,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum InfixOp {
|
||||
MUL,
|
||||
DIV,
|
||||
ADD,
|
||||
SUB,
|
||||
AND,
|
||||
OR,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub enum Expr {
|
||||
Literal(Literal),
|
||||
CellRef(CellRef),
|
||||
Function {
|
||||
name: String,
|
||||
args: Vec<Expr>,
|
||||
},
|
||||
Group(Box<Expr>),
|
||||
Prefix {
|
||||
op: PrefixOp,
|
||||
expr: Box<Expr>,
|
||||
},
|
||||
Postfix {
|
||||
op: PostfixOp,
|
||||
expr: Box<Expr>,
|
||||
},
|
||||
Infix {
|
||||
op: InfixOp,
|
||||
lhs: Box<Expr>,
|
||||
rhs: Box<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 {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Expr::Literal(lit) => write!(f, "{lit:?}"),
|
||||
Expr::Group(expr) => write!(f, "({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::Function { name, args } => write!(f, "{name}({args:?})"),
|
||||
Expr::CellRef(it) => write!(f, "{it:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl Expr {
|
||||
pub fn pretty(&self) -> String {
|
||||
// entry point for users — root printed without └──
|
||||
let mut result = String::new();
|
||||
result.push_str(&format!("{}\n", self.node_name()));
|
||||
result.push_str(&self.pretty_subtree("", true));
|
||||
result
|
||||
}
|
||||
|
||||
fn pretty_subtree(&self, prefix: &str, is_tail: bool) -> String {
|
||||
let mut result = String::new();
|
||||
let new_prefix = if is_tail {
|
||||
format!("{} ", prefix)
|
||||
} else {
|
||||
format!("{}│ ", prefix)
|
||||
};
|
||||
|
||||
match self {
|
||||
Expr::Literal(_) => {}
|
||||
Expr::CellRef(_) => {}
|
||||
Expr::Group(expr) => {
|
||||
result.push_str(&expr.pretty_branch(&new_prefix, true));
|
||||
}
|
||||
Expr::Prefix { expr, .. } => {
|
||||
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, .. } => {
|
||||
result.push_str(&lhs.pretty_branch(&new_prefix, false));
|
||||
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
|
||||
}
|
||||
|
||||
fn pretty_branch(&self, prefix: &str, is_tail: bool) -> String {
|
||||
let mut result = String::new();
|
||||
let branch = if is_tail { "└── " } else { "├── " };
|
||||
result.push_str(&format!("{}{}{}\n", prefix, branch, self.node_name()));
|
||||
result.push_str(&self.pretty_subtree(prefix, is_tail));
|
||||
result
|
||||
}
|
||||
|
||||
fn node_name(&self) -> String {
|
||||
match self {
|
||||
Expr::Literal(lit) => format!("Literal({:?})", lit),
|
||||
Expr::Group(_) => "Group".to_string(),
|
||||
Expr::Prefix { op, .. } => format!("Prefix({:?})", op),
|
||||
Expr::Postfix { op, .. } => format!("Postfix({:?})", op),
|
||||
Expr::Infix { op, .. } => format!("Infix({:?})", op),
|
||||
Expr::Function { name, .. } => format!("Function({:?})", name),
|
||||
Expr::CellRef(it) => format!("{:?}", it),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse(input: &str) -> Result<(Expr, HashSet<CellRef>), String> {
|
||||
let mut tokenizer = Tokenizer::new(input)?;
|
||||
// println!("{:?}", tokenizer.tokens);
|
||||
let mut deps = HashSet::new();
|
||||
Ok((_parse(&mut tokenizer, 0, &mut deps)?, deps))
|
||||
}
|
||||
|
||||
pub fn _parse(
|
||||
input: &mut Tokenizer,
|
||||
min_prec: u8,
|
||||
deps: &mut HashSet<CellRef>,
|
||||
) -> Result<Expr, String> {
|
||||
let mut lhs = match input.next() {
|
||||
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, deps)?;
|
||||
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, deps)?;
|
||||
|
||||
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, deps)?;
|
||||
args.push(arg);
|
||||
}
|
||||
|
||||
Expr::Function {
|
||||
name: id,
|
||||
args: args,
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let cell_ref = CellRef::new(id)?;
|
||||
deps.insert(cell_ref);
|
||||
Expr::CellRef(cell_ref)
|
||||
}
|
||||
},
|
||||
|
||||
it => return Err(format!("Parse error: did not expect token {:?}.", it)),
|
||||
};
|
||||
|
||||
// In the reference article this is a loop with match
|
||||
// statement that breaks on Eof and closing paren but this is simpler and works as expected
|
||||
while let Token::Operator(op) = input.peek() {
|
||||
if "+-*/&|".contains(op) {
|
||||
let infix_op = match op {
|
||||
'+' => InfixOp::ADD,
|
||||
'-' => InfixOp::SUB,
|
||||
'*' => InfixOp::MUL,
|
||||
'/' => InfixOp::DIV,
|
||||
'&' => InfixOp::AND,
|
||||
'|' => InfixOp::OR,
|
||||
it => {
|
||||
return Err(format!("Parse error: do not know infix operator {:?}.", it));
|
||||
}
|
||||
};
|
||||
|
||||
let (l_prec, r_prec) = infix_op.prec();
|
||||
if l_prec < min_prec {
|
||||
break;
|
||||
}
|
||||
|
||||
input.next();
|
||||
let rhs = _parse(input, r_prec, deps)?;
|
||||
lhs = Expr::Infix {
|
||||
op: infix_op,
|
||||
lhs: Box::new(lhs),
|
||||
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),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Ok(lhs)
|
||||
}
|
||||
136
backend/src/tokenizer.rs
Normal file
136
backend/src/tokenizer.rs
Normal file
@@ -0,0 +1,136 @@
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Literal {
|
||||
Integer(i64),
|
||||
Double(f64),
|
||||
Boolean(bool),
|
||||
String(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Token {
|
||||
Identifier(String), // Could be a function
|
||||
Literal(Literal),
|
||||
Operator(char),
|
||||
Paren(char),
|
||||
Comma,
|
||||
Eof,
|
||||
}
|
||||
|
||||
pub struct Tokenizer {
|
||||
pub tokens: Vec<Token>,
|
||||
}
|
||||
|
||||
impl Tokenizer {
|
||||
pub fn new(input: &str) -> Result<Tokenizer, String> {
|
||||
let mut tokens = Vec::new();
|
||||
let mut chars = input.chars().peekable();
|
||||
|
||||
while let Some(&c) = chars.peek() {
|
||||
if c.is_whitespace() {
|
||||
chars.next();
|
||||
} else if c.is_ascii_alphabetic() {
|
||||
// parse identifier
|
||||
let mut ident = String::new();
|
||||
while let Some(&ch) = chars.peek() {
|
||||
if ch.is_ascii_alphanumeric() || ch == '_' {
|
||||
ident.push(ch);
|
||||
chars.next();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
tokens.push(Token::Identifier(ident));
|
||||
} else if c.is_ascii_digit() {
|
||||
// parse number
|
||||
let mut number = String::new();
|
||||
let mut is_decimal = false;
|
||||
|
||||
while let Some(&ch) = chars.peek() {
|
||||
if ch.is_ascii_digit() {
|
||||
number.push(ch);
|
||||
chars.next();
|
||||
} else if ch == '.' && !is_decimal {
|
||||
is_decimal = true;
|
||||
number.push(ch);
|
||||
chars.next();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if is_decimal {
|
||||
tokens.push(Token::Literal(Literal::Double(number.parse().unwrap())))
|
||||
} else {
|
||||
tokens.push(Token::Literal(Literal::Integer(number.parse().unwrap())))
|
||||
};
|
||||
} else if c == '"' || c == '\'' {
|
||||
// parse string literal
|
||||
let mut string = String::new();
|
||||
|
||||
let quote = c;
|
||||
let mut escapes = 0;
|
||||
chars.next(); // consume opening quote
|
||||
|
||||
while let Some(&ch) = chars.peek() {
|
||||
chars.next();
|
||||
if ch == quote && escapes % 2 == 0 {
|
||||
break;
|
||||
}
|
||||
|
||||
if ch == '\\' {
|
||||
escapes += 1;
|
||||
} else {
|
||||
escapes = 0;
|
||||
}
|
||||
|
||||
string.push(ch);
|
||||
}
|
||||
tokens.push(Token::Literal(Literal::String(string)));
|
||||
} else if "+-*/^!%&|".contains(c) {
|
||||
tokens.push(Token::Operator(c));
|
||||
chars.next();
|
||||
} else if "()".contains(c) {
|
||||
tokens.push(Token::Paren(c));
|
||||
chars.next();
|
||||
} else if c == ',' {
|
||||
tokens.push(Token::Comma);
|
||||
chars.next();
|
||||
} else {
|
||||
return Err(format!("Encountered unknown token char: {c}"));
|
||||
}
|
||||
}
|
||||
|
||||
tokens.reverse(); // Since we want FIFO and next + peek are implemented as LILO
|
||||
Ok(Tokenizer { tokens })
|
||||
}
|
||||
|
||||
pub fn next(&mut self) -> Token {
|
||||
self.tokens.pop().unwrap_or(Token::Eof)
|
||||
}
|
||||
pub fn peek(&mut self) -> Token {
|
||||
self.tokens.last().cloned().unwrap_or(Token::Eof)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_tokenizer() {
|
||||
let raw = "hello hello 1.23 this 5 (1+2)";
|
||||
let expected: Vec<Token> = vec![
|
||||
Token::Identifier("hello".to_string()),
|
||||
Token::Identifier("hello".to_string()),
|
||||
Token::Literal(Literal::Double(1.23)),
|
||||
Token::Identifier("this".to_string()),
|
||||
Token::Literal(Literal::Integer(5)),
|
||||
Token::Paren('('),
|
||||
Token::Literal(Literal::Integer(1)),
|
||||
Token::Operator('+'),
|
||||
Token::Literal(Literal::Integer(2)),
|
||||
Token::Paren(')'),
|
||||
];
|
||||
let t = Tokenizer::new(&raw).unwrap();
|
||||
assert_eq!(t.tokens, expected);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user