use std::borrow::Cow;
use crate::refers::Shape as Part;
use super::eval::{
arithmetic, compare_numbers, compares, finite, Arg, Ctx, Env, Known, Library, Reason, Strict,
};
use super::formula::{moved_at, BinOp, Corners, Expr, RefExpr};
use super::graph::{cells_in, Area, Areas, CellId};
use super::library::{self, All, Axis, Fold, Least, Most, Rule, Some_, Sum};
use super::value::{ErrorKind, Value};
use super::vector::{ref_operand, yields_value};
const REGISTERS: usize = 32;
const DEPTH: u32 = 48;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Why {
Text,
Error,
Name,
Range,
Form,
Kinds,
Span,
Sheet,
Omitted,
Deep,
}
impl Why {
pub(crate) fn word(self) -> &'static str {
match self {
Why::Text => "text",
Why::Error => "error",
Why::Name => "name",
Why::Range => "range",
Why::Form => "form",
Why::Kinds => "kinds",
Why::Span => "span",
Why::Sheet => "sheet",
Why::Omitted => "omitted",
Why::Deep => "deep",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Kind {
Number,
Truth,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) enum Answer {
Number(f64),
Truth(bool),
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum Operand {
Reg(u8, Kind),
Ref(u16),
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum Op {
Const {
to: u8,
x: f64,
},
Read {
to: u8,
at: u16,
},
Arith {
to: u8,
op: BinOp,
b: u8,
},
Compare {
to: u8,
op: BinOp,
b: u8,
},
Neg {
to: u8,
},
Percent {
to: u8,
},
Not {
to: u8,
},
Abs {
to: u8,
},
Int {
to: u8,
},
Sin {
to: u8,
},
Mod {
to: u8,
b: u8,
},
Here {
to: u8,
axis: Axis,
},
Placed {
to: u8,
axis: Axis,
at: u16,
},
Fold {
to: u8,
known: Known,
from: u16,
count: u16,
},
Call {
to: u8,
f: u16,
from: u16,
count: u16,
},
Unless {
cond: u8,
to: u16,
},
Jump {
to: u16,
},
Choose {
index: u8,
from: u16,
count: u16,
},
Done {
from: u8,
},
}
#[derive(Clone, Copy, Debug, PartialEq)]
struct Ref {
sheet: Option<u8>,
shape: Part,
corners: Corners,
rect: (u32, u32, u32, u32),
text: u16,
}
impl Ref {
#[inline]
fn moved(&self, off: (i64, i64)) -> Option<(u32, u32, u32, u32)> {
moved_at(self.shape, &self.corners, self.rect, off.0, off.1)
}
#[inline]
fn sheet(&self, home: usize) -> usize {
self.sheet.map_or(home, usize::from)
}
#[inline]
fn area(&self, home: usize, off: (i64, i64)) -> Option<Area> {
let (top, left, bottom, right) = self.moved(off)?;
Some(Area {
sheet: self.sheet(home),
top,
left,
bottom,
right,
})
}
}
#[derive(Clone)]
pub(crate) struct Program {
code: Vec<Op>,
refs: Vec<Ref>,
texts: Vec<String>,
operands: Vec<Operand>,
table: Vec<u16>,
calls: Vec<Strict>,
kind: Kind,
}
impl std::fmt::Debug for Program {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Program")
.field("code", &self.code)
.field("refs", &self.refs)
.field("texts", &self.texts)
.field("operands", &self.operands)
.field("table", &self.table)
.field("calls", &self.calls.len())
.field("kind", &self.kind)
.finish()
}
}
pub(crate) trait Source: Env {
fn number(&self, id: CellId) -> Option<f64> {
number_of(self.read(id))
}
}
pub(crate) fn number_of(got: Result<Value, Reason>) -> Option<f64> {
match got {
Ok(Value::Number(n)) => Some(n),
Ok(Value::Empty) => Some(0.0),
_ => None,
}
}
#[inline]
fn truth(b: bool) -> f64 {
if b {
1.0
} else {
0.0
}
}
#[inline]
fn num(v: Value) -> Option<f64> {
match v {
Value::Number(n) => Some(n),
_ => guard(guarded::ANSWER),
}
}
#[inline]
fn guard<T>(_which: usize) -> Option<T> {
#[cfg(test)]
guarded::note(_which);
None
}
impl Program {
pub(crate) fn lower(lib: &Library, e: &Expr) -> Result<Program, Why> {
let mut l = Lowering {
lib,
code: Vec::new(),
refs: Vec::new(),
texts: Vec::new(),
operands: Vec::new(),
table: Vec::new(),
calls: Vec::new(),
depth: 0,
};
let kind = l.expr(e, 0)?;
l.code.push(Op::Done { from: 0 });
if l.code.len() > usize::from(u16::MAX) {
return Err(Why::Deep);
}
l.code.shrink_to_fit();
l.refs.shrink_to_fit();
l.operands.shrink_to_fit();
Ok(Program {
code: l.code,
refs: l.refs,
texts: l.texts,
operands: l.operands,
table: l.table,
calls: l.calls,
kind,
})
}
pub(crate) fn run<S: Source>(
&self,
src: &S,
lib: &Library,
id: CellId,
off: (i64, i64),
) -> Option<Answer> {
let mut reg = [0.0f64; REGISTERS];
let mut pc = 0usize;
loop {
match self.code[pc] {
Op::Const { to, x } => reg[usize::from(to)] = x,
Op::Read { to, at } => {
let r = &self.refs[usize::from(at)];
let Some((row, col, _, _)) = r.moved(off) else {
return guard(guarded::OFF);
};
let Some(x) = src.number((r.sheet(id.0), row, col)) else {
return guard(guarded::READ);
};
reg[usize::from(to)] = x;
}
Op::Arith { to, op, b } => {
let t = usize::from(to);
reg[t] = num(arithmetic(op, reg[t], reg[usize::from(b)]))?;
}
Op::Compare { to, op, b } => {
let t = usize::from(to);
reg[t] = truth(compares(op, compare_numbers(reg[t], reg[usize::from(b)])));
}
Op::Neg { to } => {
let t = usize::from(to);
reg[t] = num(finite(-reg[t]))?;
}
Op::Percent { to } => {
let t = usize::from(to);
reg[t] = num(finite(reg[t] / 100.0))?;
}
Op::Not { to } => {
let t = usize::from(to);
reg[t] = truth(reg[t] == 0.0);
}
Op::Abs { to } => {
let t = usize::from(to);
reg[t] = num(library::abs_of(reg[t]))?;
}
Op::Int { to } => {
let t = usize::from(to);
reg[t] = num(library::int_of(reg[t]))?;
}
Op::Sin { to } => {
let t = usize::from(to);
reg[t] = num(library::sin_of(reg[t]))?;
}
Op::Mod { to, b } => {
let t = usize::from(to);
reg[t] = num(library::mod_of(reg[t], reg[usize::from(b)]))?;
}
Op::Here { to, axis } => reg[usize::from(to)] = axis.of(id.1, id.2),
Op::Placed { to, axis, at } => {
let Some((top, left, _, _)) = self.refs[usize::from(at)].moved(off) else {
return guard(guarded::OFF);
};
reg[usize::from(to)] = axis.of(top, left);
}
Op::Fold {
to,
known,
from,
count,
} => {
let from = usize::from(from);
let ops = &self.operands[from..from + usize::from(count)];
reg[usize::from(to)] = match known {
Known::Sum => self.fold::<Sum, S>(src, ops, ®, id, off),
Known::Min => self.fold::<Least, S>(src, ops, ®, id, off),
Known::Max => self.fold::<Most, S>(src, ops, ®, id, off),
Known::And => self.fold::<All, S>(src, ops, ®, id, off),
_ => self.fold::<Some_, S>(src, ops, ®, id, off),
}?;
}
Op::Call { to, f, from, count } => {
let from = usize::from(from);
let ops = &self.operands[from..from + usize::from(count)];
let f = self.calls[usize::from(f)];
reg[usize::from(to)] = self.call(src, lib, f, ops, ®, id, off)?;
}
Op::Unless { cond, to } => {
if reg[usize::from(cond)] == 0.0 {
pc = usize::from(to);
continue;
}
}
Op::Jump { to } => {
pc = usize::from(to);
continue;
}
Op::Choose { index, from, count } => {
let index = Value::Number(reg[usize::from(index)]);
let Ok(k) = library::chosen(&index, usize::from(count)) else {
return guard(guarded::INDEX);
};
pc = usize::from(self.table[usize::from(from) + k - 1]);
continue;
}
Op::Done { from } => {
let x = reg[usize::from(from)];
return Some(match self.kind {
Kind::Number => Answer::Number(x),
Kind::Truth => Answer::Truth(x != 0.0),
});
}
}
pc += 1;
}
}
#[inline(never)]
fn fold<F: Fold, S: Source>(
&self,
src: &S,
ops: &[Operand],
reg: &[f64; REGISTERS],
id: CellId,
off: (i64, i64),
) -> Option<f64> {
let mut fold = F::default();
for op in ops {
match *op {
Operand::Reg(r, _) => {
fold.add(<F::Rule as Rule>::given_number(reg[usize::from(r)]));
}
Operand::Ref(at) => {
let Some(a) = self.refs[usize::from(at)].area(id.0, off) else {
return guard(guarded::OFF);
};
walk::<F::Rule, S, _>(src, &a, &mut |x| fold.add(x))?;
}
}
}
match fold.answer() {
Value::Number(n) => Some(n),
Value::Bool(b) => Some(truth(b)),
_ => guard(guarded::ANSWER),
}
}
#[allow(clippy::too_many_arguments)]
#[inline(never)]
fn call<S: Source>(
&self,
src: &S,
lib: &Library,
f: Strict,
ops: &[Operand],
reg: &[f64; REGISTERS],
id: CellId,
off: (i64, i64),
) -> Option<f64> {
let ctx = Ctx::at(src, lib, off.0, off.1);
let arg = |op: &Operand| -> Arg<'_> {
match *op {
Operand::Reg(r, Kind::Number) => Arg::Scalar(Value::Number(reg[usize::from(r)])),
Operand::Reg(r, Kind::Truth) => {
Arg::Scalar(Value::Bool(reg[usize::from(r)] != 0.0))
}
Operand::Ref(at) => {
let r = &self.refs[usize::from(at)];
match r.area(id.0, off) {
Some(a) => Arg::Area {
areas: Areas::one(a),
written: Cow::Borrowed(&self.texts[usize::from(r.text)]),
moved: off,
},
None => Arg::Scalar(Value::Error(ErrorKind::Ref)),
}
}
}
};
let got = match ops {
[] => f(&ctx, &[]),
[a] => f(&ctx, &[arg(a)]),
[a, b] => f(&ctx, &[arg(a), arg(b)]),
_ => {
let args: Vec<Arg> = ops.iter().map(arg).collect();
f(&ctx, &args)
}
};
match got {
Ok(Value::Number(n)) => Some(n),
_ => guard(guarded::CALL),
}
}
}
fn walk<R: Rule, S: Source, F: FnMut(R::Item)>(src: &S, a: &Area, f: &mut F) -> Option<()> {
if let Some(kept) = src.dense(a.sheet) {
let walked = kept.each_ref(a, |v| match v {
Value::Number(n) => {
f(R::ranged_number(*n));
Ok(())
}
_ => Err(()),
});
return match walked {
Ok(()) => Some(()),
Err(()) => guard(guarded::RANGE),
};
}
let Some(sheet) = src.model().sheets.get(a.sheet) else {
return Some(());
};
for ((row, col), _) in cells_in(sheet, src.extent(a.sheet), a) {
match src.read((a.sheet, row, col)) {
Ok(Value::Number(n)) => f(R::ranged_number(n)),
_ => return guard(guarded::RANGE),
}
}
Some(())
}
fn may_be_nothing(lib: &Library, e: &Expr) -> bool {
match e {
Expr::Empty => true,
Expr::Pos(x) => may_be_nothing(lib, x),
Expr::Call(name, args) if lib.form(name).is_some() => match lib.known(name) {
Some(Known::If) => args
.iter()
.skip(1)
.any(|a| !matches!(a, Expr::Empty) && may_be_nothing(lib, a)),
Some(Known::Choose) => args.iter().skip(1).any(|a| may_be_nothing(lib, a)),
_ => false,
},
_ => false,
}
}
struct Lowering<'l> {
lib: &'l Library,
code: Vec<Op>,
refs: Vec<Ref>,
texts: Vec<String>,
operands: Vec<Operand>,
table: Vec<u16>,
calls: Vec<Strict>,
depth: u32,
}
impl Lowering<'_> {
fn reg(at: usize) -> Result<u8, Why> {
if at < REGISTERS {
Ok(at as u8)
} else {
Err(Why::Deep)
}
}
fn here(&self) -> Result<u16, Why> {
u16::try_from(self.code.len()).map_err(|_| Why::Deep)
}
fn expr(&mut self, e: &Expr, at: usize) -> Result<Kind, Why> {
self.depth += 1;
let kind = if self.depth > DEPTH {
Err(Why::Deep)
} else {
self.node(e, at)
};
self.depth -= 1;
kind
}
fn node(&mut self, e: &Expr, at: usize) -> Result<Kind, Why> {
let to = Self::reg(at)?;
match e {
Expr::Number(v) => {
self.code.push(Op::Const { to, x: *v });
Ok(Kind::Number)
}
Expr::Bool(b) => {
self.code.push(Op::Const { to, x: truth(*b) });
Ok(Kind::Truth)
}
Expr::Ref(r) => {
if r.last_sheet.is_some() {
return Err(Why::Span);
}
if !matches!(r.shape, Part::Cell) {
return Err(Why::Range);
}
let at = self.refer(r, false)?;
self.code.push(Op::Read { to, at });
Ok(Kind::Number)
}
Expr::Neg(x) => {
self.expr(x, at)?;
self.code.push(Op::Neg { to });
Ok(Kind::Number)
}
Expr::Percent(x) => {
self.expr(x, at)?;
self.code.push(Op::Percent { to });
Ok(Kind::Number)
}
Expr::Pos(x) => self.expr(x, at),
Expr::Binary(..) => self.chain(e, at),
Expr::Call(name, args) => self.call(name, args, at),
Expr::Text(_) => Err(Why::Text),
Expr::Error(_) => Err(Why::Error),
Expr::Empty => Err(Why::Omitted),
Expr::Name { .. } => Err(Why::Name),
}
}
fn chain(&mut self, e: &Expr, at: usize) -> Result<Kind, Why> {
let mut links: Vec<(BinOp, &Expr)> = Vec::new();
let mut left = e;
while let Expr::Binary(op, l, r) = left {
links.push((*op, r));
left = l;
}
let (to, b) = (Self::reg(at)?, Self::reg(at + 1)?);
let mut kind = self.expr(left, at)?;
for &(op, r) in links.iter().rev() {
if op == BinOp::Concat {
return Err(Why::Text);
}
let k = self.expr(r, at + 1)?;
kind = match op {
BinOp::Add | BinOp::Sub | BinOp::Mul | BinOp::Div | BinOp::Pow => {
self.code.push(Op::Arith { to, op, b });
Kind::Number
}
_ => {
if k != kind {
return Err(Why::Kinds);
}
self.code.push(Op::Compare { to, op, b });
Kind::Truth
}
};
}
Ok(kind)
}
fn call(&mut self, name: &str, args: &[Expr], at: usize) -> Result<Kind, Why> {
let to = Self::reg(at)?;
if self.lib.form(name).is_some() {
return match self.lib.known(name) {
Some(Known::If) => self.if_(args, at),
Some(Known::Choose) => self.choose(args, at),
_ => Err(Why::Form),
};
}
let Some(f) = self.lib.strict(name) else {
return Err(Why::Form);
};
let one = |args: &[Expr]| {
if args.len() == 1 {
Ok(())
} else {
Err(Why::Error)
}
};
match self.lib.known(name) {
Some(known @ (Known::Sum | Known::Min | Known::Max | Known::And | Known::Or)) => {
let (from, count) = self.operands(args, at, false)?;
self.code.push(Op::Fold {
to,
known,
from,
count,
});
Ok(match known {
Known::And | Known::Or => Kind::Truth,
_ => Kind::Number,
})
}
Some(Known::Not) => {
one(args)?;
self.expr(&args[0], at)?;
self.code.push(Op::Not { to });
Ok(Kind::Truth)
}
Some(known @ (Known::Abs | Known::Int | Known::Sin)) => {
one(args)?;
self.expr(&args[0], at)?;
self.code.push(match known {
Known::Abs => Op::Abs { to },
Known::Int => Op::Int { to },
_ => Op::Sin { to },
});
Ok(Kind::Number)
}
Some(Known::Mod) => {
let [n, d] = args else {
return Err(Why::Error);
};
self.expr(n, at)?;
self.expr(d, at + 1)?;
let b = Self::reg(at + 1)?;
self.code.push(Op::Mod { to, b });
Ok(Kind::Number)
}
Some(known @ (Known::Row | Known::Column)) => {
let axis = if known == Known::Row {
Axis::Row
} else {
Axis::Column
};
match args {
[] => self.code.push(Op::Here { to, axis }),
[a] => {
let mut a = a;
while let Expr::Pos(x) = a {
a = x;
}
let Expr::Ref(r) = a else {
return Err(Why::Error);
};
let at = self.refer(r, false)?;
self.code.push(Op::Placed { to, axis, at });
}
_ => return Err(Why::Error),
}
Ok(Kind::Number)
}
Some(Known::If | Known::Choose) => Err(Why::Form),
None => {
let (from, count) = self.operands(args, at, true)?;
let f_at = u16::try_from(self.calls.len()).map_err(|_| Why::Deep)?;
self.calls.push(f);
self.code.push(Op::Call {
to,
f: f_at,
from,
count,
});
Ok(Kind::Number)
}
}
}
fn operands(&mut self, args: &[Expr], at: usize, call: bool) -> Result<(u16, u16), Why> {
let mut ops = Vec::with_capacity(args.len());
for (j, a) in args.iter().enumerate() {
let op = match ref_operand(self.lib, a) {
Some(r) => Operand::Ref(self.refer(r, call)?),
None if yields_value(self.lib, a) => {
if call && may_be_nothing(self.lib, a) {
return Err(Why::Omitted);
}
let r = at + 1 + j;
let kind = self.expr(a, r)?;
Operand::Reg(Self::reg(r)?, kind)
}
None => return Err(Why::Range),
};
ops.push(op);
}
let from = u16::try_from(self.operands.len()).map_err(|_| Why::Deep)?;
let count = u16::try_from(ops.len()).map_err(|_| Why::Deep)?;
self.operands.extend(ops);
Ok((from, count))
}
fn refer(&mut self, r: &RefExpr, text: bool) -> Result<u16, Why> {
if r.last_sheet.is_some() {
return Err(Why::Span);
}
if r.found.is_none() && r.sheet.is_some() {
return Err(Why::Sheet);
}
let text = if text {
let t = u16::try_from(self.texts.len()).map_err(|_| Why::Deep)?;
self.texts.push(r.written.clone());
t
} else {
u16::MAX
};
let at = u16::try_from(self.refs.len()).map_err(|_| Why::Deep)?;
self.refs.push(Ref {
sheet: r.found.map(|(first, _)| first),
shape: r.shape,
corners: r.corners,
rect: (r.top, r.left, r.bottom, r.right),
text,
});
Ok(at)
}
fn if_(&mut self, args: &[Expr], at: usize) -> Result<Kind, Why> {
if !(2..=3).contains(&args.len()) {
return Err(Why::Error);
}
let to = Self::reg(at)?;
self.expr(&args[0], at)?;
let unless = self.code.len();
self.code.push(Op::Unless { cond: to, to: 0 });
let then = self.branch(&args[1], at)?;
let skip = self.code.len();
self.code.push(Op::Jump { to: 0 });
self.code[unless] = Op::Unless {
cond: to,
to: self.here()?,
};
let other = match args.get(2) {
None => {
self.code.push(Op::Const { to, x: 0.0 });
Kind::Truth
}
Some(e) => self.branch(e, at)?,
};
self.code[skip] = Op::Jump { to: self.here()? };
if then == other {
Ok(then)
} else {
Err(Why::Kinds)
}
}
fn branch(&mut self, e: &Expr, at: usize) -> Result<Kind, Why> {
match e {
Expr::Empty => {
let to = Self::reg(at)?;
self.code.push(Op::Const { to, x: 0.0 });
Ok(Kind::Number)
}
_ => self.expr(e, at),
}
}
fn choose(&mut self, args: &[Expr], at: usize) -> Result<Kind, Why> {
if args.len() < 2 {
return Err(Why::Error);
}
let to = Self::reg(at)?;
self.expr(&args[0], at)?;
let from = u16::try_from(self.table.len()).map_err(|_| Why::Deep)?;
let count = u16::try_from(args.len() - 1).map_err(|_| Why::Deep)?;
self.table.extend(std::iter::repeat(0).take(args.len() - 1));
self.code.push(Op::Choose {
index: to,
from,
count,
});
let mut kind: Option<Kind> = None;
let mut ends = Vec::with_capacity(args.len() - 1);
for (j, a) in args[1..].iter().enumerate() {
self.table[usize::from(from) + j] = self.here()?;
let k = self.branch(a, at)?;
if kind.is_some_and(|was| was != k) {
return Err(Why::Kinds);
}
kind = Some(k);
ends.push(self.code.len());
self.code.push(Op::Jump { to: 0 });
}
let end = self.here()?;
for e in ends {
self.code[e] = Op::Jump { to: end };
}
Ok(kind.unwrap_or(Kind::Number))
}
}
pub(crate) mod guarded {
pub(crate) const READ: usize = 0;
pub(crate) const OFF: usize = 1;
pub(crate) const ANSWER: usize = 2;
pub(crate) const RANGE: usize = 3;
pub(crate) const INDEX: usize = 4;
pub(crate) const CALL: usize = 5;
#[cfg(test)]
pub(crate) const GUARDS: usize = 6;
#[cfg(test)]
thread_local! {
static COUNTS: std::cell::Cell<[usize; GUARDS]> = const { std::cell::Cell::new([0; GUARDS]) };
}
#[cfg(test)]
pub(crate) fn note(i: usize) {
COUNTS.with(|c| {
let mut a = c.get();
a[i] += 1;
c.set(a);
});
}
#[cfg(test)]
pub(crate) fn take() -> [usize; GUARDS] {
COUNTS.with(|c| c.replace([0; GUARDS]))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::calc::formula::parse;
use crate::calc::library;
fn twice(_: &Ctx<'_>, _: &[Arg<'_>]) -> Result<Value, Reason> {
Ok(Value::Number(2.0))
}
fn first<'e>(ctx: &Ctx<'_>, args: &'e [Expr]) -> Result<Arg<'e>, Reason> {
ctx.eval(&args[0])
}
fn lowered(text: &str) -> Result<Kind, Why> {
let mut lib = library::language();
lib.register("TWICE", twice);
lib.register_form("FIRST", first);
let e = parse(text).unwrap_or_else(|c| panic!("{text}: {c}"));
Program::lower(&lib, &e).map(|p| p.kind)
}
#[test]
fn a_parse_is_lowered_or_left_by_the_first_node_a_program_does_not_hold() {
let deep = format!("{}1", "-".repeat(52));
let shallow = format!("{}1", "-".repeat(46));
let wide = format!("SUM({}1)", "A1+0,".repeat(40));
let cases: Vec<(&str, Result<Kind, Why>)> = vec![
("1+2*3", Ok(Kind::Number)),
("-A1%", Ok(Kind::Number)),
("A1>2", Ok(Kind::Truth)),
("(A1>2)=(B1<1)", Ok(Kind::Truth)),
("AND(A1,B1:B3,1>0)", Ok(Kind::Truth)),
("SUM(A1:B2,3)-MIN(A1)", Ok(Kind::Number)),
("NOT(A1)", Ok(Kind::Truth)),
("MOD(ABS(A1),INT(SIN(B1)))", Ok(Kind::Number)),
("ROW()+COLUMN(B2:C3)", Ok(Kind::Number)),
("IF(A1,,2)", Ok(Kind::Number)),
("IF(A1,A2>1)", Ok(Kind::Truth)),
("CHOOSE(A1,1,,3)", Ok(Kind::Number)),
("TWICE(A1:A2,TRUE)", Ok(Kind::Number)),
("TWICE(IF(A1,,3))", Ok(Kind::Number)),
("SUM(CHOOSE(1,,2))", Ok(Kind::Number)),
(&shallow, Ok(Kind::Number)),
("\"x\"", Err(Why::Text)),
("A1&B1", Err(Why::Text)),
("1+\"x\"", Err(Why::Text)),
("#N/A", Err(Why::Error)),
("NOT(A1,B1)", Err(Why::Error)),
("ABS()", Err(Why::Error)),
("MOD(1)", Err(Why::Error)),
("ROW(1)", Err(Why::Error)),
("ROW(A1,B1)", Err(Why::Error)),
("IF(A1)", Err(Why::Error)),
("CHOOSE(1)", Err(Why::Error)),
("Rate", Err(Why::Name)),
("A1:B2", Err(Why::Range)),
("A:A+1", Err(Why::Range)),
("2:2*3", Err(Why::Range)),
("SUM(IF(A1,A1:A2,1))", Err(Why::Range)),
("FIRST(1)", Err(Why::Form)),
("A1=TRUE", Err(Why::Kinds)),
("IF(A1,1,TRUE)", Err(Why::Kinds)),
("IF(A1,1)", Err(Why::Kinds)),
("CHOOSE(1,2,TRUE)", Err(Why::Kinds)),
("Jan:Dec!A1", Err(Why::Span)),
("SUM(Jan:Dec!A1)", Err(Why::Span)),
("ROW(Jan:Dec!A1)", Err(Why::Span)),
("Data!A1", Err(Why::Sheet)),
("SUM(Data!A1:A2)", Err(Why::Sheet)),
("TWICE(CHOOSE(1,,2))", Err(Why::Omitted)),
("TWICE(IF(A1,CHOOSE(2,1,),3))", Err(Why::Omitted)),
("TWICE(+CHOOSE(2,1,))", Err(Why::Omitted)),
("TWICE(,1)", Err(Why::Omitted)),
("SUM(1,,2)", Err(Why::Omitted)),
(&deep, Err(Why::Deep)),
(&wide, Err(Why::Deep)),
("\"x\"+Rate", Err(Why::Text)),
("Rate+\"x\"", Err(Why::Name)),
];
for (text, want) in cases {
assert_eq!(lowered(text), want, "{text}");
}
}
#[test]
fn each_reason_is_spelled_as_a_census_row_spells_it() {
let all = [
(Why::Text, "text"),
(Why::Error, "error"),
(Why::Name, "name"),
(Why::Range, "range"),
(Why::Form, "form"),
(Why::Kinds, "kinds"),
(Why::Span, "span"),
(Why::Sheet, "sheet"),
(Why::Omitted, "omitted"),
(Why::Deep, "deep"),
];
for (why, word) in all {
assert_eq!(why.word(), word);
}
}
}