Skip to main content

ion_core/
engine.rs

1use std::collections::HashMap;
2
3use crate::error::IonError;
4use crate::host_types::{HostEnumDef, HostStructDef, IonType, IonTypeDef};
5use crate::interpreter::{Interpreter, Limits};
6use crate::lexer::Lexer;
7use crate::module::Module;
8use crate::parser::Parser;
9use crate::value::Value;
10
11/// The public embedding API for the Ion interpreter.
12pub struct Engine {
13    interpreter: Interpreter,
14}
15
16impl Engine {
17    pub fn new() -> Self {
18        Self {
19            interpreter: Interpreter::new(),
20        }
21    }
22
23    /// Evaluate a script, returning the last expression's value.
24    pub fn eval(&mut self, source: &str) -> Result<Value, IonError> {
25        let mut lexer = Lexer::new(source);
26        let tokens = lexer.tokenize()?;
27        let mut parser = Parser::new(tokens);
28        let program = parser.parse_program()?;
29        self.interpreter.eval_program(&program)
30    }
31
32    /// Inject a value into the script scope.
33    pub fn set(&mut self, name: &str, value: Value) {
34        self.interpreter.env.define(name.to_string(), value, false);
35    }
36
37    /// Read a variable from the script scope.
38    pub fn get(&self, name: &str) -> Option<Value> {
39        self.interpreter.env.get(name).cloned()
40    }
41
42
43    /// Get all top-level bindings.
44    pub fn get_all(&self) -> HashMap<String, Value> {
45        self.interpreter.env.top_level()
46    }
47
48    /// Set execution limits.
49    pub fn set_limits(&mut self, limits: Limits) {
50        self.interpreter.limits = limits;
51    }
52
53    /// Register a built-in function.
54    pub fn register_fn(&mut self, name: &str, func: fn(&[Value]) -> Result<Value, String>) {
55        self.interpreter.env.define(
56            name.to_string(),
57            Value::BuiltinFn(name.to_string(), func),
58            false,
59        );
60    }
61
62    /// Register a built-in backed by a closure. Unlike `register_fn`,
63    /// this accepts any `Fn` — including closures that capture
64    /// host-side state such as a `tokio::runtime::Handle`, a database
65    /// pool, or shared counters. See `docs/concurrency.md` for the
66    /// tokio embedding pattern.
67    pub fn register_closure<F>(&mut self, name: &str, func: F)
68    where
69        F: Fn(&[Value]) -> Result<Value, String> + Send + Sync + 'static,
70    {
71        self.interpreter.env.define(
72            name.to_string(),
73            Value::BuiltinClosure(
74                name.to_string(),
75                crate::value::BuiltinClosureFn::new(func),
76            ),
77            false,
78        );
79    }
80
81    /// Register a host struct type that scripts can construct and match on.
82    pub fn register_struct(&mut self, def: HostStructDef) {
83        self.interpreter.types.register_struct(def);
84    }
85
86    /// Register a host enum type that scripts can construct and match on.
87    pub fn register_enum(&mut self, def: HostEnumDef) {
88        self.interpreter.types.register_enum(def);
89    }
90
91    /// Register a module that scripts can access via `module::name` or `use module::*`.
92    pub fn register_module(&mut self, module: Module) {
93        let name = module.name.clone();
94        let value = module.to_value();
95        self.interpreter.env.define(name, value, false);
96    }
97
98    /// Register a type via the IonType trait (used with `#[derive(IonType)]`).
99    pub fn register_type<T: IonType>(&mut self) {
100        match T::ion_type_def() {
101            IonTypeDef::Struct(def) => self.interpreter.types.register_struct(def),
102            IonTypeDef::Enum(def) => self.interpreter.types.register_enum(def),
103        }
104    }
105
106    /// Inject a typed Rust value into the script scope.
107    pub fn set_typed<T: IonType>(&mut self, name: &str, value: &T) {
108        self.interpreter
109            .env
110            .define(name.to_string(), value.to_ion(), false);
111    }
112
113    /// Extract a typed Rust value from the script scope.
114    pub fn get_typed<T: IonType>(&self, name: &str) -> Result<T, String> {
115        let val = self
116            .interpreter
117            .env
118            .get(name)
119            .ok_or_else(|| format!("{}{}{}", ion_str!("variable '"), name, ion_str!("' not found")))?;
120        T::from_ion(val)
121    }
122
123    /// Evaluate a script via the bytecode VM. Falls back to tree-walk for
124    /// unsupported features (concurrency).
125    #[cfg(feature = "vm")]
126    pub fn vm_eval(&mut self, source: &str) -> Result<Value, IonError> {
127        let mut lexer = Lexer::new(source);
128        let tokens = lexer.tokenize()?;
129        let mut parser = Parser::new(tokens);
130        let program = parser.parse_program()?;
131
132        // Try the bytecode path first
133        let compiler = crate::compiler::Compiler::new();
134        match compiler.compile_program(&program) {
135            Ok((chunk, fn_chunks)) => {
136                let mut vm = crate::vm::Vm::with_env(std::mem::take(&mut self.interpreter.env));
137                // Pre-populate the VM's function cache with compiled chunks
138                vm.preload_fn_chunks(fn_chunks);
139                // Pass host type registry to VM
140                vm.set_types(self.interpreter.types.clone());
141                // Register builtins in VM env
142                crate::interpreter::register_builtins(vm.env_mut());
143                let result = vm.execute(&chunk);
144                // Restore env back to interpreter
145                self.interpreter.env = std::mem::take(vm.env_mut());
146                result
147            }
148            Err(_) => {
149                // Compilation failed (unsupported feature) — fall back to tree-walk
150                self.interpreter.eval_program(&program)
151            }
152        }
153    }
154}
155
156impl Default for Engine {
157    fn default() -> Self {
158        Self::new()
159    }
160}