Skip to main content

ganit_core/eval/functions/
mod.rs

1pub mod date;
2pub mod financial;
3pub mod logical;
4pub mod math;
5pub mod operator;
6pub mod parser;
7pub mod statistical;
8pub mod text;
9
10use std::collections::HashMap;
11use crate::eval::context::Context;
12use crate::parser::ast::Expr;
13use crate::types::{ErrorKind, Value};
14
15// ── EvalCtx ───────────────────────────────────────────────────────────────
16
17/// Bundles the variable context and function registry for use during evaluation.
18/// Passed to lazy functions so they can recursively evaluate sub-expressions.
19pub struct EvalCtx<'r> {
20    pub ctx: Context,
21    pub registry: &'r Registry,
22}
23
24impl<'r> EvalCtx<'r> {
25    pub fn new(ctx: Context, registry: &'r Registry) -> Self {
26        Self { ctx, registry }
27    }
28}
29
30// ── Function kinds ─────────────────────────────────────────────────────────
31
32/// A function that receives pre-evaluated arguments.
33/// Argument errors are caught before dispatch — the slice never contains `Value::Error`.
34pub type EagerFn = fn(&[Value]) -> Value;
35
36/// A function that receives raw AST nodes and controls its own evaluation order.
37/// Used for short-circuit operators like `IF`, `AND`, `OR`.
38pub type LazyFn  = fn(&[Expr], &mut EvalCtx<'_>) -> Value;
39
40pub enum FunctionKind {
41    Eager(EagerFn),
42    Lazy(LazyFn),
43}
44
45// ── FunctionMeta ──────────────────────────────────────────────────────────
46
47/// Metadata for a user-facing spreadsheet function.
48/// Co-located with the registration call so it can never drift.
49#[derive(Debug)]
50pub struct FunctionMeta {
51    pub category: &'static str,
52    pub signature: &'static str,
53    pub description: &'static str,
54}
55
56// ── Registry ──────────────────────────────────────────────────────────────
57
58/// The runtime registry of built-in and user-registered spreadsheet functions.
59pub struct Registry {
60    functions: HashMap<String, FunctionKind>,
61    metadata: HashMap<String, FunctionMeta>,
62}
63
64impl Registry {
65    pub fn new() -> Self {
66        let mut r = Self { functions: HashMap::new(), metadata: HashMap::new() };
67        math::register_math(&mut r);
68        logical::register_logical(&mut r);
69        text::register_text(&mut r);
70        financial::register_financial(&mut r);
71        statistical::register_statistical(&mut r);
72        operator::register_operator(&mut r);
73        date::register_date(&mut r);
74        parser::register_parser(&mut r);
75        r
76    }
77
78    /// Register a user-facing eager function with metadata.
79    /// Appears in `list_functions()`.
80    pub fn register_eager(&mut self, name: &str, f: EagerFn, meta: FunctionMeta) {
81        let key = name.to_uppercase();
82        self.functions.insert(key.clone(), FunctionKind::Eager(f));
83        self.metadata.insert(key, meta);
84    }
85
86    /// Register a user-facing lazy function with metadata.
87    /// Appears in `list_functions()`.
88    pub fn register_lazy(&mut self, name: &str, f: LazyFn, meta: FunctionMeta) {
89        let key = name.to_uppercase();
90        self.functions.insert(key.clone(), FunctionKind::Lazy(f));
91        self.metadata.insert(key, meta);
92    }
93
94    /// Register an internal/compiler-only eager function without metadata.
95    /// Never appears in `list_functions()`.
96    pub fn register_internal(&mut self, name: &str, f: EagerFn) {
97        self.functions.insert(name.to_uppercase(), FunctionKind::Eager(f));
98    }
99
100    /// Register an internal/compiler-only lazy function without metadata.
101    /// Never appears in `list_functions()`.
102    pub fn register_internal_lazy(&mut self, name: &str, f: LazyFn) {
103        self.functions.insert(name.to_uppercase(), FunctionKind::Lazy(f));
104    }
105
106    pub fn get(&self, name: &str) -> Option<&FunctionKind> {
107        self.functions.get(&name.to_uppercase())
108    }
109
110    /// Iterate all user-facing functions with their metadata.
111    /// The registry is the single source of truth — this can never drift.
112    pub fn list_functions(&self) -> impl Iterator<Item = (&str, &FunctionMeta)> {
113        self.metadata.iter().map(|(k, v)| (k.as_str(), v))
114    }
115}
116
117impl Default for Registry {
118    fn default() -> Self {
119        Self::new()
120    }
121}
122
123/// Validate argument count for eager functions (args already evaluated to `&[Value]`).
124/// Returns `Some(Value::Error(ErrorKind::NA))` if the count is out of range
125/// (matches Google Sheets / Excel behaviour for wrong argument count).
126pub fn check_arity(args: &[Value], min: usize, max: usize) -> Option<Value> {
127    if args.len() < min || args.len() > max {
128        Some(Value::Error(ErrorKind::NA))
129    } else {
130        None
131    }
132}
133
134/// Validate argument count for lazy functions (args are `&[Expr]`).
135/// Returns `Some(Value::Error(ErrorKind::NA))` if the count is out of range.
136pub fn check_arity_len(count: usize, min: usize, max: usize) -> Option<Value> {
137    if count < min || count > max {
138        Some(Value::Error(ErrorKind::NA))
139    } else {
140        None
141    }
142}
143
144// ── Tests ─────────────────────────────────────────────────────────────────
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149
150    #[test]
151    fn list_functions_matches_registry() {
152        let registry = Registry::new();
153        let listed: Vec<(&str, &FunctionMeta)> = registry.list_functions().collect();
154        assert!(!listed.is_empty(), "registry should expose at least one function");
155        // Every listed name must be resolvable — catches metadata/functions map skew
156        for (name, _meta) in &listed {
157            assert!(
158                registry.get(name).is_some(),
159                "listed function {name} not found via get()"
160            );
161        }
162        // metadata count == listed count (no orphaned metadata entries)
163        assert_eq!(listed.len(), registry.metadata.len());
164    }
165}