Skip to main content

ion_core/
module.rs

1use indexmap::IndexMap;
2
3use crate::value::{BuiltinClosureFn, BuiltinFn, Value};
4
5enum ModuleFn {
6    Function(BuiltinFn),
7    Closure(BuiltinClosureFn),
8}
9
10/// A named collection of functions and values that can be registered
11/// with an Engine and accessed via `module::name` syntax in Ion scripts.
12pub struct Module {
13    pub name: String,
14    functions: IndexMap<String, (String, ModuleFn)>,
15    values: IndexMap<String, Value>,
16    submodules: IndexMap<String, Module>,
17}
18
19impl Module {
20    pub fn new(name: impl Into<String>) -> Self {
21        Self {
22            name: name.into(),
23            functions: IndexMap::new(),
24            values: IndexMap::new(),
25            submodules: IndexMap::new(),
26        }
27    }
28
29    /// Register a builtin function in this module.
30    pub fn register_fn(&mut self, name: &str, func: BuiltinFn) {
31        let qualified = format!("{}::{}", self.name, name);
32        self.functions
33            .insert(name.to_string(), (qualified, ModuleFn::Function(func)));
34    }
35
36    /// Register a closure-backed builtin function in this module.
37    pub fn register_closure<F>(&mut self, name: &str, func: F)
38    where
39        F: Fn(&[Value]) -> Result<Value, String> + Send + Sync + 'static,
40    {
41        let qualified = format!("{}::{}", self.name, name);
42        self.functions.insert(
43            name.to_string(),
44            (qualified, ModuleFn::Closure(BuiltinClosureFn::new(func))),
45        );
46    }
47
48    /// Register a constant value in this module.
49    pub fn set(&mut self, name: &str, value: Value) {
50        self.values.insert(name.to_string(), value);
51    }
52
53    /// Register a submodule (e.g., `net::http`).
54    pub fn register_submodule(&mut self, sub: Module) {
55        self.submodules.insert(sub.name.clone(), sub);
56    }
57
58    /// Convert this module into a `Value::Dict` for use in the interpreter env.
59    /// Functions become `Value::BuiltinFn`, submodules become nested dicts.
60    pub fn to_value(&self) -> Value {
61        let mut map = IndexMap::new();
62
63        for (name, (qualified, func)) in &self.functions {
64            let value = match func {
65                ModuleFn::Function(func) => Value::BuiltinFn(qualified.clone(), *func),
66                ModuleFn::Closure(func) => Value::BuiltinClosure(qualified.clone(), func.clone()),
67            };
68            map.insert(name.clone(), value);
69        }
70
71        for (name, value) in &self.values {
72            map.insert(name.clone(), value.clone());
73        }
74
75        for (name, sub) in &self.submodules {
76            map.insert(name.clone(), sub.to_value());
77        }
78
79        Value::Dict(map)
80    }
81
82    /// Get all exported names (for `use mod::*`).
83    pub fn names(&self) -> Vec<String> {
84        let mut names: Vec<String> = self.functions.keys().cloned().collect();
85        names.extend(self.values.keys().cloned());
86        names.extend(self.submodules.keys().cloned());
87        names
88    }
89}