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