Skip to main content

truecalc_core/eval/functions/
mod.rs

1pub mod array;
2pub mod database;
3pub mod date;
4pub mod engineering;
5pub mod filter;
6pub mod financial;
7pub mod logical;
8pub mod lookup;
9pub mod math;
10pub mod operator;
11pub mod parser;
12pub mod statistical;
13pub mod text;
14pub mod web;
15
16use std::collections::HashMap;
17use crate::eval::context::Context;
18use crate::parser::ast::Expr;
19use crate::types::{ErrorKind, Value};
20
21// ── EvalCtx ───────────────────────────────────────────────────────────────
22
23/// Bundles the variable context and function registry for use during evaluation.
24/// Passed to lazy functions so they can recursively evaluate sub-expressions.
25pub struct EvalCtx<'r> {
26    pub ctx: Context,
27    pub registry: &'r Registry,
28}
29
30impl<'r> EvalCtx<'r> {
31    pub fn new(ctx: Context, registry: &'r Registry) -> Self {
32        Self { ctx, registry }
33    }
34}
35
36// ── Function kinds ─────────────────────────────────────────────────────────
37
38/// A function that receives pre-evaluated arguments.
39/// Argument errors are caught before dispatch — the slice never contains `Value::Error`.
40pub type EagerFn = fn(&[Value]) -> Value;
41
42/// A function that receives raw AST nodes and controls its own evaluation order.
43/// Used for short-circuit operators like `IF`, `AND`, `OR`.
44pub type LazyFn  = fn(&[Expr], &mut EvalCtx<'_>) -> Value;
45
46#[derive(Clone)]
47pub enum FunctionKind {
48    Eager(EagerFn),
49    Lazy(LazyFn),
50}
51
52// ── FunctionMeta ──────────────────────────────────────────────────────────
53
54/// Metadata for a user-facing spreadsheet function.
55/// Co-located with the registration call so it can never drift.
56#[derive(Debug, Clone)]
57pub struct FunctionMeta {
58    pub category: &'static str,
59    pub signature: &'static str,
60    pub description: &'static str,
61}
62
63/// A metadata entry returned by `Registry::get_metadata()`.
64pub struct FunctionMetaEntry<'a> {
65    pub name: &'a str,
66    pub meta: &'a FunctionMeta,
67}
68
69// ── Registry ──────────────────────────────────────────────────────────────
70
71/// The runtime registry of built-in and user-registered spreadsheet functions.
72pub struct Registry {
73    pub functions: HashMap<String, FunctionKind>,
74    pub metadata: HashMap<String, FunctionMeta>,
75}
76
77impl Registry {
78    pub fn new() -> Self {
79        let mut r = Self { functions: HashMap::new(), metadata: HashMap::new() };
80        math::register_math(&mut r);
81        logical::register_logical(&mut r);
82        text::register_text(&mut r);
83        financial::register_financial(&mut r);
84        statistical::register_statistical(&mut r);
85        operator::register_operator(&mut r);
86        date::register_date(&mut r);
87        parser::register_parser(&mut r);
88        engineering::register_engineering(&mut r);
89        filter::register_filter(&mut r);
90        array::register_array(&mut r);
91        database::register_database(&mut r);
92        lookup::register_lookup(&mut r);
93        web::register_web(&mut r);
94        r
95    }
96
97    /// Register a user-facing eager function with metadata.
98    /// Appears in `list_functions()`.
99    /// Panics if `name` is already registered (duplicate registration).
100    pub fn register_eager(&mut self, name: &str, f: EagerFn, meta: FunctionMeta) {
101        let key = name.to_uppercase();
102        assert!(
103            !self.functions.contains_key(&key),
104            "duplicate function registration: '{}'",
105            key
106        );
107        self.functions.insert(key.clone(), FunctionKind::Eager(f));
108        self.metadata.insert(key, meta);
109    }
110
111    /// Register a user-facing lazy function with metadata.
112    /// Appears in `list_functions()`.
113    /// Panics if `name` is already registered (duplicate registration).
114    pub fn register_lazy(&mut self, name: &str, f: LazyFn, meta: FunctionMeta) {
115        let key = name.to_uppercase();
116        assert!(
117            !self.functions.contains_key(&key),
118            "duplicate function registration: '{}'",
119            key
120        );
121        self.functions.insert(key.clone(), FunctionKind::Lazy(f));
122        self.metadata.insert(key, meta);
123    }
124
125    /// Register `alias` as an alternate name for `canonical`.
126    /// The alias shares the same handler but does NOT appear in function metadata
127    /// (it will not show up in `list_functions()` or autocomplete).
128    /// Panics if `alias` is already registered or `canonical` is not yet registered.
129    pub fn register_alias(&mut self, alias: &str, canonical: &str) {
130        let alias_key = alias.to_uppercase();
131        let canonical_key = canonical.to_uppercase();
132        assert!(
133            !self.functions.contains_key(&alias_key),
134            "duplicate function registration: '{}'",
135            alias_key
136        );
137        let kind = self
138            .functions
139            .get(&canonical_key)
140            .unwrap_or_else(|| {
141                panic!(
142                    "register_alias: canonical '{}' must be registered before alias '{}'",
143                    canonical_key, alias_key
144                )
145            })
146            .clone();
147        self.functions.insert(alias_key, kind);
148        // Intentionally no metadata entry — aliases are not user-facing
149    }
150
151    /// Register an internal/compiler-only eager function without metadata.
152    /// Never appears in `list_functions()`.
153    pub fn register_internal(&mut self, name: &str, f: EagerFn) {
154        self.functions.insert(name.to_uppercase(), FunctionKind::Eager(f));
155    }
156
157    /// Register an internal/compiler-only lazy function without metadata.
158    /// Never appears in `list_functions()`.
159    pub fn register_internal_lazy(&mut self, name: &str, f: LazyFn) {
160        self.functions.insert(name.to_uppercase(), FunctionKind::Lazy(f));
161    }
162
163    pub fn get(&self, name: &str) -> Option<&FunctionKind> {
164        self.functions.get(&name.to_uppercase())
165    }
166
167    /// Iterate all user-facing functions with their metadata.
168    /// The registry is the single source of truth — this can never drift.
169    pub fn list_functions(&self) -> impl Iterator<Item = (&str, &FunctionMeta)> {
170        self.metadata.iter().map(|(k, v)| (k.as_str(), v))
171    }
172
173    /// Return all function metadata entries as a Vec of named structs.
174    /// Used for inspection (e.g. counting functions, verifying aliases are absent).
175    pub fn get_metadata(&self) -> Vec<FunctionMetaEntry<'_>> {
176        self.metadata
177            .iter()
178            .map(|(k, v)| FunctionMetaEntry { name: k.as_str(), meta: v })
179            .collect()
180    }
181
182    /// Return all user-facing function names (from metadata, not aliases).
183    pub fn metadata_names(&self) -> Vec<String> {
184        self.metadata.keys().cloned().collect()
185    }
186}
187
188impl Registry {
189    /// Volatile functions — outputs change on every evaluation.
190    /// Excluded from conformance fixtures; covered by property tests instead.
191    pub const VOLATILE_FUNCTIONS: &'static [&'static str] = &[
192        "RAND", "RANDARRAY", "NOW", "TODAY", "RANDBETWEEN",
193    ];
194}
195
196impl Default for Registry {
197    fn default() -> Self {
198        Self::new()
199    }
200}
201
202/// Validate argument count for eager functions (args already evaluated to `&[Value]`).
203/// Returns `Some(Value::Error(ErrorKind::NA))` if the count is out of range
204/// (matches Google Sheets / Excel behaviour for wrong argument count).
205pub fn check_arity(args: &[Value], min: usize, max: usize) -> Option<Value> {
206    if args.len() < min || args.len() > max {
207        Some(Value::Error(ErrorKind::NA))
208    } else {
209        None
210    }
211}
212
213/// Validate argument count for lazy functions (args are `&[Expr]`).
214/// Returns `Some(Value::Error(ErrorKind::NA))` if the count is out of range.
215pub fn check_arity_len(count: usize, min: usize, max: usize) -> Option<Value> {
216    if count < min || count > max {
217        Some(Value::Error(ErrorKind::NA))
218    } else {
219        None
220    }
221}
222
223// ── Tests ─────────────────────────────────────────────────────────────────
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228
229    #[test]
230    fn list_functions_matches_registry() {
231        let registry = Registry::new();
232        let listed: Vec<(&str, &FunctionMeta)> = registry.list_functions().collect();
233        assert!(!listed.is_empty(), "registry should expose at least one function");
234        // Every listed name must be resolvable — catches metadata/functions map skew
235        for (name, _meta) in &listed {
236            assert!(
237                registry.get(name).is_some(),
238                "listed function {name} not found via get()"
239            );
240        }
241        // metadata count == listed count (no orphaned metadata entries)
242        assert_eq!(listed.len(), registry.metadata.len());
243    }
244}