#[cfg(feature = "debugger")]
use std::borrow::Cow;
use std::path::PathBuf;
#[cfg(feature = "ast-json")]
use crate::Program;
#[cfg(feature = "debugger")]
use crate::eval::env::Env;
#[cfg(feature = "debugger")]
use crate::module::ModuleId;
use crate::{
ArenaId, LocalFsModuleResolver, ModuleResolver, MqResult, Range, RuntimeValue, Shared, SharedCell, TokenKind,
token_alloc,
};
#[cfg(feature = "debugger")]
use crate::{Debugger, DebuggerHandler};
use crate::{
ModuleLoader, Token,
arena::Arena,
error::{self},
eval::Evaluator,
parse,
};
#[derive(Debug, Clone)]
pub struct Engine<T: ModuleResolver = LocalFsModuleResolver> {
pub(crate) evaluator: Evaluator<T>,
token_arena: Shared<SharedCell<Arena<Shared<Token>>>>,
}
fn create_default_token_arena() -> Shared<SharedCell<Arena<Shared<Token>>>> {
let token_arena = Shared::new(SharedCell::new(Arena::new(2048)));
token_alloc(
&token_arena,
&Shared::new(Token {
kind: TokenKind::Eof, range: Range::default(),
module_id: ArenaId::new(0), }),
);
token_arena
}
impl<T: ModuleResolver> Default for Engine<T> {
fn default() -> Self {
Self::new(T::default())
}
}
impl<T: ModuleResolver> Engine<T> {
pub fn new(module_resolver: T) -> Self {
let token_arena = create_default_token_arena();
Self {
evaluator: Evaluator::new(ModuleLoader::new(module_resolver), Shared::clone(&token_arena)),
token_arena,
}
}
pub fn set_max_call_stack_depth(&mut self, max_call_stack_depth: u32) {
self.evaluator.options.max_call_stack_depth = max_call_stack_depth;
}
pub fn set_search_paths(&mut self, paths: Vec<PathBuf>) {
self.evaluator.module_loader.set_search_paths(paths);
}
pub fn define_string_value(&self, name: &str, value: &str) {
self.evaluator.define_string_value(name, value);
}
pub fn load_builtin_module(&mut self) {
self.evaluator
.load_builtin_module()
.expect("Failed to load builtin module");
}
pub fn load_module(&mut self, module_name: &str) -> Result<(), Box<error::Error>> {
let module = self
.evaluator
.module_loader
.load_from_file(module_name, Shared::clone(&self.token_arena));
let module =
module.map_err(|e| error::Error::from_error("", e.into(), self.evaluator.module_loader.clone()))?;
self.evaluator.load_module(module).map_err(|e| {
Box::new(error::Error::from_error(
"",
e.into(),
self.evaluator.module_loader.clone(),
))
})
}
pub fn eval<I: Iterator<Item = RuntimeValue>>(&mut self, code: &str, input: I) -> MqResult {
if code.is_empty() {
return Ok(vec![].into());
}
let program = parse(code, Shared::clone(&self.token_arena))?;
#[cfg(feature = "debugger")]
self.evaluator.module_loader.set_source_code(code.to_string());
self.evaluator
.eval(&program, input.into_iter())
.map(|values| values.into())
.map_err(|e| Box::new(error::Error::from_error(code, e, self.evaluator.module_loader.clone())))
}
#[cfg(feature = "ast-json")]
pub fn eval_ast<I: Iterator<Item = RuntimeValue>>(&mut self, program: Program, input: I) -> MqResult {
self.evaluator
.eval(&program, input.into_iter())
.map(|values| values.into())
.map_err(|e| Box::new(error::Error::from_error("", e, self.evaluator.module_loader.clone())))
}
#[cfg(feature = "debugger")]
pub fn debugger(&self) -> Shared<SharedCell<Debugger>> {
self.evaluator.debugger()
}
#[cfg(feature = "debugger")]
pub fn set_debugger_handler(&mut self, handler: Box<dyn DebuggerHandler>) {
self.evaluator.set_debugger_handler(handler);
}
#[cfg(feature = "debugger")]
pub fn token_arena(&self) -> Shared<SharedCell<Arena<Shared<Token>>>> {
Shared::clone(&self.token_arena)
}
#[cfg(feature = "debugger")]
pub fn switch_env(&self, env: Shared<SharedCell<Env>>) -> Self {
#[cfg(not(feature = "sync"))]
let token_arena = Shared::new(SharedCell::new(self.token_arena.borrow().clone()));
#[cfg(feature = "sync")]
let token_arena = Shared::new(SharedCell::new(self.token_arena.read().unwrap().clone()));
Self {
evaluator: Evaluator::with_env(Shared::clone(&token_arena), Shared::clone(&env)),
token_arena: Shared::clone(&token_arena),
}
}
#[cfg(feature = "debugger")]
pub fn get_module_name(&self, module_id: ModuleId) -> Cow<'static, str> {
self.evaluator.module_loader.module_name(module_id)
}
#[cfg(feature = "debugger")]
pub fn get_source_code_for_debug(&self, module_id: ModuleId) -> Result<String, Box<error::Error>> {
let source_code = self.evaluator.module_loader.get_source_code_for_debug(module_id);
source_code.map_err(|e| {
Box::new(error::Error::from_error(
"",
e.into(),
self.evaluator.module_loader.clone(),
))
})
}
pub const fn version() -> &'static str {
env!("CARGO_PKG_VERSION")
}
}
#[cfg(test)]
mod tests {
use crate::DefaultEngine;
use scopeguard::defer;
use std::io::Write;
use std::{fs::File, path::PathBuf};
fn create_file(name: &str, content: &str) -> (PathBuf, PathBuf) {
let temp_dir = std::env::temp_dir();
let temp_file_path = temp_dir.join(name);
let mut file = File::create(&temp_file_path).expect("Failed to create temp file");
file.write_all(content.as_bytes())
.expect("Failed to write to temp file");
(temp_dir, temp_file_path)
}
#[test]
fn test_set_paths() {
let mut engine = DefaultEngine::default();
let paths = vec![PathBuf::from("/test/path")];
engine.set_search_paths(paths.clone());
assert_eq!(engine.evaluator.module_loader.search_paths(), paths);
}
#[test]
fn test_set_max_call_stack_depth() {
let mut engine = DefaultEngine::default();
let default_depth = engine.evaluator.options.max_call_stack_depth;
let new_depth = default_depth + 10;
engine.set_max_call_stack_depth(new_depth);
assert_eq!(engine.evaluator.options.max_call_stack_depth, new_depth);
}
#[test]
fn test_version() {
let version = DefaultEngine::version();
assert!(!version.is_empty());
}
#[test]
fn test_load_module() {
let (temp_dir, temp_file_path) = create_file("test_module.mq", "def func1(): 42;");
let temp_file_path_clone = temp_file_path.clone();
defer! {
if temp_file_path_clone.exists() {
std::fs::remove_file(&temp_file_path_clone).expect("Failed to delete temp file");
}
}
let mut engine = DefaultEngine::default();
engine.set_search_paths(vec![temp_dir]);
let result = engine.load_module("test_module");
assert!(result.is_ok());
}
#[test]
fn test_error_load_module() {
let (temp_dir, temp_file_path) = create_file("error.mq", "error");
let temp_file_path_clone = temp_file_path.clone();
defer! {
if temp_file_path_clone.exists() {
std::fs::remove_file(&temp_file_path_clone).expect("Failed to delete temp file");
}
}
let mut engine = DefaultEngine::default();
engine.set_search_paths(vec![temp_dir]);
let result = engine.load_module("error");
assert!(result.is_err());
}
#[test]
fn test_eval() {
let mut engine = DefaultEngine::default();
let result = engine.eval("add(1, 1)", vec!["".to_string().into()].into_iter());
assert!(result.is_ok());
let values = result.unwrap();
assert_eq!(values.len(), 1);
}
#[cfg(feature = "ast-json")]
#[test]
fn test_eval_ast() {
use crate::{AstExpr, AstLiteral, AstNode, Shared};
let mut engine = DefaultEngine::default();
engine.load_builtin_module();
let program = vec![Shared::new(AstNode {
token_id: crate::arena::ArenaId::new(1),
expr: Shared::new(AstExpr::Literal(AstLiteral::String("hello".to_string()))),
})];
let result = engine.eval_ast(program, crate::null_input().into_iter());
assert!(result.is_ok());
let values = result.unwrap();
assert_eq!(values.len(), 1);
assert_eq!(values[0], "hello".to_string().into());
}
#[cfg(feature = "sync")]
#[test]
fn test_engine_thread_usage_with_sync_feature() {
use std::sync::{Arc, Mutex};
use crate::Engine;
let engine: Arc<Mutex<Engine>> = Arc::new(Mutex::new(Engine::default()));
let engine_clone = Arc::clone(&engine);
let handle = std::thread::spawn(move || {
let mut engine = engine_clone.lock().unwrap();
let result = engine.eval("2 + 3", vec!["".to_string().into()].into_iter());
assert!(result.is_ok());
let values = result.unwrap();
assert_eq!(values.len(), 1);
assert_eq!(values[0], 5.into());
});
handle.join().expect("Threaded engine usage failed");
}
#[cfg(feature = "debugger")]
#[test]
fn test_switch_env() {
use crate::eval::env::Env;
use crate::{RuntimeValue, Shared, SharedCell, null_input};
let engine = DefaultEngine::default();
let env = Shared::new(SharedCell::new(Env::default()));
env.write().unwrap().define("runtime".into(), RuntimeValue::NONE);
let mut new_engine = engine.switch_env(env);
assert_eq!(
new_engine.eval("runtime", null_input().into_iter()).unwrap()[0],
RuntimeValue::NONE
);
}
#[cfg(feature = "debugger")]
#[test]
fn test_get_source_code_for_debug() {
use crate::module::ModuleId;
let mut engine = DefaultEngine::default();
engine.load_builtin_module();
let module_id = ModuleId::new(0);
let result = engine.get_source_code_for_debug(module_id);
assert!(result.is_ok());
}
}