use std::borrow::Cow;
use std::cell::Cell as Counter;
use std::cmp::Ordering;
use std::collections::BTreeMap;
use crate::intrinsics::fold;
use crate::rows::quoted;
use crate::sheet::Workbook;
use super::formula::{self, BinOp, Expr, RefExpr};
use super::graph::{areas_at, cells_in, Area, Areas, CellId, Extent};
use super::value::{ErrorKind, Value};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Reason {
Function(String),
Construct(String),
Cycle,
}
impl Reason {
pub fn spell(&self) -> String {
match self {
Reason::Function(name) | Reason::Construct(name) => {
format!("(not-computed {})", quoted(name))
}
Reason::Cycle => "(not-computed cycle)".to_string(),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Computed {
Value(Value),
NotComputed(Reason),
}
impl Computed {
pub fn spell(&self) -> String {
match self {
Computed::Value(v) => v.spell(),
Computed::NotComputed(r) => r.spell(),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Arg<'e> {
Scalar(Value),
Area {
areas: Areas,
written: Cow<'e, str>,
moved: (i64, i64),
},
}
impl Arg<'_> {
pub fn into_owned(self) -> Arg<'static> {
match self {
Arg::Scalar(v) => Arg::Scalar(v),
Arg::Area {
areas,
written,
moved,
} => Arg::Area {
areas,
written: Cow::Owned(written.into_owned()),
moved,
},
}
}
}
pub trait Env {
fn model(&self) -> &Workbook;
fn here(&self) -> CellId;
fn read(&self, id: CellId) -> Result<Value, Reason>;
fn extent(&self, sheet: usize) -> Extent;
fn name(&self, name: &str, scope: Option<usize>, home: usize) -> Option<&str>;
}
pub type Strict = fn(&Ctx<'_>, &[Arg<'_>]) -> Result<Value, Reason>;
pub type Form = for<'e> fn(&Ctx<'_>, &'e [Expr]) -> Result<Arg<'e>, Reason>;
#[derive(Default)]
pub struct Library {
strict: BTreeMap<String, Strict>,
forms: BTreeMap<String, Form>,
}
impl Library {
pub fn empty() -> Library {
Library::default()
}
pub fn register(&mut self, name: &str, f: Strict) {
let key = name.to_ascii_uppercase();
self.forms.remove(&key);
self.strict.insert(key, f);
}
pub fn register_form(&mut self, name: &str, f: Form) {
let key = name.to_ascii_uppercase();
self.strict.remove(&key);
self.forms.insert(key, f);
}
pub fn strict(&self, name: &str) -> Option<Strict> {
self.strict.get(&*key(name)).copied()
}
pub fn form(&self, name: &str) -> Option<Form> {
self.forms.get(&*key(name)).copied()
}
pub fn has(&self, name: &str) -> bool {
let wanted = key(name);
self.strict.contains_key(&*wanted) || self.forms.contains_key(&*wanted)
}
pub fn names(&self) -> Vec<&str> {
let mut names: Vec<&str> = self
.strict
.keys()
.chain(self.forms.keys())
.map(String::as_str)
.collect();
names.sort_unstable();
names
}
pub fn len(&self) -> usize {
self.strict.len() + self.forms.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
fn key(name: &str) -> Cow<'_, str> {
if name.bytes().any(|b| b.is_ascii_lowercase()) {
Cow::Owned(name.to_ascii_uppercase())
} else {
Cow::Borrowed(name)
}
}
const NAME_DEPTH: u32 = 16;
pub struct Ctx<'a> {
pub env: &'a dyn Env,
pub lib: &'a Library,
depth: Counter<u32>,
offset: Counter<(i64, i64)>,
}
impl<'a> Ctx<'a> {
pub fn new(env: &'a dyn Env, lib: &'a Library) -> Ctx<'a> {
Ctx::at(env, lib, 0, 0)
}
pub fn at(env: &'a dyn Env, lib: &'a Library, d_row: i64, d_col: i64) -> Ctx<'a> {
Ctx {
env,
lib,
depth: Counter::new(0),
offset: Counter::new((d_row, d_col)),
}
}
pub fn eval<'e>(&self, e: &'e Expr) -> Result<Arg<'e>, Reason> {
match e {
Expr::Number(v) => Ok(Arg::Scalar(Value::Number(*v))),
Expr::Text(t) => Ok(Arg::Scalar(Value::Text(t.clone()))),
Expr::Bool(b) => Ok(Arg::Scalar(Value::Bool(*b))),
Expr::Error(k) => Ok(Arg::Scalar(Value::Error(*k))),
Expr::Empty => Ok(Arg::Scalar(Value::Empty)),
Expr::Ref(r) => self.reference(r),
Expr::Name { name, sheet } => self.named(name, sheet.as_deref()),
Expr::Call(name, args) => self.call(name, args),
Expr::Neg(x) => {
let v = self.scalar(x)?;
Ok(Arg::Scalar(match v.number() {
Ok(n) => finite(-n),
Err(k) => Value::Error(k),
}))
}
Expr::Pos(x) => self.eval(x),
Expr::Percent(x) => {
let v = self.scalar(x)?;
Ok(Arg::Scalar(match v.number() {
Ok(n) => finite(n / 100.0),
Err(k) => Value::Error(k),
}))
}
Expr::Binary(op, l, r) => {
let a = self.scalar(l)?;
let b = self.scalar(r)?;
Ok(Arg::Scalar(binary(*op, &a, &b)))
}
}
}
pub fn scalar(&self, e: &Expr) -> Result<Value, Reason> {
let arg = self.eval(e)?;
self.one(&arg)
}
pub fn one(&self, arg: &Arg<'_>) -> Result<Value, Reason> {
match arg {
Arg::Scalar(v) => Ok(v.clone()),
Arg::Area {
areas,
written,
moved,
} => match &areas[..] {
[a] if a.is_cell() => self.env.read((a.sheet, a.top, a.left)),
_ if *moved == (0, 0) => Err(Reason::Construct(written.to_string())),
_ => Err(Reason::Construct(crate::refers::shift_a1_references(
written, moved.0, moved.1,
))),
},
}
}
pub fn truth(&self, e: &Expr) -> Result<Result<bool, ErrorKind>, Reason> {
Ok(self.scalar(e)?.truth())
}
pub fn truth_arg(&self, arg: &Arg<'_>) -> Result<Result<bool, ErrorKind>, Reason> {
Ok(self.one(arg)?.truth())
}
pub fn each<F>(&self, areas: &[Area], mut f: F) -> Result<(), Reason>
where
F: FnMut(Value) -> Result<(), Reason>,
{
let model = self.env.model();
for a in areas {
let Some(sheet) = model.sheets.get(a.sheet) else {
continue;
};
for ((row, col), _) in cells_in(sheet, self.env.extent(a.sheet), a) {
let v = self.env.read((a.sheet, row, col))?;
if v != Value::Empty {
f(v)?;
}
}
}
Ok(())
}
fn reference<'e>(&self, r: &'e RefExpr) -> Result<Arg<'e>, Reason> {
let home = self.env.here().0;
let (d_row, d_col) = self.offset.get();
match areas_at(self.env.model(), home, r, d_row, d_col) {
Ok(areas) => Ok(Arg::Area {
areas,
written: Cow::Borrowed(&r.written),
moved: (d_row, d_col),
}),
Err(k) => Ok(Arg::Scalar(Value::Error(k))),
}
}
fn named(&self, name: &str, sheet: Option<&str>) -> Result<Arg<'static>, Reason> {
let model = self.env.model();
let home = self.env.here().0;
let scope = match sheet {
None => None,
Some(s) => match model.find_sheet(s) {
Some(i) => Some(i),
None => return Ok(Arg::Scalar(Value::Error(ErrorKind::Ref))),
},
};
let Some(text) = self.env.name(name, scope, home) else {
return Ok(Arg::Scalar(Value::Error(ErrorKind::Name)));
};
if self.depth.get() >= NAME_DEPTH {
return Ok(Arg::Scalar(Value::Error(ErrorKind::Name)));
}
let parsed = formula::parse(text).map_err(Reason::Construct)?;
let offset = self.offset.replace((0, 0));
self.depth.set(self.depth.get() + 1);
let result = self.eval(&parsed).map(Arg::into_owned);
self.depth.set(self.depth.get() - 1);
self.offset.set(offset);
result
}
fn call<'e>(&self, name: &str, args: &'e [Expr]) -> Result<Arg<'e>, Reason> {
if let Some(form) = self.lib.form(name) {
return form(self, args);
}
if let Some(f) = self.lib.strict(name) {
let mut values = Vec::with_capacity(args.len());
for a in args {
values.push(self.eval(a)?);
}
return f(self, &values).map(Arg::Scalar);
}
Err(Reason::Function(name.to_string()))
}
}
pub fn finite(v: f64) -> Value {
if v == 0.0 {
Value::Number(0.0)
} else if v.is_finite() {
Value::Number(v)
} else {
Value::Error(ErrorKind::Num)
}
}
pub fn binary(op: BinOp, a: &Value, b: &Value) -> Value {
if let Value::Error(k) = a {
return Value::Error(*k);
}
if let Value::Error(k) = b {
return Value::Error(*k);
}
match op {
BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div | BinOp::Pow => {
let x = match a.number() {
Ok(n) => n,
Err(k) => return Value::Error(k),
};
let y = match b.number() {
Ok(n) => n,
Err(k) => return Value::Error(k),
};
match op {
BinOp::Add => finite(x + y),
BinOp::Sub => finite(x - y),
BinOp::Mul => finite(x * y),
BinOp::Div => {
if y == 0.0 {
Value::Error(ErrorKind::Div0)
} else {
finite(x / y)
}
}
_ => power(x, y),
}
}
BinOp::Concat => match (a.text(), b.text()) {
(Ok(s), Ok(t)) => Value::Text(s + &t),
(Err(k), _) | (_, Err(k)) => Value::Error(k),
},
BinOp::Eq => Value::Bool(compare(a, b) == Ordering::Equal),
BinOp::Ne => Value::Bool(compare(a, b) != Ordering::Equal),
BinOp::Lt => Value::Bool(compare(a, b) == Ordering::Less),
BinOp::Gt => Value::Bool(compare(a, b) == Ordering::Greater),
BinOp::Le => Value::Bool(compare(a, b) != Ordering::Greater),
BinOp::Ge => Value::Bool(compare(a, b) != Ordering::Less),
}
}
fn power(x: f64, y: f64) -> Value {
if x == 0.0 && y == 0.0 {
return Value::Error(ErrorKind::Num);
}
if x == 0.0 && y < 0.0 {
return Value::Error(ErrorKind::Div0);
}
finite(x.powf(y))
}
pub fn compare(a: &Value, b: &Value) -> Ordering {
use Value::*;
match (a, b) {
(Empty, Empty) => Ordering::Equal,
(Empty, Number(y)) => 0.0f64.partial_cmp(y).unwrap_or(Ordering::Equal),
(Number(x), Empty) => x.partial_cmp(&0.0).unwrap_or(Ordering::Equal),
(Empty, Text(t)) => {
if t.is_empty() {
Ordering::Equal
} else {
Ordering::Less
}
}
(Text(t), Empty) => {
if t.is_empty() {
Ordering::Equal
} else {
Ordering::Greater
}
}
(Empty, Bool(y)) => false.cmp(y),
(Bool(x), Empty) => x.cmp(&false),
(Number(x), Number(y)) => x.partial_cmp(y).unwrap_or(Ordering::Equal),
(Text(x), Text(y)) => fold(x).cmp(&fold(y)),
(Bool(x), Bool(y)) => x.cmp(y),
_ => rank(a).cmp(&rank(b)),
}
}
fn rank(v: &Value) -> u8 {
match v {
Value::Number(_) | Value::Empty => 0,
Value::Text(_) => 1,
Value::Bool(_) => 2,
Value::Error(_) => 3,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn operators_coerce_as_excel_does() {
let n = |v: f64| Value::Number(v);
let t = |s: &str| Value::Text(s.to_string());
assert_eq!(binary(BinOp::Add, &t("5"), &n(1.0)), n(6.0));
assert_eq!(
binary(BinOp::Add, &t("x"), &n(1.0)),
Value::Error(ErrorKind::Value)
);
assert_eq!(binary(BinOp::Mul, &Value::Bool(true), &n(3.0)), n(3.0));
assert_eq!(binary(BinOp::Add, &Value::Empty, &n(1.0)), n(1.0));
assert_eq!(
binary(BinOp::Div, &n(1.0), &n(0.0)),
Value::Error(ErrorKind::Div0)
);
assert_eq!(
binary(BinOp::Div, &n(1.0), &Value::Empty),
Value::Error(ErrorKind::Div0)
);
assert_eq!(
binary(BinOp::Pow, &n(0.0), &n(0.0)),
Value::Error(ErrorKind::Num)
);
assert_eq!(
binary(BinOp::Pow, &n(0.0), &n(-1.0)),
Value::Error(ErrorKind::Div0)
);
assert_eq!(
binary(BinOp::Pow, &n(-8.0), &n(1.0 / 3.0)),
Value::Error(ErrorKind::Num)
);
assert_eq!(binary(BinOp::Pow, &n(2.0), &n(10.0)), n(1024.0));
assert_eq!(
binary(BinOp::Mul, &n(1e308), &n(10.0)),
Value::Error(ErrorKind::Num)
);
assert_eq!(binary(BinOp::Concat, &n(1.0), &n(2.0)), t("12"));
assert_eq!(
binary(BinOp::Concat, &Value::Bool(true), &Value::Empty),
t("TRUE")
);
assert_eq!(binary(BinOp::Concat, &n(0.1 + 0.2), &t("")), t("0.3"));
assert_eq!(
binary(
BinOp::Add,
&Value::Error(ErrorKind::NA),
&Value::Error(ErrorKind::Div0)
),
Value::Error(ErrorKind::NA)
);
}
#[test]
fn comparisons_follow_excel_s_order_of_kinds() {
let n = |v: f64| Value::Number(v);
let t = |s: &str| Value::Text(s.to_string());
let yes = Value::Bool(true);
assert_eq!(binary(BinOp::Eq, &t("ABC"), &t("abc")), yes);
assert_eq!(binary(BinOp::Lt, &n(1e9), &t("")), yes);
assert_eq!(binary(BinOp::Gt, &Value::Bool(false), &t("zzz")), yes);
assert_eq!(binary(BinOp::Eq, &Value::Empty, &n(0.0)), yes);
assert_eq!(binary(BinOp::Eq, &Value::Empty, &t("")), yes);
assert_eq!(binary(BinOp::Eq, &Value::Empty, &Value::Bool(false)), yes);
assert_eq!(binary(BinOp::Ne, &n(1.0), &t("1")), yes);
assert_eq!(binary(BinOp::Le, &n(2.0), &n(2.0)), yes);
assert_eq!(binary(BinOp::Ge, &n(1.0), &n(2.0)), Value::Bool(false));
}
#[test]
fn the_library_is_keyed_without_case_and_a_name_holds_one_kind() {
let mut lib = Library::empty();
fn one(_: &Ctx<'_>, _: &[Arg]) -> Result<Value, Reason> {
Ok(Value::Number(1.0))
}
fn two<'e>(_: &Ctx<'_>, _: &'e [Expr]) -> Result<Arg<'e>, Reason> {
Ok(Arg::Scalar(Value::Number(2.0)))
}
lib.register("one", one);
lib.register_form("Two", two);
assert!(lib.has("ONE") && lib.has("two"));
assert_eq!(lib.names(), vec!["ONE", "TWO"]);
assert!(lib.strict("one").is_some() && lib.form("one").is_none());
lib.register_form("one", two);
assert!(lib.strict("one").is_none() && lib.form("one").is_some());
assert_eq!(lib.len(), 2);
}
#[test]
fn a_name_is_upper_cased_only_when_that_would_change_it() {
fn one(_: &Ctx<'_>, _: &[Arg]) -> Result<Value, Reason> {
Ok(Value::Number(1.0))
}
assert!(matches!(key("SUM"), Cow::Borrowed("SUM")));
assert!(matches!(key("ÄRGER"), Cow::Borrowed("ÄRGER")));
assert_eq!(key("Sum"), "SUM");
assert_eq!(key("ärger"), "äRGER");
let mut lib = Library::empty();
lib.register("Ärger", one);
lib.register("sum", one);
assert_eq!(lib.names(), vec!["SUM", "ÄRGER"]);
for (name, found) in [
("SUM", true),
("Sum", true),
("sum", true),
("ÄRGER", true),
("Ärger", true),
("ärger", false),
("äRGER", false),
] {
assert_eq!(lib.strict(name).is_some(), found, "{name}");
assert_eq!(lib.has(name), found, "{name}");
assert!(lib.form(name).is_none(), "{name}");
}
}
}