Skip to main content

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