Skip to main content

ion_core/
module.rs

1use indexmap::IndexMap;
2#[cfg(feature = "async-runtime")]
3use std::future::Future;
4
5#[cfg(feature = "async-runtime")]
6use crate::error::IonError;
7#[cfg(feature = "async-runtime")]
8use crate::value::AsyncBuiltinClosureFn;
9use crate::value::{BuiltinClosureFn, BuiltinFn, Value};
10
11enum ModuleFn {
12    Function(BuiltinFn),
13    Closure(BuiltinClosureFn),
14    #[cfg(feature = "async-runtime")]
15    AsyncClosure(AsyncBuiltinClosureFn),
16}
17
18/// A named collection of functions and values that can be registered
19/// with an Engine and accessed via `module::name` syntax in Ion scripts.
20pub struct Module {
21    pub name: String,
22    functions: IndexMap<String, (String, ModuleFn)>,
23    values: IndexMap<String, Value>,
24    submodules: IndexMap<String, Module>,
25}
26
27impl Module {
28    pub fn new(name: impl Into<String>) -> Self {
29        Self {
30            name: name.into(),
31            functions: IndexMap::new(),
32            values: IndexMap::new(),
33            submodules: IndexMap::new(),
34        }
35    }
36
37    /// Register a builtin function in this module.
38    pub fn register_fn(&mut self, name: &str, func: BuiltinFn) {
39        let qualified = format!("{}::{}", self.name, name);
40        self.functions
41            .insert(name.to_string(), (qualified, ModuleFn::Function(func)));
42    }
43
44    /// Register a closure-backed builtin function in this module.
45    pub fn register_closure<F>(&mut self, name: &str, func: F)
46    where
47        F: Fn(&[Value]) -> Result<Value, String> + Send + Sync + 'static,
48    {
49        let qualified = format!("{}::{}", self.name, name);
50        self.functions.insert(
51            name.to_string(),
52            (qualified, ModuleFn::Closure(BuiltinClosureFn::new(func))),
53        );
54    }
55
56    /// Register an async closure-backed builtin function in this module.
57    ///
58    /// Scripts call async module functions like normal functions under
59    /// `Engine::eval_async`; synchronous evaluation rejects them explicitly.
60    #[cfg(feature = "async-runtime")]
61    pub fn register_async_fn<F, Fut>(&mut self, name: &str, func: F)
62    where
63        F: Fn(Vec<Value>) -> Fut + 'static,
64        Fut: Future<Output = Result<Value, IonError>> + 'static,
65    {
66        let qualified = format!("{}::{}", self.name, name);
67        self.functions.insert(
68            name.to_string(),
69            (
70                qualified,
71                ModuleFn::AsyncClosure(AsyncBuiltinClosureFn::new(func)),
72            ),
73        );
74    }
75
76    /// Register a constant value in this module.
77    pub fn set(&mut self, name: &str, value: Value) {
78        self.values.insert(name.to_string(), value);
79    }
80
81    /// Register a submodule (e.g., `net::http`).
82    pub fn register_submodule(&mut self, sub: Module) {
83        self.submodules.insert(sub.name.clone(), sub);
84    }
85
86    /// Convert this module into a `Value::Dict` for use in the interpreter env.
87    /// Functions become `Value::BuiltinFn`, submodules become nested dicts.
88    pub fn to_value(&self) -> Value {
89        let mut map = IndexMap::new();
90
91        for (name, (qualified, func)) in &self.functions {
92            let value = match func {
93                ModuleFn::Function(func) => Value::BuiltinFn(qualified.clone(), *func),
94                ModuleFn::Closure(func) => Value::BuiltinClosure(qualified.clone(), func.clone()),
95                #[cfg(feature = "async-runtime")]
96                ModuleFn::AsyncClosure(func) => {
97                    Value::AsyncBuiltinClosure(qualified.clone(), func.clone())
98                }
99            };
100            map.insert(name.clone(), value);
101        }
102
103        for (name, value) in &self.values {
104            map.insert(name.clone(), value.clone());
105        }
106
107        for (name, sub) in &self.submodules {
108            map.insert(name.clone(), sub.to_value());
109        }
110
111        Value::Dict(map)
112    }
113
114    /// Get all exported names (for `use mod::*`).
115    pub fn names(&self) -> Vec<String> {
116        let mut names: Vec<String> = self.functions.keys().cloned().collect();
117        names.extend(self.values.keys().cloned());
118        names.extend(self.submodules.keys().cloned());
119        names
120    }
121}