ocas_eval/
function_map.rs1use ocas_core::FastHashMap as HashMap;
8
9use crate::domain::EvaluationDomain;
10
11type ExternalFn<T> = Box<dyn Fn(&[T]) -> T + Send + Sync>;
12
13pub struct FunctionMap<T: EvaluationDomain> {
15 entries: Vec<(String, FunctionEntry<T>)>,
16 name_to_idx: HashMap<String, usize>,
17 aliases: HashMap<String, String>,
18}
19
20pub struct FunctionEntry<T: EvaluationDomain> {
22 pub arity: usize,
24 func: ExternalFn<T>,
25}
26
27impl<T: EvaluationDomain> FunctionMap<T> {
28 pub fn new() -> Self {
30 Self {
31 entries: Vec::new(),
32 name_to_idx: HashMap::default(),
33 aliases: HashMap::default(),
34 }
35 }
36
37 pub fn register(&mut self, name: &str, arity: usize, func: ExternalFn<T>) {
39 let idx = self.entries.len();
40 self.entries
41 .push((name.to_string(), FunctionEntry { arity, func }));
42 self.name_to_idx.insert(name.to_string(), idx);
43 }
44
45 pub fn register_alias(&mut self, alias: &str, canonical: &str) {
47 self.aliases
48 .insert(alias.to_string(), canonical.to_string());
49 }
50
51 pub fn resolve(&self, name: &str) -> Option<&FunctionEntry<T>> {
53 self.resolve_idx(name).map(|idx| &self.entries[idx].1)
54 }
55
56 pub fn index_of(&self, name: &str) -> Option<usize> {
58 self.resolve_idx(name)
59 }
60
61 fn resolve_idx(&self, name: &str) -> Option<usize> {
62 if let Some(idx) = self.name_to_idx.get(name) {
63 return Some(*idx);
64 }
65 let lower = name.to_lowercase();
66 if let Some(idx) = self.name_to_idx.get(&lower) {
67 return Some(*idx);
68 }
69 if let Some(canonical) = self.aliases.get(name) {
70 return self.name_to_idx.get(canonical.as_str()).copied();
71 }
72 if let Some(canonical) = self.aliases.get(&lower) {
73 return self.name_to_idx.get(canonical.as_str()).copied();
74 }
75 None
76 }
77
78 pub fn call_by_index(&self, idx: usize, args: &[T]) -> Option<T> {
80 self.entries.get(idx).map(|(_, entry)| (entry.func)(args))
81 }
82
83 pub fn len(&self) -> usize {
85 self.entries.len()
86 }
87
88 pub fn is_empty(&self) -> bool {
90 self.entries.is_empty()
91 }
92}
93
94impl<T: EvaluationDomain> Default for FunctionMap<T> {
95 fn default() -> Self {
96 Self::new()
97 }
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn register_and_resolve() {
106 let mut map = FunctionMap::<f64>::new();
107 map.register("square", 1, Box::new(|args| args[0] * args[0]));
108 assert!(map.resolve("square").is_some());
109 assert!(map.resolve("Square").is_some());
110 assert!(map.resolve("unknown").is_none());
111 }
112
113 #[test]
114 fn alias_resolution() {
115 let mut map = FunctionMap::<f64>::new();
116 map.register("log", 1, Box::new(|args| args[0].ln()));
117 map.register_alias("ln", "log");
118 assert!(map.resolve("ln").is_some());
119 assert!(map.resolve("Ln").is_some());
120 }
121
122 #[test]
123 fn call_by_index() {
124 let mut map = FunctionMap::<f64>::new();
125 map.register("square", 1, Box::new(|args| args[0] * args[0]));
126 let result = map.call_by_index(0, &[3.0]).unwrap();
127 assert!((result - 9.0).abs() < 1e-10);
128 }
129
130 #[test]
131 fn empty_map() {
132 let map = FunctionMap::<f64>::new();
133 assert!(map.is_empty());
134 assert_eq!(map.len(), 0);
135 }
136}