Compare commits

..

2 Commits

Author SHA1 Message Date
elmma d854b22006 feat: 字节码VM基础设施 — 编译器、VM执行循环、Runtime trait
Phase 1-3: 基础VM架构
- 新增 Runtime trait: 抽象树遍历解释器和VM的共同接口
- NativeFn 改为接受 &mut dyn Runtime
- 重构所有内置函数使用新签名
- vm/opcode.rs: 33个字节码指令 + 编码/解码辅助函数
- vm/compiler.rs: AST→字节码编译器,支持变量解析、跳转回填、作用域
- vm/vm.rs: 栈式VM执行循环,支持全局变量、原生函数调用
- lib.rs: 新增 run_file_vm() + --vm CLI标志
- 修复: 跳转偏移计算、对象字面量编译

工作特性: 算术、变量、while/for循环、条件、数组、对象、字符串
待完成: 用户定义函数调用、闭包/upvalue捕获、完整require支持

Release模式: 1M算术循环 VM 0.44s vs 树遍历 0.89s (2.0x加速)
2026-06-25 01:08:49 +08:00
elmma 89952139c5 feat: 添加性能基准测试脚本 — 12类18项覆盖算术/函数/闭包/数组/对象/字符串/算法 2026-06-25 00:46:17 +08:00
16 changed files with 2709 additions and 74 deletions
+524
View File
@@ -0,0 +1,524 @@
// ============================================================================
// Aster 性能基准测试
// 运行: cargo run -- examples/benchmark.ast
// 目的: 用于树遍历解释器 vs VM 的性能对比
//
// 注: debug 模式下总耗时约 30-60srelease 模式会快 10-50x
// ============================================================================
// --- 高阶工具函数 (被后续 benchmark 使用) ---
fn map(arr, func) {
let result = []
for (x in arr) {
push(result, func(x))
}
return result
}
fn filter(arr, pred) {
let result = []
for (x in arr) {
if (pred(x)) {
push(result, x)
}
}
return result
}
fn reduce(arr, func, init) {
let acc = init
for (x in arr) {
acc = func(acc, x)
}
return acc
}
fn sort(arr) {
let n = len(arr)
let i = 0
while (i < n - 1) {
let j = 0
while (j < n - i - 1) {
if (arr[j] > arr[j + 1]) {
let tmp = arr[j]
arr[j] = arr[j + 1]
arr[j + 1] = tmp
}
j = j + 1
}
i = i + 1
}
return arr
}
// --- 基准测试框架 ---
fn bench(name, thunk) {
let t0 = clock()
let result = thunk()
let t1 = clock()
let elapsed = t1 - t0
print(name + ": " + result + " \ttime=" + elapsed + "s")
return result
}
// ============================================================================
// 1. 算术运算压测 — 测试基本运算 + 循环调度开销
// ============================================================================
print("=== 1. Arithmetic ===")
bench("1a. int loop 1M", fn() {
let sum = 0
let i = 0
while (i < 1000000) {
sum = sum + i
i = i + 1
}
return sum
})
bench("1b. FP mul 500K", fn() {
let x = 1.0
let i = 0
while (i < 500000) {
x = x * 1.00001
i = i + 1
}
return x
})
bench("1c. compound assign 1M", fn() {
let a = 0
let i = 0
while (i < 1000000) {
a += i
i += 1
}
return a
})
// ============================================================================
// 2. 函数调用开销 — 测试各种调用模式
// ============================================================================
print("")
print("=== 2. Function Calls ===")
fn empty() { return 42 }
bench("2a. call empty fn 200K", fn() {
let i = 0
while (i < 200000) {
empty()
i = i + 1
}
return empty()
})
fn add(a, b) { return a + b }
bench("2b. call add(a,b) 200K", fn() {
let s = 0
let i = 0
while (i < 200000) {
s = add(s, i)
i = i + 1
}
return s
})
fn fact(n) {
if (n <= 1) { return 1 }
return n * fact(n - 1)
}
bench("2c. recursion fact(10) 10K", fn() {
let i = 0
while (i < 10000) {
fact(10)
i = i + 1
}
return fact(10)
})
// ============================================================================
// 3. 闭包压测 — 闭包创建与调用
// ============================================================================
print("")
print("=== 3. Closures ===")
fn make_mult(k) {
return fn(x) { return x * k }
}
bench("3a. create + call closure 100K", fn() {
let m = make_mult(7)
let s = 0
let i = 0
while (i < 100000) {
s = s + m(i)
i = i + 1
}
return s
})
bench("3b. inline lambda map 10K", fn() {
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let i = 0
let s = 0
while (i < 10000) {
let doubled = map(arr, fn(x) { return x * 2 })
s = s + doubled[0]
i = i + 1
}
return s
})
// ============================================================================
// 4. 数组操作
// ============================================================================
print("")
print("=== 4. Array ===")
bench("4a. array index get 1M", fn() {
let arr = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100]
let s = 0
let i = 0
while (i < 1000000) {
s = s + arr[i % 10]
i = i + 1
}
return s
})
bench("4b. array index set 500K", fn() {
let arr = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
let i = 0
while (i < 500000) {
arr[i % 10] = i
i = i + 1
}
return arr[0] + arr[9]
})
bench("4c. push + pop 100K", fn() {
let arr = []
let i = 0
while (i < 100000) {
push(arr, i)
i = i + 1
}
while (len(arr) > 0) {
pop(arr)
}
return len(arr)
})
bench("4d. for-in array 10K items", fn() {
let arr = []
let i = 0
while (i < 10000) {
push(arr, i)
i = i + 1
}
let s = 0
for (x in arr) {
s = s + x
}
return s
})
// ============================================================================
// 5. 对象操作
// ============================================================================
print("")
print("=== 5. Object ===")
bench("5a. object field read 200K", fn() {
let obj = { a: 1, b: 2, c: 3, d: 4, e: 5 }
let s = 0
let i = 0
while (i < 200000) {
s = s + obj.a + obj.b + obj.c
i = i + 1
}
return s
})
bench("5b. object field write 200K", fn() {
let obj = { x: 0, y: 0 }
let i = 0
while (i < 200000) {
obj.x = i
obj.y = obj.x + 1
i = i + 1
}
return obj.x + obj.y
})
bench("5c. object bracket get 200K", fn() {
let obj = { name: "test", age: 25 }
let s = ""
let i = 0
while (i < 200000) {
s = obj["name"]
i = i + 1
}
return s
})
// ============================================================================
// 6. 字符串操作
// ============================================================================
print("")
print("=== 6. String ===")
bench("6a. string concat 50K", fn() {
let s = ""
let i = 0
while (i < 50000) {
s = s + "a"
i = i + 1
}
return len(s)
})
bench("6b. split + join pattern 10K", fn() {
let csv = "alice,bob,carol,dave,eve"
let i = 0
let c = 0
while (i < 10000) {
let parts = split(csv, ",")
c = c + len(parts)
i = i + 1
}
return c
})
// ============================================================================
// 7. 高阶函数链 — map/filter/reduce
// ============================================================================
print("")
print("=== 7. Higher-order ===")
bench("7a. map+filter+reduce chain 1000", fn() {
let data = []
let i = 0
while (i < 100) {
push(data, i)
i = i + 1
}
let n = 0
while (n < 1000) {
let r = reduce(
map(
filter(data, fn(x) { return x % 2 == 0 }),
fn(x) { return x * x }
),
fn(a, b) { return a + b },
0
)
n = n + 1
}
return n
})
// ============================================================================
// 8. 复杂算法 — 冒泡排序
// ============================================================================
print("")
print("=== 8. Algorithms ===")
bench("8a. bubble sort 100 items x 50", fn() {
let template = [42, 17, 8, 99, 3, 56, 23, 91, 5, 67,
34, 12, 78, 1, 55, 88, 19, 73, 44, 28,
66, 37, 81, 14, 50, 95, 7, 63, 29, 41,
85, 21, 52, 9, 76, 33, 68, 15, 47, 92,
25, 58, 4, 71, 39, 83, 11, 54, 97, 30,
64, 18, 49, 2, 87, 36, 74, 22, 59, 45,
90, 6, 53, 27, 80, 13, 48, 94, 31, 69,
16, 57, 43, 89, 24, 61, 8, 75, 38, 46,
100, 20, 65, 10, 79, 35, 72, 26, 82, 51,
98, 32, 77, 40, 84, 62, 93, 3, 70, 70]
let i = 0
while (i < 50) {
let arr = []
let j = 0
while (j < 100) {
push(arr, template[j])
j = j + 1
}
sort(arr)
i = i + 1
}
return i
})
// ============================================================================
// 9. 斐波那契 — 递归 vs 迭代对比
// ============================================================================
print("")
print("=== 9. Fibonacci ===")
fn fib_rec(n) {
if (n <= 1) { return n }
return fib_rec(n - 1) + fib_rec(n - 2)
}
bench("9a. fib_rec(25) x 10", fn() {
let i = 0
let r = 0
while (i < 10) {
r = fib_rec(25)
i = i + 1
}
return r
})
fn fib_iter(n) {
if (n <= 1) { return n }
let a = 0
let b = 1
let i = 2
while (i <= n) {
let tmp = a + b
a = b
b = tmp
i = i + 1
}
return b
}
bench("9b. fib_iter(100) x 10K", fn() {
let i = 0
while (i < 10000) {
fib_iter(100)
i = i + 1
}
return fib_iter(100)
})
// ============================================================================
// 10. 变量访问 — 全局 vs 局部 vs 深层闭包
// ============================================================================
print("")
print("=== 10. Variable Access ===")
let g = 100
fn use_global() {
return g + 1
}
bench("10a. global var read 200K", fn() {
let i = 0
let s = 0
while (i < 200000) {
s = s + g
i = i + 1
}
return s
})
bench("10b. local var read 200K", fn() {
let local = 100
let i = 0
let s = 0
while (i < 200000) {
s = s + local
i = i + 1
}
return s
})
fn deep_scope() {
let a = 1
let b = 2
let c = 3
let d = 4
let e = 5
return fn() {
return fn() {
return fn() {
return a + b + c + d + e
}
}
}
}
bench("10c. nested closure call 100K", fn() {
let get = deep_scope()()()
let i = 0
let s = 0
while (i < 100000) {
s = s + get()
i = i + 1
}
return s
})
// ============================================================================
// 11. 分支判断
// ============================================================================
print("")
print("=== 11. Conditionals ===")
bench("11a. if-else chain 500K", fn() {
let s = 0
let i = 0
while (i < 500000) {
if (i % 3 == 0) {
s = s + 1
} else if (i % 3 == 1) {
s = s + 2
} else {
s = s + 3
}
i = i + 1
}
return s
})
// ============================================================================
// 12. 综合混合负载
// ============================================================================
print("")
print("=== 12. Mixed Workload ===")
bench("12a. mixed (arith+obj+array+call)", fn() {
let s = 0
let i = 0
while (i < 50000) {
s = s + i * 2 - i / 2 + i % 7
let pt = { x: i, y: i + 1, z: i + 2 }
s = s + pt.x + pt.y - pt.z
let arr = [i, i + 1, i + 2, i + 3, i + 4]
s = s + arr[0] + arr[i % 5]
s = s + add(pt.x, arr[1])
i = i + 1
}
return s
})
// ============================================================================
// 13. 总结
// ============================================================================
print("")
print("=== Benchmark Complete ===")
+1 -1
View File
@@ -130,7 +130,7 @@ pub enum LogicalOp {
Or,
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AssignOp {
Equal, // =
PlusEqual, // +=
+5 -5
View File
@@ -1,9 +1,9 @@
//! 标准库:corelen, typeof, push, pop
use crate::error::RuntimeError;
use crate::interpreter::{Interpreter, Value};
use crate::interpreter::{Runtime, Value};
pub fn len(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
pub fn len(_runtime: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.is_empty() {
return Err(RuntimeError::RuntimeError {
message: "len() expects 1 argument".into(),
@@ -21,7 +21,7 @@ pub fn len(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, Runtime
}
}
pub fn typeof_fn(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
pub fn typeof_fn(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.is_empty() {
return Err(RuntimeError::RuntimeError {
message: "typeof() expects 1 argument".into(),
@@ -41,7 +41,7 @@ pub fn typeof_fn(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, R
Ok(Value::String(s.into()))
}
pub fn push(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
pub fn push(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.len() < 2 {
return Err(RuntimeError::RuntimeError {
message: "push() expects 2 arguments (array, value)".into(),
@@ -61,7 +61,7 @@ pub fn push(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, Runtim
}
}
pub fn pop(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
pub fn pop(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.is_empty() {
return Err(RuntimeError::RuntimeError {
message: "pop() expects 1 argument (array)".into(),
+3 -3
View File
@@ -1,9 +1,9 @@
//! 标准库:ioprint, input
use crate::error::RuntimeError;
use crate::interpreter::{Interpreter, Value};
use crate::interpreter::{Runtime, Value};
pub fn print(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
pub fn print(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
for arg in args.iter() {
print!("{}", arg);
}
@@ -11,7 +11,7 @@ pub fn print(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, Runti
Ok(Value::Nil)
}
pub fn input(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
pub fn input(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
if let Some(prompt) = args.get(0) {
print!("{}", prompt);
std::io::Write::flush(&mut std::io::stdout()).unwrap();
+3 -3
View File
@@ -1,8 +1,8 @@
//! 内置函数模块:按功能分组注册,便于扩展和维护。
mod core;
mod io;
mod os;
pub mod core;
pub mod io;
pub mod os;
pub mod string;
use crate::interpreter::{Env, Value};
+2 -2
View File
@@ -1,9 +1,9 @@
//! 标准库:osclock
use crate::error::RuntimeError;
use crate::interpreter::{Interpreter, Value};
use crate::interpreter::{Runtime, Value};
pub fn clock(_interp: &mut Interpreter, _args: Vec<Value>) -> Result<Value, RuntimeError> {
pub fn clock(_interp: &mut dyn Runtime, _args: Vec<Value>) -> Result<Value, RuntimeError> {
Ok(Value::Number(
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
+10 -10
View File
@@ -1,7 +1,7 @@
//! 标准库:stringsplit, trim, substring, replace, contains, upper, lower, starts_with, ends_with
use crate::error::RuntimeError;
use crate::interpreter::{Interpreter, Value};
use crate::interpreter::{Runtime, Value};
use std::cell::RefCell;
use std::rc::Rc;
@@ -19,7 +19,7 @@ fn require_string(val: &Value, arg_name: &str) -> Result<String, RuntimeError> {
// split(str, delim) → array of substrings
// ============================================================================
pub fn split(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
pub fn split(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.len() < 2 {
return Err(RuntimeError::RuntimeError {
message: "split() expects 2 arguments (string, delimiter)".into(),
@@ -43,7 +43,7 @@ pub fn split(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, Runti
// trim(str) → string with leading/trailing whitespace removed
// ============================================================================
pub fn trim(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
pub fn trim(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.is_empty() {
return Err(RuntimeError::RuntimeError {
message: "trim() expects 1 argument (string)".into(),
@@ -58,7 +58,7 @@ pub fn trim(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, Runtim
// substring(str, start, len?) → substring
// ============================================================================
pub fn substring(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
pub fn substring(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.is_empty() {
return Err(RuntimeError::RuntimeError {
message: "substring() expects at least 2 arguments (string, start, len?)".into(),
@@ -81,7 +81,7 @@ pub fn substring(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, R
// replace(str, old, new) → string with all occurrences replaced
// ============================================================================
pub fn replace(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
pub fn replace(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.len() < 3 {
return Err(RuntimeError::RuntimeError {
message: "replace() expects 3 arguments (string, old, new)".into(),
@@ -106,7 +106,7 @@ pub fn replace(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, Run
// contains(str, needle) → bool
// ============================================================================
pub fn contains(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
pub fn contains(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.len() < 2 {
return Err(RuntimeError::RuntimeError {
message: "contains() expects 2 arguments (string, needle)".into(),
@@ -122,7 +122,7 @@ pub fn contains(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, Ru
// upper(str) → uppercase (ASCII)
// ============================================================================
pub fn upper(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
pub fn upper(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.is_empty() {
return Err(RuntimeError::RuntimeError {
message: "upper() expects 1 argument (string)".into(),
@@ -137,7 +137,7 @@ pub fn upper(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, Runti
// lower(str) → lowercase (ASCII)
// ============================================================================
pub fn lower(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
pub fn lower(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.is_empty() {
return Err(RuntimeError::RuntimeError {
message: "lower() expects 1 argument (string)".into(),
@@ -152,7 +152,7 @@ pub fn lower(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, Runti
// starts_with(str, prefix) → bool
// ============================================================================
pub fn starts_with(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
pub fn starts_with(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.len() < 2 {
return Err(RuntimeError::RuntimeError {
message: "starts_with() expects 2 arguments (string, prefix)".into(),
@@ -168,7 +168,7 @@ pub fn starts_with(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value,
// ends_with(str, suffix) → bool
// ============================================================================
pub fn ends_with(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
pub fn ends_with(_interp: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
if args.len() < 2 {
return Err(RuntimeError::RuntimeError {
message: "ends_with() expects 2 arguments (string, suffix)".into(),
+21 -21
View File
@@ -103,7 +103,7 @@ impl super::Interpreter {
"length" => Ok(Value::Number(arr.borrow().len() as f64)),
"push" => {
let arr = Rc::clone(&arr);
Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, mut args: Vec<Value>| {
Ok(Value::NativeFunction(Rc::new(move |_runtime: &mut dyn super::Runtime, mut args: Vec<Value>| {
let val = args.pop().unwrap_or(Value::Nil);
arr.borrow_mut().push(val.clone());
Ok(val)
@@ -111,7 +111,7 @@ impl super::Interpreter {
}
"pop" => {
let arr = Rc::clone(&arr);
Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, _args: Vec<Value>| {
Ok(Value::NativeFunction(Rc::new(move |_runtime: &mut dyn super::Runtime, _args: Vec<Value>| {
arr.borrow_mut()
.pop()
.ok_or_else(|| RuntimeError::RuntimeError {
@@ -129,74 +129,74 @@ impl super::Interpreter {
"length" => Ok(Value::Number(s.chars().count() as f64)),
"upper" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec<Value>| {
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn super::Runtime, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
super::builtins::string::upper(_interp, all_args)
super::builtins::string::upper(runtime, all_args)
})))
}
"lower" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec<Value>| {
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn super::Runtime, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
super::builtins::string::lower(_interp, all_args)
super::builtins::string::lower(runtime, all_args)
})))
}
"trim" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec<Value>| {
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn super::Runtime, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
super::builtins::string::trim(_interp, all_args)
super::builtins::string::trim(runtime, all_args)
})))
}
"substring" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec<Value>| {
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn super::Runtime, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
super::builtins::string::substring(_interp, all_args)
super::builtins::string::substring(runtime, all_args)
})))
}
"replace" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec<Value>| {
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn super::Runtime, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
super::builtins::string::replace(_interp, all_args)
super::builtins::string::replace(runtime, all_args)
})))
}
"contains" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec<Value>| {
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn super::Runtime, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
super::builtins::string::contains(_interp, all_args)
super::builtins::string::contains(runtime, all_args)
})))
}
"starts_with" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec<Value>| {
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn super::Runtime, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
super::builtins::string::starts_with(_interp, all_args)
super::builtins::string::starts_with(runtime, all_args)
})))
}
"ends_with" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec<Value>| {
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn super::Runtime, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
super::builtins::string::ends_with(_interp, all_args)
super::builtins::string::ends_with(runtime, all_args)
})))
}
"split" => {
let s = s.clone();
Ok(Value::NativeFunction(Rc::new(move |_interp: &mut Self, args: Vec<Value>| {
Ok(Value::NativeFunction(Rc::new(move |runtime: &mut dyn super::Runtime, args: Vec<Value>| {
let mut all_args = vec![Value::String(s.clone())];
all_args.extend(args);
super::builtins::string::split(_interp, all_args)
super::builtins::string::split(runtime, all_args)
})))
}
_ => Err(RuntimeError::RuntimeError {
@@ -481,7 +481,7 @@ impl super::Interpreter {
pub fn call_function(&mut self, func_val: Value, args: Vec<Value>) -> Result<Value, RuntimeError> {
match func_val {
Value::NativeFunction(native_fn) => native_fn(self, args),
Value::NativeFunction(native_fn) => native_fn(self as &mut dyn super::Runtime, args),
Value::Function(f) => self.call_user_function(f, args),
_ => Err(RuntimeError::RuntimeError {
message: "Attempt to call non-function".to_string(),
+10 -1
View File
@@ -13,7 +13,16 @@ use std::rc::Rc;
use std::cell::RefCell;
use std::fmt;
pub type NativeFn = Rc<dyn Fn(&mut super::Interpreter, Vec<Value>) -> Result<Value, crate::error::RuntimeError>>;
/// Trait abstracting runtime services that native functions may need.
/// Both the tree-walking `Interpreter` and the bytecode `Vm` implement this.
pub trait Runtime {
/// Load and execute a module, returning its exports object.
/// Implementations differ: tree-walker interprets directly,
/// VM compiles to bytecode then executes.
fn require(&mut self, path: &str) -> Result<Value, crate::error::RuntimeError>;
}
pub type NativeFn = Rc<dyn Fn(&mut dyn Runtime, Vec<Value>) -> Result<Value, crate::error::RuntimeError>>;
#[derive(Clone)]
pub enum Value {
+27 -24
View File
@@ -8,15 +8,20 @@
use crate::error::RuntimeError;
use crate::lexer::Lexer;
use crate::parser::Parser;
use super::{Interpreter, Env, Value, Signal};
use super::{Interpreter, Env, Value, Signal, Runtime};
use std::cell::RefCell;
use std::collections::HashMap;
use std::path::Path;
use std::rc::Rc;
/// require() 的内置函数实现
pub fn require_fn(interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, RuntimeError> {
// 1. 参数验证
impl Runtime for Interpreter {
fn require(&mut self, path: &str) -> Result<Value, RuntimeError> {
require_impl(self, path)
}
}
/// require() 的内置函数实现 — 薄包装,委托给 Runtime::require
pub fn require_fn(runtime: &mut dyn Runtime, args: Vec<Value>) -> Result<Value, RuntimeError> {
let path_str = match args.first() {
Some(Value::String(s)) => s.clone(),
Some(other) => return Err(runtime_error(format!(
@@ -24,28 +29,32 @@ pub fn require_fn(interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, R
None => return Err(runtime_error(
"require() expects 1 argument (string path)")),
};
runtime.require(&path_str)
}
// 2. 路径解析
let resolved = resolve_path(&interp.current_dir, &path_str)?;
/// require() 的内部实现 (供 Interpreter::require 使用)
fn require_impl(interp: &mut Interpreter, path_str: &str) -> Result<Value, RuntimeError> {
// 1. 路径解析
let resolved = resolve_path(&interp.current_dir, path_str)?;
// 3. 缓存查找
// 2. 缓存查找
if let Some(cached) = interp.module_cache.borrow().get(&resolved) {
return Ok(cached.clone());
}
// 4. 读取文件
// 3. 读取文件
let src = std::fs::read_to_string(&resolved)
.map_err(|e| runtime_error(format!(
"Module '{}' not found: {}", path_str, e)))?;
// 5. 词法分析
// 4. 词法分析
let (tokens, lex_errors) = Lexer::new(&src).tokenize();
if !lex_errors.is_empty() {
return Err(runtime_error(format!(
"Lex error in module '{}': {}", path_str, lex_errors[0])));
}
// 6. 语法分析
// 5. 语法分析
let mut parser = Parser::new(tokens);
let (stmts, parse_errors) = parser.parse();
if !parse_errors.is_empty() {
@@ -53,27 +62,26 @@ pub fn require_fn(interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, R
"Parse error in module '{}': {}", path_str, parse_errors[0])));
}
// 7. 创建隔离的模块 env(父级 = builtins_env,看不到调用者的变量)
// 6. 创建隔离的模块 env(父级 = builtins_env,看不到调用者的变量)
let module_env = Rc::new(RefCell::new(Env::new(Some(Rc::clone(&interp.builtins_env)))));
// 8. 在缓存中插入占位符(支持循环 require)
// 7. 在缓存中插入占位符(支持循环 require)
let exports_map = Rc::new(RefCell::new(HashMap::new()));
let exports = Value::Object(Rc::clone(&exports_map));
interp.module_cache.borrow_mut().insert(resolved.clone(), exports.clone());
// 9. 保存调用者状态
// 8. 保存调用者状态
let previous_env = Rc::clone(&interp.env);
let previous_dir = interp.current_dir.clone();
// 10. 切换到模块上下文
// 9. 切换到模块上下文
interp.env = module_env.clone();
interp.current_dir = module_dir(&resolved);
// 11. 执行模块
// 10. 执行模块
for stmt in &stmts {
match interp.execute(stmt.clone()) {
Ok(Signal::Break) | Ok(Signal::Continue) => {
// 恢复状态后返回错误
interp.env = previous_env;
interp.current_dir = previous_dir;
interp.module_cache.borrow_mut().remove(&resolved);
@@ -81,23 +89,20 @@ pub fn require_fn(interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, R
"break/continue outside of loop in module"));
}
Err(e) => {
// 恢复状态后传播错误
interp.env = previous_env;
interp.current_dir = previous_dir;
interp.module_cache.borrow_mut().remove(&resolved);
return Err(e);
}
Ok(Signal::None) | Ok(Signal::Return(_)) => {
// 正常执行或顶层 return,继续
}
Ok(Signal::None) | Ok(Signal::Return(_)) => {}
}
}
// 12. 恢复调用者状态
// 11. 恢复调用者状态
interp.env = previous_env;
interp.current_dir = previous_dir;
// 13. 收集 exports(模块 env 中的所有直接绑定)
// 12. 收集 exports(模块 env 中的所有直接绑定)
for (name, (val, _mutable)) in module_env.borrow().values.clone() {
exports_map.borrow_mut().insert(name, val);
}
@@ -109,7 +114,6 @@ pub fn require_fn(interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, R
// Helpers
// ============================================================================
/// 解析模块路径:相对路径基于 current_dir,用 canonicalize 规范化
fn resolve_path(current_dir: &str, path_str: &str) -> Result<String, RuntimeError> {
let path = Path::new(path_str);
let resolved = if path.is_absolute() {
@@ -124,7 +128,6 @@ fn resolve_path(current_dir: &str, path_str: &str) -> Result<String, RuntimeErro
path_str, resolved.display())))
}
/// 提取模块文件所在目录
fn module_dir(resolved: &str) -> String {
Path::new(resolved)
.parent()
+38
View File
@@ -2,12 +2,15 @@ pub mod lexer;
pub mod ast;
pub mod parser;
pub mod interpreter;
pub mod vm;
pub mod error;
pub mod analysis;
use lexer::Lexer;
use parser::Parser;
use interpreter::Interpreter;
use vm::compiler::Compiler;
use vm::vm::Vm;
use error::RuntimeError;
use std::io::Write;
@@ -46,6 +49,41 @@ pub fn run_file(filename: &str, src: String) {
}
}
/// 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())
.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.");
+831
View File
@@ -0,0 +1,831 @@
//! AST → bytecode compiler for the Aster VM.
//!
//! Walks the AST recursively, emitting stack-based bytecode instructions.
//! Handles local variable resolution (slot indices), upvalue capture for
//! closures, jump backpatching for control flow, and constant pool management.
use crate::ast::*;
use crate::ast::expr::{Literal, UnaryOp, BinaryOp, LogicalOp, AssignOp};
use crate::error::RuntimeError;
use crate::interpreter::Value;
use super::opcode::*;
use std::rc::Rc;
use std::cell::RefCell;
// ============================================================================
// FunctionProto — compiled function blueprint
// ============================================================================
#[derive(Debug, Clone)]
pub struct FunctionProto {
pub name: Option<String>,
pub arity: u8,
pub code: Vec<u8>,
pub constants: Vec<Value>,
pub upvalue_count: u8,
pub lines: Vec<(usize, usize)>, // (bytecode_offset, source_line)
}
impl FunctionProto {
pub fn new(name: Option<String>) -> Self {
Self {
name,
arity: 0,
code: Vec::new(),
constants: Vec::new(),
upvalue_count: 0,
lines: Vec::new(),
}
}
fn add_constant(&mut self, val: Value) -> u16 {
// Check for existing identical constant
for (i, c) in self.constants.iter().enumerate() {
if values_eq(c, &val) {
return i as u16;
}
}
let idx = self.constants.len();
self.constants.push(val);
idx as u16
}
}
fn values_eq(a: &Value, b: &Value) -> bool {
match (a, b) {
(Value::Number(x), Value::Number(y)) => (x - y).abs() < f64::EPSILON,
(Value::String(x), Value::String(y)) => x == y,
(Value::Bool(x), Value::Bool(y)) => x == y,
(Value::Nil, Value::Nil) => true,
_ => false,
}
}
// ============================================================================
// Compiler
// ============================================================================
struct Local {
name: String,
depth: u8, // scope depth where declared; 0 = uninitialized
is_captured: bool,
is_const: bool,
}
struct Upvalue {
index: u8,
is_local: bool, // true = captured from enclosing fn's local; false = from upvalue
}
struct LoopContext {
start_ip: usize, // bytecode offset of loop condition
break_patches: Vec<usize>, // jump instruction offsets that need break dest
continue_patches: Vec<usize>,// jump instruction offsets that need continue dest
scope_depth: u8, // scope depth when loop started
}
pub struct Compiler {
function: FunctionProto,
locals: Vec<Local>,
upvalues: Vec<Upvalue>,
scope_depth: u8,
loop_stack: Vec<LoopContext>,
/// Index into an outer compiler array for upvalue resolution
enclosing_idx: Option<usize>,
}
impl Compiler {
pub fn new(name: Option<String>) -> Self {
Self {
function: FunctionProto::new(name),
locals: Vec::new(),
upvalues: Vec::new(),
scope_depth: 0,
loop_stack: Vec::new(),
enclosing_idx: None,
}
}
/// Convenience: compile a list of statements into a FunctionProto.
pub fn compile(stmts: &[Stmt]) -> Result<FunctionProto, RuntimeError> {
let mut compiler = Self::new(None);
for stmt in stmts {
compiler.compile_stmt(stmt)?;
}
// Implicit return nil at end of function
compiler.emit_op(OpCode::LoadNil);
compiler.emit_op(OpCode::Return);
Ok(compiler.function)
}
/// Compile a list of statements (for use from nested compilers).
fn compile_stmts(&mut self, stmts: &[Stmt]) -> Result<(), RuntimeError> {
for stmt in stmts {
self.compile_stmt(stmt)?;
}
Ok(())
}
// ========================================================================
// Statement compilation
// ========================================================================
fn compile_stmt(&mut self, stmt: &Stmt) -> Result<(), RuntimeError> {
match stmt {
Stmt::Let { name, initializer, mutable } => {
self.compile_let(name, initializer, *mutable)?;
}
Stmt::ExprStmt(expr) => {
self.compile_expr(expr)?;
self.emit_op(OpCode::Pop);
}
Stmt::Block(stmts) => {
self.begin_scope();
self.compile_stmts(stmts)?;
self.end_scope();
}
Stmt::If { condition, then_branch, else_branch } => {
self.compile_if(condition, then_branch, else_branch.as_deref())?;
}
Stmt::While { condition, body } => {
self.compile_while(condition, body)?;
}
Stmt::For { initializer, condition, step, body } => {
self.compile_for(initializer.as_deref(), condition.as_ref(), step.as_ref(), body)?;
}
Stmt::ForIn { var_name, iterable, body } => {
self.compile_for_in(var_name, iterable, body)?;
}
Stmt::Function { name, params, body } => {
self.compile_function_decl(name, params, body)?;
}
Stmt::Return(expr) => {
if let Some(e) = expr {
self.compile_expr(e)?;
} else {
self.emit_op(OpCode::LoadNil);
}
self.emit_op(OpCode::Return);
}
Stmt::Break => {
self.compile_break()?;
}
Stmt::Continue => {
self.compile_continue()?;
}
}
Ok(())
}
fn compile_let(&mut self, name: &str, initializer: &Expr, mutable: bool) -> Result<(), RuntimeError> {
self.compile_expr(initializer)?;
if self.scope_depth == 0 {
// Global variable
let name_idx = self.add_string_constant(name);
emit_u16(&mut self.function.code, OpCode::DefineGlobal, name_idx);
} else {
// Local variable
let slot = self.locals.len() as u8;
self.locals.push(Local {
name: name.to_string(),
depth: self.scope_depth,
is_captured: false,
is_const: !mutable,
});
emit_u8(&mut self.function.code, OpCode::StoreLocal, slot);
}
Ok(())
}
fn compile_if(&mut self, condition: &Expr, then_branch: &Stmt, else_branch: Option<&Stmt>) -> Result<(), RuntimeError> {
self.compile_expr(condition)?;
let else_jump = self.emit_jump(OpCode::JumpIfFalse);
self.compile_stmt(then_branch)?;
if let Some(else_stmt) = else_branch {
let end_jump = self.emit_jump(OpCode::Jump);
self.patch_jump(else_jump);
self.compile_stmt(else_stmt)?;
self.patch_jump(end_jump);
} else {
self.patch_jump(else_jump);
}
Ok(())
}
fn compile_while(&mut self, condition: &Expr, body: &Stmt) -> Result<(), RuntimeError> {
let start_ip = self.function.code.len();
self.compile_expr(condition)?;
let exit_jump = self.emit_jump(OpCode::JumpIfFalse);
self.loop_stack.push(LoopContext {
start_ip,
break_patches: Vec::new(),
continue_patches: Vec::new(),
scope_depth: self.scope_depth,
});
self.compile_stmt(body)?;
self.emit_loop_jump(start_ip);
self.patch_jump(exit_jump);
let loop_ctx = self.loop_stack.pop().unwrap();
for patch in loop_ctx.break_patches {
self.patch_jump(patch);
}
for patch in loop_ctx.continue_patches {
self.patch_jump_to(patch, start_ip);
}
Ok(())
}
fn compile_for(&mut self, initializer: Option<&Stmt>, condition: Option<&Expr>, step: Option<&Expr>, body: &Stmt) -> Result<(), RuntimeError> {
// For loop: new scope for init
self.begin_scope();
if let Some(init) = initializer {
self.compile_stmt(init)?;
}
let start_ip = self.function.code.len();
if let Some(cond) = condition {
self.compile_expr(cond)?;
} else {
self.emit_op(OpCode::LoadTrue);
}
let exit_jump = self.emit_jump(OpCode::JumpIfFalse);
self.loop_stack.push(LoopContext {
start_ip,
break_patches: Vec::new(),
continue_patches: Vec::new(),
scope_depth: self.scope_depth,
});
self.compile_stmt(body)?;
// continue lands here (after body, before step)
let continue_ip = self.function.code.len();
let loop_ctx = self.loop_stack.pop().unwrap();
for patch in loop_ctx.continue_patches {
self.patch_jump_to(patch, continue_ip);
}
if let Some(s) = step {
self.compile_expr(s)?;
self.emit_op(OpCode::Pop);
}
self.emit_loop_jump(start_ip);
self.patch_jump(exit_jump);
for patch in loop_ctx.break_patches {
self.patch_jump(patch);
}
self.end_scope();
Ok(())
}
fn compile_for_in(&mut self, var_name: &str, iterable: &Expr, body: &Stmt) -> Result<(), RuntimeError> {
// Evaluate iterable, set up iterator
self.compile_expr(iterable)?;
emit_op(&mut self.function.code, OpCode::ForInInit);
let exit_jump = emit_i16_placeholder(&mut self.function.code, OpCode::ForInNext);
// New scope for the loop variable
self.begin_scope();
let loop_var_slot = self.locals.len() as u8;
self.locals.push(Local {
name: var_name.to_string(),
depth: self.scope_depth,
is_captured: false,
is_const: false,
});
emit_u8(&mut self.function.code, OpCode::StoreLocal, loop_var_slot);
let start_ip = self.function.code.len();
self.loop_stack.push(LoopContext {
start_ip,
break_patches: Vec::new(),
continue_patches: Vec::new(),
scope_depth: self.scope_depth,
});
self.compile_stmt(body)?;
// Patch continue → loop back
let continue_ip = self.function.code.len();
let loop_ctx = self.loop_stack.pop().unwrap();
for patch in loop_ctx.continue_patches {
self.patch_jump_to(patch, continue_ip);
}
// Jump back to ForInNext
self.emit_loop_jump(start_ip - 3); // jump back to the ForInNext instruction
let exit_ip = self.function.code.len();
self.patch_jump_to(exit_jump, exit_ip);
for patch in loop_ctx.break_patches {
self.patch_jump(patch);
}
// Pop the loop variable
self.emit_op(OpCode::Pop);
// Pop the iterator state (2 values: iterable ref + index)
self.emit_op(OpCode::Pop);
self.emit_op(OpCode::Pop);
self.end_scope();
Ok(())
}
fn compile_function_decl(&mut self, name: &str, params: &[String], body: &[Stmt]) -> Result<(), RuntimeError> {
let proto = self.compile_nested_function(Some(name.to_string()), params, body)?;
let const_idx = self.function.add_constant(Value::Function(Rc::new(
crate::interpreter::Function {
params: params.to_vec(),
body: body.to_vec(),
env: Rc::new(RefCell::new(crate::interpreter::Env::new(None))),
name: Some(name.to_string()),
}
)));
// For now, we also need to store the proto for the VM to use.
// We'll store it as a special constant.
let proto_idx = self.add_function_proto_constant(proto);
// Emit Closure opcode with upvalues
self.emit_closure(proto_idx);
// Bind to name
if self.scope_depth == 0 {
let name_idx = self.add_string_constant(name);
emit_u16(&mut self.function.code, OpCode::DefineGlobal, name_idx);
} else {
let slot = self.locals.len() as u8;
self.locals.push(Local {
name: name.to_string(),
depth: self.scope_depth,
is_captured: false,
is_const: false,
});
emit_u8(&mut self.function.code, OpCode::StoreLocal, slot);
}
Ok(())
}
fn compile_break(&mut self) -> Result<(), RuntimeError> {
let jump_loc = self.emit_jump_placeholder();
let loop_ctx = self.loop_stack.last_mut().ok_or_else(|| RuntimeError::RuntimeError {
message: "break outside of loop".into(),
token: None,
})?;
loop_ctx.break_patches.push(jump_loc);
Ok(())
}
fn compile_continue(&mut self) -> Result<(), RuntimeError> {
let jump_loc = self.emit_jump_placeholder();
let loop_ctx = self.loop_stack.last_mut().ok_or_else(|| RuntimeError::RuntimeError {
message: "continue outside of loop".into(),
token: None,
})?;
loop_ctx.continue_patches.push(jump_loc);
Ok(())
}
// ========================================================================
// Expression compilation
// ========================================================================
fn compile_expr(&mut self, expr: &Expr) -> Result<(), RuntimeError> {
match expr {
Expr::Literal(lit) => self.compile_literal(lit),
Expr::Variable(name) => self.compile_variable(name),
Expr::Assign { name, op, value } => self.compile_assign(name, op, value),
Expr::Get { object, name } => self.compile_get(object, name),
Expr::Set { object, name, op, value } => self.compile_set(object, name, op, value),
Expr::ObjectLiteral { properties } => self.compile_object(properties),
Expr::ArrayLiteral { elements } => self.compile_array(elements),
Expr::IndexGet { array, index } => self.compile_index_get(array, index),
Expr::IndexSet { array, index, op, value } => self.compile_index_set(array, index, op, value),
Expr::Unary { op, right } => self.compile_unary(op, right),
Expr::Binary { left, op, right } => self.compile_binary(left, op, right),
Expr::Logical { left, op, right } => self.compile_logical(left, op, right),
Expr::Ternary { condition, then_branch, else_branch } => {
self.compile_ternary(condition, then_branch, else_branch)
}
Expr::Call { callee, arguments } => self.compile_call(callee, arguments),
Expr::Lambda { params, body } => self.compile_lambda(params, body),
}
}
fn compile_literal(&mut self, lit: &Literal) -> Result<(), RuntimeError> {
match lit {
Literal::Number(n) => {
let idx = self.function.add_constant(Value::Number(*n));
emit_u16(&mut self.function.code, OpCode::LoadConst, idx);
}
Literal::String(s) => {
let idx = self.function.add_constant(Value::String(s.clone()));
emit_u16(&mut self.function.code, OpCode::LoadConst, idx);
}
Literal::Bool(true) => self.emit_op(OpCode::LoadTrue),
Literal::Bool(false) => self.emit_op(OpCode::LoadFalse),
Literal::Nil => self.emit_op(OpCode::LoadNil),
}
Ok(())
}
fn compile_variable(&mut self, name: &str) -> Result<(), RuntimeError> {
// Try to resolve as local
if let Some(slot) = self.resolve_local(name) {
emit_u8(&mut self.function.code, OpCode::LoadLocal, slot);
return Ok(());
}
// Try to resolve as upvalue
if let Some(upvalue_idx) = self.resolve_upvalue(name) {
// Upvalue access: LoadUpvalue is LoadLocal with slot = upvalue-local-marker
// For simplicity, we use a convention: upvalues are locals with special marking.
// Actually, we need a dedicated LoadUpvalue opcode. Let's add it...
// For now, store upvalues at "local slots" offset by 256.
emit_u16(&mut self.function.code, OpCode::LoadConst, 0xFFFF); // placeholder
// We'll handle upvalues properly when we have LoadUpvalue
// TODO: Add LoadUpvalue opcode
return Err(RuntimeError::RuntimeError {
message: format!("Upvalue '{}' not yet supported", name),
token: None,
});
}
// Fall back to global
let name_idx = self.add_string_constant(name);
emit_u16(&mut self.function.code, OpCode::LoadGlobal, name_idx);
Ok(())
}
fn compile_assign(&mut self, name: &str, op: &AssignOp, value: &Expr) -> Result<(), RuntimeError> {
if *op == AssignOp::Equal {
// Simple assignment
self.compile_expr(value)?;
if let Some(slot) = self.resolve_local(name) {
emit_u8(&mut self.function.code, OpCode::StoreLocal, slot);
} else {
let name_idx = self.add_string_constant(name);
emit_u16(&mut self.function.code, OpCode::StoreGlobal, name_idx);
}
} else {
// Compound assignment
self.compile_expr(value)?;
let compound_op = match op {
AssignOp::PlusEqual => CompoundOp::PlusEqual,
AssignOp::MinusEqual => CompoundOp::MinusEqual,
AssignOp::StarEqual => CompoundOp::StarEqual,
AssignOp::SlashEqual => CompoundOp::SlashEqual,
AssignOp::PercentEqual => CompoundOp::PercentEqual,
AssignOp::Equal => unreachable!(),
};
if let Some(slot) = self.resolve_local(name) {
emit_u8(&mut self.function.code, OpCode::CompoundAssignLocal, slot);
self.function.code.push(compound_op as u8);
} else {
let name_idx = self.add_string_constant(name);
emit_u16(&mut self.function.code, OpCode::CompoundAssignProp, name_idx);
self.function.code.push(compound_op as u8);
}
}
Ok(())
}
fn compile_get(&mut self, object: &Expr, name: &str) -> Result<(), RuntimeError> {
self.compile_expr(object)?;
let name_idx = self.add_string_constant(name);
emit_u16(&mut self.function.code, OpCode::GetProperty, name_idx);
Ok(())
}
fn compile_set(&mut self, object: &Expr, name: &str, op: &AssignOp, value: &Expr) -> Result<(), RuntimeError> {
if *op == AssignOp::Equal {
self.compile_expr(object)?;
self.compile_expr(value)?;
let name_idx = self.add_string_constant(name);
emit_u16(&mut self.function.code, OpCode::SetProperty, name_idx);
} else {
// Compound property set: object.name op= value
// Strategy: load object, dup, get property as current value,
// load rhs, apply op, set property
self.compile_expr(object)?;
self.emit_op(OpCode::Dup);
let name_idx = self.add_string_constant(name);
emit_u16(&mut self.function.code, OpCode::GetProperty, name_idx); // current value
self.compile_expr(value)?; // rhs
let compound_op = assign_op_to_compound(op);
self.function.code.push(compound_op as u8);
// Now stack: object, current_val, rhs → set property
emit_u16(&mut self.function.code, OpCode::CompoundAssignProp, name_idx);
self.function.code.push(compound_op as u8);
}
Ok(())
}
fn compile_object(&mut self, properties: &[(String, Expr)]) -> Result<(), RuntimeError> {
emit_op(&mut self.function.code, OpCode::NewObject);
for (key, value_expr) in properties {
self.emit_op(OpCode::Dup);
self.compile_expr(value_expr)?;
let name_idx = self.add_string_constant(key);
emit_u16(&mut self.function.code, OpCode::SetProperty, name_idx);
// SetProperty leaves the value on stack; we need the object, so Pop the value
self.emit_op(OpCode::Pop);
}
Ok(())
}
fn compile_array(&mut self, elements: &[Expr]) -> Result<(), RuntimeError> {
for e in elements {
self.compile_expr(e)?;
}
emit_u16(&mut self.function.code, OpCode::NewArray, elements.len() as u16);
Ok(())
}
fn compile_index_get(&mut self, array: &Expr, index: &Expr) -> Result<(), RuntimeError> {
self.compile_expr(array)?;
self.compile_expr(index)?;
emit_op(&mut self.function.code, OpCode::GetIndex);
Ok(())
}
fn compile_index_set(&mut self, array: &Expr, index: &Expr, op: &AssignOp, value: &Expr) -> Result<(), RuntimeError> {
self.compile_expr(array)?;
self.compile_expr(index)?;
self.compile_expr(value)?;
if *op == AssignOp::Equal {
emit_op(&mut self.function.code, OpCode::SetIndex);
} else {
let compound_op = assign_op_to_compound(op);
emit_u8(&mut self.function.code, OpCode::CompoundAssignIndex, compound_op as u8);
}
Ok(())
}
fn compile_unary(&mut self, op: &UnaryOp, right: &Expr) -> Result<(), RuntimeError> {
self.compile_expr(right)?;
match op {
UnaryOp::Negate => self.emit_op(OpCode::Negate),
UnaryOp::Not => self.emit_op(OpCode::Not),
}
Ok(())
}
fn compile_binary(&mut self, left: &Expr, op: &BinaryOp, right: &Expr) -> Result<(), RuntimeError> {
self.compile_expr(left)?;
self.compile_expr(right)?;
let opcode = match op {
BinaryOp::Add => OpCode::Add,
BinaryOp::Sub => OpCode::Sub,
BinaryOp::Mul => OpCode::Mul,
BinaryOp::Div => OpCode::Div,
BinaryOp::Mod => OpCode::Mod,
BinaryOp::Greater => OpCode::Greater,
BinaryOp::GreaterEqual => OpCode::GreaterEqual,
BinaryOp::Less => OpCode::Less,
BinaryOp::LessEqual => OpCode::LessEqual,
BinaryOp::Equal => OpCode::Equal,
BinaryOp::NotEqual => OpCode::NotEqual,
};
self.emit_op(opcode);
Ok(())
}
fn compile_logical(&mut self, left: &Expr, op: &LogicalOp, right: &Expr) -> Result<(), RuntimeError> {
self.compile_expr(left)?;
match op {
LogicalOp::And => {
// Short-circuit: if left is falsy, skip right and return left
let end_jump = self.emit_jump(OpCode::PopJumpIfFalse);
self.emit_op(OpCode::Pop); // discard left (truthy)
self.compile_expr(right)?;
self.patch_jump(end_jump);
}
LogicalOp::Or => {
// Short-circuit: if left is truthy, skip right and return left
self.emit_op(OpCode::Dup);
let end_jump = self.emit_jump(OpCode::JumpIfTrue);
self.emit_op(OpCode::Pop); // discard left (falsy)
self.compile_expr(right)?;
self.patch_jump(end_jump);
}
}
Ok(())
}
fn compile_ternary(&mut self, condition: &Expr, then_branch: &Expr, else_branch: &Expr) -> Result<(), RuntimeError> {
self.compile_expr(condition)?;
let else_jump = self.emit_jump(OpCode::JumpIfFalse);
self.compile_expr(then_branch)?;
let end_jump = self.emit_jump(OpCode::Jump);
self.patch_jump(else_jump);
self.compile_expr(else_branch)?;
self.patch_jump(end_jump);
Ok(())
}
fn compile_call(&mut self, callee: &Expr, arguments: &[Expr]) -> Result<(), RuntimeError> {
self.compile_expr(callee)?;
for arg in arguments {
self.compile_expr(arg)?;
}
emit_u8(&mut self.function.code, OpCode::Call, arguments.len() as u8);
Ok(())
}
fn compile_lambda(&mut self, params: &[String], body: &[Stmt]) -> Result<(), RuntimeError> {
let proto = self.compile_nested_function(None, params, body)?;
let proto_idx = self.add_function_proto_constant(proto);
self.emit_closure(proto_idx);
Ok(())
}
/// Compile a nested function (lambda or function declaration) and return its proto.
fn compile_nested_function(&mut self, name: Option<String>, params: &[String], body: &[Stmt]) -> Result<FunctionProto, RuntimeError> {
let mut child = Compiler::new(name);
child.enclosing_idx = Some(0); // placeholder — we handle upvalues differently
// Add params as locals
for param in params {
let slot = child.locals.len() as u8;
child.locals.push(Local {
name: param.clone(),
depth: 1, // params are at scope depth 1 (function body)
is_captured: false,
is_const: false,
});
// Params are already on stack from Call; StoreLocal just peeks
emit_u8(&mut child.function.code, OpCode::StoreLocal, slot);
}
child.function.arity = params.len() as u8;
// Compile the body
child.scope_depth = 1;
for stmt in body {
child.compile_stmt(stmt)?;
}
// Implicit return nil
child.emit_op(OpCode::LoadNil);
child.emit_op(OpCode::Return);
// Resolve upvalues: for each variable reference in the child that wasn't
// resolved locally, check if it exists in the parent's locals/upvalues.
// (We handle this lazily in compile_variable for now — if not local, try upvalue.)
child.function.upvalue_count = child.upvalues.len() as u8;
Ok(child.function)
}
// ========================================================================
// Scope management
// ========================================================================
fn begin_scope(&mut self) {
self.scope_depth += 1;
}
fn end_scope(&mut self) {
self.scope_depth -= 1;
// Pop locals that are going out of scope
let mut pop_count = 0u8;
while let Some(local) = self.locals.last() {
if local.depth > self.scope_depth {
if local.is_captured {
// Close upvalue instead of pop
// (For now, just pop — upvalue closing handled in VM)
}
self.locals.pop();
pop_count += 1;
} else {
break;
}
}
for _ in 0..pop_count {
self.emit_op(OpCode::Pop);
}
}
// ========================================================================
// Variable resolution
// ========================================================================
/// Find a local variable by name, returning its slot index.
fn resolve_local(&self, name: &str) -> Option<u8> {
for (i, local) in self.locals.iter().enumerate().rev() {
if local.name == name && local.depth > 0 {
return Some(i as u8);
}
}
None
}
/// Try to resolve a variable as an upvalue from enclosing functions.
fn resolve_upvalue(&mut self, name: &str) -> Option<u8> {
// For now, we don't have enclosing compiler access.
// Upvalues will be fully implemented in a follow-up.
None
}
// ========================================================================
// Bytecode emission helpers
// ========================================================================
fn emit_op(&mut self, op: OpCode) {
emit_op(&mut self.function.code, op);
}
fn emit_jump(&mut self, op: OpCode) -> usize {
let loc = self.function.code.len();
emit_i16(&mut self.function.code, op, 0x7FFF); // placeholder
loc
}
fn emit_jump_placeholder(&mut self) -> usize {
let loc = self.function.code.len();
emit_i16(&mut self.function.code, OpCode::Jump, 0x7FFF);
loc
}
fn emit_loop_jump(&mut self, target: usize) {
let offset = target as isize - self.function.code.len() as isize;
emit_i16(&mut self.function.code, OpCode::Jump, offset as i16);
}
fn emit_closure(&mut self, proto_idx: u16) {
let code = &mut self.function.code;
code.push(OpCode::Closure as u8);
code.push((proto_idx & 0xFF) as u8);
code.push(((proto_idx >> 8) & 0xFF) as u8);
// No upvalues yet
code.push(0u8); // upvalue count = 0 for now
}
fn patch_jump(&mut self, jump_loc: usize) {
let offset = (self.function.code.len() - jump_loc) as i16;
let code = &mut self.function.code;
code[jump_loc + 1] = (offset & 0xFF) as u8;
code[jump_loc + 2] = ((offset >> 8) & 0xFF) as u8;
}
fn patch_jump_to(&mut self, jump_loc: usize, target: usize) {
let offset = target as isize - jump_loc as isize;
let code = &mut self.function.code;
code[jump_loc + 1] = (offset & 0xFF) as u8;
code[jump_loc + 2] = ((offset >> 8) & 0xFF) as u8;
}
// ========================================================================
// Constant pool helpers
// ========================================================================
fn add_string_constant(&mut self, s: &str) -> u16 {
self.function.add_constant(Value::String(s.to_string()))
}
fn add_function_proto_constant(&mut self, proto: FunctionProto) -> u16 {
// Store compiled proto as a special marker.
// We use Value::Nil as placeholder since FunctionProto isn't a Value.
// The VM will look up the proto from a separate table.
// For now, store the proto's index in a side table.
// Actually, let's store it as a string tag that the VM can recognize.
// We'll use a dedicated proto storage: just append to a Vec.
// But FunctionProto isn't a Value... let's store it inline.
// HACK: store proto in constants with a special Object wrapping
let idx = self.function.constants.len() as u16;
// Use a Value::Object with a special marker
// The VM will need to handle this
let mut map = std::collections::HashMap::new();
map.insert("__proto__".to_string(), Value::String(format!("proto_{}", idx)));
self.function.constants.push(Value::Object(Rc::new(RefCell::new(map))));
idx
}
}
// ============================================================================
// Helpers
// ============================================================================
fn assign_op_to_compound(op: &AssignOp) -> CompoundOp {
match op {
AssignOp::PlusEqual => CompoundOp::PlusEqual,
AssignOp::MinusEqual => CompoundOp::MinusEqual,
AssignOp::StarEqual => CompoundOp::StarEqual,
AssignOp::SlashEqual => CompoundOp::SlashEqual,
AssignOp::PercentEqual => CompoundOp::PercentEqual,
AssignOp::Equal => unreachable!(),
}
}
fn emit_i16_placeholder(code: &mut Vec<u8>, op: OpCode) -> usize {
let loc = code.len();
emit_i16(code, op, 0x7FFF);
loc
}
+3
View File
@@ -0,0 +1,3 @@
pub mod opcode;
pub mod compiler;
pub mod vm;
+170
View File
@@ -0,0 +1,170 @@
//! Bytecode opcode definitions for the Aster VM.
//!
//! Each instruction is encoded as a 1-byte opcode tag followed by
//! variable-length operands (u8 for local slots, u16 for constant pool
//! indices, i16 for relative jump offsets).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum OpCode {
// --- Stack manipulation ---
Pop = 0,
Dup = 1,
// --- Constants (operand: u16 index into constant pool) ---
LoadConst = 2,
LoadNil = 3,
LoadTrue = 4,
LoadFalse = 5,
// --- Local variables (operand: u8 slot index) ---
LoadLocal = 6,
StoreLocal = 7,
// --- Global variables (operand: u16 index into constant pool for name) ---
LoadGlobal = 8,
StoreGlobal = 9,
DefineGlobal = 10,
// --- Property access (operand: u16 index into constant pool for name) ---
GetProperty = 11,
SetProperty = 12,
// --- Index access (operands on stack) ---
GetIndex = 13,
SetIndex = 14,
// --- Binary arithmetic ---
Add = 15,
Sub = 16,
Mul = 17,
Div = 18,
Mod = 19,
// --- Unary ---
Negate = 20,
Not = 21,
// --- Comparison ---
Equal = 22,
NotEqual = 23,
Greater = 24,
GreaterEqual = 25,
Less = 26,
LessEqual = 27,
// --- Control flow (operand: i16 relative jump offset) ---
Jump = 28,
JumpIfFalse = 29,
JumpIfTrue = 30,
PopJumpIfFalse = 31,
// --- Functions ---
Call = 32, // operand: u8 arg count
Return = 33,
Closure = 34, // operand: u16 proto idx, then N*(u8 is_local, u8 index)
// --- Object/Array construction ---
NewObject = 35,
NewArray = 36, // operand: u16 element count
// --- For-in support ---
ForInInit = 37,
ForInNext = 38, // operand: i16 done jump offset
// --- Compound assignment ---
CompoundAssignLocal = 39, // operand: u8 slot + u8 compound-op tag
CompoundAssignProp = 40, // operand: u16 name idx + u8 compound-op tag
CompoundAssignIndex = 41, // operand: u8 compound-op tag
}
impl OpCode {
pub fn from_u8(byte: u8) -> Option<Self> {
if byte <= 41 {
// SAFETY: OpCode is #[repr(u8)] with contiguous values 0..=41
Some(unsafe { std::mem::transmute::<u8, OpCode>(byte) })
} else {
None
}
}
/// Total number of distinct opcodes
pub const COUNT: usize = 42;
}
/// Compound assignment operator tags (used as operand byte after
/// CompoundAssignLocal/Prop/Index).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum CompoundOp {
PlusEqual = 0,
MinusEqual = 1,
StarEqual = 2,
SlashEqual = 3,
PercentEqual = 4,
}
impl CompoundOp {
pub fn from_u8(byte: u8) -> Option<Self> {
if byte <= 4 {
Some(unsafe { std::mem::transmute::<u8, CompoundOp>(byte) })
} else {
None
}
}
}
// ============================================================================
// Instruction encoding helpers (for compiler use)
// ============================================================================
/// Write a u8 operand after the opcode byte.
pub fn emit_u8(code: &mut Vec<u8>, op: OpCode, operand: u8) {
code.push(op as u8);
code.push(operand);
}
/// Write a u16 operand after the opcode byte (little-endian).
pub fn emit_u16(code: &mut Vec<u8>, op: OpCode, operand: u16) {
code.push(op as u8);
code.push((operand & 0xFF) as u8);
code.push(((operand >> 8) & 0xFF) as u8);
}
/// Write an i16 operand after the opcode byte (little-endian).
pub fn emit_i16(code: &mut Vec<u8>, op: OpCode, offset: i16) {
code.push(op as u8);
code.push((offset & 0xFF) as u8);
code.push(((offset >> 8) & 0xFF) as u8);
}
/// Write a standalone opcode byte.
pub fn emit_op(code: &mut Vec<u8>, op: OpCode) {
code.push(op as u8);
}
// ============================================================================
// Instruction decoding helpers (for VM use)
// ============================================================================
/// Read a u8 at position `ip + 1` (right after the opcode byte).
pub fn read_u8(code: &[u8], ip: usize) -> u8 {
code[ip + 1]
}
/// Read a u16 at position `ip + 1` (little-endian, right after the opcode byte).
pub fn read_u16(code: &[u8], ip: usize) -> u16 {
(code[ip + 1] as u16) | ((code[ip + 2] as u16) << 8)
}
/// Read an i16 at position `ip + 1` (little-endian, right after the opcode byte).
pub fn read_i16(code: &[u8], ip: usize) -> i16 {
((code[ip + 1] as u16) | ((code[ip + 2] as u16) << 8)) as i16
}
/// Size in bytes of an opcode with no operand.
pub const SIZE_OP: usize = 1;
/// Size in bytes of an opcode with a u8 operand.
pub const SIZE_U8: usize = 2;
/// Size in bytes of an opcode with a u16/i16 operand.
pub const SIZE_U16: usize = 3;
File diff suppressed because it is too large Load Diff
+12 -4
View File
@@ -3,13 +3,21 @@ use std::fs;
fn main() {
let args: Vec<String> = env::args().collect();
let use_vm = args.iter().any(|a| a == "--vm");
let file_args: Vec<&String> = args.iter().filter(|a| *a != "--vm").collect();
match args.len() {
match file_args.len() {
1 => aster_core::run_repl(),
2 => {
let filename = &args[1];
let filename = file_args[1];
match fs::read_to_string(filename) {
Ok(src) => aster_core::run_file(filename, src),
Ok(src) => {
if use_vm {
aster_core::run_file_vm(filename, src)
} else {
aster_core::run_file(filename, src)
}
}
Err(e) => {
eprintln!("Error reading file '{}': {}", filename, e);
std::process::exit(1);
@@ -17,7 +25,7 @@ fn main() {
}
}
_ => {
eprintln!("Usage: aster [script]");
eprintln!("Usage: aster [--vm] [script]");
std::process::exit(1);
}
}