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#[derive(Debug, Clone)]
27pub struct CompiledProgram {
28 pub(crate) source: String,
29 pub(crate) program: crate::ast::Program,
30}
31
32impl CompiledProgram {
33 pub fn source(&self) -> &str {
35 &self.source
36 }
37
38 pub fn program(&self) -> &crate::ast::Program {
40 &self.program
41 }
42}
43
44impl From<crate::ast::Program> for CompiledProgram {
45 fn from(program: crate::ast::Program) -> Self {
47 Self {
48 source: String::new(),
49 program,
50 }
51 }
52}
53
54#[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 kind: TokenKind::Eof, range: Range::default(),
86 module_id: ArenaId::new(0), }),
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 pub fn set_optimization_level(&mut self, level: OptimizationLevel) {
110 self.optimization_level = level;
111 }
112
113 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 pub fn set_search_paths(&mut self, paths: Vec<PathBuf>) {
126 self.evaluator.module_loader.set_search_paths(paths);
127 }
128
129 pub fn define_string_value(&self, name: &str, value: &str) {
134 self.evaluator.define_string_value(name, value);
135 }
136
137 pub fn define_value(&self, name: &str, value: RuntimeValue) {
139 self.evaluator.define_value(name, value);
140 }
141
142 pub fn load_builtin_module(&mut self) {
147 self.evaluator
148 .load_builtin_module()
149 .expect("Failed to load builtin module");
150 }
151
152 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 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 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 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 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 #[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 #[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 pub fn set_http_allowed_domains(&mut self, domains: Vec<String>) {
351 self.evaluator.module_loader.set_http_allowed_domains(domains);
352 }
353
354 pub fn clear_http_cache(&self) -> Result<(), crate::module::error::ModuleError> {
359 self.evaluator.module_loader.clear_http_cache()
360 }
361
362 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 #[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 #[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 #[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 #[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 #[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 #[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}