1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
//! Reference resolution (P1.3, issue #525).
//!
//! Core is the *language*: it parses formulas and evaluates them, but it has
//! no notion of a sheet, a grid, or a named range. [`Resolver`] is the seam
//! through which an embedding application (the workbook crate, a host app, a
//! test harness) supplies the *value* a [`Ref`] points at.
//!
//! The evaluator calls [`Resolver::resolve`] every time a formula reads a
//! reference that is not already bound as a local variable (e.g. a LAMBDA
//! parameter). The resolver owns all workbook semantics:
//!
//! - a cross-sheet cell ref to a missing sheet -> [`ErrorKind::Ref`] (`#REF!`),
//! - a bare name that is not a defined name -> [`ErrorKind::Name`] (`#NAME?`),
//! - an empty cell -> [`Value::Empty`],
//! - a range -> [`Value::Array`] of the cells in reading order.
//!
//! Core never invents these; it simply forwards the [`Ref`] and returns
//! whatever the resolver decides.
//!
//! [`ErrorKind::Ref`]: crate::ErrorKind::Ref
//! [`ErrorKind::Name`]: crate::ErrorKind::Name
//!
//! ```
//! use std::collections::HashMap;
//! use truecalc_core::{Engine, Ref, Resolver, Value};
//!
//! /// A resolver backed by a flat map keyed by canonical reference text.
//! struct MapResolver(HashMap<String, Value>);
//!
//! impl Resolver for MapResolver {
//! fn resolve(&mut self, r: &Ref) -> Value {
//! self.0.get(&r.to_string()).cloned().unwrap_or(Value::Empty)
//! }
//! }
//!
//! let mut cells = HashMap::new();
//! cells.insert("Data!A1".to_string(), Value::Number(10.0));
//! cells.insert("Data!B1".to_string(), Value::Number(5.0));
//! let mut resolver = MapResolver(cells);
//!
//! let engine = Engine::sheets();
//! let result = engine.evaluate_with_resolver("=Data!A1+Data!B1", &mut resolver);
//! assert_eq!(result, Value::Number(15.0));
//! ```
use crateExpr;
use crateRef;
use crateValue;
/// Resolves a [`Ref`] to the [`Value`] it points at.
///
/// Implementors own all workbook semantics: missing sheets, undefined names,
/// empty cells, and range materialization. See the module docs for the
/// expected error mapping (`#REF!` for missing sheets, `#NAME?` for undefined
/// names). The evaluator only calls this for references that are not shadowed
/// by a local binding such as a LAMBDA parameter.
/// Collect every [`Ref`] a formula reads, in left-to-right source order,
/// without re-parsing -- the input that lets the workbook build a dependency
/// graph (plan section 3.2).
///
/// Both reference shapes are reported as a [`Ref`]:
///
/// - sheet-qualified references ([`Expr::Reference`]) are returned verbatim;
/// - bare identifiers ([`Expr::Variable`]) -- which the parser keeps as
/// variable nodes for compatibility -- are classified with
/// [`Ref::classify`], so `A1` becomes [`Ref::Cell`], `A1:D4` becomes
/// [`Ref::Range`], and `TAX_RATE` becomes [`Ref::Name`].
///
/// Refs appearing in *any* argument position of a function call are found,
/// because the walk descends into every child expression. Duplicates are
/// preserved (a formula that reads `A1` twice yields two entries); callers
/// that want a dependency set deduplicate themselves.
///
/// ```
/// use truecalc_core::{extract_refs, CellAddr, Engine, Ref};
///
/// let expr = Engine::sheets().parse("=SUM(A1, Data!B2, TAX_RATE)").unwrap();
/// let refs = extract_refs(&expr);
/// assert_eq!(
/// refs,
/// vec![
/// Ref::Cell { sheet: None, addr: CellAddr { col: 1, row: 1 } },
/// Ref::Cell { sheet: Some("Data".to_string()), addr: CellAddr { col: 2, row: 2 } },
/// Ref::Name("TAX_RATE".to_string()),
/// ],
/// );
/// ```