diff --git a/backend/src/cell.rs b/backend/src/cell.rs index 8f2c9d8..d10ccf9 100644 --- a/backend/src/cell.rs +++ b/backend/src/cell.rs @@ -4,59 +4,93 @@ use serde::{Deserialize, Serialize}; use crate::evaluator::*; -#[derive(Clone)] -pub struct Cell { - eval: Eval, - raw: String, - i_dep: HashSet, - they_dep: HashSet, -} - -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(Serialize, Deserialize, Debug, Clone, Copy, Hash, PartialEq, Eq)] pub struct CellRef { pub row: usize, pub col: usize, } +#[derive(Clone, Debug)] +pub struct Cell { + reference: CellRef, + eval: Eval, + raw: String, + precedents: HashSet, // Cells that this cell reads + dependents: HashSet, // Cells that read this cell +} + +impl Cell { + pub fn new(reference: CellRef, eval: Eval, raw: String) -> Self { + Self { + reference, + eval, + raw, + precedents: HashSet::new(), + dependents: HashSet::new(), + } + } + + pub fn new_all( + reference: CellRef, + eval: Eval, + raw: String, + precedents: HashSet, + dependents: HashSet, + ) -> Self { + Self { + reference, + eval, + raw, + precedents, + dependents, + } + } + + pub fn raw(&self) -> String { + self.raw.to_owned() + } + pub fn eval(&self) -> Eval { + self.eval.to_owned() + } + pub fn reference(&self) -> CellRef { + self.reference.to_owned() + } + + pub fn set_raw(&mut self, raw: String) { + self.raw = raw; + } + pub fn set_eval(&mut self, eval: Eval) { + self.eval = eval; + } + pub fn set_ref(&mut self, reference: CellRef) { + self.reference = reference; + } + + pub fn add_dep(&mut self, it: CellRef) { + self.dependents.insert(it); + } + + pub fn remove_dep(&mut self, it: &CellRef) { + self.dependents.remove(&it); + } + + pub fn add_prec(&mut self, it: CellRef) { + self.precedents.insert(it); + } + + pub fn set_precs(&mut self, it: HashSet) { + self.precedents = it; + } + + pub fn deps(&self) -> HashSet { + self.dependents.to_owned() + } + + pub fn precs(&self) -> HashSet { + self.precedents.to_owned() + } +} + impl CellRef { // Zero indexed pub fn new(s: String) -> Result { diff --git a/backend/src/evaluator.rs b/backend/src/evaluator.rs index 4afe1e3..e69af5a 100644 --- a/backend/src/evaluator.rs +++ b/backend/src/evaluator.rs @@ -8,49 +8,72 @@ use std::fmt; #[derive(Debug, PartialEq, Clone)] pub enum Eval { Literal(Literal), + CellRef { eval: Box, reference: CellRef }, + Range(Vec), + Unset, } impl fmt::Display for Eval { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Eval::Literal(lit) => write!(f, "{lit:?}"), + Eval::Range(it) => write!(f, "Range({it:?})"), + Eval::CellRef { eval, reference } => write!(f, "EvalRef({eval:?}, {reference:?})"), + Eval::Unset => write!(f, "Unset"), } } } pub fn evaluate(str: String, grid: Option<&Grid>) -> Result<(Eval, HashSet), String> { - let (expr, deps) = parse(&str)?; + let (expr, _) = parse(&str)?; - match evaluate_expr(&expr, grid) { - Ok(it) => Ok((it, deps)), + 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), } } -fn evaluate_expr(expr: &Expr, grid: Option<&Grid>) -> Result { +fn evaluate_expr( + expr: &Expr, + precs: &mut HashSet, + grid: Option<&Grid>, +) -> Result { let res = match expr { Expr::Literal(lit) => Eval::Literal(lit.clone()), Expr::CellRef(re) => { if let Some(g) = grid { - g.get_cell(re.to_owned())? + Eval::CellRef { + eval: Box::new( + g.get_cell(re.to_owned()) + .map_or(Eval::Unset, |cell| cell.eval()), + ), + reference: { + precs.insert(*re); + *re + }, + } } else { return Err("Evaluation error: Found cell reference but no grid.".into()); } } Expr::Infix { op, lhs, rhs } => { - let lval = evaluate_expr(lhs, grid)?; - let rval = evaluate_expr(rhs, grid)?; + let lval = evaluate_expr(lhs, precs, grid)?; + let rval = evaluate_expr(rhs, precs, grid)?; 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)?, + InfixOp::RANGE => eval_range(&lval, &rval, precs, grid)?, _ => return Err(format!("Evaluation error: Unsupported operator {:?}", op)), } } Expr::Prefix { op, expr } => { - let val = evaluate_expr(expr, grid)?; + let val = evaluate_expr(expr, precs, grid)?; match op { PrefixOp::POS => eval_pos(&val)?, @@ -59,13 +82,115 @@ fn evaluate_expr(expr: &Expr, grid: Option<&Grid>) -> Result { // _ => return Err(format!("Evaluation error: Unsupported operator {:?}", op)), } } - Expr::Group(g) => evaluate_expr(g, grid)?, + 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(format!("Evaluation error: Unsupported expression {:?}", it)), }; Ok(res) } +fn eval_range( + lval: &Eval, + rval: &Eval, + precs: &mut HashSet, + grid: Option<&Grid>, +) -> Result { + match (lval, rval) { + ( + Eval::CellRef { + eval: _, + reference: a_ref, + }, + Eval::CellRef { + eval: _, + reference: b_ref, + }, + ) => { + let mut cells = Vec::new(); + + // assume row-major expansion + let row_start = a_ref.row.min(b_ref.row); + let row_end = a_ref.row.max(b_ref.row); + let col_start = a_ref.col.min(b_ref.col); + let col_end = a_ref.col.max(b_ref.col); + + for row in row_start..=row_end { + for col in col_start..=col_end { + let reference = CellRef { row, col }; + + let Some(g) = grid else { + return Err("Evaluation error: Found cell range but no grid.".into()); + }; + + cells.push(Eval::CellRef { + eval: Box::new( + g.get_cell(reference.to_owned()) + .map_or(Eval::Unset, |cell| cell.eval()), + ), + reference: { + precs.insert(reference); + reference + }, + }); + } + } + + Ok(Eval::Range(cells)) + } + _ => Err("Evaluation error: expected cellref types for RANGE function.".to_string()), + } +} + +fn eval_avg( + args: &Vec, + precs: &mut HashSet, + grid: Option<&Grid>, +) -> Result { + let mut res = 0.0; + let mut count = 0; + + for arg in args { + match evaluate_expr(arg, precs, grid)? { + Eval::Literal(Literal::Number(num)) => { + res += num; + count += 1; + } + Eval::Range(range) => { + for cell in range { + let Eval::CellRef { eval, reference: _ } = cell else { + panic!("Found non cellref in evaluation time RANGE!."); + }; + + if let Eval::Literal(Literal::Number(num)) = *eval { + res += num; + count += 1; + } else if matches!(*eval, Eval::Unset) { + continue; + } else { + return Err("Evaluation error: expected numeric types for AVG function." + .to_string()); + } + } + } + _ => { + return Err( + "Evaluation error: expected numeric types for AVG function.".to_string() + ); + } + } + } + + if count == 0 { + Err("Evaluation error: attempted to divide by zero.".to_string()) + } else { + Ok(Eval::Literal(Literal::Number(res / count as f64))) + } +} + fn eval_add(lval: &Eval, rval: &Eval) -> Result { match (lval, rval) { (Eval::Literal(a), Eval::Literal(b)) => { @@ -82,6 +207,9 @@ fn eval_add(lval: &Eval, rval: &Eval) -> Result { 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()) + } } } @@ -92,8 +220,9 @@ fn eval_sub(lval: &Eval, rval: &Eval) -> Result { return Ok(Eval::Literal(res)); } - Err("Evaluation error: expected string or numeric types for SUB function.".to_string()) + Err("Evaluation error: expected numeric types for SUB function.".to_string()) } + _ => Err("Evaluation error: expected numeric types for SUB function.".to_string()), } } fn eval_mul(lval: &Eval, rval: &Eval) -> Result { @@ -103,8 +232,9 @@ fn eval_mul(lval: &Eval, rval: &Eval) -> Result { return Ok(Eval::Literal(res)); } - Err("Evaluation error: expected string or numeric types for MUL function.".to_string()) + Err("Evaluation error: expected numeric types for MUL function.".to_string()) } + _ => Err("Evaluation error: expected numeric types for MUL function.".to_string()), } } fn eval_div(lval: &Eval, rval: &Eval) -> Result { @@ -122,8 +252,9 @@ fn eval_div(lval: &Eval, rval: &Eval) -> Result { return Ok(Eval::Literal(res)); } - Err("Evaluation error: expected string or numeric types for DIV function.".to_string()) + Err("Evaluation error: expected numeric types for DIV function.".to_string()) } + _ => Err("Evaluation error: expected numeric types for DIV function.".to_string()), } } diff --git a/backend/src/grid.rs b/backend/src/grid.rs index 0ae5f62..6f0dff4 100644 --- a/backend/src/grid.rs +++ b/backend/src/grid.rs @@ -1,5 +1,7 @@ use std::collections::{HashMap, HashSet}; +use log::info; + use crate::{ cell::{Cell, CellRef}, evaluator::{Eval, evaluate}, @@ -16,38 +18,43 @@ impl Grid { cells: HashMap::new(), }; } -} -impl Grid { - pub fn set_cell(&mut self, cell_ref: CellRef, raw_val: String) -> Result { + pub fn update_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 self.get_cell(cell_ref); + return Ok(Vec::new()); } let eval: Eval; - let deps: HashSet; + let mut precs: HashSet = HashSet::new(); + let mut updated_cells = vec![cell_ref]; - if let Some(c) = raw_val.chars().nth(0) - && c == '=' - { - (eval, deps) = evaluate(raw_val[1..].to_owned(), Some(&self))?; - // for dep in deps {} + if raw_val.chars().nth(0) != Some('=') { + eval = Eval::Literal(Literal::String(raw_val.to_owned())); } else { - match evaluate(raw_val.to_owned(), Some(&self)) { - Ok(e) => { - (eval, deps) = e; - } - Err(_) => eval = Eval::Literal(Literal::String(raw_val.to_owned())), - } + // Evaluate raw expr and get precedents + let (res_eval, res_precs) = evaluate(raw_val[1..].to_owned(), Some(&self))?; + eval = res_eval; + precs = res_precs; } - self.cells - .insert(cell_ref, Cell::new(eval.clone(), raw_val)); - Ok(eval) + if self.cells.contains_key(&cell_ref) { + updated_cells = self + .update_exisiting_cell(raw_val, eval, precs, cell_ref)? + .into_iter() + .chain(updated_cells) + .collect(); + } else { + self.create_cell(raw_val, eval, precs, cell_ref); + } + + Ok(updated_cells) } - // pub fn get_cell(&mut self, cell_ref: CellRef) -> Result<(String, Eval), String> { - pub fn get_cell(&self, cell_ref: CellRef) -> Result { + pub fn get_cell(&self, cell_ref: CellRef) -> Result { if !self.cells.contains_key(&cell_ref) { return Err(format!("Cell at {:?} not found.", cell_ref)); } @@ -55,18 +62,200 @@ impl Grid { let cell = &self.cells[&cell_ref]; // Ok((cell.raw(), cell.eval())) - Ok(cell.eval()) + Ok(cell.to_owned()) } - pub fn add_cell_dep(&mut self, cell_ref: CellRef, dep_ref: CellRef) -> Result<(), String> { - if !self.cells.contains_key(&cell_ref) { - return Err(format!("Cell at {:?} not found.", cell_ref)); + pub fn get_cell_mut(&mut self, cell_ref: CellRef) -> Result<&mut Cell, String> { + if let Some(res) = self.cells.get_mut(&cell_ref) { + Ok(res) + } else { + Err(format!("Cell at {:?} not found.", cell_ref)) + } + } + + // This is a topological order on the precedents graph + // i.e. if a requires b (e.g. a = 1 + b) then a -> b + // so a comes before b in the topo order + fn topo_order(&self, from: CellRef) -> Result, String> { + let mut res: Vec = Vec::new(); + let mut search_set = Vec::new(); + let mut temp = HashSet::new(); + let mut perm = HashSet::new(); + + search_set.push(from); + + let cell_data = &self.cells[&from]; + search_set.extend(cell_data.deps().iter()); + + temp.insert(from); + perm.insert(from); + + let mut searched = 1; + + while searched != search_set.len() { + if perm.contains(&search_set[searched]) { + searched += 1; + continue; + } + + self.topo_visit( + search_set[searched], + &mut temp, + &mut perm, + &mut search_set, + &mut res, + )?; + searched += 1; } - if let Some(cell) = self.cells.get_mut(&cell_ref) { - cell.add_i_dep(dep_ref); + Ok(res) + } + + fn topo_visit( + &self, + cell: CellRef, + temp: &mut HashSet, + perm: &mut HashSet, + search_set: &mut Vec, + res: &mut Vec, + ) -> Result<(), String> { + if perm.contains(&cell) { + return Ok(()); } + if temp.contains(&cell) { + return Err("Evalutation error: Cycle detected in cell refs.".into()); + } + + temp.insert(cell); + + if !self.cells.contains_key(&cell) { + perm.insert(cell); + res.push(cell); + return Ok(()); + } + + let cell_data = &self.cells[&cell]; + + search_set.extend(cell_data.deps().iter()); + // search_set.extend(cell_data.precedents.iter().cloned()); + + for prec in cell_data.precs().iter() { + self.topo_visit(*prec, temp, perm, search_set, res)?; + } + + perm.insert(cell); + + res.push(cell); Ok(()) } + + fn update_exisiting_cell( + &mut self, + raw: String, + new_eval: Eval, + new_precs: HashSet, + cell_ref: CellRef, + ) -> Result, String> { + let (old_precs, old_eval) = match self.cells.get_mut(&cell_ref) { + Some(cell) => { + cell.set_raw(raw); + (cell.precs().clone(), cell.eval().clone()) + } + None => return Ok(Vec::new()), + }; + + // diffs (outside any borrow) + let removed: Vec<_> = old_precs.difference(&new_precs).cloned().collect(); // old \ new + let added: Vec<_> = new_precs.difference(&old_precs).cloned().collect(); // new \ old + let eval_changed = old_eval != new_eval; + + // ---- phase 2: apply (fresh borrows) ---- + for p in &removed { + if let Some(c) = self.cells.get_mut(p) { + c.remove_dep(&cell_ref); + } + } + for p in &added { + if let Some(c) = self.cells.get_mut(p) { + c.add_dep(cell_ref); + } else { + self.cells.insert( + *p, + Cell::new_all( + *p, + Eval::Unset, + "".into(), + HashSet::new(), + HashSet::from([cell_ref]), + ), + ); + } + } + + let cell = self.cells.get_mut(&cell_ref).unwrap(); // Should be impossible to crash + cell.set_precs(new_precs); + cell.set_eval(new_eval); + + if eval_changed { + self.propagate(cell_ref) + } else { + Ok(Vec::new()) + } + } + + fn create_cell(&mut self, raw: String, eval: Eval, precs: HashSet, cell_ref: CellRef) { + for prec in &precs { + if let Some(it) = self.cells.get_mut(&prec) { + it.add_dep(cell_ref); + } else { + self.cells.insert( + *prec, + Cell::new_all( + *prec, + Eval::Unset, + "".into(), + HashSet::new(), + HashSet::from([cell_ref]), + ), + ); + + info!("{:?}", self.cells.get(&prec)); + } + } + + self.cells.insert( + cell_ref, + Cell::new_all(cell_ref, eval, raw, precs, HashSet::new()), + ); + } + + fn propagate(&mut self, from: CellRef) -> Result, String> { + let mut res = Vec::new(); + let topo = self.topo_order(from)?; + + for cell_ref in topo { + res.push(cell_ref); + + let raw = if let Some(cell) = self.cells.get(&cell_ref) { + let s = cell.raw(); + if let Some(rest) = s.strip_prefix('=') { + rest.to_owned() + } else { + continue; + } + } else { + continue; + }; + + // Now we dropped the borrow of self.cells before this point + let (e, _) = evaluate(raw, Some(self))?; + + if let Some(cell) = self.cells.get_mut(&cell_ref) { + cell.set_eval(e); + } + } + + Ok(res) + } } diff --git a/backend/src/main.rs b/backend/src/main.rs index db7f500..7bdd329 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -14,6 +14,7 @@ use crate::{ evaluator::Eval, grid::Grid, messages::{LeadMsg, MsgType}, + tokenizer::Literal, }; #[tokio::main] @@ -63,26 +64,45 @@ async fn accept_connection(stream: TcpStream) { let Some(cell_ref) = req.cell else { continue }; let Some(raw) = req.raw else { continue }; - match grid.set_cell(cell_ref.clone(), raw.to_owned()) { - Ok(eval) => match eval { - Eval::Literal(lit) => { - let res = LeadMsg { - msg_type: MsgType::Set, - cell: Some(cell_ref), - raw: Some(raw.to_string()), - eval: Some(lit), - }; - let _ = write - .send(serde_json::to_string(&res).unwrap().into()) - .await; + match grid.update_cell(cell_ref.clone(), raw.to_owned()) { + Ok(updates) => { + let mut msgs = Vec::new(); + + for update in &updates { + if let Ok(cell) = grid.get_cell(*update) { + let Eval::Literal(lit) = cell.eval() else { + continue; + }; + + msgs.push(LeadMsg { + msg_type: MsgType::Set, + cell: Some(*update), + raw: Some(cell.raw()), + eval: Some(lit), + bulk_msgs: None, + }); + } } - }, + + let msg = LeadMsg { + cell: None, + raw: None, + eval: None, + bulk_msgs: Some(msgs), + msg_type: MsgType::Bulk, + }; + + let _ = write + .send(serde_json::to_string(&msg).unwrap().into()) + .await; + } Err(e) => { let res = LeadMsg { msg_type: MsgType::Error, cell: Some(cell_ref), raw: Some(e.to_string()), eval: None, + bulk_msgs: None, }; let _ = write .send(serde_json::to_string(&res).unwrap().into()) diff --git a/backend/src/messages.rs b/backend/src/messages.rs index e9791cc..f3a26e6 100644 --- a/backend/src/messages.rs +++ b/backend/src/messages.rs @@ -8,6 +8,7 @@ pub enum MsgType { Set, Get, Error, + Bulk, } #[derive(Serialize, Deserialize)] @@ -16,4 +17,5 @@ pub struct LeadMsg { pub cell: Option, pub raw: Option, pub eval: Option, + pub bulk_msgs: Option>, } diff --git a/backend/src/parser.rs b/backend/src/parser.rs index 4c31cd6..5bbf8c2 100644 --- a/backend/src/parser.rs +++ b/backend/src/parser.rs @@ -1,3 +1,5 @@ +use log::info; + use crate::{cell::CellRef, tokenizer::*}; use std::{collections::HashSet, fmt}; @@ -21,6 +23,7 @@ pub enum InfixOp { SUB, AND, OR, + RANGE, } #[derive(Debug, PartialEq, Clone)] @@ -57,6 +60,7 @@ pub trait Precedence { impl Precedence for InfixOp { fn prec(&self) -> (u8, u8) { match self { + InfixOp::RANGE => (7, 8), InfixOp::MUL | InfixOp::DIV | InfixOp::AND => (3, 4), InfixOp::ADD | InfixOp::SUB | InfixOp::OR => (1, 2), } @@ -157,20 +161,21 @@ impl Expr { pub fn parse(input: &str) -> Result<(Expr, HashSet), String> { let mut tokenizer = Tokenizer::new(input)?; - // println!("{:?}", tokenizer.tokens); - let mut deps = HashSet::new(); - Ok((_parse(&mut tokenizer, 0, &mut deps)?, deps)) + let mut precs = HashSet::new(); + let expr = _parse(&mut tokenizer, 0, &mut precs)?; + info!("{}", expr.pretty()); + Ok((expr, precs)) } pub fn _parse( input: &mut Tokenizer, min_prec: u8, - deps: &mut HashSet, + precedents: &mut HashSet, ) -> Result { let mut lhs = match input.next() { Token::Literal(it) => Expr::Literal(it), Token::OpenParen => { - let lhs = _parse(input, 0, deps)?; + let lhs = _parse(input, 0, precedents)?; if input.next() != Token::CloseParen { return Err(format!("Parse error: expected closing paren.")); } @@ -184,7 +189,7 @@ pub fn _parse( it => return Err(format!("Parse error: unknown prefix operator {:?}.", it)), }; - let rhs = _parse(input, prefix_op.prec().1, deps)?; + let rhs = _parse(input, prefix_op.prec().1, precedents)?; Expr::Prefix { op: prefix_op, @@ -213,7 +218,7 @@ pub fn _parse( input.next(); // Skip comma } - let arg = _parse(input, 0, deps)?; + let arg = _parse(input, 0, precedents)?; args.push(arg); } @@ -224,7 +229,7 @@ pub fn _parse( } _ => { let cell_ref = CellRef::new(id)?; - deps.insert(cell_ref); + precedents.insert(cell_ref); Expr::CellRef(cell_ref) } }, @@ -235,7 +240,7 @@ pub fn _parse( // 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) { + if OPERATORS_STR.contains(op) { let infix_op = match op { '+' => InfixOp::ADD, '-' => InfixOp::SUB, @@ -243,6 +248,7 @@ pub fn _parse( '/' => InfixOp::DIV, '&' => InfixOp::AND, '|' => InfixOp::OR, + ':' => InfixOp::RANGE, it => { return Err(format!("Parse error: do not know infix operator {:?}.", it)); } @@ -254,7 +260,7 @@ pub fn _parse( } input.next(); - let rhs = _parse(input, r_prec, deps)?; + let rhs = _parse(input, r_prec, precedents)?; lhs = Expr::Infix { op: infix_op, lhs: Box::new(lhs), diff --git a/backend/src/tokenizer.rs b/backend/src/tokenizer.rs index 794b698..3695053 100644 --- a/backend/src/tokenizer.rs +++ b/backend/src/tokenizer.rs @@ -19,6 +19,8 @@ pub enum Token { Eof, } +pub const OPERATORS_STR: &str = "+-*/^!%&|:"; + pub struct Tokenizer { pub tokens: Vec, } @@ -97,7 +99,7 @@ impl Tokenizer { string.push(ch); } tokens.push(Token::Literal(Literal::String(string))); - } else if "+-*/^!%&|".contains(c) { + } else if OPERATORS_STR.contains(c) { tokens.push(Token::Operator(c)); chars.next(); } else if "()".contains(c) { diff --git a/frontend/src/lib/components/grid/grid.svelte b/frontend/src/lib/components/grid/grid.svelte index 425ae6d..cbc130b 100644 --- a/frontend/src/lib/components/grid/grid.svelte +++ b/frontend/src/lib/components/grid/grid.svelte @@ -25,26 +25,38 @@ return; } - switch (res.msg_type) { + handle_msg(res); + }; + + function handle_msg(msg: LeadMsg) { + switch (msg.msg_type) { case 'error': { toast.error('Error', { - description: res.raw + description: msg.raw }); break; } case 'set': { - if (res.cell === undefined) { - console.error('Expected cell ref for SET response from server.'); + if (msg.cell === undefined) { + console.error('Expected cell ref for SET msgponse from server.'); return; - } else if (res.eval === undefined) { - console.error('Expected cell value for SET response from server.'); + } else if (msg.eval === undefined) { + console.error('Expected cell value for SET msgponse from server.'); return; } - setCellVal(res.cell.row, res.cell.col, res.eval.value); + setCellVal(msg.cell.row, msg.cell.col, msg.eval.value); break; } + case 'bulk': { + if (msg.bulk_msgs === undefined) { + console.error('Expected bulk_msgs field to be defined for BULK message.'); + return; + } + + for (const m of msg.bulk_msgs) handle_msg(m); + } } - }; + } let rows = 50; let cols = 40; diff --git a/frontend/src/lib/components/grid/messages.d.ts b/frontend/src/lib/components/grid/messages.ts similarity index 79% rename from frontend/src/lib/components/grid/messages.d.ts rename to frontend/src/lib/components/grid/messages.ts index 224614b..5523d4b 100644 --- a/frontend/src/lib/components/grid/messages.d.ts +++ b/frontend/src/lib/components/grid/messages.ts @@ -1,8 +1,9 @@ interface LeadMsg { - msg_type: 'set' | 'get' | 'error'; + msg_type: 'set' | 'get' | 'error' | 'bulk'; cell?: CellRef; raw?: string; eval?: Literal; + bulk_msgs?: Array; } interface CellRef { diff --git a/frontend/src/lib/components/ui/sidebar/sidebar.svelte b/frontend/src/lib/components/ui/sidebar/sidebar.svelte index f825157..50bbf2b 100644 --- a/frontend/src/lib/components/ui/sidebar/sidebar.svelte +++ b/frontend/src/lib/components/ui/sidebar/sidebar.svelte @@ -66,7 +66,7 @@