Skip to main content

ganit_core/eval/functions/
mod.rs

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