aimx/function_registry.rs
1//! Function registry and calling interface.
2//!
3//! This module provides the function registry system that manages
4//! standalone functions and methods available in expressions.
5//! It also includes utilities for converting between [`Value`] types
6//! and native Rust types for function implementations.
7//!
8//! The function registry is a thread-safe singleton that uses [`OnceLock`] and [`RwLock`]
9//! to ensure one-time initialization with all built-in functions registered while
10//! allowing multiple concurrent readers.
11//!
12//! # Usage
13//!
14//! Functions are typically registered using the macros provided in [`crate::macros`]
15//! and are automatically available through the global registry:
16//!
17//! ```rust
18//! use aimx::{aimx_parse, ExpressionLike, Context};
19//!
20//! let mut context = Context::new();
21//! let expression = aimx_parse("sqrt(16) + abs(-5)");
22//! let result = expression.evaluate(&mut context).unwrap();
23//! assert_eq!(result.to_string(), "9");
24//! ```
25//!
26//! Custom functions can be registered with the registry:
27//!
28//! ```rust
29//! use aimx::{function_registry::FunctionRegistry, define_direct_function};
30//!
31//! // Get a write lock on the registry
32//! let mut registry = FunctionRegistry::write_lock().unwrap();
33//!
34//! // Register a custom function
35//! define_direct_function!(
36//! double,
37//! args: [f64],
38//! |x: f64| x * 2.0
39//! )(&mut *registry);
40//!
41//! // Drop the lock to allow other threads to access the registry
42//! drop(registry);
43//! ```
44//!
45//! Functions can be called as standalone functions or as methods on values:
46//!
47//! ```aimx
48//! // Standalone function call
49//! sqrt(16)
50//!
51//! // Method call on array
52//! (1, 2, 3).sum()
53//! ```
54
55use std::collections::HashMap;
56use std::sync::OnceLock;
57use std::sync::RwLock;
58use anyhow::{anyhow, Result};
59use crate::{
60 ContextLike,
61 Value,
62 Literal,
63 functions,
64};
65use jiff::civil::DateTime;
66
67/// Registry for managing function handlers.
68///
69/// The function registry maintains a collection of named function handlers
70/// that can be called during expression evaluation. It supports both
71/// standalone functions and methods called on values.
72///
73/// This registry is a thread-safe singleton that ensures one-time initialization
74/// with all built-in functions registered.
75pub struct FunctionRegistry {
76 handlers: HashMap<String, Box<dyn FunctionHandler>>,
77}
78
79/// Trait for function handlers.
80///
81/// This trait defines the interface that all function handlers must implement.
82/// Function handlers are responsible for executing the actual function logic
83/// and returning the result as a [`Value`].
84///
85/// This trait is automatically implemented when using the function registration
86/// macros in [`crate::macros`].
87pub trait FunctionHandler: Send + Sync {
88 /// Call the function with the provided arguments.
89 ///
90 /// # Arguments
91 ///
92 /// * `context` - The evaluation context for closures
93 /// * `args` - A slice of [`Value`] arguments passed to the function
94 ///
95 /// # Returns
96 ///
97 /// Returns a `Result<Value>` containing the function result or an error
98 /// if the function execution fails.
99 ///
100 /// # Note
101 ///
102 /// For method calls, the first argument is typically the value the method
103 /// is being called on, followed by any additional arguments.
104 fn call(&self, context: &mut dyn ContextLike, args: &[Value]) -> Result<Value>;
105}
106
107impl FunctionRegistry {
108 /// Get the global singleton instance of the function registry.
109 ///
110 /// This method uses [`OnceLock`] to ensure thread-safe one-time initialization
111 /// of the function registry with all built-in functions registered.
112 /// Uses [`RwLock`] to allow multiple concurrent readers while ensuring
113 /// exclusive access during writes.
114 ///
115 /// # Returns
116 ///
117 /// Returns a reference to the RwLock-protected singleton function registry.
118 ///
119 /// # Example
120 ///
121 /// ```
122 /// use aimx::function_registry::FunctionRegistry;
123 ///
124 /// let registry = FunctionRegistry::get_instance();
125 /// // Typically you'd use read_lock() or write_lock() instead of accessing directly
126 /// ```
127 pub fn get_instance() -> &'static RwLock<Self> {
128 static INSTANCE: OnceLock<RwLock<FunctionRegistry>> = OnceLock::new();
129
130 INSTANCE.get_or_init(|| {
131 let mut registry = Self::new();
132 functions::register_all_functions(&mut registry);
133 RwLock::new(registry)
134 })
135 }
136
137 /// Get a read lock on the function registry for function calls.
138 ///
139 /// This is a convenience method that returns a read guard, allowing
140 /// multiple concurrent function calls without blocking each other.
141 ///
142 /// # Returns
143 ///
144 /// Returns a `Result` containing the read guard or an error if the lock is poisoned.
145 ///
146 /// # Example
147 ///
148 /// ```
149 /// use aimx::function_registry::FunctionRegistry;
150 ///
151 /// let registry = FunctionRegistry::read_lock().unwrap();
152 /// // Use registry for function calls
153 /// ```
154 pub fn read_lock() -> Result<std::sync::RwLockReadGuard<'static, Self>> {
155 Self::get_instance().read().map_err(|_| anyhow!("Function registry lock is poisoned"))
156 }
157
158 /// Get a write lock on the function registry for registration.
159 ///
160 /// This is a convenience method that returns a write guard, providing
161 /// exclusive access for registering new functions.
162 ///
163 /// # Returns
164 ///
165 /// Returns a `Result` containing the write guard or an error if the lock is poisoned.
166 ///
167 /// # Example
168 ///
169 /// ```
170 /// use aimx::{function_registry::FunctionRegistry, define_direct_function};
171 ///
172 /// let mut registry = FunctionRegistry::write_lock().unwrap();
173 /// define_direct_function!(
174 /// custom_func,
175 /// args: [f64],
176 /// |x: f64| x * 2.0
177 /// )(&mut *registry);
178 /// // Drop the lock to allow other threads to access the registry
179 /// ```
180 pub fn write_lock() -> Result<std::sync::RwLockWriteGuard<'static, Self>> {
181 Self::get_instance().write().map_err(|_| anyhow!("Function registry lock is poisoned"))
182 }
183
184 /// Create a new, empty function registry.
185 ///
186 /// This is primarily used internally during initialization.
187 fn new() -> Self {
188 Self {
189 handlers: HashMap::new(),
190 }
191 }
192
193 /// Register a function handler with the given name.
194 ///
195 /// # Arguments
196 ///
197 /// * `name` - The name to register the function under
198 /// * `handler` - The function handler implementation
199 ///
200 /// # Example
201 ///
202 /// ```
203 /// use aimx::function_registry::{FunctionRegistry, FunctionHandler};
204 /// use aimx::{ContextLike, Value, Literal};
205 /// use anyhow::Result;
206 ///
207 /// struct CustomHandler;
208 ///
209 /// impl FunctionHandler for CustomHandler {
210 /// fn call(&self, context: &mut dyn ContextLike, args: &[Value]) -> Result<Value> {
211 /// Ok(Value::from(42.0))
212 /// }
213 /// }
214 ///
215 /// // In practice, you would typically register functions through the write_lock() method
216 /// // This example shows the trait implementation for documentation purposes
217 /// ```
218 pub fn register<F>(&mut self, name: String, handler: F)
219 where
220 F: FunctionHandler + 'static,
221 {
222 self.handlers.insert(name, Box::new(handler));
223 }
224
225 /// Call a standalone function with the given name and argument.
226 ///
227 /// This method handles flattening array arguments into individual arguments
228 /// before passing them to the function handler.
229 ///
230 /// # Arguments
231 ///
232 /// * `context` - The evaluation context for closures
233 /// * `name` - The name of the function to call
234 /// * `arg` - The argument(s) to pass to the function (can be Empty, Literal, or Array)
235 ///
236 /// # Returns
237 ///
238 /// Returns a `Result<Value>` containing the function result or an error
239 /// if the function is not found or execution fails.
240 ///
241 /// # Example
242 ///
243 /// ```rust
244 /// # use aimx::{function_registry::FunctionRegistry, Context, Value, Literal};
245 /// # let mut context = Context::new();
246 /// # let registry = FunctionRegistry::read_lock().unwrap();
247 /// // Call a function with a single argument
248 /// let result = registry.function_call(&mut context, "abs", Value::from(-5.0));
249 ///
250 /// // Call a function with multiple arguments as an array
251 /// let args = Value::Array(vec![
252 /// Box::new(Value::from(2.0)),
253 /// Box::new(Value::from(3.0))
254 /// ]);
255 /// let result = registry.function_call(&mut context, "pow", args);
256 /// # assert!(result.is_ok());
257 /// ```
258 pub fn function_call(&self, context: &mut dyn ContextLike, name: &str, arg: Value) -> Result<Value> {
259 let mut args:Vec<Value> = Vec::new();
260 match arg {
261 Value::Literal(_) => args.push(arg),
262 Value::Array(array) => {
263 for box_value in array.into_iter() {
264 let value = *box_value;
265 args.push(value);
266 }
267 }
268 _ => {}
269 };
270 self.handlers
271 .get(name)
272 .ok_or_else(|| anyhow!("Function '{}' not found~{}", name, name))?
273 .call(context, &args)
274 }
275
276 /// Call a method on a value with the given name and argument.
277 ///
278 /// This method passes the target value as the first argument to the
279 /// function handler, followed by any additional arguments.
280 ///
281 /// # Arguments
282 ///
283 /// * `context` - The evaluation context for closures
284 /// * `name` - The name of the method to call
285 /// * `value` - The value on which to call the method
286 /// * `arg` - The argument(s) to pass to the method (can be Empty, Literal, or Array)
287 ///
288 /// # Returns
289 ///
290 /// Returns a `Result<Value>` containing the method result or an error
291 /// if the method is not found or execution fails.
292 ///
293 /// # Example
294 ///
295 /// ```rust
296 /// # use aimx::{function_registry::FunctionRegistry, Context, Value, Literal};
297 /// # let mut context = Context::new();
298 /// # let registry = FunctionRegistry::read_lock().unwrap();
299 /// // Call a method on an array value
300 /// let array_value = Value::Array(vec![
301 /// Box::new(Value::from(1.0)),
302 /// Box::new(Value::from(2.0)),
303 /// Box::new(Value::from(3.0))
304 /// ]);
305 /// let result = registry.method_call(&mut context, "sum", array_value, Value::Array(vec![]));
306 /// # assert!(result.is_ok());
307 /// ```
308 pub fn method_call(&self, context: &mut dyn ContextLike, name: &str, value: Value, arg: Value) -> Result<Value> {
309 let mut args:Vec<Value> = Vec::new();
310 args.push(value);
311 match arg {
312 Value::Literal(_) => {
313 args.push(arg);
314 }
315 Value::Array(array) => {
316 for box_value in array.into_iter() {
317 let value = *box_value;
318 args.push(value);
319 }
320 }
321 _ => {}
322 };
323 self.handlers
324 .get(name)
325 .ok_or_else(|| anyhow!("Method '{}' not found~{}", name, name))?
326 .call(context, &args)
327 }
328}
329
330/// Convert a Value to a native Rust type based on the expected type T.
331///
332/// This utility function simplifies converting [`Value`] instances to
333/// native Rust types in function implementations.
334///
335/// This function is typically used internally by the function registration macros
336/// in [`crate::macros`] and is automatically called when functions are invoked.
337///
338/// # Arguments
339///
340/// * `value` - The value to convert
341///
342/// # Returns
343///
344/// Returns a `Result<T>` containing the converted value or an error
345/// if conversion fails.
346///
347/// # Example
348///
349/// ```
350/// use aimx::{function_registry::convert_value_to_type, Value, Literal};
351///
352/// let value = Value::from(42.0);
353/// let number: f64 = convert_value_to_type(&value).unwrap();
354/// assert_eq!(number, 42.0);
355/// ```
356pub fn convert_value_to_type<T>(value: &Value) -> Result<T>
357where
358 T: ValueConverter
359{
360 T::from_value(value)
361}
362
363/// Convert a native Rust type back to a Value.
364///
365/// This utility function simplifies converting native Rust types to
366/// [`Value`] instances for function return values.
367///
368/// This function is typically used internally by the function registration macros
369/// in [`crate::macros`] and is automatically called when functions return results.
370///
371/// # Arguments
372///
373/// * `result` - The value to convert
374///
375/// # Returns
376///
377/// Returns a `Result<Value>` containing the converted value.
378///
379/// # Example
380///
381/// ```
382/// use aimx::function_registry::convert_result_to_value;
383///
384/// let number = 42.0;
385/// let value = convert_result_to_value(number).unwrap();
386/// ```
387pub fn convert_result_to_value<T>(result: T) -> Result<Value>
388where
389 T: Into<Value>
390{
391 Ok(result.into())
392}
393
394/// Trait for converting Values to native types.
395///
396/// This trait enables automatic conversion between [`Value`] instances and
397/// native Rust types. It is implemented for common types like `f64`, `String`,
398/// `bool`, `DateTime`, and `Vec<T>`.
399///
400/// This trait is automatically implemented for common types and is typically
401/// used internally by the function registration macros in [`crate::macros`].
402pub trait ValueConverter: Sized {
403 /// Convert a Value to this native type.
404 ///
405 /// # Arguments
406 ///
407 /// * `value` - The value to convert
408 ///
409 /// # Returns
410 ///
411 /// Returns a `Result<Self>` containing the converted value or an error
412 /// if conversion fails.
413 fn from_value(value: &Value) -> Result<Self>;
414}
415
416// Implementations for common types
417impl ValueConverter for f64 {
418 fn from_value(value: &Value) -> Result<Self> {
419 value.to_number()
420 }
421}
422
423impl ValueConverter for String {
424 fn from_value(value: &Value) -> Result<Self> {
425 match value {
426 Value::Literal(Literal::Text(s)) => Ok(s.clone()),
427 Value::Literal(literal) => Ok(literal.to_string()),
428 Value::Array(_) => Ok(value.to_string()),
429 _ => Ok(String::new()),
430 }
431 }
432}
433
434impl ValueConverter for bool {
435 fn from_value(value: &Value) -> Result<Self> {
436 match value {
437 Value::Literal(literal) => Ok(literal.to_bool()),
438 Value::Array(array) => Ok(!array.is_empty()),
439 _ => Ok(false),
440 }
441 }
442}
443
444impl ValueConverter for DateTime {
445 fn from_value(value: &Value) -> Result<Self> {
446 match value {
447 Value::Literal(Literal::Date(dt)) => Ok(*dt),
448 Value::Literal(literal) => {
449 // Try to convert the literal to a date
450 literal.clone().as_date().and_then(|date_literal| {
451 match date_literal {
452 Literal::Date(dt) => Ok(dt),
453 _ => Err(anyhow!("Failed to convert to DateTime"))
454 }
455 })
456 },
457 _ => Err(anyhow!("Cannot convert {} to DateTime", value.type_as_string()))
458 }
459 }
460}
461
462impl<T: ValueConverter> ValueConverter for Vec<T> {
463 fn from_value(value: &Value) -> Result<Self> {
464 match value {
465 Value::Array(array) => {
466 array.iter().map(|box_value| {
467 let value = box_value.as_ref();
468 T::from_value(&value.clone())
469 }).collect()
470 },
471 _ => {
472 // For single values, create a single-element vector
473 Ok(vec![T::from_value(value)?])
474 }
475 }
476 }
477}
478
479impl From<f64> for Value {
480 fn from(value: f64) -> Self {
481 Value::Literal(Literal::Number(value))
482 }
483}
484
485impl From<String> for Value {
486 fn from(value: String) -> Self {
487 Value::Literal(Literal::Text(value))
488 }
489}
490
491impl From<bool> for Value {
492 fn from(value: bool) -> Self {
493 Value::Literal(Literal::Bool(value))
494 }
495}
496
497impl From<DateTime> for Value {
498 fn from(value: DateTime) -> Self {
499 Value::Literal(Literal::Date(value))
500 }
501}
502
503impl From<Vec<f64>> for Value {
504 fn from(value: Vec<f64>) -> Self {
505 Value::Array(value.into_iter().map(|value|Box::new(Value::Literal(Literal::Number(value)))).collect())
506 }
507}
508
509impl From<Vec<String>> for Value {
510 fn from(value: Vec<String>) -> Self {
511 Value::Array(value.into_iter().map(|value|Box::new(Value::Literal(Literal::Text(value)))).collect())
512 }
513}
514
515impl From<Vec<bool>> for Value {
516 fn from(value: Vec<bool>) -> Self {
517 Value::Array(value.into_iter().map(|value|Box::new(Value::Literal(Literal::Bool(value)))).collect())
518 }
519}
520
521impl From<Vec<DateTime>> for Value {
522 fn from(value: Vec<DateTime>) -> Self {
523 Value::Array(value.into_iter().map(|value|Box::new(Value::Literal(Literal::Date(value)))).collect())
524 }
525}
526
527#[cfg(test)]
528mod tests {
529 use super::*;
530 use crate::Literal;
531
532 #[test]
533 fn test_function_registry_singleton() {
534 let registry1 = FunctionRegistry::get_instance();
535 let registry2 = FunctionRegistry::get_instance();
536 assert!(std::ptr::eq(registry1, registry2));
537 }
538
539 #[test]
540 fn test_read_lock() {
541 let registry = FunctionRegistry::read_lock();
542 assert!(registry.is_ok());
543 }
544
545 #[test]
546 fn test_write_lock() {
547 let registry = FunctionRegistry::write_lock();
548 assert!(registry.is_ok());
549 }
550
551 #[test]
552 fn test_convert_value_to_type_f64() {
553 let value = Value::from(42.5);
554 let result: Result<f64> = convert_value_to_type(&value);
555 assert!(result.is_ok());
556 assert_eq!(result.unwrap(), 42.5);
557 }
558
559 #[test]
560 fn test_convert_value_to_type_string() {
561 let value = Value::from("hello".to_string());
562 let result: Result<String> = convert_value_to_type(&value);
563 assert!(result.is_ok());
564 assert_eq!(result.unwrap(), "hello");
565 }
566
567 #[test]
568 fn test_convert_value_to_type_bool() {
569 let value = Value::from(true);
570 let result: Result<bool> = convert_value_to_type(&value);
571 assert!(result.is_ok());
572 assert_eq!(result.unwrap(), true);
573 }
574
575 #[test]
576 fn test_convert_result_to_value() {
577 let result = convert_result_to_value(42.0);
578 assert!(result.is_ok());
579 match result.unwrap() {
580 Value::Literal(Literal::Number(n)) => assert_eq!(n, 42.0),
581 _ => panic!("Expected Number literal"),
582 }
583 }
584
585 #[test]
586 fn test_vec_conversion() {
587 let values = vec![Value::from(1.0), Value::from(2.0), Value::from(3.0)];
588 let array_value = Value::Array(values.into_iter().map(Box::new).collect());
589
590 let result: Result<Vec<f64>> = convert_value_to_type(&array_value);
591 assert!(result.is_ok());
592 assert_eq!(result.unwrap(), vec![1.0, 2.0, 3.0]);
593 }
594}