diff --git a/backend/src/cell.rs b/backend/src/cell.rs index d10ccf9..7b8aa32 100644 --- a/backend/src/cell.rs +++ b/backend/src/cell.rs @@ -2,7 +2,10 @@ use std::collections::HashSet; use serde::{Deserialize, Serialize}; -use crate::evaluator::*; +use crate::{ + common::{LeadErr, LeadErrCode}, + evaluator::*, +}; #[derive(Serialize, Deserialize, Debug, Clone, Copy, Hash, PartialEq, Eq)] pub struct CellRef { @@ -93,7 +96,7 @@ impl Cell { impl CellRef { // Zero indexed - pub fn new(s: String) -> Result { + pub fn new(s: String) -> Result { let s = s.trim(); let mut col: usize = 0; let mut i = 0; @@ -111,20 +114,26 @@ impl CellRef { } if col <= 0 { - return Err(format!( - "Parse error: missing column letters in cell ref: {s}" - )); + return Err(LeadErr { + title: "Parse error.".into(), + desc: format!("Missing column letters in cell ref: {s}."), + code: LeadErrCode::Syntax, + }); } let row_part = &s[i..]; if row_part.is_empty() { - return Err(format!( - "Parse error: missing column letters in cell ref: {s}" - )); + return Err(LeadErr { + title: "Parse error.".into(), + desc: format!("Missing column letters in cell ref: {s}."), + code: LeadErrCode::Syntax, + }); } 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}" - )); + return Err(LeadErr { + title: "Parse error.".into(), + desc: format!("Row part must be numeric in cell ref: {s}."), + code: LeadErrCode::Syntax, + }); } if let Ok(row) = row_part.parse::() { @@ -133,7 +142,11 @@ impl CellRef { col: col - 1, }) } else { - Err(format!("Parse error: invalid row number.")) + Err(LeadErr { + title: "Parse error.".into(), + desc: format!("Invalid row number."), + code: LeadErrCode::Syntax, + }) } } } diff --git a/backend/src/common.rs b/backend/src/common.rs index e69de29..fccf258 100644 --- a/backend/src/common.rs +++ b/backend/src/common.rs @@ -0,0 +1,30 @@ +use serde::{Deserialize, Serialize}; + +// Example usage: +// +// title: "Evaluation error." +// desc: "Function ADD requires numeric type arguments." +// code: TypeErr +#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] +pub struct LeadErr { + pub title: String, + pub desc: String, + pub code: LeadErrCode, +} + +#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] +pub enum LeadErrCode { + DivZero, + TypeErr, + Syntax, + Server, + Unsupported, +} + +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(tag = "type", content = "value")] +pub enum Literal { + Number(f64), + Boolean(bool), + String(String), +} diff --git a/backend/src/evaluator.rs b/backend/src/evaluator.rs index e72555b..2df45ca 100644 --- a/backend/src/evaluator.rs +++ b/backend/src/evaluator.rs @@ -1,9 +1,11 @@ use serde::{Deserialize, Serialize}; use crate::cell::CellRef; +use crate::common::LeadErr; +use crate::common::LeadErrCode; +use crate::common::Literal; use crate::grid::Grid; use crate::parser::*; -use crate::tokenizer::Literal; use std::collections::HashSet; use std::fmt; @@ -13,6 +15,7 @@ pub enum Eval { Literal(Literal), CellRef { eval: Box, reference: CellRef }, Range(Vec), + Err(LeadErr), Unset, } @@ -23,19 +26,22 @@ impl fmt::Display for Eval { Eval::Range(it) => write!(f, "Range({it:?})"), Eval::CellRef { eval, reference } => write!(f, "EvalRef({eval:?}, {reference:?})"), Eval::Unset => write!(f, "Unset"), + Eval::Err(it) => write!(f, "{it:?}"), } } } -pub fn evaluate(str: String, grid: Option<&Grid>) -> Result<(Eval, HashSet), String> { - let (expr, _) = parse(&str)?; +pub fn evaluate(str: String, grid: Option<&Grid>) -> (Eval, HashSet) { + match parse(&str) { + Ok((expr, _)) => { + let mut precs = HashSet::new(); - let mut precs = HashSet::new(); - - // Make evaulator adds precs for ranges - match evaluate_expr(&expr, &mut precs, grid) { - Ok(it) => Ok((it, precs)), - Err(it) => Err(it), + match evaluate_expr(&expr, &mut precs, grid) { + Ok(it) => (it, precs), + Err(it) => (Eval::Err(it), precs), + } + } + Err(e) => (Eval::Err(e), HashSet::new()), } } @@ -43,7 +49,7 @@ fn evaluate_expr( expr: &Expr, precs: &mut HashSet, grid: Option<&Grid>, -) -> Result { +) -> Result { let res = match expr { Expr::Literal(lit) => Eval::Literal(lit.clone()), Expr::CellRef(re) => { @@ -59,7 +65,11 @@ fn evaluate_expr( }, } } else { - return Err("Evaluation error: Found cell reference but no grid.".into()); + return Err(LeadErr { + title: "Evaluation error.".into(), + desc: "Found cell reference with no grid.".into(), + code: LeadErrCode::Server, + }); } } Expr::Infix { op, lhs, rhs } => { @@ -81,7 +91,13 @@ fn evaluate_expr( InfixOp::MUL => eval_mul(&lval, &rval)?, InfixOp::DIV => eval_div(&lval, &rval)?, InfixOp::RANGE => eval_range(&lval, &rval, precs, grid)?, - _ => return Err(format!("Evaluation error: Unsupported operator {:?}", op)), + _ => { + return Err(LeadErr { + title: "Evaluation error.".into(), + desc: format!("Unsupported operator {:?}", op), + code: LeadErrCode::Unsupported, + }); + } } } Expr::Prefix { op, expr } => { @@ -101,9 +117,21 @@ fn evaluate_expr( Expr::Group(g) => evaluate_expr(g, precs, grid)?, Expr::Function { name, args } => match name.as_str() { "AVG" => eval_avg(args, precs, grid)?, - it => return Err(format!("Evaluation error: Unsupported function {}.", it)), + it => { + return Err(LeadErr { + title: "Evaluation error.".into(), + desc: format!("Unsupported function {:?}", it), + code: LeadErrCode::Unsupported, + }); + } }, - it => return Err(format!("Evaluation error: Unsupported expression {:?}", it)), + it => { + return Err(LeadErr { + title: "Evaluation error.".into(), + desc: format!("Unsupported expression {:?}", it), + code: LeadErrCode::Unsupported, + }); + } }; Ok(res) @@ -114,7 +142,7 @@ fn eval_range( rval: &Eval, precs: &mut HashSet, grid: Option<&Grid>, -) -> Result { +) -> Result { match (lval, rval) { ( Eval::CellRef { @@ -139,7 +167,11 @@ fn eval_range( let reference = CellRef { row, col }; let Some(g) = grid else { - return Err("Evaluation error: Found cell range but no grid.".into()); + return Err(LeadErr { + title: "Evaluation error.".into(), + desc: "Found cell range but no grid.".into(), + code: LeadErrCode::Server, + }); }; cells.push(Eval::CellRef { @@ -157,7 +189,11 @@ fn eval_range( Ok(Eval::Range(cells)) } - _ => Err("Evaluation error: expected cellref types for RANGE function.".to_string()), + _ => Err(LeadErr { + title: "Evaluation error.".into(), + desc: "Expected cell reference types for RANGE function.".into(), + code: LeadErrCode::Unsupported, + }), } } @@ -165,7 +201,7 @@ fn eval_avg( args: &Vec, precs: &mut HashSet, grid: Option<&Grid>, -) -> Result { +) -> Result { let mut res = 0.0; let mut count = 0; @@ -178,7 +214,11 @@ fn eval_avg( Eval::Range(range) => { for cell in range { let Eval::CellRef { eval, reference: _ } = cell else { - panic!("Found non cellref in evaluation time RANGE!."); + return Err(LeadErr { + title: "Evaluation error.".into(), + desc: "Found non-cellref in RANGE during AVG evaluation.".into(), + code: LeadErrCode::Server, + }); }; if let Eval::Literal(Literal::Number(num)) = *eval { @@ -187,27 +227,36 @@ fn eval_avg( } else if matches!(*eval, Eval::Unset) { continue; } else { - return Err("Evaluation error: expected numeric types for AVG function." - .to_string()); + return Err(LeadErr { + title: "Evaluation error.".into(), + desc: "Expected numeric types for AVG function.".into(), + code: LeadErrCode::Unsupported, + }); } } } _ => { - return Err( - "Evaluation error: expected numeric types for AVG function.".to_string() - ); + return Err(LeadErr { + title: "Evaluation error.".into(), + desc: "Expected numeric types for AVG function.".into(), + code: LeadErrCode::Unsupported, + }); } } } if count == 0 { - Err("Evaluation error: attempted to divide by zero.".to_string()) + Err(LeadErr { + title: "Evaluation error.".into(), + desc: "Attempted to divide by zero.".into(), + code: LeadErrCode::DivZero, + }) } else { Ok(Eval::Literal(Literal::Number(res / count as f64))) } } -fn eval_add(lval: &Eval, rval: &Eval) -> Result { +fn eval_add(lval: &Eval, rval: &Eval) -> Result { match (lval, rval) { (Eval::Literal(a), Eval::Literal(b)) => { if let Some(res) = eval_numeric_infix(a, b, |x, y| x + y) { @@ -221,46 +270,72 @@ fn eval_add(lval: &Eval, rval: &Eval) -> Result { return Ok(Eval::Literal(Literal::String(res))); } - Err("Evaluation error: expected string or numeric types for ADD function.".to_string()) - } - _ => { - Err("Evaluation error: expected string or numeric types for ADD function.".to_string()) + Err(LeadErr { + title: "Evaluation error.".into(), + desc: "Expected string or numeric types for ADD function.".into(), + code: LeadErrCode::Unsupported, + }) } + _ => Err(LeadErr { + title: "Evaluation error.".into(), + desc: "Expected string or numeric types for ADD function.".into(), + code: LeadErrCode::Unsupported, + }), } } -fn eval_sub(lval: &Eval, rval: &Eval) -> Result { +fn eval_sub(lval: &Eval, rval: &Eval) -> Result { match (lval, rval) { (Eval::Literal(a), Eval::Literal(b)) => { if let Some(res) = eval_numeric_infix(a, b, |x, y| x - y) { return Ok(Eval::Literal(res)); } - Err("Evaluation error: expected numeric types for SUB function.".to_string()) + Err(LeadErr { + title: "Evaluation error.".into(), + desc: "Expected numeric types for SUB function.".into(), + code: LeadErrCode::Unsupported, + }) } - _ => Err("Evaluation error: expected numeric types for SUB function.".to_string()), + _ => Err(LeadErr { + title: "Evaluation error.".into(), + desc: "Expected numeric types for SUB function.".into(), + code: LeadErrCode::Unsupported, + }), } } -fn eval_mul(lval: &Eval, rval: &Eval) -> Result { + +fn eval_mul(lval: &Eval, rval: &Eval) -> Result { match (lval, rval) { (Eval::Literal(a), Eval::Literal(b)) => { if let Some(res) = eval_numeric_infix(a, b, |x, y| x * y) { return Ok(Eval::Literal(res)); } - Err("Evaluation error: expected numeric types for MUL function.".to_string()) + Err(LeadErr { + title: "Evaluation error.".into(), + desc: "Expected numeric types for MUL function.".into(), + code: LeadErrCode::Unsupported, + }) } - _ => Err("Evaluation error: expected numeric types for MUL function.".to_string()), + _ => Err(LeadErr { + title: "Evaluation error.".into(), + desc: "Expected numeric types for MUL function.".into(), + code: LeadErrCode::Unsupported, + }), } } -fn eval_div(lval: &Eval, rval: &Eval) -> Result { + +fn eval_div(lval: &Eval, rval: &Eval) -> Result { match (lval, rval) { (Eval::Literal(a), Eval::Literal(b)) => { if let (Literal::Number(_), Literal::Number(y)) = (a, b) { if *y == 0f64 { - return Err( - "Evaluation error: integers attempted to divide by zero.".to_string() - ); + return Err(LeadErr { + title: "Evaluation error.".into(), + desc: "Attempted to divide by zero.".into(), + code: LeadErrCode::DivZero, + }); } } @@ -268,9 +343,50 @@ fn eval_div(lval: &Eval, rval: &Eval) -> Result { return Ok(Eval::Literal(res)); } - Err("Evaluation error: expected numeric types for DIV function.".to_string()) + Err(LeadErr { + title: "Evaluation error.".into(), + desc: "Expected numeric types for DIV function.".into(), + code: LeadErrCode::Unsupported, + }) } - _ => Err("Evaluation error: expected numeric types for DIV function.".to_string()), + _ => Err(LeadErr { + title: "Evaluation error.".into(), + desc: "Expected numeric types for DIV function.".into(), + code: LeadErrCode::Unsupported, + }), + } +} + +fn eval_pos(val: &Eval) -> Result { + match val { + Eval::Literal(Literal::Number(it)) => Ok(Eval::Literal(Literal::Number(*it))), + _ => Err(LeadErr { + title: "Evaluation error.".into(), + desc: "Expected numeric type for POS function.".into(), + code: LeadErrCode::Unsupported, + }), + } +} + +fn eval_neg(val: &Eval) -> Result { + match val { + Eval::Literal(Literal::Number(it)) => Ok(Eval::Literal(Literal::Number(-it))), + _ => Err(LeadErr { + title: "Evaluation error.".into(), + desc: "Expected numeric type for NEG function.".into(), + code: LeadErrCode::Unsupported, + }), + } +} + +fn eval_not(val: &Eval) -> Result { + match val { + Eval::Literal(Literal::Boolean(it)) => Ok(Eval::Literal(Literal::Boolean(!it))), + _ => Err(LeadErr { + title: "Evaluation error.".into(), + desc: "Expected boolean type for NOT function.".into(), + code: LeadErrCode::Unsupported, + }), } } @@ -280,23 +396,3 @@ fn eval_numeric_infix(lhs: &Literal, rhs: &Literal, op: fn(f64, f64) -> f64) -> _ => None, } } - -fn eval_pos(val: &Eval) -> Result { - match val { - Eval::Literal(Literal::Number(it)) => Ok(Eval::Literal(Literal::Number(*it))), - _ => Err("Evaluation error: expected numeric type for POS function.".to_string()), - } -} - -fn eval_neg(val: &Eval) -> Result { - match val { - Eval::Literal(Literal::Number(it)) => Ok(Eval::Literal(Literal::Number(-it))), - _ => Err("Evaluation error: expected numeric type for NEG function.".to_string()), - } -} -fn eval_not(val: &Eval) -> Result { - match val { - Eval::Literal(Literal::Boolean(it)) => Ok(Eval::Literal(Literal::Boolean(!it))), - _ => Err("Evaluation error: expected boolean type for NEG function.".to_string()), - } -} diff --git a/backend/src/grid.rs b/backend/src/grid.rs index 6f0dff4..b5bef38 100644 --- a/backend/src/grid.rs +++ b/backend/src/grid.rs @@ -4,8 +4,8 @@ use log::info; use crate::{ cell::{Cell, CellRef}, + common::Literal, evaluator::{Eval, evaluate}, - tokenizer::Literal, }; pub struct Grid { @@ -36,7 +36,7 @@ impl Grid { eval = Eval::Literal(Literal::String(raw_val.to_owned())); } else { // Evaluate raw expr and get precedents - let (res_eval, res_precs) = evaluate(raw_val[1..].to_owned(), Some(&self))?; + let (res_eval, res_precs) = evaluate(raw_val[1..].to_owned(), Some(&self)); eval = res_eval; precs = res_precs; } @@ -88,7 +88,7 @@ impl Grid { search_set.extend(cell_data.deps().iter()); temp.insert(from); - perm.insert(from); + perm.insert(from); // Make this inside the inner topo_visit let mut searched = 1; @@ -249,7 +249,7 @@ impl Grid { }; // Now we dropped the borrow of self.cells before this point - let (e, _) = evaluate(raw, Some(self))?; + let (e, _) = evaluate(raw, Some(self)); if let Some(cell) = self.cells.get_mut(&cell_ref) { cell.set_eval(e); diff --git a/backend/src/main.rs b/backend/src/main.rs index 3bf4e67..3f7019f 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,4 +1,5 @@ mod cell; +mod common; mod evaluator; mod grid; mod messages; diff --git a/backend/src/parser.rs b/backend/src/parser.rs index 5bbf8c2..ae6cbd6 100644 --- a/backend/src/parser.rs +++ b/backend/src/parser.rs @@ -1,6 +1,10 @@ use log::info; -use crate::{cell::CellRef, tokenizer::*}; +use crate::{ + cell::CellRef, + common::{LeadErr, LeadErrCode, Literal}, + tokenizer::*, +}; use std::{collections::HashSet, fmt}; #[derive(Debug, PartialEq, Clone)] @@ -159,7 +163,7 @@ impl Expr { } } -pub fn parse(input: &str) -> Result<(Expr, HashSet), String> { +pub fn parse(input: &str) -> Result<(Expr, HashSet), LeadErr> { let mut tokenizer = Tokenizer::new(input)?; let mut precs = HashSet::new(); let expr = _parse(&mut tokenizer, 0, &mut precs)?; @@ -171,13 +175,17 @@ pub fn _parse( input: &mut Tokenizer, min_prec: u8, precedents: &mut HashSet, -) -> Result { +) -> Result { let mut lhs = match input.next() { Token::Literal(it) => Expr::Literal(it), Token::OpenParen => { let lhs = _parse(input, 0, precedents)?; if input.next() != Token::CloseParen { - return Err(format!("Parse error: expected closing paren.")); + return Err(LeadErr { + title: "Parse error.".into(), + desc: "Expected closing paren.".into(), + code: LeadErrCode::Syntax, + }); } Expr::Group(Box::new(lhs)) } @@ -186,7 +194,13 @@ pub fn _parse( '+' => PrefixOp::POS, '-' => PrefixOp::NEG, '!' => PrefixOp::NOT, - it => return Err(format!("Parse error: unknown prefix operator {:?}.", it)), + it => { + return Err(LeadErr { + title: "Parse error.".into(), + desc: format!("Unknown prefix operator {:?}.", it), + code: LeadErrCode::Syntax, + }); + } }; let rhs = _parse(input, prefix_op.prec().1, precedents)?; @@ -208,10 +222,13 @@ pub fn _parse( input.next(); break; } else if nxt != Token::Comma && args.len() != 0 { - return Err(format!( - "Parse error: expected comma while parsing argument of function {:?}.", - id - )); + return Err(LeadErr { + title: "Parse error.".into(), + desc: format!( + "Expected comma while parsing argument of function {id:?}" + ), + code: LeadErrCode::Syntax, + }); } if args.len() != 0 { @@ -234,7 +251,13 @@ pub fn _parse( } }, - it => return Err(format!("Parse error: did not expect token {:?}.", it)), + it => { + return Err(LeadErr { + title: "Parse error.".into(), + desc: format!("Unexpected token {:?}.", it), + code: LeadErrCode::Syntax, + }); + } }; // In the reference article this is a loop with match @@ -250,7 +273,11 @@ pub fn _parse( '|' => InfixOp::OR, ':' => InfixOp::RANGE, it => { - return Err(format!("Parse error: do not know infix operator {:?}.", it)); + return Err(LeadErr { + title: "Parse error.".into(), + desc: format!("Unknown infix operator {:?}.", it), + code: LeadErrCode::Syntax, + }); } }; @@ -270,10 +297,11 @@ pub fn _parse( let postfix_op = match op { '%' => PostfixOp::PERCENT, it => { - return Err(format!( - "Parse error: do not know postfix operator {:?}.", - it - )); + return Err(LeadErr { + title: "Parse error.".into(), + desc: format!("Unknown postfix operator {:?}.", it), + code: LeadErrCode::Syntax, + }); } }; diff --git a/backend/src/tokenizer.rs b/backend/src/tokenizer.rs index 3695053..f051380 100644 --- a/backend/src/tokenizer.rs +++ b/backend/src/tokenizer.rs @@ -1,18 +1,11 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] -#[serde(tag = "type", content = "value")] -pub enum Literal { - Number(f64), - Boolean(bool), - String(String), -} +use crate::common::{LeadErr, LeadErrCode, Literal}; #[derive(Debug, Clone, PartialEq)] pub enum Token { Identifier(String), // Could be a function Literal(Literal), Operator(char), + Err(LeadErr), OpenParen, CloseParen, Comma, @@ -26,7 +19,7 @@ pub struct Tokenizer { } impl Tokenizer { - pub fn new(input: &str) -> Result { + pub fn new(input: &str) -> Result { let mut tokens = Vec::new(); let mut chars = input.chars().peekable(); @@ -113,7 +106,11 @@ impl Tokenizer { tokens.push(Token::Comma); chars.next(); } else { - return Err(format!("Encountered unknown token char: {c}")); + return Err(LeadErr { + title: "Tokenizer error.".into(), + desc: format!("Encountered unknown token char: {c}"), + code: LeadErrCode::Syntax, + }); } } diff --git a/frontend/src/lib/components/grid/cell.svelte b/frontend/src/lib/components/grid/cell.svelte index 36b88a1..1f01234 100644 --- a/frontend/src/lib/components/grid/cell.svelte +++ b/frontend/src/lib/components/grid/cell.svelte @@ -1,13 +1,13 @@