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:
Michael Schimmel
2026-02-18 00:56:29 +01:00
parent 25e3bc1d01
commit faada16723
5 changed files with 212 additions and 30 deletions
+43 -10
View File
@@ -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))
}
}