Skip to main content

mq_lang/
engine.rs

1#[cfg(feature = "debugger")]
2use std::borrow::Cow;
3use std::path::PathBuf;
4
5#[cfg(feature = "debugger")]
6use crate::eval::env::Env;
7#[cfg(feature = "debugger")]
8use crate::module::ModuleId;
9use crate::{
10    ArenaId, ModuleResolver, MqResult, Range, RuntimeValue, Shared, SharedCell, TokenKind,
11    module::resolver::DefaultModuleResolver, token_alloc,
12};
13#[cfg(feature = "debugger")]
14use crate::{Debugger, DebuggerHandler};
15
16use crate::{
17    ModuleLoader, Token,
18    arena::Arena,
19    error::{self},
20    eval::Evaluator,
21    optimizer::{OptimizationLevel, Optimizer},
22    parse,
23};
24
25/// A compiled mq program bundled with its original source, returned by [`Engine::compile`].
26#[derive(Debug, Clone)]
27pub struct CompiledProgram {
28    pub(crate) source: String,
29    pub(crate) program: crate::ast::Program,
30}
31
32impl CompiledProgram {
33    /// Returns the original source code.
34    pub fn source(&self) -> &str {
35        &self.source
36    }
37
38    /// Returns the underlying AST nodes.
39    pub fn program(&self) -> &crate::ast::Program {
40        &self.program
41    }
42}
43
44impl From<crate::ast::Program> for CompiledProgram {
45    /// Wraps a raw `Program` (e.g. from `ast_from_json`) with no source context.
46    fn from(program: crate::ast::Program) -> Self {
47        Self {
48            source: String::new(),
49            program,
50        }
51    }
52}
53
54/// The main execution engine for the mq.
55///
56/// The `Engine` manages parsing, optimization, and evaluation of mq code.
57/// It provides methods for configuration, loading modules, and evaluating code.
58///
59/// # Examples
60///
61/// ```rust
62/// use mq_lang::DefaultEngine;
63///
64/// let mut engine = DefaultEngine::default();
65/// engine.load_builtin_module();
66///
67/// let input = mq_lang::parse_text_input("hello").unwrap();
68/// let result = engine.eval("add(\" world\")", input.into_iter());
69/// assert_eq!(result.unwrap(), vec!["hello world".to_string().into()].into());
70/// ```
71#[derive(Debug, Clone)]
72pub struct Engine<T: ModuleResolver = DefaultModuleResolver> {
73    pub(crate) evaluator: Evaluator<T>,
74    token_arena: Shared<SharedCell<Arena<Shared<Token>>>>,
75    optimization_level: OptimizationLevel,
76}
77
78fn create_default_token_arena() -> Shared<SharedCell<Arena<Shared<Token>>>> {
79    let token_arena = Shared::new(SharedCell::new(Arena::new(2048)));
80    token_alloc(
81        &token_arena,
82        &Shared::new(Token {
83            // Ensure at least one token for ArenaId::new(0)
84            kind: TokenKind::Eof, // Dummy token
85            range: Range::default(),
86            module_id: ArenaId::new(0), // Dummy module_id
87        }),
88    );
89    token_arena
90}
91
92impl<T: ModuleResolver> Default for Engine<T> {
93    fn default() -> Self {
94        Self::new(T::default())
95    }
96}
97
98impl<T: ModuleResolver> Engine<T> {
99    pub fn new(module_resolver: T) -> Self {
100        let token_arena = create_default_token_arena();
101        Self {
102            evaluator: Evaluator::new(ModuleLoader::new(module_resolver), Shared::clone(&token_arena)),
103            token_arena,
104            optimization_level: OptimizationLevel::default(),
105        }
106    }
107
108    /// Set the optimization level for AST transformations applied before evaluation.
109    pub fn set_optimization_level(&mut self, level: OptimizationLevel) {
110        self.optimization_level = level;
111    }
112
113    /// Set the maximum call stack depth for function calls.
114    ///
115    /// This prevents infinite recursion by limiting how deep function
116    /// calls can be nested. Useful for controlling resource usage.
117    pub fn set_max_call_stack_depth(&mut self, max_call_stack_depth: u32) {
118        self.evaluator.options.max_call_stack_depth = max_call_stack_depth;
119    }
120
121    /// Set search paths for module loading.
122    ///
123    /// These paths will be searched when loading external modules
124    /// via the `include` statement in mq code.
125    pub fn set_search_paths(&mut self, paths: Vec<PathBuf>) {
126        self.evaluator.module_loader.set_search_paths(paths);
127    }
128
129    /// Define a string variable that can be used in mq code.
130    ///
131    /// This allows you to inject values from the host environment
132    /// into the mq execution context.
133    pub fn define_string_value(&self, name: &str, value: &str) {
134        self.evaluator.define_string_value(name, value);
135    }
136
137    /// Defines an arbitrary runtime value in the current environment.
138    pub fn define_value(&self, name: &str, value: RuntimeValue) {
139        self.evaluator.define_value(name, value);
140    }
141
142    /// Load the built-in function modules.
143    ///
144    /// This must be called to enable access to standard functions
145    /// like `add`, `sub`, `map`, `filter`, etc.
146    pub fn load_builtin_module(&mut self) {
147        self.evaluator
148            .load_builtin_module()
149            .expect("Failed to load builtin module");
150    }
151
152    /// Import an external module by name.
153    ///
154    /// The module will be searched for in the configured search paths
155    /// and made available for use in mq code.
156    pub fn import_module(&mut self, module_name: &str) -> Result<(), Box<error::Error>> {
157        let module = self
158            .evaluator
159            .module_loader
160            .load_from_file(module_name, Shared::clone(&self.token_arena));
161        let module =
162            module.map_err(|e| error::Error::from_error("", e.into(), self.evaluator.module_loader.clone()))?;
163
164        let _ = self.evaluator.import_module(module).map_err(|e| {
165            Box::new(error::Error::from_error(
166                "",
167                e.into(),
168                self.evaluator.module_loader.clone(),
169            ))
170        })?;
171
172        Ok(())
173    }
174
175    /// Load an external module by name.
176    ///
177    /// The module will be searched for in the configured search paths
178    /// and made available for use in mq code.
179    pub fn load_module(&mut self, module_name: &str) -> Result<(), Box<error::Error>> {
180        let module = self
181            .evaluator
182            .module_loader
183            .load_from_file(module_name, Shared::clone(&self.token_arena));
184        let module =
185            module.map_err(|e| error::Error::from_error("", e.into(), self.evaluator.module_loader.clone()))?;
186
187        self.evaluator.load_module(module).map_err(|e| {
188            Box::new(error::Error::from_error(
189                "",
190                e.into(),
191                self.evaluator.module_loader.clone(),
192            ))
193        })
194    }
195
196    /// The main engine for evaluating mq code.
197    ///
198    /// The `Engine` manages parsing, optimization, and evaluation of mq.
199    /// It provides methods for configuration, loading modules, and evaluating code.
200    ///
201    /// # Examples
202    ///
203    /// ```
204    /// let mut engine = mq_lang::DefaultEngine::default();
205    /// engine.load_builtin_module();
206    ///
207    /// let input = mq_lang::parse_text_input("hello").unwrap();
208    /// let result = engine.eval("add(\" world\")", input.into_iter());
209    /// assert_eq!(result.unwrap(), vec!["hello world".to_string().into()].into());
210    /// ```
211    ///
212    pub fn eval<I: Iterator<Item = RuntimeValue>>(&mut self, code: &str, input: I) -> MqResult {
213        if code.is_empty() {
214            return Ok(vec![].into());
215        }
216
217        let program = parse(code, Shared::clone(&self.token_arena))?;
218        let program = Optimizer::with_level(self.optimization_level).optimize(program);
219
220        #[cfg(feature = "debugger")]
221        self.evaluator.module_loader.set_source_code(code.to_string());
222
223        self.evaluator
224            .eval(&program, input.into_iter())
225            .map(|values| values.into())
226            .map_err(|e| Box::new(error::Error::from_error(code, e, self.evaluator.module_loader.clone())))
227    }
228
229    /// Compiles mq code into a [`CompiledProgram`] that can be evaluated multiple times.
230    ///
231    /// Use this with `eval_compiled` to avoid re-parsing the same query for each input.
232    pub fn compile(&mut self, code: &str) -> Result<CompiledProgram, Box<error::Error>> {
233        if code.is_empty() {
234            return Ok(CompiledProgram {
235                source: String::new(),
236                program: vec![],
237            });
238        }
239        let program = parse(code, Shared::clone(&self.token_arena))?;
240        let program = Optimizer::with_level(self.optimization_level).optimize(program);
241        Ok(CompiledProgram {
242            source: code.to_string(),
243            program,
244        })
245    }
246
247    /// Evaluates a pre-compiled program against the given input.
248    ///
249    /// Use with `compile` to avoid re-parsing the same query for each input file,
250    /// or with a [`CompiledProgram`] constructed from a deserialized JSON AST (`ast-json` feature).
251    ///
252    /// # Examples
253    ///
254    /// ```rust
255    /// let mut engine = mq_lang::DefaultEngine::default();
256    /// engine.load_builtin_module();
257    ///
258    /// let compiled = engine.compile("add(\" world\")").unwrap();
259    /// let input = mq_lang::parse_text_input("hello").unwrap();
260    /// let result = engine.eval_compiled(&compiled, input.into_iter());
261    /// assert_eq!(result.unwrap(), vec!["hello world".to_string().into()].into());
262    /// ```
263    pub fn eval_compiled<I: Iterator<Item = RuntimeValue>>(
264        &mut self,
265        compiled: &CompiledProgram,
266        input: I,
267    ) -> MqResult {
268        #[cfg(feature = "debugger")]
269        self.evaluator.module_loader.set_source_code(compiled.source.clone());
270
271        self.evaluator
272            .eval(&compiled.program, input)
273            .map(|values| values.into())
274            .map_err(|e| {
275                Box::new(error::Error::from_error(
276                    &compiled.source,
277                    e,
278                    self.evaluator.module_loader.clone(),
279                ))
280            })
281    }
282
283    /// Returns a reference to the debugger instance.
284    ///
285    /// This allows interactive debugging of mq code execution when the
286    /// `debugger` feature is enabled. Use this to inspect or control
287    /// the execution state for advanced debugging scenarios.
288    #[cfg(feature = "debugger")]
289    pub fn debugger(&self) -> Shared<SharedCell<Debugger>> {
290        self.evaluator.debugger()
291    }
292
293    #[cfg(feature = "debugger")]
294    pub fn set_debugger_handler(&mut self, handler: Box<dyn DebuggerHandler>) {
295        self.evaluator.set_debugger_handler(handler);
296    }
297
298    #[cfg(feature = "debugger")]
299    pub fn token_arena(&self) -> Shared<SharedCell<Arena<Shared<Token>>>> {
300        Shared::clone(&self.token_arena)
301    }
302
303    /// Returns a reference to the underlying evaluator.
304    ///
305    /// This is primarily intended for advanced use cases such as debugging,
306    /// where direct access to the evaluator internals is required.
307    #[cfg(feature = "debugger")]
308    pub fn switch_env(&self, env: Shared<SharedCell<Env>>) -> Self {
309        #[cfg(not(feature = "sync"))]
310        let token_arena = Shared::new(SharedCell::new(self.token_arena.borrow().clone()));
311        #[cfg(feature = "sync")]
312        let token_arena = Shared::new(SharedCell::new(self.token_arena.read().unwrap().clone()));
313
314        Self {
315            evaluator: Evaluator::with_env(Shared::clone(&token_arena), Shared::clone(&env)),
316            token_arena: Shared::clone(&token_arena),
317            optimization_level: self.optimization_level,
318        }
319    }
320
321    #[cfg(feature = "debugger")]
322    pub fn get_module_name(&self, module_id: ModuleId) -> Cow<'static, str> {
323        self.evaluator.module_loader.module_name(module_id)
324    }
325
326    #[cfg(feature = "debugger")]
327    pub fn get_source_code_for_debug(&self, module_id: ModuleId) -> Result<String, Box<error::Error>> {
328        let source_code = self.evaluator.module_loader.get_source_code_for_debug(module_id);
329
330        source_code.map_err(|e| {
331            Box::new(error::Error::from_error(
332                "",
333                e.into(),
334                self.evaluator.module_loader.clone(),
335            ))
336        })
337    }
338
339    pub const fn version() -> &'static str {
340        env!("CARGO_PKG_VERSION")
341    }
342}
343
344#[cfg(feature = "http-import")]
345impl Engine<DefaultModuleResolver> {
346    /// Replaces the HTTP resolver's domain allowlist.
347    ///
348    /// An empty list restricts access to the built-in default domain
349    /// (`raw.githubusercontent.com/harehare`) only; it does not open up all URLs.
350    pub fn set_http_allowed_domains(&mut self, domains: Vec<String>) {
351        self.evaluator.module_loader.set_http_allowed_domains(domains);
352    }
353
354    /// Clears all locally-cached HTTP module files.
355    ///
356    /// Call this once before processing to force a re-fetch of all cached modules
357    /// on the next resolve (e.g. when `--refresh-modules` is passed on the CLI).
358    pub fn clear_http_cache(&self) -> Result<(), crate::module::error::ModuleError> {
359        self.evaluator.module_loader.clear_http_cache()
360    }
361
362    /// Clears all HTTP module cache including versioned modules and lock files.
363    ///
364    /// Use this when `--clear-cache` is passed on the CLI to wipe everything.
365    pub fn clear_http_cache_all(&self) -> Result<(), crate::module::error::ModuleError> {
366        self.evaluator.module_loader.clear_http_cache_all()
367    }
368}
369
370#[cfg(test)]
371mod tests {
372    use super::CompiledProgram;
373    use crate::DefaultEngine;
374    use rstest::rstest;
375    use scopeguard::defer;
376    use std::io::Write;
377    use std::{fs::File, path::PathBuf};
378
379    fn create_file(name: &str, content: &str) -> (PathBuf, PathBuf) {
380        let temp_dir = std::env::temp_dir();
381        let temp_file_path = temp_dir.join(name);
382        let mut file = File::create(&temp_file_path).expect("Failed to create temp file");
383        file.write_all(content.as_bytes())
384            .expect("Failed to write to temp file");
385
386        (temp_dir, temp_file_path)
387    }
388
389    #[test]
390    fn test_set_paths() {
391        let mut engine = DefaultEngine::default();
392        let paths = vec![PathBuf::from("/test/path")];
393        engine.set_search_paths(paths.clone());
394        assert_eq!(engine.evaluator.module_loader.search_paths(), paths);
395    }
396
397    #[test]
398    fn test_set_max_call_stack_depth() {
399        let mut engine = DefaultEngine::default();
400        let default_depth = engine.evaluator.options.max_call_stack_depth;
401        let new_depth = default_depth + 10;
402
403        engine.set_max_call_stack_depth(new_depth);
404        assert_eq!(engine.evaluator.options.max_call_stack_depth, new_depth);
405    }
406
407    #[test]
408    fn test_version() {
409        let version = DefaultEngine::version();
410        assert!(!version.is_empty());
411    }
412
413    #[test]
414    fn test_load_module() {
415        let (temp_dir, temp_file_path) = create_file("test_module.mq", "def func1(): 42;");
416        let temp_file_path_clone = temp_file_path.clone();
417
418        defer! {
419            if temp_file_path_clone.exists() {
420                std::fs::remove_file(&temp_file_path_clone).expect("Failed to delete temp file");
421            }
422        }
423
424        let mut engine = DefaultEngine::default();
425        engine.set_search_paths(vec![temp_dir]);
426
427        let result = engine.load_module("test_module");
428        assert!(result.is_ok());
429    }
430
431    #[test]
432    fn test_error_load_module() {
433        let (temp_dir, temp_file_path) = create_file("error.mq", "error");
434        let temp_file_path_clone = temp_file_path.clone();
435
436        defer! {
437            if temp_file_path_clone.exists() {
438                std::fs::remove_file(&temp_file_path_clone).expect("Failed to delete temp file");
439            }
440        }
441
442        let mut engine = DefaultEngine::default();
443        engine.set_search_paths(vec![temp_dir]);
444
445        let result = engine.load_module("error");
446        assert!(result.is_err());
447    }
448
449    #[test]
450    fn test_eval() {
451        let mut engine = DefaultEngine::default();
452        let result = engine.eval("add(1, 1)", vec!["".to_string().into()].into_iter());
453        assert!(result.is_ok());
454        let values = result.unwrap();
455        assert_eq!(values.len(), 1);
456    }
457
458    #[rstest]
459    #[case("add(1, 1)", "add(1, 1)")]
460    #[case(".", ".")]
461    #[case("length(.)", "length(.)")]
462    fn test_compiled_program_source(#[case] query: &str, #[case] expected: &str) {
463        let mut engine = DefaultEngine::default();
464        let compiled = engine.compile(query).unwrap();
465        assert_eq!(compiled.source(), expected);
466        assert!(!compiled.program().is_empty());
467        assert_eq!(compiled.clone().source(), expected);
468    }
469
470    #[rstest]
471    #[case("")]
472    fn test_compile_empty_code(#[case] query: &str) {
473        let mut engine = DefaultEngine::default();
474        let compiled = engine.compile(query).unwrap();
475        assert_eq!(compiled.source(), "");
476        assert!(compiled.program().is_empty());
477    }
478
479    // --- builtin cache tests ---
480
481    /// Two sequential engines calling the same builtin functions must produce identical results,
482    /// whether the builtin module was loaded from a fresh parse or replayed from the cache.
483    #[rstest]
484    #[case("add(1, 2)", vec!["".to_string().into()], vec![3.into()])]
485    #[case("not(false)", vec!["".to_string().into()], vec![true.into()])]
486    #[case("to_string(42)", vec!["".to_string().into()], vec!["42".to_string().into()])]
487    fn test_builtin_cache_sequential_engines_consistent(
488        #[case] query: &str,
489        #[case] input: Vec<crate::RuntimeValue>,
490        #[case] expected: Vec<crate::RuntimeValue>,
491    ) {
492        let mut engine1 = DefaultEngine::default();
493        engine1.load_builtin_module();
494        let result1 = engine1.eval(query, input.clone().into_iter()).unwrap();
495
496        let mut engine2 = DefaultEngine::default();
497        engine2.load_builtin_module();
498        let result2 = engine2.eval(query, input.into_iter()).unwrap();
499
500        assert_eq!(result1.values(), &expected);
501        assert_eq!(result2.values(), &expected);
502    }
503
504    /// Compiling and evaluating a builtin function call on a second engine (cache path) must
505    /// produce the correct result — verifying that token_ids in the compiled program are valid
506    /// when the builtin tokens were injected from cache rather than freshly parsed.
507    #[rstest]
508    #[case("add(1, 2)", vec!["".to_string().into()], vec![3.into()])]
509    #[case("not(false)", vec!["".to_string().into()], vec![true.into()])]
510    #[case("len(\"hello\")", vec!["".to_string().into()], vec![5.into()])]
511    fn test_builtin_cache_eval_compiled_token_ids_valid(
512        #[case] query: &str,
513        #[case] input: Vec<crate::RuntimeValue>,
514        #[case] expected: Vec<crate::RuntimeValue>,
515    ) {
516        let mut engine1 = DefaultEngine::default();
517        engine1.load_builtin_module();
518
519        let mut engine2 = DefaultEngine::default();
520        engine2.load_builtin_module();
521        let compiled = engine2.compile(query).unwrap();
522        let result = engine2.eval_compiled(&compiled, input.into_iter()).unwrap();
523        assert_eq!(result.values(), &expected);
524    }
525
526    /// Runtime errors on a cache-using engine must carry the correct source_code.
527    #[rstest]
528    #[case("undefined_fn()", "undefined_fn()")]
529    #[case("unknown_call(1, 2)", "unknown_call(1, 2)")]
530    fn test_builtin_cache_runtime_error_preserves_source(#[case] query: &str, #[case] expected_source: &str) {
531        let mut engine1 = DefaultEngine::default();
532        engine1.load_builtin_module();
533
534        let mut engine2 = DefaultEngine::default();
535        engine2.load_builtin_module();
536        let compiled = engine2.compile(query).unwrap();
537        let err = engine2
538            .eval_compiled(&compiled, crate::null_input().into_iter())
539            .unwrap_err();
540        assert_eq!(err.source_code.inner(), expected_source);
541    }
542
543    /// The error location (token offset + span) must point to the erroring identifier in
544    /// source_code.  If cached tokens were injected at shifted positions the offset would
545    /// land on the wrong character.
546    #[rstest]
547    #[case("undefined_fn()", "undefined_fn")]
548    #[case("1 | undefined_fn()", "undefined_fn")]
549    #[case("add(1) | unknown_fn()", "unknown_fn")]
550    fn test_builtin_cache_runtime_error_token_location_correct(#[case] query: &str, #[case] expected_ident: &str) {
551        let mut engine1 = DefaultEngine::default();
552        engine1.load_builtin_module();
553
554        let mut engine2 = DefaultEngine::default();
555        engine2.load_builtin_module();
556        let compiled = engine2.compile(query).unwrap();
557        let err = engine2
558            .eval_compiled(&compiled, crate::null_input().into_iter())
559            .unwrap_err();
560
561        let offset = err.location.offset();
562        let len = err.location.len();
563        assert_eq!(
564            &err.source_code.inner()[offset..offset + len],
565            expected_ident,
566            "location must point to the erroring identifier, not a shifted position"
567        );
568        assert_eq!(offset, query.find(expected_ident).unwrap());
569    }
570
571    /// Two sequential engines (one possibly fresh-parse, one cache) must produce identical
572    /// error locations — confirming that token_id indices are not shifted by cache replay.
573    #[rstest]
574    #[case("undefined_fn()")]
575    #[case("1 | undefined_fn()")]
576    #[case("add(1) | unknown_fn()")]
577    fn test_builtin_cache_and_fresh_parse_error_location_identical(#[case] query: &str) {
578        let mut engine1 = DefaultEngine::default();
579        engine1.load_builtin_module();
580        let compiled1 = engine1.compile(query).unwrap();
581        let err1 = engine1
582            .eval_compiled(&compiled1, crate::null_input().into_iter())
583            .unwrap_err();
584
585        let mut engine2 = DefaultEngine::default();
586        engine2.load_builtin_module();
587        let compiled2 = engine2.compile(query).unwrap();
588        let err2 = engine2
589            .eval_compiled(&compiled2, crate::null_input().into_iter())
590            .unwrap_err();
591
592        assert_eq!(
593            err1.location, err2.location,
594            "error location must be identical regardless of whether builtin cache was used"
595        );
596    }
597
598    // --- CompiledProgram unit tests ---
599
600    #[test]
601    fn test_compiled_program_from_has_empty_source() {
602        let compiled = CompiledProgram::from(vec![]);
603        assert_eq!(compiled.source(), "");
604        assert!(compiled.program().is_empty());
605    }
606
607    #[rstest]
608    #[case("add(1, 1)", vec!["".to_string().into()], vec![2.into()])]
609    #[case("add(\" world\")", vec!["hello".to_string().into()], vec!["hello world".to_string().into()])]
610    #[case("add(\" world\")", vec!["hi".to_string().into()], vec!["hi world".to_string().into()])]
611    fn test_eval_compiled(
612        #[case] query: &str,
613        #[case] input: Vec<crate::RuntimeValue>,
614        #[case] expected: Vec<crate::RuntimeValue>,
615    ) {
616        let mut engine = DefaultEngine::default();
617        engine.load_builtin_module();
618        let compiled = engine.compile(query).unwrap();
619        let result = engine.eval_compiled(&compiled, input.into_iter());
620        assert!(result.is_ok());
621        assert_eq!(result.unwrap().values(), &expected);
622    }
623
624    #[rstest]
625    #[case("undefined_fn()", "undefined_fn()")]
626    #[case("unknown()", "unknown()")]
627    fn test_eval_compiled_runtime_error_preserves_source(#[case] query: &str, #[case] expected_source: &str) {
628        let mut engine = DefaultEngine::default();
629        let compiled = engine.compile(query).unwrap();
630        let err = engine
631            .eval_compiled(&compiled, crate::null_input().into_iter())
632            .unwrap_err();
633        assert_eq!(err.source_code.inner(), expected_source);
634    }
635
636    #[rstest]
637    #[case("undefined_fn()")]
638    #[case("unknown()")]
639    fn test_eval_compiled_from_program_has_empty_source_in_error(#[case] query: &str) {
640        let mut engine = DefaultEngine::default();
641        let original = engine.compile(query).unwrap();
642        let no_source = CompiledProgram::from(original.program().clone());
643        assert_eq!(no_source.source(), "");
644        let err = engine
645            .eval_compiled(&no_source, crate::null_input().into_iter())
646            .unwrap_err();
647        assert_eq!(err.source_code.inner(), "");
648    }
649
650    #[test]
651    fn test_eval_compiled_with_ast() {
652        use crate::{AstExpr, AstLiteral, AstNode, Shared};
653
654        let mut engine = DefaultEngine::default();
655        engine.load_builtin_module();
656
657        let program = vec![Shared::new(AstNode {
658            token_id: crate::arena::ArenaId::new(1),
659            expr: Shared::new(AstExpr::Literal(AstLiteral::String("hello".to_string()))),
660        })];
661
662        let compiled = CompiledProgram::from(program);
663        let result = engine.eval_compiled(&compiled, crate::null_input().into_iter());
664        assert!(result.is_ok());
665        let values = result.unwrap();
666        assert_eq!(values.len(), 1);
667        assert_eq!(values[0], "hello".to_string().into());
668    }
669
670    #[cfg(feature = "sync")]
671    #[test]
672    fn test_engine_thread_usage_with_sync_feature() {
673        use std::sync::{Arc, Mutex};
674
675        use crate::Engine;
676
677        let engine: Arc<Mutex<Engine>> = Arc::new(Mutex::new(Engine::default()));
678        let engine_clone = Arc::clone(&engine);
679
680        let handle = std::thread::spawn(move || {
681            let mut engine = engine_clone.lock().unwrap();
682            let result = engine.eval("2 + 3", vec!["".to_string().into()].into_iter());
683            assert!(result.is_ok());
684            let values = result.unwrap();
685            assert_eq!(values.len(), 1);
686            assert_eq!(values[0], 5.into());
687        });
688
689        handle.join().expect("Threaded engine usage failed");
690    }
691
692    #[cfg(feature = "debugger")]
693    #[test]
694    fn test_switch_env() {
695        use crate::eval::env::Env;
696        use crate::{RuntimeValue, Shared, SharedCell, null_input};
697
698        let engine = DefaultEngine::default();
699        let env = Shared::new(SharedCell::new(Env::default()));
700
701        env.write().unwrap().define("runtime".into(), RuntimeValue::NONE);
702
703        let mut new_engine = engine.switch_env(env);
704
705        assert_eq!(
706            new_engine.eval("runtime", null_input().into_iter()).unwrap()[0],
707            RuntimeValue::NONE
708        );
709    }
710
711    #[cfg(feature = "debugger")]
712    #[test]
713    fn test_get_source_code_for_debug() {
714        use crate::module::ModuleId;
715
716        let mut engine = DefaultEngine::default();
717        engine.load_builtin_module();
718
719        let module_id = ModuleId::new(0);
720        let result = engine.get_source_code_for_debug(module_id);
721
722        assert!(result.is_ok());
723    }
724}