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