refactor: VM-only architecture — remove tree-walking interpreter

Eliminate dual-engine architecture (Interpreter + Vm) in favor of
a single bytecode VM executor.

- Extract shared types (Value, FunctionProto, Closure, UpvalueObj,
  Runtime trait, NativeFn) into new  module
- Consolidate builtin registration in  — single
   used by VM, eliminates 40-line duplicate
- Delete tree-walker: exec.rs, eval.rs, interpreter.rs, env.rs, module.rs
- Change Value::Function(Rc<Function>) → Value::Function(Rc<Closure>)
  eliminating the closures HashMap pointer-key hack
- Fix VM semantic gaps found during migration:
  * Structural equality for Object/Array in is_equal
  * CompoundAssignGlobal opcode (global compound assigns were broken)
  * 9 string methods added to VM get_property
  * ForInInit error on non-iterable values
- Switch run_file/run_repl to VM; remove --vm CLI flag
- Move 327 tests from interpreter/ to vm/ — all pass

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
0264408
2026-06-26 10:31:32 +08:00
co-authored by Claude
parent abe0844816
commit fa6ea512b3
19 changed files with 432 additions and 1471 deletions
+13 -33
View File
@@ -1,6 +1,7 @@
pub mod lexer;
pub mod ast;
pub mod parser;
pub mod runtime;
pub mod interpreter;
pub mod vm;
pub mod error;
@@ -8,7 +9,6 @@ pub mod analysis;
use lexer::Lexer;
use parser::Parser;
use interpreter::Interpreter;
use vm::compiler::Compiler;
use vm::vm::Vm;
use error::RuntimeError;
@@ -37,33 +37,6 @@ pub fn run_file(filename: &str, src: String) {
std::process::exit(65);
}
let script_dir = std::path::Path::new(filename)
.parent()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|| ".".to_string());
let mut interpreter = Interpreter::with_current_dir(script_dir);
if let Err(e) = interpreter.interpret(stmts) {
eprintln!("Error: {}", e);
std::process::exit(70);
}
}
/// Run a file using the bytecode VM (for performance comparison).
pub fn run_file_vm(filename: &str, src: String) {
let (tokens, lex_errors) = Lexer::new(&src).tokenize();
if !lex_errors.is_empty() {
print_errors(&lex_errors);
std::process::exit(65);
}
let mut parser = Parser::new(tokens);
let (stmts, parse_errors) = parser.parse();
if !parse_errors.is_empty() {
print_errors(&parse_errors);
std::process::exit(65);
}
let script_dir = std::path::Path::new(filename)
.parent()
.map(|p| p.to_string_lossy().to_string())
@@ -87,9 +60,9 @@ pub fn run_file_vm(filename: &str, src: String) {
pub fn run_repl() {
println!("Welcome to Aster REPL!");
println!("Type ':exit' to quit.");
println!("Type ':reset' to reset the interpreter state.");
println!("Type ':reset' to reset the VM state.");
let mut interpreter = Interpreter::new();
let mut vm = Vm::new();
let mut line = String::new();
loop {
@@ -113,8 +86,8 @@ pub fn run_repl() {
break;
}
":reset" => {
interpreter = Interpreter::new();
println!("Interpreter reset.");
vm = Vm::new();
println!("VM reset.");
continue;
}
_ => {}
@@ -133,7 +106,14 @@ pub fn run_repl() {
continue;
}
if let Err(e) = interpreter.interpret(stmts) {
let proto = match Compiler::compile(&stmts) {
Ok(p) => p,
Err(e) => {
eprintln!("Compile Error: {}", e);
continue;
}
};
if let Err(e) = vm.run(std::rc::Rc::new(proto)) {
eprintln!("Error: {}", e);
}
}