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>
121 lines
2.8 KiB
Rust
121 lines
2.8 KiB
Rust
pub mod lexer;
|
|
pub mod ast;
|
|
pub mod parser;
|
|
pub mod runtime;
|
|
pub mod interpreter;
|
|
pub mod vm;
|
|
pub mod error;
|
|
pub mod analysis;
|
|
|
|
use lexer::Lexer;
|
|
use parser::Parser;
|
|
use vm::compiler::Compiler;
|
|
use vm::vm::Vm;
|
|
use error::RuntimeError;
|
|
|
|
use std::io::Write;
|
|
use std::io::stdin;
|
|
use std::io::stdout;
|
|
|
|
pub fn print_errors(errors: &[RuntimeError]) {
|
|
for err in errors {
|
|
eprintln!("{}", err);
|
|
}
|
|
}
|
|
|
|
pub fn run_file(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())
|
|
.unwrap_or_else(|| ".".to_string());
|
|
|
|
let proto = match Compiler::compile(&stmts) {
|
|
Ok(p) => p,
|
|
Err(e) => {
|
|
eprintln!("Compile Error: {}", e);
|
|
std::process::exit(70);
|
|
}
|
|
};
|
|
|
|
let mut vm = Vm::with_current_dir(script_dir);
|
|
if let Err(e) = vm.run(std::rc::Rc::new(proto)) {
|
|
eprintln!("Error: {}", e);
|
|
std::process::exit(70);
|
|
}
|
|
}
|
|
|
|
pub fn run_repl() {
|
|
println!("Welcome to Aster REPL!");
|
|
println!("Type ':exit' to quit.");
|
|
println!("Type ':reset' to reset the VM state.");
|
|
|
|
let mut vm = Vm::new();
|
|
let mut line = String::new();
|
|
|
|
loop {
|
|
print!(">>> ");
|
|
stdout().flush().unwrap();
|
|
|
|
line.clear();
|
|
if let Err(e) = stdin().read_line(&mut line) {
|
|
eprintln!("Error reading input: {}", e);
|
|
continue;
|
|
}
|
|
|
|
let line = line.trim();
|
|
if line.is_empty() {
|
|
continue;
|
|
}
|
|
|
|
match line {
|
|
":exit" => {
|
|
println!("Goodbye!");
|
|
break;
|
|
}
|
|
":reset" => {
|
|
vm = Vm::new();
|
|
println!("VM reset.");
|
|
continue;
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
let (tokens, lex_errors) = Lexer::new(line).tokenize();
|
|
if !lex_errors.is_empty() {
|
|
print_errors(&lex_errors);
|
|
continue;
|
|
}
|
|
|
|
let mut parser = Parser::new(tokens);
|
|
let (stmts, parse_errors) = parser.parse();
|
|
if !parse_errors.is_empty() {
|
|
print_errors(&parse_errors);
|
|
continue;
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|