🙃
This commit is contained in:
@@ -2,7 +2,10 @@ use std::collections::HashSet;
|
|||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::evaluator::*;
|
use crate::{
|
||||||
|
common::{LeadErr, LeadErrCode},
|
||||||
|
evaluator::*,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||||
pub struct CellRef {
|
pub struct CellRef {
|
||||||
@@ -93,7 +96,7 @@ impl Cell {
|
|||||||
|
|
||||||
impl CellRef {
|
impl CellRef {
|
||||||
// Zero indexed
|
// Zero indexed
|
||||||
pub fn new(s: String) -> Result<CellRef, String> {
|
pub fn new(s: String) -> Result<CellRef, LeadErr> {
|
||||||
let s = s.trim();
|
let s = s.trim();
|
||||||
let mut col: usize = 0;
|
let mut col: usize = 0;
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
@@ -111,20 +114,26 @@ impl CellRef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if col <= 0 {
|
if col <= 0 {
|
||||||
return Err(format!(
|
return Err(LeadErr {
|
||||||
"Parse error: missing column letters in cell ref: {s}"
|
title: "Parse error.".into(),
|
||||||
));
|
desc: format!("Missing column letters in cell ref: {s}."),
|
||||||
|
code: LeadErrCode::Syntax,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let row_part = &s[i..];
|
let row_part = &s[i..];
|
||||||
if row_part.is_empty() {
|
if row_part.is_empty() {
|
||||||
return Err(format!(
|
return Err(LeadErr {
|
||||||
"Parse error: missing column letters in cell ref: {s}"
|
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()) {
|
} else if !row_part.chars().all(|c| c.is_ascii_digit()) {
|
||||||
return Err(format!(
|
return Err(LeadErr {
|
||||||
"Parse error: row part must be numeric in cell ref: {s}"
|
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::<usize>() {
|
if let Ok(row) = row_part.parse::<usize>() {
|
||||||
@@ -133,7 +142,11 @@ impl CellRef {
|
|||||||
col: col - 1,
|
col: col - 1,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
Err(format!("Parse error: invalid row number."))
|
Err(LeadErr {
|
||||||
|
title: "Parse error.".into(),
|
||||||
|
desc: format!("Invalid row number."),
|
||||||
|
code: LeadErrCode::Syntax,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::cell::CellRef;
|
use crate::cell::CellRef;
|
||||||
|
use crate::common::LeadErr;
|
||||||
|
use crate::common::LeadErrCode;
|
||||||
|
use crate::common::Literal;
|
||||||
use crate::grid::Grid;
|
use crate::grid::Grid;
|
||||||
use crate::parser::*;
|
use crate::parser::*;
|
||||||
use crate::tokenizer::Literal;
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
@@ -13,6 +15,7 @@ pub enum Eval {
|
|||||||
Literal(Literal),
|
Literal(Literal),
|
||||||
CellRef { eval: Box<Eval>, reference: CellRef },
|
CellRef { eval: Box<Eval>, reference: CellRef },
|
||||||
Range(Vec<Eval>),
|
Range(Vec<Eval>),
|
||||||
|
Err(LeadErr),
|
||||||
Unset,
|
Unset,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,19 +26,22 @@ impl fmt::Display for Eval {
|
|||||||
Eval::Range(it) => write!(f, "Range({it:?})"),
|
Eval::Range(it) => write!(f, "Range({it:?})"),
|
||||||
Eval::CellRef { eval, reference } => write!(f, "EvalRef({eval:?}, {reference:?})"),
|
Eval::CellRef { eval, reference } => write!(f, "EvalRef({eval:?}, {reference:?})"),
|
||||||
Eval::Unset => write!(f, "Unset"),
|
Eval::Unset => write!(f, "Unset"),
|
||||||
|
Eval::Err(it) => write!(f, "{it:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn evaluate(str: String, grid: Option<&Grid>) -> Result<(Eval, HashSet<CellRef>), String> {
|
pub fn evaluate(str: String, grid: Option<&Grid>) -> (Eval, HashSet<CellRef>) {
|
||||||
let (expr, _) = parse(&str)?;
|
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) {
|
match evaluate_expr(&expr, &mut precs, grid) {
|
||||||
Ok(it) => Ok((it, precs)),
|
Ok(it) => (it, precs),
|
||||||
Err(it) => Err(it),
|
Err(it) => (Eval::Err(it), precs),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => (Eval::Err(e), HashSet::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +49,7 @@ fn evaluate_expr(
|
|||||||
expr: &Expr,
|
expr: &Expr,
|
||||||
precs: &mut HashSet<CellRef>,
|
precs: &mut HashSet<CellRef>,
|
||||||
grid: Option<&Grid>,
|
grid: Option<&Grid>,
|
||||||
) -> Result<Eval, String> {
|
) -> Result<Eval, LeadErr> {
|
||||||
let res = match expr {
|
let res = match expr {
|
||||||
Expr::Literal(lit) => Eval::Literal(lit.clone()),
|
Expr::Literal(lit) => Eval::Literal(lit.clone()),
|
||||||
Expr::CellRef(re) => {
|
Expr::CellRef(re) => {
|
||||||
@@ -59,7 +65,11 @@ fn evaluate_expr(
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
} else {
|
} 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 } => {
|
Expr::Infix { op, lhs, rhs } => {
|
||||||
@@ -81,7 +91,13 @@ fn evaluate_expr(
|
|||||||
InfixOp::MUL => eval_mul(&lval, &rval)?,
|
InfixOp::MUL => eval_mul(&lval, &rval)?,
|
||||||
InfixOp::DIV => eval_div(&lval, &rval)?,
|
InfixOp::DIV => eval_div(&lval, &rval)?,
|
||||||
InfixOp::RANGE => eval_range(&lval, &rval, precs, grid)?,
|
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 } => {
|
Expr::Prefix { op, expr } => {
|
||||||
@@ -101,9 +117,21 @@ fn evaluate_expr(
|
|||||||
Expr::Group(g) => evaluate_expr(g, precs, grid)?,
|
Expr::Group(g) => evaluate_expr(g, precs, grid)?,
|
||||||
Expr::Function { name, args } => match name.as_str() {
|
Expr::Function { name, args } => match name.as_str() {
|
||||||
"AVG" => eval_avg(args, precs, grid)?,
|
"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)
|
Ok(res)
|
||||||
@@ -114,7 +142,7 @@ fn eval_range(
|
|||||||
rval: &Eval,
|
rval: &Eval,
|
||||||
precs: &mut HashSet<CellRef>,
|
precs: &mut HashSet<CellRef>,
|
||||||
grid: Option<&Grid>,
|
grid: Option<&Grid>,
|
||||||
) -> Result<Eval, String> {
|
) -> Result<Eval, LeadErr> {
|
||||||
match (lval, rval) {
|
match (lval, rval) {
|
||||||
(
|
(
|
||||||
Eval::CellRef {
|
Eval::CellRef {
|
||||||
@@ -139,7 +167,11 @@ fn eval_range(
|
|||||||
let reference = CellRef { row, col };
|
let reference = CellRef { row, col };
|
||||||
|
|
||||||
let Some(g) = grid else {
|
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 {
|
cells.push(Eval::CellRef {
|
||||||
@@ -157,7 +189,11 @@ fn eval_range(
|
|||||||
|
|
||||||
Ok(Eval::Range(cells))
|
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<Expr>,
|
args: &Vec<Expr>,
|
||||||
precs: &mut HashSet<CellRef>,
|
precs: &mut HashSet<CellRef>,
|
||||||
grid: Option<&Grid>,
|
grid: Option<&Grid>,
|
||||||
) -> Result<Eval, String> {
|
) -> Result<Eval, LeadErr> {
|
||||||
let mut res = 0.0;
|
let mut res = 0.0;
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
|
|
||||||
@@ -178,7 +214,11 @@ fn eval_avg(
|
|||||||
Eval::Range(range) => {
|
Eval::Range(range) => {
|
||||||
for cell in range {
|
for cell in range {
|
||||||
let Eval::CellRef { eval, reference: _ } = cell else {
|
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 {
|
if let Eval::Literal(Literal::Number(num)) = *eval {
|
||||||
@@ -187,27 +227,36 @@ fn eval_avg(
|
|||||||
} else if matches!(*eval, Eval::Unset) {
|
} else if matches!(*eval, Eval::Unset) {
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
return Err("Evaluation error: expected numeric types for AVG function."
|
return Err(LeadErr {
|
||||||
.to_string());
|
title: "Evaluation error.".into(),
|
||||||
|
desc: "Expected numeric types for AVG function.".into(),
|
||||||
|
code: LeadErrCode::Unsupported,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(
|
return Err(LeadErr {
|
||||||
"Evaluation error: expected numeric types for AVG function.".to_string()
|
title: "Evaluation error.".into(),
|
||||||
);
|
desc: "Expected numeric types for AVG function.".into(),
|
||||||
|
code: LeadErrCode::Unsupported,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if count == 0 {
|
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 {
|
} else {
|
||||||
Ok(Eval::Literal(Literal::Number(res / count as f64)))
|
Ok(Eval::Literal(Literal::Number(res / count as f64)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_add(lval: &Eval, rval: &Eval) -> Result<Eval, String> {
|
fn eval_add(lval: &Eval, rval: &Eval) -> Result<Eval, LeadErr> {
|
||||||
match (lval, rval) {
|
match (lval, rval) {
|
||||||
(Eval::Literal(a), Eval::Literal(b)) => {
|
(Eval::Literal(a), Eval::Literal(b)) => {
|
||||||
if let Some(res) = eval_numeric_infix(a, b, |x, y| x + y) {
|
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<Eval, String> {
|
|||||||
return Ok(Eval::Literal(Literal::String(res)));
|
return Ok(Eval::Literal(Literal::String(res)));
|
||||||
}
|
}
|
||||||
|
|
||||||
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(),
|
||||||
Err("Evaluation error: expected string or numeric types for ADD function.".to_string())
|
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<Eval, String> {
|
fn eval_sub(lval: &Eval, rval: &Eval) -> Result<Eval, LeadErr> {
|
||||||
match (lval, rval) {
|
match (lval, rval) {
|
||||||
(Eval::Literal(a), Eval::Literal(b)) => {
|
(Eval::Literal(a), Eval::Literal(b)) => {
|
||||||
if let Some(res) = eval_numeric_infix(a, b, |x, y| x - y) {
|
if let Some(res) = eval_numeric_infix(a, b, |x, y| x - y) {
|
||||||
return Ok(Eval::Literal(res));
|
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<Eval, String> {
|
|
||||||
|
fn eval_mul(lval: &Eval, rval: &Eval) -> Result<Eval, LeadErr> {
|
||||||
match (lval, rval) {
|
match (lval, rval) {
|
||||||
(Eval::Literal(a), Eval::Literal(b)) => {
|
(Eval::Literal(a), Eval::Literal(b)) => {
|
||||||
if let Some(res) = eval_numeric_infix(a, b, |x, y| x * y) {
|
if let Some(res) = eval_numeric_infix(a, b, |x, y| x * y) {
|
||||||
return Ok(Eval::Literal(res));
|
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<Eval, String> {
|
|
||||||
|
fn eval_div(lval: &Eval, rval: &Eval) -> Result<Eval, LeadErr> {
|
||||||
match (lval, rval) {
|
match (lval, rval) {
|
||||||
(Eval::Literal(a), Eval::Literal(b)) => {
|
(Eval::Literal(a), Eval::Literal(b)) => {
|
||||||
if let (Literal::Number(_), Literal::Number(y)) = (a, b) {
|
if let (Literal::Number(_), Literal::Number(y)) = (a, b) {
|
||||||
if *y == 0f64 {
|
if *y == 0f64 {
|
||||||
return Err(
|
return Err(LeadErr {
|
||||||
"Evaluation error: integers attempted to divide by zero.".to_string()
|
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<Eval, String> {
|
|||||||
return Ok(Eval::Literal(res));
|
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<Eval, LeadErr> {
|
||||||
|
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<Eval, LeadErr> {
|
||||||
|
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<Eval, LeadErr> {
|
||||||
|
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,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn eval_pos(val: &Eval) -> Result<Eval, String> {
|
|
||||||
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<Eval, String> {
|
|
||||||
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<Eval, String> {
|
|
||||||
match val {
|
|
||||||
Eval::Literal(Literal::Boolean(it)) => Ok(Eval::Literal(Literal::Boolean(!it))),
|
|
||||||
_ => Err("Evaluation error: expected boolean type for NEG function.".to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ use log::info;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cell::{Cell, CellRef},
|
cell::{Cell, CellRef},
|
||||||
|
common::Literal,
|
||||||
evaluator::{Eval, evaluate},
|
evaluator::{Eval, evaluate},
|
||||||
tokenizer::Literal,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Grid {
|
pub struct Grid {
|
||||||
@@ -36,7 +36,7 @@ impl Grid {
|
|||||||
eval = Eval::Literal(Literal::String(raw_val.to_owned()));
|
eval = Eval::Literal(Literal::String(raw_val.to_owned()));
|
||||||
} else {
|
} else {
|
||||||
// Evaluate raw expr and get precedents
|
// 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;
|
eval = res_eval;
|
||||||
precs = res_precs;
|
precs = res_precs;
|
||||||
}
|
}
|
||||||
@@ -88,7 +88,7 @@ impl Grid {
|
|||||||
search_set.extend(cell_data.deps().iter());
|
search_set.extend(cell_data.deps().iter());
|
||||||
|
|
||||||
temp.insert(from);
|
temp.insert(from);
|
||||||
perm.insert(from);
|
perm.insert(from); // Make this inside the inner topo_visit
|
||||||
|
|
||||||
let mut searched = 1;
|
let mut searched = 1;
|
||||||
|
|
||||||
@@ -249,7 +249,7 @@ impl Grid {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Now we dropped the borrow of self.cells before this point
|
// 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) {
|
if let Some(cell) = self.cells.get_mut(&cell_ref) {
|
||||||
cell.set_eval(e);
|
cell.set_eval(e);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
mod cell;
|
mod cell;
|
||||||
|
mod common;
|
||||||
mod evaluator;
|
mod evaluator;
|
||||||
mod grid;
|
mod grid;
|
||||||
mod messages;
|
mod messages;
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
use log::info;
|
use log::info;
|
||||||
|
|
||||||
use crate::{cell::CellRef, tokenizer::*};
|
use crate::{
|
||||||
|
cell::CellRef,
|
||||||
|
common::{LeadErr, LeadErrCode, Literal},
|
||||||
|
tokenizer::*,
|
||||||
|
};
|
||||||
use std::{collections::HashSet, fmt};
|
use std::{collections::HashSet, fmt};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
@@ -159,7 +163,7 @@ impl Expr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse(input: &str) -> Result<(Expr, HashSet<CellRef>), String> {
|
pub fn parse(input: &str) -> Result<(Expr, HashSet<CellRef>), LeadErr> {
|
||||||
let mut tokenizer = Tokenizer::new(input)?;
|
let mut tokenizer = Tokenizer::new(input)?;
|
||||||
let mut precs = HashSet::new();
|
let mut precs = HashSet::new();
|
||||||
let expr = _parse(&mut tokenizer, 0, &mut precs)?;
|
let expr = _parse(&mut tokenizer, 0, &mut precs)?;
|
||||||
@@ -171,13 +175,17 @@ pub fn _parse(
|
|||||||
input: &mut Tokenizer,
|
input: &mut Tokenizer,
|
||||||
min_prec: u8,
|
min_prec: u8,
|
||||||
precedents: &mut HashSet<CellRef>,
|
precedents: &mut HashSet<CellRef>,
|
||||||
) -> Result<Expr, String> {
|
) -> Result<Expr, LeadErr> {
|
||||||
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::OpenParen => {
|
Token::OpenParen => {
|
||||||
let lhs = _parse(input, 0, precedents)?;
|
let lhs = _parse(input, 0, precedents)?;
|
||||||
if input.next() != Token::CloseParen {
|
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))
|
Expr::Group(Box::new(lhs))
|
||||||
}
|
}
|
||||||
@@ -186,7 +194,13 @@ pub fn _parse(
|
|||||||
'+' => PrefixOp::POS,
|
'+' => PrefixOp::POS,
|
||||||
'-' => PrefixOp::NEG,
|
'-' => PrefixOp::NEG,
|
||||||
'!' => PrefixOp::NOT,
|
'!' => 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)?;
|
let rhs = _parse(input, prefix_op.prec().1, precedents)?;
|
||||||
@@ -208,10 +222,13 @@ pub fn _parse(
|
|||||||
input.next();
|
input.next();
|
||||||
break;
|
break;
|
||||||
} else if nxt != Token::Comma && args.len() != 0 {
|
} else if nxt != Token::Comma && args.len() != 0 {
|
||||||
return Err(format!(
|
return Err(LeadErr {
|
||||||
"Parse error: expected comma while parsing argument of function {:?}.",
|
title: "Parse error.".into(),
|
||||||
id
|
desc: format!(
|
||||||
));
|
"Expected comma while parsing argument of function {id:?}"
|
||||||
|
),
|
||||||
|
code: LeadErrCode::Syntax,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if args.len() != 0 {
|
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
|
// In the reference article this is a loop with match
|
||||||
@@ -250,7 +273,11 @@ pub fn _parse(
|
|||||||
'|' => InfixOp::OR,
|
'|' => InfixOp::OR,
|
||||||
':' => InfixOp::RANGE,
|
':' => InfixOp::RANGE,
|
||||||
it => {
|
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 {
|
let postfix_op = match op {
|
||||||
'%' => PostfixOp::PERCENT,
|
'%' => PostfixOp::PERCENT,
|
||||||
it => {
|
it => {
|
||||||
return Err(format!(
|
return Err(LeadErr {
|
||||||
"Parse error: do not know postfix operator {:?}.",
|
title: "Parse error.".into(),
|
||||||
it
|
desc: format!("Unknown postfix operator {:?}.", it),
|
||||||
));
|
code: LeadErrCode::Syntax,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,11 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use crate::common::{LeadErr, LeadErrCode, Literal};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
||||||
#[serde(tag = "type", content = "value")]
|
|
||||||
pub enum Literal {
|
|
||||||
Number(f64),
|
|
||||||
Boolean(bool),
|
|
||||||
String(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Token {
|
pub enum Token {
|
||||||
Identifier(String), // Could be a function
|
Identifier(String), // Could be a function
|
||||||
Literal(Literal),
|
Literal(Literal),
|
||||||
Operator(char),
|
Operator(char),
|
||||||
|
Err(LeadErr),
|
||||||
OpenParen,
|
OpenParen,
|
||||||
CloseParen,
|
CloseParen,
|
||||||
Comma,
|
Comma,
|
||||||
@@ -26,7 +19,7 @@ pub struct Tokenizer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Tokenizer {
|
impl Tokenizer {
|
||||||
pub fn new(input: &str) -> Result<Tokenizer, String> {
|
pub fn new(input: &str) -> Result<Tokenizer, LeadErr> {
|
||||||
let mut tokens = Vec::new();
|
let mut tokens = Vec::new();
|
||||||
let mut chars = input.chars().peekable();
|
let mut chars = input.chars().peekable();
|
||||||
|
|
||||||
@@ -113,7 +106,11 @@ impl Tokenizer {
|
|||||||
tokens.push(Token::Comma);
|
tokens.push(Token::Comma);
|
||||||
chars.next();
|
chars.next();
|
||||||
} else {
|
} 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,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Input } from '$lib/components/ui/input/index.js';
|
import { Input } from '$lib/components/ui/input/index.js';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
import type { CellT } from './utils';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
cla = '',
|
cla = '',
|
||||||
width = '80px',
|
width = '80px',
|
||||||
height = '30px',
|
height = '30px',
|
||||||
raw_val = $bindable(''),
|
cell = $bindable(undefined),
|
||||||
val = undefined,
|
|
||||||
onmousedown = () => {},
|
onmousedown = () => {},
|
||||||
startediting = () => {},
|
startediting = () => {},
|
||||||
stopediting = () => {},
|
stopediting = () => {},
|
||||||
@@ -17,8 +17,7 @@
|
|||||||
cla?: string;
|
cla?: string;
|
||||||
width?: string;
|
width?: string;
|
||||||
height?: string;
|
height?: string;
|
||||||
raw_val?: string;
|
cell?: CellT;
|
||||||
val?: LiteralValue | undefined;
|
|
||||||
onmousedown?: (e: MouseEvent) => void;
|
onmousedown?: (e: MouseEvent) => void;
|
||||||
startediting?: () => void;
|
startediting?: () => void;
|
||||||
stopediting?: () => void;
|
stopediting?: () => void;
|
||||||
@@ -31,7 +30,7 @@
|
|||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
const el = node.querySelector('input') as HTMLInputElement | null;
|
const el = node.querySelector('input') as HTMLInputElement | null;
|
||||||
if (el !== null) {
|
if (el !== null) {
|
||||||
el.value = raw_val;
|
el.value = cell?.raw_val ?? '';
|
||||||
el.focus();
|
el.focus();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -56,7 +55,11 @@
|
|||||||
class="relative rounded-none p-1 !transition-none delay-0 duration-0
|
class="relative rounded-none p-1 !transition-none delay-0 duration-0
|
||||||
focus:z-20 focus:shadow-[0_0_0_1px_var(--color-primary)] focus:outline-none"
|
focus:z-20 focus:shadow-[0_0_0_1px_var(--color-primary)] focus:outline-none"
|
||||||
onblur={(e) => {
|
onblur={(e) => {
|
||||||
raw_val = (e.target as HTMLInputElement).value;
|
cell = {
|
||||||
|
isErr: false,
|
||||||
|
val: undefined,
|
||||||
|
raw_val: (e.target as HTMLInputElement).value
|
||||||
|
};
|
||||||
stopediting();
|
stopediting();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -69,12 +72,12 @@
|
|||||||
style:height
|
style:height
|
||||||
class={clsx('placeholder bg-background p-1', { active }, cla)}
|
class={clsx('placeholder bg-background p-1', { active }, cla)}
|
||||||
>
|
>
|
||||||
{#if raw_val !== '' || val !== ''}
|
{#if cell && (cell.raw_val !== '' || cell.val !== '')}
|
||||||
<span class="pointer-events-none select-none">
|
<span class={clsx('pointer-events-none select-none', { err: cell.isErr })}>
|
||||||
{#if val !== undefined}
|
{#if cell.val}
|
||||||
{val}
|
{cell.val}
|
||||||
{:else}
|
{:else}
|
||||||
{raw_val}
|
{cell.raw_val}
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -94,4 +97,21 @@
|
|||||||
border: 1px solid var(--color-primary);
|
border: 1px solid var(--color-primary);
|
||||||
outline: 1px solid var(--color-primary);
|
outline: 1px solid var(--color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.active:has(.err),
|
||||||
|
.placeholder:has(.err) {
|
||||||
|
position: relative; /* needed for absolute positioning */
|
||||||
|
}
|
||||||
|
|
||||||
|
.active:has(.err)::after,
|
||||||
|
.placeholder:has(.err)::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
border-top: 12px solid red; /* size & color of the triangle */
|
||||||
|
border-left: 12px solid transparent;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import Cell from '$lib/components/grid/cell.svelte';
|
import Cell from '$lib/components/grid/cell.svelte';
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
import CellHeader from './cell-header.svelte';
|
import CellHeader from './cell-header.svelte';
|
||||||
import { colToStr, getEvalLiteral, refToStr, type CellT } from './utils';
|
import { colToStr, getEvalLiteral, isErr, refToStr, type CellT } from './utils';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
console.error('Expected cell value for SET msgponse from server.');
|
console.error('Expected cell value for SET msgponse from server.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setCellVal(msg.cell.row, msg.cell.col, getEvalLiteral(msg.eval));
|
setCellVal(msg.cell.row, msg.cell.col, getEvalLiteral(msg.eval), isErr(msg.eval));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'bulk': {
|
case 'bulk': {
|
||||||
@@ -107,38 +107,41 @@
|
|||||||
|
|
||||||
const key = (i: number, j: number) => `${i}:${j}`;
|
const key = (i: number, j: number) => `${i}:${j}`;
|
||||||
|
|
||||||
const getCell = (i: number, j: number) => grid_vals[key(i, j)] ?? undefined;
|
const getCell = (i: number, j: number) => grid_vals[key(i, j)];
|
||||||
|
|
||||||
const getCellRaw = (i: number, j: number) => getCell(i, j)?.raw_val ?? '';
|
const getCellRaw = (i: number, j: number) => getCell(i, j)?.raw_val ?? '';
|
||||||
const setCellRaw = (i: number, j: number, val: string) => {
|
const setCellRaw = (i: number, j: number, val: string) => {
|
||||||
if (grid_vals[key(i, j)] === undefined) {
|
if (grid_vals[key(i, j)] === undefined) {
|
||||||
grid_vals[key(i, j)] = {
|
grid_vals[key(i, j)] = {
|
||||||
raw_val: val,
|
raw_val: val,
|
||||||
|
isErr: false,
|
||||||
val: undefined
|
val: undefined
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
grid_vals[key(i, j)].raw_val = val;
|
grid_vals[key(i, j)].raw_val = val;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const getCellVal = (i: number, j: number) => getCell(i, j)?.val ?? undefined;
|
const getCellVal = (i: number, j: number) => getCell(i, j);
|
||||||
const setCellVal = (i: number, j: number, val: LiteralValue) => {
|
const setCellVal = (i: number, j: number, val: LiteralValue, isErr: boolean) => {
|
||||||
if (grid_vals[key(i, j)] === undefined) {
|
if (grid_vals[key(i, j)] === undefined) {
|
||||||
console.warn('Cell raw value was undefined but recieved eval.');
|
console.warn('Cell raw value was undefined but recieved eval.');
|
||||||
} else {
|
} else {
|
||||||
grid_vals[key(i, j)].val = val;
|
let cell = grid_vals[key(i, j)];
|
||||||
|
cell.val = val;
|
||||||
|
cell.isErr = isErr;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const setCell = (row: number, col: number, v: string | undefined) => {
|
const setCell = (row: number, col: number, v: CellT | undefined) => {
|
||||||
// ignore “no value” so we don’t create keys on mount
|
// ignore “no value” so we don’t create keys on mount
|
||||||
if (v == null || v === '') delete grid_vals[key(row, col)];
|
if (v?.raw_val == null || v.raw_val === '') delete grid_vals[key(row, col)];
|
||||||
else {
|
else {
|
||||||
setCellRaw(row, col, v);
|
setCellRaw(row, col, v.raw_val);
|
||||||
|
|
||||||
let msg: LeadMsg = {
|
let msg: LeadMsg = {
|
||||||
msg_type: 'set',
|
msg_type: 'set',
|
||||||
cell: { row, col },
|
cell: { row, col },
|
||||||
raw: v
|
raw: v.raw_val
|
||||||
};
|
};
|
||||||
|
|
||||||
socket.send(JSON.stringify(msg));
|
socket.send(JSON.stringify(msg));
|
||||||
@@ -237,8 +240,7 @@
|
|||||||
onmousedown={(e) => {
|
onmousedown={(e) => {
|
||||||
handleCellInteraction(i, j, e);
|
handleCellInteraction(i, j, e);
|
||||||
}}
|
}}
|
||||||
bind:raw_val={() => getCellRaw(i, j), (v) => setCell(i, j, v)}
|
bind:cell={() => getCell(i, j), (v) => setCell(i, j, v)}
|
||||||
val={getCellVal(i, j)}
|
|
||||||
active={active_cell !== null && active_cell[0] === i && active_cell[1] === j}
|
active={active_cell !== null && active_cell[0] === i && active_cell[1] === j}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
|
|||||||
@@ -25,5 +25,16 @@ interface EvalCellRef {
|
|||||||
reference: CellRef;
|
reference: CellRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface LeadErr {
|
||||||
|
code: 'DivZero' | 'TypeErr' | 'Syntax' | 'Server' | 'Unsupported';
|
||||||
|
desc: string;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
// Tagged union
|
// Tagged union
|
||||||
type Eval = { literal: Literal } | { cellref: EvalCellRef } | { range: Range } | 'unset';
|
type Eval =
|
||||||
|
| { literal: Literal }
|
||||||
|
| { cellref: EvalCellRef }
|
||||||
|
| { range: Range }
|
||||||
|
| { err: LeadErr }
|
||||||
|
| 'unset';
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
export interface CellT {
|
export interface CellT {
|
||||||
raw_val: string;
|
raw_val: string;
|
||||||
val: LiteralValue | undefined;
|
val: LiteralValue | undefined;
|
||||||
|
isErr: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,6 +48,12 @@ export function getEvalLiteral(value: Eval): LiteralValue {
|
|||||||
if (value === 'unset') return '';
|
if (value === 'unset') return '';
|
||||||
if ('literal' in value) return value.literal.value;
|
if ('literal' in value) return value.literal.value;
|
||||||
if ('cellref' in value) return getEvalLiteral(value.cellref.eval);
|
if ('cellref' in value) return getEvalLiteral(value.cellref.eval);
|
||||||
|
if ('err' in value) return `err: ${value.err.code}`;
|
||||||
// if ('range' in value) return 'err';
|
// if ('range' in value) return 'err';
|
||||||
return 'todo!';
|
return 'todo!';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isErr(value: Eval): boolean {
|
||||||
|
if (value === 'unset') return false;
|
||||||
|
return 'err' in value;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user