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