Add debug mode and VM tracing
Introduce a `debug_mode` flag to the `Environment` and a new `run_debug` method. This method uses a `TracingObserver` to log the VM's execution flow, including node evaluation and scope state changes. This allows for detailed inspection of script execution. The `BoundKind` enum now also includes a `display_name` method for better log readability.
This commit is contained in:
+43
-10
@@ -4,7 +4,7 @@ use std::collections::HashMap;
|
||||
use crate::ast::types::{Value, StaticType};
|
||||
use crate::ast::parser::Parser;
|
||||
use crate::ast::compiler::binder::Binder;
|
||||
use crate::ast::vm::VM;
|
||||
use crate::ast::vm::{VM, TracingObserver};
|
||||
|
||||
use crate::ast::compiler::tco::TCO;
|
||||
|
||||
@@ -13,6 +13,7 @@ use crate::ast::compiler::dumper::Dumper;
|
||||
pub struct Environment {
|
||||
pub global_names: Rc<RefCell<HashMap<String, (u32, StaticType)>>>,
|
||||
pub global_values: Rc<RefCell<Vec<Value>>>,
|
||||
pub debug_mode: bool,
|
||||
}
|
||||
|
||||
impl Default for Environment {
|
||||
@@ -26,11 +27,16 @@ impl Environment {
|
||||
let env = Self {
|
||||
global_names: Rc::new(RefCell::new(HashMap::new())),
|
||||
global_values: Rc::new(RefCell::new(Vec::new())),
|
||||
debug_mode: false,
|
||||
};
|
||||
env.register_stdlib();
|
||||
env
|
||||
}
|
||||
|
||||
pub fn set_debug_mode(&mut self, enabled: bool) {
|
||||
self.debug_mode = enabled;
|
||||
}
|
||||
|
||||
pub fn register_native(&self, name: &str, ty: StaticType, func: impl Fn(Vec<Value>) -> Value + 'static) {
|
||||
let mut names = self.global_names.borrow_mut();
|
||||
let mut values = self.global_values.borrow_mut();
|
||||
@@ -118,23 +124,50 @@ impl Environment {
|
||||
}
|
||||
|
||||
pub fn run_script(&self, source: &str) -> Result<Value, String> {
|
||||
if self.debug_mode {
|
||||
let (res, logs) = self.run_debug(source)?;
|
||||
for line in logs {
|
||||
println!("{}", line);
|
||||
}
|
||||
res
|
||||
} else {
|
||||
// 1. Parse
|
||||
let mut parser = Parser::new(source)?;
|
||||
let untyped_ast = parser.parse_expression()?;
|
||||
|
||||
// 2. Check for trailing tokens
|
||||
if !parser.at_eof() {
|
||||
return Err("Unexpected trailing expressions in script. Use (do ...) for sequences.".to_string());
|
||||
}
|
||||
|
||||
// 3. Bind & Type Check
|
||||
let bound_ast = Binder::bind_root(self.global_names.clone(), &untyped_ast)?;
|
||||
|
||||
// 4. Optimize
|
||||
let bound_ast = TCO::optimize(bound_ast);
|
||||
|
||||
// 5. Execute
|
||||
let mut vm = VM::new(self.global_values.clone());
|
||||
vm.run(&bound_ast)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_debug(&self, source: &str) -> Result<(Result<Value, String>, Vec<String>), String> {
|
||||
// 1. Parse
|
||||
let mut parser = Parser::new(source)?;
|
||||
let untyped_ast = parser.parse_expression()?;
|
||||
|
||||
// 2. Check for trailing tokens
|
||||
if !parser.at_eof() {
|
||||
return Err("Unexpected trailing expressions in script. Use (do ...) for sequences.".to_string());
|
||||
}
|
||||
|
||||
// 3. Bind & Type Check
|
||||
// 2. Bind
|
||||
let bound_ast = Binder::bind_root(self.global_names.clone(), &untyped_ast)?;
|
||||
|
||||
// 4. Optimize
|
||||
// 3. Optimize (TCO)
|
||||
let bound_ast = TCO::optimize(bound_ast);
|
||||
|
||||
// 5. Execute
|
||||
// 4. Execute with TracingObserver
|
||||
let mut vm = VM::new(self.global_values.clone());
|
||||
vm.run(&bound_ast)
|
||||
let mut observer = TracingObserver::new();
|
||||
let result = vm.run_with_observer(&mut observer, &bound_ast);
|
||||
|
||||
Ok((result, observer.logs))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user