From abe084481696e8f2b339176223f0fd97a35b8ae9 Mon Sep 17 00:00:00 2001 From: 0264408 Date: Fri, 26 Jun 2026 09:42:38 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20CompoundAssignUpvalue=E6=94=AF=E6=8C=81?= =?UTF-8?q?=20+=20const=E5=8F=98=E9=87=8F=E8=B5=8B=E5=80=BC=E6=A3=80?= =?UTF-8?q?=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 CompoundAssignUpvalue 字节码,闭包中 x+=5 不再报错 - Local/Upvalue 上的 is_const 编译期检查 - globals 改为 HashMap 追踪 const,StoreGlobal 运行时拒绝 - DefineGlobal 指令新增 1 字节 mutable 标志 - 更新 README:双执行模式、for-in、require()、VM 模块说明 Co-Authored-By: Claude --- README.md | 31 +++++++++++++++++---- aster-core/src/vm/compiler.rs | 47 +++++++++++++++++++++++-------- aster-core/src/vm/opcode.rs | 11 ++++---- aster-core/src/vm/vm.rs | 52 ++++++++++++++++++++++++++++++----- 4 files changed, 112 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 2a83313..22ab06e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Aster -Aster 是一门使用 Rust 编写的动态类型脚本语言解释器,基于树遍历(tree-walker)实现。无外部依赖,纯标准库。 +Aster 是一门使用 Rust 编写的动态类型脚本语言解释器,支持两种执行模式:**树遍历(tree-walker)** 和 **字节码 VM**。无外部依赖,纯标准库。 ## 快速开始 @@ -11,9 +11,12 @@ cargo build # 启动 REPL(交互式) cargo run -# 执行脚本文件 +# 执行脚本文件(树遍历模式) cargo run -- examples/script.ast +# 执行脚本文件(字节码 VM 模式,性能更优) +cargo run -- --vm examples/script.ast + # 运行测试 cargo test ``` @@ -84,6 +87,21 @@ while (true) { if (condition) { break; } if (skip) { continue; } } + +// for-in 遍历(数组 / 字符串) +for (let item in [1, 2, 3]) { + print(item); +} +``` + +### 模块系统 + +```js +// 加载并执行外部脚本,返回模块导出的对象 +let math = require("math.ast"); +print(math.add(3, 4)); + +// 第二次 require 同一文件会返回缓存的对象(支持循环引用) ``` ### 函数与闭包 @@ -144,23 +162,26 @@ let t = os.clock(); // 模块化调用 ## 架构 ``` -源码文本 → Lexer → Tokens → Parser → AST → Interpreter → 输出 +源码文本 → Lexer → Tokens → Parser → AST ──→ Interpreter(树遍历)→ 输出 + └─→ Compiler → Bytecode → VM → 输出 ``` | 模块 | 职责 | |------|------| -| `lexer/` | 词法分析。`Lexer::tokenize()` 将源码转为 `(Vec, Vec)`,Token 记录行列号用于错误报告,支持 `//` 单行注释 | +| `lexer/` | 词法分析。`Lexer::tokenize()` 将源码转为 `(Vec, Vec)`,Token 记录行列号用于错误报告,支持 `//` 单行注释和 `/* */` 块注释 | | `parser/` | 递归下降 + Pratt 解析器。`Parser::parse()` 返回 `(Vec, Vec)`,遇到语法错误通过 `synchronize()` 跳过至下一条语句边界继续解析 | | `ast/` | AST 节点定义。`Expr`(表达式)涵盖字面量、变量、赋值、属性/索引访问、一元/二元/逻辑运算、函数调用、lambda 和对象/数组字面量。`Stmt`(语句)涵盖 let、表达式语句、块、if/while/for、函数、return/break/continue | | `interpreter/` | 树遍历求值器。`Env` 是基于 `Rc>` 的链式作用域。`Signal` 枚举通过调用栈传播 `Return`/`Break`/`Continue`。`builtins/` 按 `io` 和 `os` 模块组织原生函数,同时注册为全局函数以方便使用 | +| `vm/` | 字节码 VM。`Compiler` 将 AST 编译为基于栈的字节码,`Vm` 执行字节码指令。支持闭包 upvalue 捕获、3 层嵌套闭包、`require()` 模块加载。通过 `--vm` 标志启用 | | `error/` | 三种错误:`LexError`(行列号)、`ParseError`(Token)、`RuntimeError`(可选 Token) | ### 关键设计决策 - **零外部依赖** — 全部基于 Rust 标准库构建 +- **双执行模式** — 树遍历模式(默认,适合交互和调试)和字节码 VM 模式(`--vm`,约 40% 性能提升),共享同一 AST 和运行时语义 - **错误容忍解析** — 词法分析器和解析器均将错误收集到 `Vec` 中,单次运行可报告多个诊断信息,而非在第一个错误处就中止 - **`Rc>` 共享所有权** — 用于 `Env` 链、`Value::Object`、`Value::Array` 和 `Value::Function`,提供动态可变语义 -- **词法作用域闭包** — `Function` 在定义时捕获 `Env`,lambda 表达式同理 +- **词法作用域闭包** — `Function` 在定义时捕获 `Env`,lambda 表达式同理;VM 通过 upvalue 机制支持最多 3 层传递式捕获 ## 许可证 diff --git a/aster-core/src/vm/compiler.rs b/aster-core/src/vm/compiler.rs index 8b83213..c58de13 100644 --- a/aster-core/src/vm/compiler.rs +++ b/aster-core/src/vm/compiler.rs @@ -77,7 +77,6 @@ struct Local { name: String, depth: u8, // scope depth where declared; 0 = uninitialized is_captured: bool, - #[allow(dead_code)] is_const: bool, } @@ -85,6 +84,7 @@ struct Local { struct Upvalue { index: u8, is_local: bool, // true = captured from enclosing fn's local; false = from upvalue + is_const: bool, // true = the source variable was declared `const` name: String, // variable name (for transitive upvalue resolution) } @@ -203,6 +203,7 @@ impl Compiler { if self.scope_depth == 0 { let name_idx = self.add_string_constant(name); emit_u16(&mut self.function.code, OpCode::DefineGlobal, name_idx); + self.function.code.push(if mutable { 1 } else { 0 }); // DefineGlobal pushes value back; pop it for statement-level let self.emit_op(OpCode::Pop); } else { @@ -395,6 +396,7 @@ impl Compiler { if self.scope_depth == 0 { let name_idx = self.add_string_constant(name); emit_u16(&mut self.function.code, OpCode::DefineGlobal, name_idx); + self.function.code.push(1); // mutable=true for fn declarations } else { let slot = self.locals.len() as u8; self.locals.push(Local { @@ -493,8 +495,23 @@ impl Compiler { // Simple assignment self.compile_expr(value)?; if let Some(slot) = self.resolve_local(name) { + // Check const for local + if self.locals[slot as usize].is_const { + return Err(RuntimeError::RuntimeError { + message: format!("Cannot reassign constant '{}'", name), + token: None, + }); + } emit_u8(&mut self.function.code, OpCode::StoreLocal, slot); } else if let Some(uv_idx) = self.resolve_upvalue(name) { + // Check const for upvalue + let upvalues = self.upvalues.borrow(); + if upvalues[uv_idx as usize].is_const { + return Err(RuntimeError::RuntimeError { + message: format!("Cannot reassign constant '{}'", name), + token: None, + }); + } emit_u8(&mut self.function.code, OpCode::StoreUpvalue, uv_idx); } else { let name_idx = self.add_string_constant(name); @@ -514,14 +531,9 @@ impl Compiler { 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 if self.resolve_upvalue(name).is_some() { - // Compound assign on upvalue: load upvalue, compound op, store - // For simplicity, use a workaround: re-emit this as a separate step - // TODO: add CompoundAssignUpvalue opcode - return Err(RuntimeError::RuntimeError { - message: "Compound assignment on upvalue not yet supported".into(), - token: None, - }); + } else if let Some(uv_idx) = self.resolve_upvalue(name) { + emit_u8(&mut self.function.code, OpCode::CompoundAssignUpvalue, uv_idx); + 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); @@ -793,7 +805,9 @@ impl Compiler { // Found in direct parent's locals → capture as upvalue let idx = self.upvalues.borrow().len() as u8; self.upvalues.borrow_mut().push(Upvalue { - index: i as u8, is_local: true, name: name.to_string() + index: i as u8, is_local: true, + is_const: local.is_const, + name: name.to_string() }); return Some(idx); } else { @@ -832,15 +846,18 @@ impl Compiler { gp_upvalues.borrow_mut().push(Upvalue { index: (i - self.parent_locals_count) as u8, is_local: true, + is_const: false, // can't easily resolve const-ness at this depth name: name.to_string() }); idx } }; // Parent's upvalue is transitive through grandparent + let gp_uv_is_const = gp_upvalues.borrow()[gp_idx as usize].is_const; enc_upvalues.borrow_mut().push(Upvalue { index: gp_idx, is_local: false, + is_const: gp_uv_is_const, name: name.to_string() }); } else { @@ -848,6 +865,7 @@ impl Compiler { enc_upvalues.borrow_mut().push(Upvalue { index: (i - self.parent_locals_count) as u8, is_local: true, + is_const: local.is_const, name: name.to_string() }); } @@ -855,9 +873,12 @@ impl Compiler { } }; // Add transitive upvalue in self pointing to parent's + let parent_uv_is_const = enc_upvalues.borrow()[parent_idx as usize].is_const; let idx = self.upvalues.borrow().len() as u8; self.upvalues.borrow_mut().push(Upvalue { - index: parent_idx, is_local: false, name: name.to_string() + index: parent_idx, is_local: false, + is_const: parent_uv_is_const, + name: name.to_string() }); return Some(idx); } @@ -871,7 +892,9 @@ impl Compiler { if uv.name == name { let idx = self.upvalues.borrow().len() as u8; self.upvalues.borrow_mut().push(Upvalue { - index: uv.index, is_local: false, name: name.to_string() + index: uv.index, is_local: false, + is_const: uv.is_const, + name: name.to_string() }); return Some(idx); } diff --git a/aster-core/src/vm/opcode.rs b/aster-core/src/vm/opcode.rs index 667cb04..6c4eabc 100644 --- a/aster-core/src/vm/opcode.rs +++ b/aster-core/src/vm/opcode.rs @@ -75,15 +75,16 @@ pub enum OpCode { 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 + 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 + CompoundAssignUpvalue = 44, // operand: u8 upvalue idx + u8 compound-op tag } impl OpCode { pub fn from_u8(byte: u8) -> Option { match byte { - 0..=41 => Some(unsafe { std::mem::transmute::(byte) }), + 0..=41 | 44 => Some(unsafe { std::mem::transmute::(byte) }), 42 => Some(OpCode::LoadUpvalue), 43 => Some(OpCode::StoreUpvalue), _ => None, @@ -91,7 +92,7 @@ impl OpCode { } /// Total number of distinct opcodes - pub const COUNT: usize = 44; + pub const COUNT: usize = 45; } /// Compound assignment operator tags (used as operand byte after diff --git a/aster-core/src/vm/vm.rs b/aster-core/src/vm/vm.rs index 690df32..d464892 100644 --- a/aster-core/src/vm/vm.rs +++ b/aster-core/src/vm/vm.rs @@ -48,8 +48,8 @@ pub struct Vm { /// Call frames frames: Vec, - /// Script-level globals (top-level let bindings) - pub globals: Rc>>, + /// Script-level globals (top-level let/const bindings). Value is (value, is_mutable). + pub globals: Rc>>, /// Builtins (shared across modules) pub builtins: Rc>>, @@ -246,7 +246,8 @@ impl Vm { OpCode::LoadGlobal => { let name_idx = read_u16(code, ip) as usize; let name = proto_string(&proto, name_idx)?; - let val = self.globals.borrow().get(&name).cloned() + let val = self.globals.borrow().get(&name) + .map(|(v, _)| v.clone()) .or_else(|| self.builtins.borrow().get(&name).cloned()) .ok_or_else(|| RuntimeError::RuntimeError { message: format!("Undefined variable '{}'", name), @@ -259,16 +260,31 @@ impl Vm { let name_idx = read_u16(code, ip) as usize; let name = proto_string(&proto,name_idx)?; let val = self.stack.last().unwrap().clone(); - self.globals.borrow_mut().insert(name, val); + { + let mut globals = self.globals.borrow_mut(); + match globals.get(&name) { + Some((_, false)) => { + return Err(RuntimeError::RuntimeError { + message: format!("Cannot reassign constant '{}'", name), + token: None, + }); + } + _ => { + // Exists and mutable, or not yet defined — store as mutable + globals.insert(name, (val, true)); + } + } + } self.advance_ip(SIZE_U16); } OpCode::DefineGlobal => { let name_idx = read_u16(code, ip) as usize; let name = proto_string(&proto,name_idx)?; + let mutable = code[ip + 3] != 0; // 0 = const, 1 = mutable let val = self.stack.pop().unwrap(); - self.globals.borrow_mut().insert(name, val.clone()); + self.globals.borrow_mut().insert(name, (val.clone(), mutable)); self.stack.push(val); - self.advance_ip(SIZE_U16); + self.advance_ip(SIZE_U16 + 1); // 4 bytes: opcode + u16 + u8 } // --- Properties --- @@ -594,6 +610,28 @@ impl Vm { self.stack.push(result); self.advance_ip(SIZE_OP + 1); } + OpCode::CompoundAssignUpvalue => { + let uv_idx = read_u8(code, ip) as usize; + let op_tag = code[ip + 2]; + let compound_op = CompoundOp::from_u8(op_tag).unwrap(); + let rhs = self.stack.pop().unwrap(); + let uv = Rc::clone(&self.frames.last().unwrap().closure.upvalues[uv_idx]); + let mut uv_ref = uv.borrow_mut(); + let lhs = if let Some(ref closed) = uv_ref.closed { + closed.clone() + } else { + self.stack[uv_ref.location].clone() + }; + let result = self.apply_compound_op(lhs, rhs, compound_op)?; + if let Some(ref mut closed) = uv_ref.closed { + *closed = result.clone(); + } else { + self.stack[uv_ref.location] = result.clone(); + } + drop(uv_ref); + self.stack.push(result); + self.advance_ip(SIZE_U8 + 1); + } } } } @@ -1037,7 +1075,7 @@ impl Runtime for Vm { // 10. Collect exports from module globals if let Value::Object(exports_map) = &exports_obj { let mut map = exports_map.borrow_mut(); - for (name, val) in module_vm.globals.borrow().iter() { + for (name, (val, _)) in module_vm.globals.borrow().iter() { map.insert(name.clone(), val.clone()); } }