1pub(crate) mod census;
44pub mod dense;
45pub mod eval;
46pub mod formula;
47pub mod graph;
48pub mod library;
49pub(crate) mod program;
50pub(crate) mod reach;
51pub mod shape;
52pub mod value;
53pub(crate) mod vector;
54
55use std::cell::Cell as Slot;
56use std::collections::HashMap;
57
58use crate::messages::{raise, Refusal};
59use crate::refers::quote_sheet;
60use crate::sheet::{cell_ref, formula_text, shared_masters, Content, Workbook};
61
62pub use dense::Dense;
63pub use eval::{Arg, Computed, Ctx, Env, Form, Library, Reason, Strict};
64pub use formula::{calls, parse, Expr};
65pub use graph::{Area, Areas, CellId, Extent, Names, Plan};
66pub use shape::{Place, Shape, Shapes};
67pub use value::{agrees, ErrorKind, Value};
68
69#[derive(Clone, Copy, Debug, PartialEq, Eq)]
72pub struct Progress {
73 pub evaluated: usize,
74 pub of: usize,
75 pub done: bool,
76}
77
78pub struct Calc<'m> {
81 model: &'m Workbook,
82 library: &'m Library,
83 names: Names,
84 extents: Vec<Extent>,
85 texts: HashMap<CellId, String>,
86 plan: Plan,
87 values: HashMap<CellId, Computed>,
88 next: usize,
89 here: Slot<CellId>,
90}
91
92impl<'m> Calc<'m> {
93 pub fn new(model: &'m Workbook, library: &'m Library) -> Calc<'m> {
98 let names = Names::of(model);
99 let extents: Vec<Extent> = model.sheets.iter().map(|s| s.extent()).collect();
100 let mut texts = HashMap::new();
101 for (si, sheet) in model.sheets.iter().enumerate() {
102 let masters = shared_masters(sheet);
103 for (&(row, col), cell) in &sheet.cells {
104 if let Some(text) = formula_text(&cell.content, row, col, &masters) {
105 texts.insert((si, row, col), text);
106 }
107 }
108 }
109 let plan = graph::plan(model, &names, &extents, &texts);
110 let mut values = HashMap::with_capacity(texts.len());
111 for cycle in &plan.cycles {
112 for id in cycle {
113 values.insert(*id, Computed::NotComputed(Reason::Cycle));
114 }
115 }
116 Calc {
117 model,
118 library,
119 names,
120 extents,
121 texts,
122 plan,
123 values,
124 next: 0,
125 here: Slot::new((0, 1, 1)),
126 }
127 }
128
129 pub fn run(model: &'m Workbook, library: &'m Library) -> Calc<'m> {
131 let mut calc = Calc::new(model, library);
132 calc.step(0);
133 calc
134 }
135
136 pub fn model(&self) -> &'m Workbook {
138 self.model
139 }
140
141 pub fn library(&self) -> &'m Library {
143 self.library
144 }
145
146 pub fn plan(&self) -> &Plan {
148 &self.plan
149 }
150
151 pub fn cycles(&self) -> &[Vec<CellId>] {
153 &self.plan.cycles
154 }
155
156 pub fn formulas(&self) -> usize {
158 self.texts.len()
159 }
160
161 pub fn formula(&self, id: CellId) -> Option<&str> {
163 self.texts.get(&id).map(String::as_str)
164 }
165
166 pub fn formula_cells(&self) -> Vec<CellId> {
168 let mut ids: Vec<CellId> = self.texts.keys().copied().collect();
169 ids.sort_unstable();
170 ids
171 }
172
173 pub fn computed(&self, id: CellId) -> Option<&Computed> {
176 self.values.get(&id)
177 }
178
179 pub fn step(&mut self, budget: usize) -> Progress {
184 let order_len = self.plan.order.len();
185 let end = if budget == 0 {
186 order_len
187 } else {
188 self.next.saturating_add(budget).min(order_len)
189 };
190 for i in self.next..end {
191 let id = self.plan.order[i];
192 let computed = self.evaluate(id);
193 self.values.insert(id, computed);
194 }
195 self.next = end;
196 self.progress()
197 }
198
199 pub fn progress(&self) -> Progress {
201 let in_cycles = self.plan.cycles.iter().map(Vec::len).sum::<usize>();
202 Progress {
203 evaluated: self.next + in_cycles,
204 of: self.plan.formulas(),
205 done: self.next == self.plan.order.len(),
206 }
207 }
208
209 pub fn restart(&mut self) {
211 self.values
212 .retain(|_, v| matches!(v, Computed::NotComputed(Reason::Cycle)));
213 self.next = 0;
214 }
215
216 fn evaluate(&self, id: CellId) -> Computed {
217 let Some(text) = self.texts.get(&id) else {
218 return Computed::NotComputed(Reason::Construct(String::new()));
219 };
220 self.here.set(id);
221 let parsed = match formula::parse(text) {
222 Ok(e) => e,
223 Err(construct) => return Computed::NotComputed(Reason::Construct(construct)),
224 };
225 let ctx = Ctx::new(self, self.library);
226 match ctx.eval(&parsed).and_then(|arg| ctx.one(&arg)) {
227 Ok(v) => Computed::Value(v.settled()),
228 Err(reason) => Computed::NotComputed(reason),
229 }
230 }
231
232 pub fn missing_functions(&self) -> Vec<(CellId, String)> {
236 let mut seen: HashMap<String, CellId> = HashMap::new();
237 for id in self.formula_cells() {
238 for name in formula::calls(&self.texts[&id]) {
239 if !self.library.has(&name) {
240 seen.entry(name).or_insert(id);
241 }
242 }
243 }
244 let mut out: Vec<(CellId, String)> = seen.into_iter().map(|(n, id)| (id, n)).collect();
245 out.sort();
246 out
247 }
248
249 pub fn spell_cell(&self, id: CellId) -> String {
251 let sheet = self
252 .model
253 .sheets
254 .get(id.0)
255 .map(|s| s.name.as_str())
256 .unwrap_or("");
257 format!("{}!{}", quote_sheet(sheet), cell_ref(id.1, id.2))
258 }
259
260 pub fn first_refusal(&self) -> Option<Refusal> {
271 if let Some(cycle) = self.plan.cycles.first() {
272 let cells: Vec<String> = cycle.iter().map(|id| self.spell_cell(*id)).collect();
273 return Some(raise("calc-cycle", &[("cells", &cells.join(", "))]));
274 }
275 if let Some((id, name)) = self.missing_functions().into_iter().next() {
276 return Some(raise(
277 "calc-function-not-computed",
278 &[("cell", &self.spell_cell(id)), ("function", &name)],
279 ));
280 }
281 let (id, construct) = self.formula_cells().into_iter().find_map(|id| {
282 formula::parse(&self.texts[&id])
283 .err()
284 .map(|construct| (id, construct))
285 })?;
286 Some(formula::unread_refusal(&self.spell_cell(id), &construct))
287 }
288}
289
290pub trait Values: std::fmt::Debug {
296 fn computed(&self, id: CellId) -> Option<&Computed>;
297
298 fn lags(&self, _sheet: usize) -> bool {
304 false
305 }
306
307 fn content(&self, _id: CellId) -> Option<Content> {
310 None
311 }
312
313 fn kept_extent(&self, _sheet: usize) -> Option<Extent> {
320 None
321 }
322}
323
324impl Values for Calc<'_> {
325 fn computed(&self, id: CellId) -> Option<&Computed> {
326 Calc::computed(self, id)
327 }
328
329 fn kept_extent(&self, sheet: usize) -> Option<Extent> {
330 self.extents.get(sheet).copied()
331 }
332}
333
334impl std::fmt::Debug for Calc<'_> {
335 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
336 f.debug_struct("Calc")
337 .field("formulas", &self.texts.len())
338 .field("cycles", &self.plan.cycles.len())
339 .field("progress", &self.progress())
340 .finish()
341 }
342}
343
344impl Env for Calc<'_> {
345 fn model(&self) -> &Workbook {
346 self.model
347 }
348
349 fn here(&self) -> CellId {
350 self.here.get()
351 }
352
353 fn read(&self, id: CellId) -> Result<Value, Reason> {
354 if self.texts.contains_key(&id) {
355 return match self.values.get(&id) {
356 Some(Computed::Value(v)) => Ok(v.clone()),
357 Some(Computed::NotComputed(r)) => Err(r.clone()),
358 None => Err(Reason::Cycle),
361 };
362 }
363 let Some(cell) = self
364 .model
365 .sheets
366 .get(id.0)
367 .and_then(|s| s.cells.get(&(id.1, id.2)))
368 else {
369 return Ok(Value::Empty);
370 };
371 Ok(match &cell.content {
372 Content::Text(t) => Value::Text(t.clone()),
373 Content::Number(n) => Value::Number(*n),
374 Content::Bool(b) => Value::Bool(*b),
375 Content::Error(e) => Value::Error(ErrorKind::parse(e).unwrap_or(ErrorKind::Value)),
376 _ => Value::Empty,
377 })
378 }
379
380 fn extent(&self, sheet: usize) -> Extent {
381 self.extents.get(sheet).copied().flatten()
382 }
383
384 fn name(&self, name: &str, scope: Option<usize>, home: usize) -> Option<&str> {
385 self.names.resolve(name, scope, home)
386 }
387}
388
389#[cfg(test)]
390mod tests {
391 use super::*;
392 use crate::sheet::{parse_a1_range, Cell, Sheet};
393
394 fn model(rows: &[(&str, &str, &str)]) -> Workbook {
397 let mut wb = Workbook::new();
398 for (sheet, addr, text) in rows {
399 let i = wb.ensure_sheet(sheet);
400 let range = parse_a1_range(addr).unwrap();
401 let content = if let Some(f) = text.strip_prefix('=') {
402 Content::Formula(f.to_string())
403 } else if let Ok(n) = text.parse::<f64>() {
404 Content::Number(n)
405 } else if *text == "TRUE" || *text == "FALSE" {
406 Content::Bool(*text == "TRUE")
407 } else if let Some(e) = ErrorKind::parse(text) {
408 Content::Error(e.text().to_string())
409 } else {
410 Content::Text(text.to_string())
411 };
412 wb.sheets[i].set(range.top, range.left, Cell { content, style: 0 });
413 }
414 wb
415 }
416
417 fn at(calc: &Calc<'_>, sheet: &str, addr: &str) -> Computed {
418 let i = calc.model().find_sheet(sheet).unwrap();
419 let r = parse_a1_range(addr).unwrap();
420 calc.computed((i, r.top, r.left)).cloned().unwrap()
421 }
422
423 fn number(calc: &Calc<'_>, sheet: &str, addr: &str) -> f64 {
424 match at(calc, sheet, addr) {
425 Computed::Value(Value::Number(n)) => n,
426 other => panic!("{sheet}!{addr}: {other:?}"),
427 }
428 }
429
430 #[test]
431 fn arithmetic_runs_in_dependency_order_whatever_the_cell_order() {
432 let wb = model(&[
433 ("S", "A3", "=A2*2"),
434 ("S", "A2", "=A1+1"),
435 ("S", "A1", "5"),
436 ("S", "B1", "=A3-A2"),
437 ]);
438 let lib = library::language();
439 let calc = Calc::run(&wb, &lib);
440 assert_eq!(number(&calc, "S", "A2"), 6.0);
441 assert_eq!(number(&calc, "S", "A3"), 12.0);
442 assert_eq!(number(&calc, "S", "B1"), 6.0);
443 assert_eq!(calc.formulas(), 3);
444 assert!(calc.cycles().is_empty());
445 assert_eq!(
446 calc.progress(),
447 Progress {
448 evaluated: 3,
449 of: 3,
450 done: true
451 }
452 );
453 }
454
455 #[test]
456 fn a_budget_never_changes_a_value() {
457 let wb = model(&[
458 ("S", "A1", "1"),
459 ("S", "A2", "=A1+1"),
460 ("S", "A3", "=A2+1"),
461 ("S", "A4", "=A3+1"),
462 ("S", "A5", "=SUM(A1:A4)"),
463 ]);
464 let lib = library::language();
465 let whole = Calc::run(&wb, &lib);
466 let mut chunked = Calc::new(&wb, &lib);
467 let p1 = chunked.step(2);
468 assert_eq!(
469 p1,
470 Progress {
471 evaluated: 2,
472 of: 4,
473 done: false
474 }
475 );
476 assert!(
477 chunked.computed((0, 4, 1)).is_none(),
478 "A4 waits for the next step"
479 );
480 let p2 = chunked.step(1);
481 assert!(!p2.done);
482 let p3 = chunked.step(5);
483 assert!(p3.done && p3.evaluated == 4);
484 for id in whole.formula_cells() {
485 assert_eq!(whole.computed(id), chunked.computed(id), "{id:?}");
486 }
487 assert_eq!(number(&whole, "S", "A5"), 10.0);
488 chunked.restart();
489 assert!(chunked.computed((0, 5, 1)).is_none());
490 chunked.step(0);
491 assert_eq!(number(&chunked, "S", "A5"), 10.0);
492 }
493
494 #[test]
495 fn a_cycle_is_named_and_what_reads_it_is_not_computed() {
496 let wb = model(&[
497 ("S", "A1", "=B1+1"),
498 ("S", "B1", "=A1+1"),
499 ("S", "C1", "=A1*2"),
500 ("S", "D1", "=D1"),
501 ("S", "E1", "=1+1"),
502 ]);
503 let lib = library::language();
504 let calc = Calc::run(&wb, &lib);
505 assert_eq!(
506 calc.cycles(),
507 &[vec![(0, 1, 1), (0, 1, 2)], vec![(0, 1, 4)]]
508 );
509 assert_eq!(at(&calc, "S", "A1"), Computed::NotComputed(Reason::Cycle));
510 assert_eq!(at(&calc, "S", "C1"), Computed::NotComputed(Reason::Cycle));
511 assert_eq!(at(&calc, "S", "D1"), Computed::NotComputed(Reason::Cycle));
512 assert_eq!(number(&calc, "S", "E1"), 2.0);
513 let refusal = calc.first_refusal().unwrap();
514 assert_eq!(refusal.id, "calc-cycle");
515 assert!(refusal.text.contains("S!A1, S!B1"), "{}", refusal.text);
516 assert_eq!(
517 calc.progress(),
518 Progress {
519 evaluated: 5,
520 of: 5,
521 done: true
522 }
523 );
524 }
525
526 #[test]
527 fn a_cell_that_reads_itself_through_another_sheet_s_twin_is_no_cycle() {
528 let wb = model(&[
530 ("Screen", "B2", "=Screen.last!B2+1"),
531 ("Screen.last", "B2", "41"),
532 ]);
533 let lib = library::language();
534 let calc = Calc::run(&wb, &lib);
535 assert!(calc.cycles().is_empty());
536 assert_eq!(number(&calc, "Screen", "B2"), 42.0);
537 }
538
539 #[test]
540 fn ranges_names_and_spans_are_read_as_excel_reads_them() {
541 let mut wb = model(&[
542 ("Model", "B1", "1200"),
543 ("Model", "B2", "800"),
544 ("Model", "B3", "=B1-B2"),
545 ("Model", "C1", "x"),
546 ("Model", "C2", "TRUE"),
547 ("Model", "D1", "=SUM(B:B)"),
548 ("Model", "D2", "=SUM(B1:C2)"),
549 ("Model", "D3", "=SUM(B1,\"5\",TRUE,C2)"),
550 ("Model", "D4", "=B3*Rate"),
551 ("Model", "D5", "=Local+1"),
552 ("Model", "D6", "=SUM(Model:Scratch!B1)"),
553 ("Model", "D7", "=Nothing"),
554 ("Model", "D8", "=Missing!A1"),
555 ("Model", "D9", "=MAX(B1:B3,C1)"),
556 ("Model", "D10", "=MIN(C1:C2)"),
557 ("Scratch", "B1", "7"),
558 ("Scratch", "B2", "=Model!D1*2"),
559 ]);
560 wb.defined_names
561 .push(("Rate".to_string(), "0.2".to_string()));
562 wb.defined_names
563 .push(("Model!Local".to_string(), "Model!$B$2".to_string()));
564 let lib = library::language();
565 let calc = Calc::run(&wb, &lib);
566 assert_eq!(number(&calc, "Model", "B3"), 400.0);
567 assert_eq!(
568 number(&calc, "Model", "D1"),
569 2400.0,
570 "the formula in B3 counts"
571 );
572 assert_eq!(
573 number(&calc, "Model", "D2"),
574 2000.0,
575 "a text and a truth value in a range are skipped"
576 );
577 assert_eq!(
578 number(&calc, "Model", "D3"),
579 1206.0,
580 "given directly they are coerced; C2 is read through a reference and skipped"
581 );
582 assert_eq!(number(&calc, "Model", "D4"), 80.0);
583 assert_eq!(number(&calc, "Model", "D5"), 801.0);
584 assert_eq!(
585 number(&calc, "Model", "D6"),
586 1207.0,
587 "a span sums both sheets"
588 );
589 assert_eq!(
590 at(&calc, "Model", "D7"),
591 Computed::Value(Value::Error(ErrorKind::Name))
592 );
593 assert_eq!(
594 at(&calc, "Model", "D8"),
595 Computed::Value(Value::Error(ErrorKind::Ref))
596 );
597 assert_eq!(number(&calc, "Model", "D9"), 1200.0);
598 assert_eq!(
599 number(&calc, "Model", "D10"),
600 0.0,
601 "no number in the range is 0"
602 );
603 assert_eq!(
604 number(&calc, "Scratch", "B2"),
605 4800.0,
606 "a sheet reads another's formula after it is computed"
607 );
608 }
609
610 #[test]
611 fn an_aggregate_folds_in_the_walk_s_order_and_keeps_the_first_answer() {
612 let wb = model(&[
616 ("S", "A1", "1E16"),
617 ("S", "B1", "1"),
618 ("S", "A2", "-1E16"),
619 ("S", "B2", "0"),
620 ("S", "C1", "#N/A"),
621 ("S", "C2", "=VLOOKUP(1,A1:A2,2)"),
622 ("S", "D1", "=SUM(A1:B2)"),
623 ("S", "D2", "=SUM(C1:C2)"),
624 ("S", "D3", "=SUM(C1+0,C2:C2)"),
625 ("S", "D4", "=AND(FALSE,C1)"),
626 ("S", "D5", "=OR(TRUE,C1)"),
627 ("S", "D6", "=MIN(Z1:Z3)"),
628 ("S", "D7", "=MAX(Z1:Z3)"),
629 ("S", "D8", "=MIN(A1:B2)"),
630 ("S", "D9", "=MAX(A1:B2,-5)"),
631 ("S", "D10", "=OR(A2:B2)"),
632 ("S", "D11", "=AND(A1:B1,TRUE)"),
633 ]);
634 let lib = library::language();
635 let calc = Calc::run(&wb, &lib);
636 let err = |k| Computed::Value(Value::Error(k));
637 let by_rows = [1e16, 1.0, -1e16, 0.0].iter().fold(-0.0, |a, b| a + b);
638 let by_columns = [1e16, -1e16, 1.0, 0.0].iter().fold(-0.0, |a, b| a + b);
639 assert_ne!(
640 by_rows, by_columns,
641 "the fixture tells the two orders apart"
642 );
643 assert_eq!(number(&calc, "S", "D1"), by_rows);
644 assert_eq!(number(&calc, "S", "D1"), 0.0);
645 assert_eq!(
646 at(&calc, "S", "D2"),
647 Computed::NotComputed(Reason::Function("VLOOKUP".to_string())),
648 "a range is walked to its end: the cell not computed answers after the error"
649 );
650 assert_eq!(
651 at(&calc, "S", "D3"),
652 err(ErrorKind::NA),
653 "an error given directly answers before a later range is walked"
654 );
655 assert_eq!(at(&calc, "S", "D4"), err(ErrorKind::NA), "AND is strict");
656 assert_eq!(at(&calc, "S", "D5"), err(ErrorKind::NA), "OR is strict");
657 assert_eq!(number(&calc, "S", "D6"), 0.0, "MIN of no number");
658 assert_eq!(number(&calc, "S", "D7"), 0.0, "MAX of no number");
659 assert_eq!(number(&calc, "S", "D8"), -1e16);
660 assert_eq!(number(&calc, "S", "D9"), 1e16);
661 assert_eq!(
662 at(&calc, "S", "D10"),
663 Computed::Value(Value::Bool(true)),
664 "-1E16 is true, 0 false"
665 );
666 assert_eq!(at(&calc, "S", "D11"), Computed::Value(Value::Bool(true)));
667 }
668
669 #[test]
670 fn errors_are_values_and_propagate_as_excel_propagates_them() {
671 let wb = model(&[
672 ("S", "A1", "=1/0"),
673 ("S", "A2", "=A1+1"),
674 ("S", "A3", "=IF(A1>0,1,2)"),
675 ("S", "A4", "=IF(TRUE,1,A1)"),
676 ("S", "A5", "=SUM(A1:A2)"),
677 ("S", "A6", "#N/A"),
678 ("S", "A7", "=A6"),
679 ("S", "A8", "=\"x\"+1"),
680 ("S", "A9", "=-\"5\""),
681 ("S", "A10", "=50%"),
682 ("S", "A11", "=1&2"),
683 ("S", "A12", "=2^10"),
684 ("S", "A13", "=B99"),
685 ("S", "A14", "=#REF!+1"),
686 ]);
687 let lib = library::language();
688 let calc = Calc::run(&wb, &lib);
689 let err = |k| Computed::Value(Value::Error(k));
690 assert_eq!(at(&calc, "S", "A1"), err(ErrorKind::Div0));
691 assert_eq!(at(&calc, "S", "A2"), err(ErrorKind::Div0));
692 assert_eq!(
693 at(&calc, "S", "A3"),
694 err(ErrorKind::Div0),
695 "the condition's error"
696 );
697 assert_eq!(
698 number(&calc, "S", "A4"),
699 1.0,
700 "the branch not taken is not computed"
701 );
702 assert_eq!(at(&calc, "S", "A5"), err(ErrorKind::Div0));
703 assert_eq!(
704 at(&calc, "S", "A7"),
705 err(ErrorKind::NA),
706 "an error typed into a cell"
707 );
708 assert_eq!(at(&calc, "S", "A8"), err(ErrorKind::Value));
709 assert_eq!(number(&calc, "S", "A9"), -5.0);
710 assert_eq!(number(&calc, "S", "A10"), 0.5);
711 assert_eq!(
712 at(&calc, "S", "A11"),
713 Computed::Value(Value::Text("12".to_string()))
714 );
715 assert_eq!(number(&calc, "S", "A12"), 1024.0);
716 assert_eq!(
717 number(&calc, "S", "A13"),
718 0.0,
719 "an empty cell read is 0 in the cell"
720 );
721 assert_eq!(at(&calc, "S", "A14"), err(ErrorKind::Ref));
722 }
723
724 #[test]
725 fn the_day_one_functions_compute_exactly() {
726 let wb = model(&[
727 ("S", "A1", "=IF(1>0,\"yes\",\"no\")"),
728 ("S", "A2", "=IF(0)"),
729 ("S", "A3", "=IF(FALSE,1)"),
730 ("S", "A4", "=IF(TRUE,,1)"),
731 ("S", "A5", "=AND(TRUE,1,\"TRUE\")"),
732 ("S", "A6", "=AND(TRUE,0)"),
733 ("S", "A7", "=OR(FALSE,0,2)"),
734 ("S", "A8", "=NOT(0)"),
735 ("S", "A9", "=AND(\"yes\")"),
736 ("S", "A10", "=ABS(-3.5)"),
737 ("S", "A11", "=INT(-1.5)"),
738 ("S", "A12", "=MOD(-7,3)"),
739 ("S", "A13", "=MOD(7,-3)"),
740 ("S", "A14", "=MOD(7,0)"),
741 ("S", "A15", "=ROW()"),
742 ("S", "A16", "=COLUMN()"),
743 ("S", "A17", "=ROW(C9)"),
744 ("S", "A18", "=COLUMN(C9:D10)"),
745 ("S", "A19", "=CHOOSE(2,\"a\",\"b\",\"c\")"),
746 ("S", "A20", "=CHOOSE(4,\"a\",\"b\",\"c\")"),
747 ("S", "A21", "=SIN(0)"),
748 ("S", "A22", "=MOD(5.5,2)"),
749 ("S", "A23", "=CHOOSE(1.9,\"a\",\"b\")"),
750 ("S", "A24", "=AND()"),
751 ("S", "B1", "=VLOOKUP(1,A1:A2,2)"),
752 ("S", "B2", "=B1+1"),
753 ("S", "B3", "=SUM(A1:A2 B1)"),
754 ("S", "B4", "={1,2}"),
755 ("S", "B5", "=A1:A2*2"),
756 ]);
757 let lib = library::language();
758 let calc = Calc::run(&wb, &lib);
759 let text = |s: &str| Computed::Value(Value::Text(s.to_string()));
760 let truth = |b: bool| Computed::Value(Value::Bool(b));
761 let err = |k| Computed::Value(Value::Error(k));
762 assert_eq!(at(&calc, "S", "A1"), text("yes"));
763 assert_eq!(
764 at(&calc, "S", "A2"),
765 err(ErrorKind::Value),
766 "IF wants a branch"
767 );
768 assert_eq!(at(&calc, "S", "A3"), truth(false), "no else is FALSE");
769 assert_eq!(number(&calc, "S", "A4"), 0.0, "a branch left out is 0");
770 assert_eq!(at(&calc, "S", "A5"), truth(true));
771 assert_eq!(at(&calc, "S", "A6"), truth(false));
772 assert_eq!(at(&calc, "S", "A7"), truth(true));
773 assert_eq!(at(&calc, "S", "A8"), truth(true));
774 assert_eq!(at(&calc, "S", "A9"), err(ErrorKind::Value));
775 assert_eq!(number(&calc, "S", "A10"), 3.5);
776 assert_eq!(number(&calc, "S", "A11"), -2.0);
777 assert_eq!(number(&calc, "S", "A12"), 2.0, "the divisor's sign");
778 assert_eq!(number(&calc, "S", "A13"), -2.0);
779 assert_eq!(at(&calc, "S", "A14"), err(ErrorKind::Div0));
780 assert_eq!(number(&calc, "S", "A15"), 15.0);
781 assert_eq!(number(&calc, "S", "A16"), 1.0);
782 assert_eq!(number(&calc, "S", "A17"), 9.0);
783 assert_eq!(number(&calc, "S", "A18"), 3.0);
784 assert_eq!(at(&calc, "S", "A19"), text("b"));
785 assert_eq!(at(&calc, "S", "A20"), err(ErrorKind::Value));
786 assert_eq!(number(&calc, "S", "A21"), 0.0);
787 assert_eq!(number(&calc, "S", "A22"), 1.5);
788 assert_eq!(
789 at(&calc, "S", "A23"),
790 text("a"),
791 "the index is read down to a whole number"
792 );
793 assert_eq!(
794 at(&calc, "S", "A24"),
795 err(ErrorKind::Value),
796 "nothing to AND"
797 );
798 assert_eq!(
799 at(&calc, "S", "B1"),
800 Computed::NotComputed(Reason::Function("VLOOKUP".to_string()))
801 );
802 assert_eq!(
803 at(&calc, "S", "B2"),
804 Computed::NotComputed(Reason::Function("VLOOKUP".to_string())),
805 "a reader inherits the reason"
806 );
807 assert_eq!(
808 at(&calc, "S", "B3"),
809 Computed::NotComputed(Reason::Construct("intersection".to_string()))
810 );
811 assert_eq!(
812 at(&calc, "S", "B4"),
813 Computed::NotComputed(Reason::Construct("{".to_string()))
814 );
815 assert_eq!(
816 at(&calc, "S", "B5"),
817 Computed::NotComputed(Reason::Construct("A1:A2".to_string())),
818 "a range where one value is wanted waits for KERNEL.8"
819 );
820 assert_eq!(
821 calc.missing_functions(),
822 vec![((0, 1, 2), "VLOOKUP".to_string())]
823 );
824 let refusal = calc.first_refusal().unwrap();
825 assert_eq!(refusal.id, "calc-function-not-computed");
826 assert!(
827 refusal.text.contains("VLOOKUP") && refusal.text.contains("S!B1"),
828 "{}",
829 refusal.text
830 );
831 assert_eq!(at(&calc, "S", "A1").spell(), "\"yes\"");
832 assert_eq!(at(&calc, "S", "B1").spell(), "(not-computed \"VLOOKUP\")");
833 }
834
835 #[test]
836 fn a_construct_not_read_is_the_third_thing_a_load_refuses() {
837 let wb = model(&[
840 ("S", "A1", "=1+1"),
841 ("S", "B2", "={1,2}"),
842 ("S", "C3", "=@A1"),
843 ]);
844 let lib = library::language();
845 let calc = Calc::new(&wb, &lib);
846 let r = calc.first_refusal().unwrap();
847 assert_eq!(r.id, "calc-construct-not-read");
848 assert!(
849 r.text.contains("S!B2") && r.text.contains("reaches {"),
850 "{}",
851 r.text
852 );
853 let wb = model(&[("S", "B2", "={1,2}"), ("S", "C3", "=NOW()")]);
855 let calc = Calc::new(&wb, &lib);
856 assert_eq!(
857 calc.first_refusal().unwrap().id,
858 "calc-function-not-computed"
859 );
860 let wb = model(&[("S", "A1", "=1+1")]);
861 assert!(Calc::new(&wb, &lib).first_refusal().is_none());
862 }
863
864 #[test]
865 fn a_chain_of_operators_evaluates_left_to_right_however_long() {
866 let long = format!("={}1", "1+".repeat(4095));
870 let wb = model(&[
871 ("S", "A1", "=10-1-1-1"),
872 ("S", "A2", "=2^3^2"),
873 ("S", "A3", "=1+2*3-4/2+5"),
874 ("S", "A4", "=\"a\"&\"b\"&\"c\""),
875 ("S", "A5", long.as_str()),
876 ("S", "A6", "=1/0+1+1"),
877 ("S", "A7", "=1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1+1"),
878 ]);
879 let lib = library::language();
880 let calc = Calc::run(&wb, &lib);
881 assert_eq!(number(&calc, "S", "A1"), 7.0);
882 assert_eq!(number(&calc, "S", "A2"), 64.0);
883 assert_eq!(number(&calc, "S", "A3"), 10.0);
884 assert_eq!(
885 at(&calc, "S", "A4"),
886 Computed::Value(Value::Text("abc".to_string()))
887 );
888 assert_eq!(number(&calc, "S", "A5"), 4096.0);
889 assert_eq!(
890 at(&calc, "S", "A6"),
891 Computed::Value(Value::Error(ErrorKind::Div0))
892 );
893 assert_eq!(number(&calc, "S", "A7"), 20.0);
895 }
896
897 #[test]
898 fn excel_s_two_limits_are_refused_by_their_own_ids() {
899 let deep = format!("={}1{}", "(".repeat(65), ")".repeat(65));
903 let long = format!("={}1", "1+".repeat(4096));
904 let lib = library::language();
905 let wb = model(&[("S", "A1", deep.as_str())]);
906 let r = Calc::new(&wb, &lib).first_refusal().unwrap();
907 assert_eq!(r.id, "calc-formula-too-deep");
908 assert_eq!(
909 r.text,
910 "The formula in S!A1 nests more than 64 levels deep, more than Excel itself allows, so the formula cannot be computed."
911 );
912 let wb = model(&[("S", "A1", long.as_str())]);
913 let r = Calc::new(&wb, &lib).first_refusal().unwrap();
914 assert_eq!(r.id, "calc-formula-too-long");
915 assert_eq!(
916 r.text,
917 "The formula in S!A1 is longer than 8,192 characters, more than Excel itself allows, so the formula cannot be computed."
918 );
919 let calc = Calc::run(&wb, &lib);
920 assert_eq!(
921 at(&calc, "S", "A1"),
922 Computed::NotComputed(Reason::Construct(formula::TOO_LONG.to_string()))
923 );
924 }
925
926 #[test]
927 fn a_logical_test_coerces_a_text_read_through_a_reference() {
928 let mut wb = model(&[
935 ("S", "C1", "=IF(A1,1,2)"),
936 ("S", "C2", "=IF(A2,1,2)"),
937 ("S", "C3", "=AND(A1)"),
938 ("S", "C4", "=NOT(A1)"),
939 ]);
940 for (row, text) in [(1, "TRUE"), (2, "x")] {
941 wb.sheets[0].set(
942 row,
943 1,
944 Cell {
945 content: Content::Text(text.to_string()),
946 style: 0,
947 },
948 );
949 }
950 let lib = library::language();
951 let calc = Calc::run(&wb, &lib);
952 let err = |k| Computed::Value(Value::Error(k));
953 assert_eq!(number(&calc, "S", "C1"), 1.0);
954 assert_eq!(at(&calc, "S", "C2"), err(ErrorKind::Value));
955 assert_eq!(at(&calc, "S", "C3"), err(ErrorKind::Value));
956 assert_eq!(at(&calc, "S", "C4"), Computed::Value(Value::Bool(false)));
957 }
958
959 #[test]
960 fn shared_and_dynamic_formulas_are_read_through_their_masters() {
961 let mut wb = Workbook::new();
962 let i = wb.ensure_sheet("Output");
963 let sheet: &mut Sheet = &mut wb.sheets[i];
964 sheet.set(
965 2,
966 2,
967 Cell {
968 content: Content::Number(5.0),
969 style: 0,
970 },
971 );
972 sheet.set(
973 3,
974 2,
975 Cell {
976 content: Content::Formula("B2*2".to_string()),
977 style: 0,
978 },
979 );
980 sheet.set_formula(parse_a1_range("C2:C4").unwrap(), "B2+B3", 0);
981 sheet.set_formula_dynamic(parse_a1_range("D2:D3").unwrap(), "B2*10", 0);
982 let lib = library::language();
983 let calc = Calc::run(&wb, &lib);
984 assert_eq!(
985 calc.formula((0, 3, 3)),
986 Some("B3+B4"),
987 "the child's text is the master's moved"
988 );
989 assert_eq!(number(&calc, "Output", "C2"), 15.0);
990 assert_eq!(number(&calc, "Output", "C3"), 10.0);
991 assert_eq!(number(&calc, "Output", "C4"), 0.0);
992 assert_eq!(number(&calc, "Output", "D2"), 50.0);
993 assert_eq!(number(&calc, "Output", "D3"), 100.0);
994 assert_eq!(calc.spell_cell((0, 3, 3)), "Output!C3");
995 }
996}