truecalc_core/eval/context/mod.rs
1use std::collections::HashMap;
2use crate::types::Value;
3
4/// Holds the named variable bindings for a formula evaluation.
5///
6/// All keys are stored and looked up in uppercase, so variable names are
7/// case-insensitive (`A1`, `a1`, and `A1` all refer to the same binding).
8pub struct Context {
9 pub vars: HashMap<String, Value>,
10 /// Pinned "now" for volatile date functions (`NOW`, `TODAY`), as a
11 /// local-time spreadsheet serial datetime (integer part = day serial,
12 /// fraction = time of day). `None` ⇒ the functions read the ambient
13 /// local clock. Set via [`crate::Engine::evaluate_at`] (P1.4, issue #526).
14 pub now_serial: Option<f64>,
15}
16
17impl Context {
18 /// Create a `Context` from a map of variable name → value.
19 /// Keys are normalised to uppercase on insertion.
20 pub fn new(vars: HashMap<String, Value>) -> Self {
21 let normalized = vars.into_iter()
22 .map(|(k, v)| (k.to_uppercase(), v))
23 .collect();
24 Self { vars: normalized, now_serial: None }
25 }
26
27 /// Create an empty `Context` with no variable bindings.
28 pub fn empty() -> Self {
29 Self { vars: HashMap::new(), now_serial: None }
30 }
31
32 /// Look up a variable by name (case-insensitive). Returns `Value::Empty` if not found.
33 pub fn get(&self, name: &str) -> Value {
34 self.vars
35 .get(&name.to_uppercase())
36 .cloned()
37 .unwrap_or(Value::Empty)
38 }
39
40 /// Look up a binding by name (case-insensitive), distinguishing an absent
41 /// binding (`None`) from one explicitly bound to [`Value::Empty`]
42 /// (`Some(Value::Empty)`). Used by the evaluator to decide whether a
43 /// reference should fall through to the resolver.
44 pub fn lookup(&self, name: &str) -> Option<Value> {
45 self.vars.get(&name.to_uppercase()).cloned()
46 }
47
48 /// Insert or overwrite a binding. Returns the previous value if one existed.
49 pub fn set(&mut self, name: String, value: Value) -> Option<Value> {
50 self.vars.insert(name.to_uppercase(), value)
51 }
52
53 /// Remove a binding. Used to restore context after lambda/let evaluation.
54 pub fn remove(&mut self, name: &str) {
55 self.vars.remove(&name.to_uppercase());
56 }
57}
58
59#[cfg(test)]
60mod tests;