use crate::function::Function;
use dashmap::DashMap;
use once_cell::sync::Lazy;
use std::sync::Arc;
static REG: Lazy<DashMap<(String, String), Arc<dyn Function>>> = Lazy::new(DashMap::new);
static ALIASES: Lazy<DashMap<(String, String), (String, String)>> = Lazy::new(DashMap::new);
#[inline]
fn norm<S: AsRef<str>>(s: S) -> String {
s.as_ref().to_uppercase()
}
pub fn register_function(f: Arc<dyn Function>) {
let ns = norm(f.namespace());
let name = norm(f.name());
let key = (ns.clone(), name.clone());
REG.insert(key.clone(), Arc::clone(&f));
for &alias in f.aliases() {
if alias.eq_ignore_ascii_case(&name) {
continue;
}
let akey = (ns.clone(), norm(alias));
ALIASES.insert(akey, key.clone());
}
}
const EXCEL_PREFIXES: &[&str] = &["_XLFN.", "_XLL.", "_XLWS."];
fn resolve_registered(key: &(String, String)) -> Option<Arc<dyn Function>> {
if let Some(v) = REG.get(key) {
return Some(Arc::clone(v.value()));
}
if let Some(canon) = ALIASES.get(key)
&& let Some(v) = REG.get(canon.value())
{
return Some(Arc::clone(v.value()));
}
None
}
pub fn get(ns: &str, name: &str) -> Option<Arc<dyn Function>> {
let ns_norm = norm(ns);
let normalized_name = norm(name);
let key = (ns_norm.clone(), normalized_name.clone());
if let Some(v) = resolve_registered(&key) {
return Some(v);
}
let mut candidate = normalized_name.as_str();
loop {
let mut stripped_any = false;
for prefix in EXCEL_PREFIXES {
if let Some(rest) = candidate.strip_prefix(prefix) {
candidate = rest;
stripped_any = true;
let stripped_key = (ns_norm.clone(), candidate.to_string());
if let Some(v) = resolve_registered(&stripped_key) {
ALIASES.insert(key.clone(), stripped_key);
return Some(v);
}
break;
}
}
if !stripped_any {
break;
}
}
None
}
pub fn register_alias(ns: &str, alias: &str, target_ns: &str, target_name: &str) {
let akey = (norm(ns), norm(alias));
let tkey = (norm(target_ns), norm(target_name));
ALIASES.insert(akey, tkey);
}
pub fn snapshot_registered() -> Vec<(String, String, Arc<dyn Function>)> {
REG.iter()
.map(|entry| {
let ((ns, name), func) = entry.pair();
(ns.clone(), name.clone(), Arc::clone(func))
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::function::FnCaps;
struct TestFn {
ns: &'static str,
name: &'static str,
aliases: &'static [&'static str],
}
impl Function for TestFn {
fn caps(&self) -> FnCaps {
FnCaps::PURE
}
fn name(&self) -> &'static str {
self.name
}
fn namespace(&self) -> &'static str {
self.ns
}
fn aliases(&self) -> &'static [&'static str] {
self.aliases
}
fn eval<'a, 'b, 'c>(
&self,
_args: &'c [crate::traits::ArgumentHandle<'a, 'b>],
_ctx: &dyn crate::traits::FunctionContext<'b>,
) -> Result<crate::traits::CalcValue<'b>, formualizer_common::ExcelError> {
Ok(crate::traits::CalcValue::Scalar(
formualizer_common::LiteralValue::Number(1.0),
))
}
}
#[test]
fn resolves_single_excel_prefix() {
let ns = "__REG_PREFIX_SINGLE__";
register_function(Arc::new(TestFn {
ns,
name: "SUM",
aliases: &[],
}));
let f = get(ns, "_xlfn.sum").expect("function should resolve");
assert_eq!(f.name(), "SUM");
}
#[test]
fn resolves_chained_excel_prefixes() {
let ns = "__REG_PREFIX_CHAINED__";
register_function(Arc::new(TestFn {
ns,
name: "FILTER",
aliases: &[],
}));
let f = get(ns, "_xlfn._xlws.filter").expect("function should resolve");
assert_eq!(f.name(), "FILTER");
}
#[test]
fn resolves_chained_prefixes_with_alias_target() {
let ns = "__REG_PREFIX_ALIAS__";
register_function(Arc::new(TestFn {
ns,
name: "MODERN",
aliases: &["LEGACY"],
}));
let f = get(ns, "_xlfn._xlws.legacy").expect("function should resolve");
assert_eq!(f.name(), "MODERN");
}
#[test]
fn direct_prefixed_registration_wins_before_compat_stripping() {
let ns = "__REG_DIRECT_PREFIX__";
register_function(Arc::new(TestFn {
ns,
name: "SUM",
aliases: &[],
}));
register_function(Arc::new(TestFn {
ns,
name: "_XLFN.SUM",
aliases: &[],
}));
let f = get(ns, "_xlfn.sum").expect("function should resolve");
assert_eq!(f.name(), "_XLFN.SUM");
}
}