Skip to main content

formualizer_eval/
function_registry.rs

1use crate::function::Function;
2use dashmap::DashMap;
3use once_cell::sync::Lazy;
4use std::sync::Arc;
5
6// Case-insensitive registry keyed by (NAMESPACE, NAME) in uppercase
7static REG: Lazy<DashMap<(String, String), Arc<dyn Function>>> = Lazy::new(DashMap::new);
8
9// Optional alias map: (NS, ALIAS) -> (NS, CANONICAL_NAME), all uppercase
10static ALIASES: Lazy<DashMap<(String, String), (String, String)>> = Lazy::new(DashMap::new);
11
12#[inline]
13fn norm<S: AsRef<str>>(s: S) -> String {
14    s.as_ref().to_uppercase()
15}
16
17pub fn register_function(f: Arc<dyn Function>) {
18    let ns = norm(f.namespace());
19    let name = norm(f.name());
20    let key = (ns.clone(), name.clone());
21    // Insert canonical
22    REG.insert(key.clone(), Arc::clone(&f));
23    // Register aliases
24    for &alias in f.aliases() {
25        if alias.eq_ignore_ascii_case(&name) {
26            continue;
27        }
28        let akey = (ns.clone(), norm(alias));
29        ALIASES.insert(akey, key.clone());
30    }
31}
32
33// Known Excel function prefixes that should be stripped for compatibility
34const EXCEL_PREFIXES: &[&str] = &["_XLFN.", "_XLL.", "_XLWS."];
35
36fn resolve_registered(key: &(String, String)) -> Option<Arc<dyn Function>> {
37    // Try direct lookup
38    if let Some(v) = REG.get(key) {
39        return Some(Arc::clone(v.value()));
40    }
41
42    // Try existing alias
43    if let Some(canon) = ALIASES.get(key)
44        && let Some(v) = REG.get(canon.value())
45    {
46        return Some(Arc::clone(v.value()));
47    }
48
49    None
50}
51
52pub fn get(ns: &str, name: &str) -> Option<Arc<dyn Function>> {
53    let ns_norm = norm(ns);
54    let normalized_name = norm(name);
55    let key = (ns_norm.clone(), normalized_name.clone());
56
57    if let Some(v) = resolve_registered(&key) {
58        return Some(v);
59    }
60
61    // Try repeatedly stripping known Excel prefixes and cache discovered aliases.
62    //
63    // This handles formulas like:
64    //   _xlfn.SUM(...)
65    //   _xlfn._xlws.FILTER(...)
66    // without mutating original formula text/AST.
67    let mut candidate = normalized_name.as_str();
68    loop {
69        let mut stripped_any = false;
70        for prefix in EXCEL_PREFIXES {
71            if let Some(rest) = candidate.strip_prefix(prefix) {
72                candidate = rest;
73                stripped_any = true;
74
75                let stripped_key = (ns_norm.clone(), candidate.to_string());
76                if let Some(v) = resolve_registered(&stripped_key) {
77                    // Cache this discovery as an alias for future lookups.
78                    ALIASES.insert(key.clone(), stripped_key);
79                    return Some(v);
80                }
81
82                break;
83            }
84        }
85
86        if !stripped_any {
87            break;
88        }
89    }
90
91    None
92}
93
94/// Register an alias name for an existing function. All names are normalized to uppercase.
95pub fn register_alias(ns: &str, alias: &str, target_ns: &str, target_name: &str) {
96    let akey = (norm(ns), norm(alias));
97    let tkey = (norm(target_ns), norm(target_name));
98    ALIASES.insert(akey, tkey);
99}
100
101/// Snapshot canonical registered functions (namespace, name, function object).
102///
103/// Keys are normalized uppercase. Aliases are not included in this list.
104pub fn snapshot_registered() -> Vec<(String, String, Arc<dyn Function>)> {
105    REG.iter()
106        .map(|entry| {
107            let ((ns, name), func) = entry.pair();
108            (ns.clone(), name.clone(), Arc::clone(func))
109        })
110        .collect()
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116    use crate::function::FnCaps;
117
118    struct TestFn {
119        ns: &'static str,
120        name: &'static str,
121        aliases: &'static [&'static str],
122    }
123
124    impl Function for TestFn {
125        fn caps(&self) -> FnCaps {
126            FnCaps::PURE
127        }
128
129        fn name(&self) -> &'static str {
130            self.name
131        }
132
133        fn namespace(&self) -> &'static str {
134            self.ns
135        }
136
137        fn aliases(&self) -> &'static [&'static str] {
138            self.aliases
139        }
140
141        fn eval<'a, 'b, 'c>(
142            &self,
143            _args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
144            _ctx: &dyn crate::traits::FunctionContext<'b>,
145        ) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
146            Ok(crate::traits::CalcValue::Scalar(
147                formualizer_common::LiteralValue::Number(1.0),
148            ))
149        }
150    }
151
152    #[test]
153    fn resolves_single_excel_prefix() {
154        let ns = "__REG_PREFIX_SINGLE__";
155        register_function(Arc::new(TestFn {
156            ns,
157            name: "SUM",
158            aliases: &[],
159        }));
160
161        let f = get(ns, "_xlfn.sum").expect("function should resolve");
162        assert_eq!(f.name(), "SUM");
163    }
164
165    #[test]
166    fn resolves_chained_excel_prefixes() {
167        let ns = "__REG_PREFIX_CHAINED__";
168        register_function(Arc::new(TestFn {
169            ns,
170            name: "FILTER",
171            aliases: &[],
172        }));
173
174        let f = get(ns, "_xlfn._xlws.filter").expect("function should resolve");
175        assert_eq!(f.name(), "FILTER");
176    }
177
178    #[test]
179    fn resolves_chained_prefixes_with_alias_target() {
180        let ns = "__REG_PREFIX_ALIAS__";
181        register_function(Arc::new(TestFn {
182            ns,
183            name: "MODERN",
184            aliases: &["LEGACY"],
185        }));
186
187        let f = get(ns, "_xlfn._xlws.legacy").expect("function should resolve");
188        assert_eq!(f.name(), "MODERN");
189    }
190
191    #[test]
192    fn direct_prefixed_registration_wins_before_compat_stripping() {
193        let ns = "__REG_DIRECT_PREFIX__";
194        register_function(Arc::new(TestFn {
195            ns,
196            name: "SUM",
197            aliases: &[],
198        }));
199        register_function(Arc::new(TestFn {
200            ns,
201            name: "_XLFN.SUM",
202            aliases: &[],
203        }));
204
205        let f = get(ns, "_xlfn.sum").expect("function should resolve");
206        assert_eq!(f.name(), "_XLFN.SUM");
207    }
208}