Skip to main content

ion_core/
engine.rs

1use std::collections::HashMap;
2#[cfg(feature = "async-runtime")]
3use std::future::Future;
4use std::sync::Arc;
5
6use crate::error::IonError;
7use crate::host_types::{HostEnumDef, HostStructDef, IonType, IonTypeDef};
8use crate::interpreter::{Interpreter, Limits};
9use crate::lexer::Lexer;
10use crate::module::Module;
11use crate::parser::Parser;
12use crate::stdlib::OutputHandler;
13use crate::value::Value;
14
15/// The public embedding API for the Ion interpreter.
16pub struct Engine {
17    interpreter: Interpreter,
18    output: Arc<dyn OutputHandler>,
19    #[cfg(feature = "async-runtime")]
20    external_queue: crate::async_runtime::ExternalQueue,
21}
22
23impl Engine {
24    pub fn new() -> Self {
25        let output = crate::stdlib::missing_output_handler();
26        Self {
27            interpreter: Interpreter::with_output(Arc::clone(&output)),
28            output,
29            #[cfg(feature = "async-runtime")]
30            external_queue: crate::async_runtime::ExternalQueue::new(),
31        }
32    }
33
34    /// Create an engine with a host-provided output handler for `io::print*`.
35    pub fn with_output<H>(output: H) -> Self
36    where
37        H: OutputHandler + 'static,
38    {
39        Self::with_output_handler(Arc::new(output))
40    }
41
42    /// Create an engine with a shared host-provided output handler.
43    pub fn with_output_handler(output: Arc<dyn OutputHandler>) -> Self {
44        Self {
45            interpreter: Interpreter::with_output(Arc::clone(&output)),
46            output,
47            #[cfg(feature = "async-runtime")]
48            external_queue: crate::async_runtime::ExternalQueue::new(),
49        }
50    }
51
52    /// Evaluate a script, returning the last expression's value.
53    pub fn eval(&mut self, source: &str) -> Result<Value, IonError> {
54        let mut lexer = Lexer::new(source);
55        let tokens = lexer.tokenize()?;
56        let mut parser = Parser::new(tokens);
57        let program = parser.parse_program()?;
58        self.interpreter.eval_program(&program)
59    }
60
61    /// Evaluate a script through the async-runtime entry point.
62    ///
63    /// Pure synchronous scripts execute through the existing evaluator.
64    /// Compilable scripts that reference async host functions run through
65    /// the pollable bytecode continuation runtime.
66    #[cfg(feature = "async-runtime")]
67    pub fn eval_async<'a>(
68        &'a mut self,
69        source: &'a str,
70    ) -> crate::async_runtime::IonEvalFuture<'a> {
71        crate::async_runtime::IonEvalFuture::new(self, source)
72    }
73
74    /// Return a cloneable handle that host async code can use to schedule
75    /// callbacks into the async runtime.
76    #[cfg(feature = "async-runtime")]
77    pub fn handle(&self) -> crate::async_runtime::EngineHandle {
78        self.external_queue.handle()
79    }
80
81    #[cfg(feature = "async-runtime")]
82    #[allow(dead_code)]
83    pub(crate) fn external_queue(&self) -> crate::async_runtime::ExternalQueue {
84        self.external_queue.clone()
85    }
86
87    #[cfg(feature = "async-runtime")]
88    pub(crate) fn interpreter_mut(&mut self) -> &mut Interpreter {
89        &mut self.interpreter
90    }
91
92    #[cfg(feature = "async-runtime")]
93    pub(crate) fn interpreter(&self) -> &Interpreter {
94        &self.interpreter
95    }
96
97    #[cfg(feature = "async-runtime")]
98    pub(crate) fn output_handler(&self) -> Arc<dyn OutputHandler> {
99        Arc::clone(&self.output)
100    }
101
102    /// Drain externally scheduled requests.
103    ///
104    /// This is exposed while the async runtime is scaffolded so tests and
105    /// future runtime code can validate the non-reentrant handle path.
106    #[cfg(feature = "async-runtime")]
107    #[doc(hidden)]
108    pub fn drain_external_requests(&self) -> Vec<crate::async_runtime::ExternalRequest> {
109        self.external_queue.drain()
110    }
111
112    /// Inject a value into the script scope.
113    pub fn set(&mut self, name: &str, value: Value) {
114        self.interpreter.env.define(name.to_string(), value, false);
115    }
116
117    /// Read a variable from the script scope.
118    pub fn get(&self, name: &str) -> Option<Value> {
119        self.interpreter.env.get(name).cloned()
120    }
121
122    /// Get all top-level bindings.
123    pub fn get_all(&self) -> HashMap<String, Value> {
124        self.interpreter.env.top_level()
125    }
126
127    /// Set execution limits.
128    pub fn set_limits(&mut self, limits: Limits) {
129        self.interpreter.limits = limits;
130    }
131
132    /// Set the host output handler used by `io::print`, `io::println`, and
133    /// `io::eprintln`.
134    pub fn set_output<H>(&mut self, output: H)
135    where
136        H: OutputHandler + 'static,
137    {
138        self.set_output_handler(Arc::new(output));
139    }
140
141    /// Set a shared host output handler used by `io::print*`.
142    pub fn set_output_handler(&mut self, output: Arc<dyn OutputHandler>) {
143        self.output = Arc::clone(&output);
144        let io = crate::stdlib::io_module_with_output(output);
145        self.interpreter
146            .env
147            .define(io.name.clone(), io.to_value(), false);
148    }
149
150    /// Register a built-in function.
151    pub fn register_fn(&mut self, name: &str, func: fn(&[Value]) -> Result<Value, String>) {
152        self.interpreter.env.define(
153            name.to_string(),
154            Value::BuiltinFn(name.to_string(), func),
155            false,
156        );
157    }
158
159    /// Register a built-in backed by a closure. Unlike `register_fn`,
160    /// this accepts any `Fn` — including closures that capture
161    /// host-side state such as a `tokio::runtime::Handle`, a database
162    /// pool, or shared counters. See `docs/concurrency.md` for the
163    /// tokio embedding pattern.
164    pub fn register_closure<F>(&mut self, name: &str, func: F)
165    where
166        F: Fn(&[Value]) -> Result<Value, String> + Send + Sync + 'static,
167    {
168        self.interpreter.env.define(
169            name.to_string(),
170            Value::BuiltinClosure(name.to_string(), crate::value::BuiltinClosureFn::new(func)),
171            false,
172        );
173    }
174
175    /// Register a host async function. Scripts call this like a normal
176    /// function under `eval_async`; sync `eval` rejects it explicitly.
177    #[cfg(feature = "async-runtime")]
178    pub fn register_async_fn<F, Fut>(&mut self, name: &str, func: F)
179    where
180        F: Fn(Vec<Value>) -> Fut + 'static,
181        Fut: Future<Output = Result<Value, IonError>> + 'static,
182    {
183        self.interpreter.env.define(
184            name.to_string(),
185            Value::AsyncBuiltinClosure(
186                name.to_string(),
187                crate::value::AsyncBuiltinClosureFn::new(func),
188            ),
189            false,
190        );
191    }
192
193    /// Register a host struct type that scripts can construct and match on.
194    pub fn register_struct(&mut self, def: HostStructDef) {
195        self.interpreter.types.register_struct(def);
196    }
197
198    /// Register a host enum type that scripts can construct and match on.
199    pub fn register_enum(&mut self, def: HostEnumDef) {
200        self.interpreter.types.register_enum(def);
201    }
202
203    /// Register a module that scripts can access via `module::name` or `use module::*`.
204    pub fn register_module(&mut self, module: Module) {
205        let name = module.name.clone();
206        let value = module.to_value();
207        self.interpreter.env.define(name, value, false);
208    }
209
210    /// Register a type via the IonType trait (used with `#[derive(IonType)]`).
211    pub fn register_type<T: IonType>(&mut self) {
212        match T::ion_type_def() {
213            IonTypeDef::Struct(def) => self.interpreter.types.register_struct(def),
214            IonTypeDef::Enum(def) => self.interpreter.types.register_enum(def),
215        }
216    }
217
218    /// Inject a typed Rust value into the script scope.
219    pub fn set_typed<T: IonType>(&mut self, name: &str, value: &T) {
220        self.interpreter
221            .env
222            .define(name.to_string(), value.to_ion(), false);
223    }
224
225    /// Extract a typed Rust value from the script scope.
226    pub fn get_typed<T: IonType>(&self, name: &str) -> Result<T, String> {
227        let val = self.interpreter.env.get(name).ok_or_else(|| {
228            format!(
229                "{}{}{}",
230                ion_str!("variable '"),
231                name,
232                ion_str!("' not found")
233            )
234        })?;
235        T::from_ion(val)
236    }
237
238    /// Evaluate a script via the bytecode VM. Falls back to tree-walk for
239    /// unsupported features (concurrency).
240    #[cfg(feature = "vm")]
241    pub fn vm_eval(&mut self, source: &str) -> Result<Value, IonError> {
242        let mut lexer = Lexer::new(source);
243        let tokens = lexer.tokenize()?;
244        let mut parser = Parser::new(tokens);
245        let program = parser.parse_program()?;
246
247        // Try the bytecode path first
248        let compiler = crate::compiler::Compiler::new();
249        match compiler.compile_program(&program) {
250            Ok((chunk, fn_chunks)) => {
251                let mut vm = crate::vm::Vm::with_env_and_output(
252                    std::mem::take(&mut self.interpreter.env),
253                    Arc::clone(&self.output),
254                );
255                // Pre-populate the VM's function cache with compiled chunks
256                vm.preload_fn_chunks(fn_chunks);
257                // Pass host type registry to VM
258                vm.set_types(self.interpreter.types.clone());
259                let result = vm.execute(&chunk);
260                // Restore env back to interpreter
261                self.interpreter.env = std::mem::take(vm.env_mut());
262                result
263            }
264            Err(_) => {
265                // Compilation failed (unsupported feature) — fall back to tree-walk
266                self.interpreter.eval_program(&program)
267            }
268        }
269    }
270}
271
272impl Default for Engine {
273    fn default() -> Self {
274        Self::new()
275    }
276}