Merge pull request 'fix: CompoundAssignUpvalue支持 + const变量赋值检查' (#2) from vm-bytecode into master

Reviewed-on: #2
This commit was merged in pull request #2.
This commit is contained in:
2026-06-26 09:43:18 +08:00
3 changed files with 86 additions and 24 deletions
+35 -12
View File
@@ -77,7 +77,6 @@ struct Local {
name: String, name: String,
depth: u8, // scope depth where declared; 0 = uninitialized depth: u8, // scope depth where declared; 0 = uninitialized
is_captured: bool, is_captured: bool,
#[allow(dead_code)]
is_const: bool, is_const: bool,
} }
@@ -85,6 +84,7 @@ struct Local {
struct Upvalue { struct Upvalue {
index: u8, index: u8,
is_local: bool, // true = captured from enclosing fn's local; false = from upvalue 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) name: String, // variable name (for transitive upvalue resolution)
} }
@@ -203,6 +203,7 @@ impl Compiler {
if self.scope_depth == 0 { if self.scope_depth == 0 {
let name_idx = self.add_string_constant(name); let name_idx = self.add_string_constant(name);
emit_u16(&mut self.function.code, OpCode::DefineGlobal, name_idx); 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 // DefineGlobal pushes value back; pop it for statement-level let
self.emit_op(OpCode::Pop); self.emit_op(OpCode::Pop);
} else { } else {
@@ -395,6 +396,7 @@ impl Compiler {
if self.scope_depth == 0 { if self.scope_depth == 0 {
let name_idx = self.add_string_constant(name); let name_idx = self.add_string_constant(name);
emit_u16(&mut self.function.code, OpCode::DefineGlobal, name_idx); emit_u16(&mut self.function.code, OpCode::DefineGlobal, name_idx);
self.function.code.push(1); // mutable=true for fn declarations
} else { } else {
let slot = self.locals.len() as u8; let slot = self.locals.len() as u8;
self.locals.push(Local { self.locals.push(Local {
@@ -493,8 +495,23 @@ impl Compiler {
// Simple assignment // Simple assignment
self.compile_expr(value)?; self.compile_expr(value)?;
if let Some(slot) = self.resolve_local(name) { 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); emit_u8(&mut self.function.code, OpCode::StoreLocal, slot);
} else if let Some(uv_idx) = self.resolve_upvalue(name) { } 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); emit_u8(&mut self.function.code, OpCode::StoreUpvalue, uv_idx);
} else { } else {
let name_idx = self.add_string_constant(name); let name_idx = self.add_string_constant(name);
@@ -514,14 +531,9 @@ impl Compiler {
if let Some(slot) = self.resolve_local(name) { if let Some(slot) = self.resolve_local(name) {
emit_u8(&mut self.function.code, OpCode::CompoundAssignLocal, slot); emit_u8(&mut self.function.code, OpCode::CompoundAssignLocal, slot);
self.function.code.push(compound_op as u8); self.function.code.push(compound_op as u8);
} else if self.resolve_upvalue(name).is_some() { } else if let Some(uv_idx) = self.resolve_upvalue(name) {
// Compound assign on upvalue: load upvalue, compound op, store emit_u8(&mut self.function.code, OpCode::CompoundAssignUpvalue, uv_idx);
// For simplicity, use a workaround: re-emit this as a separate step self.function.code.push(compound_op as u8);
// TODO: add CompoundAssignUpvalue opcode
return Err(RuntimeError::RuntimeError {
message: "Compound assignment on upvalue not yet supported".into(),
token: None,
});
} else { } else {
let name_idx = self.add_string_constant(name); let name_idx = self.add_string_constant(name);
emit_u16(&mut self.function.code, OpCode::CompoundAssignProp, name_idx); 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 // Found in direct parent's locals → capture as upvalue
let idx = self.upvalues.borrow().len() as u8; let idx = self.upvalues.borrow().len() as u8;
self.upvalues.borrow_mut().push(Upvalue { 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); return Some(idx);
} else { } else {
@@ -832,15 +846,18 @@ impl Compiler {
gp_upvalues.borrow_mut().push(Upvalue { gp_upvalues.borrow_mut().push(Upvalue {
index: (i - self.parent_locals_count) as u8, index: (i - self.parent_locals_count) as u8,
is_local: true, is_local: true,
is_const: false, // can't easily resolve const-ness at this depth
name: name.to_string() name: name.to_string()
}); });
idx idx
} }
}; };
// Parent's upvalue is transitive through grandparent // 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 { enc_upvalues.borrow_mut().push(Upvalue {
index: gp_idx, index: gp_idx,
is_local: false, is_local: false,
is_const: gp_uv_is_const,
name: name.to_string() name: name.to_string()
}); });
} else { } else {
@@ -848,6 +865,7 @@ impl Compiler {
enc_upvalues.borrow_mut().push(Upvalue { enc_upvalues.borrow_mut().push(Upvalue {
index: (i - self.parent_locals_count) as u8, index: (i - self.parent_locals_count) as u8,
is_local: true, is_local: true,
is_const: local.is_const,
name: name.to_string() name: name.to_string()
}); });
} }
@@ -855,9 +873,12 @@ impl Compiler {
} }
}; };
// Add transitive upvalue in self pointing to parent's // 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; let idx = self.upvalues.borrow().len() as u8;
self.upvalues.borrow_mut().push(Upvalue { 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); return Some(idx);
} }
@@ -871,7 +892,9 @@ impl Compiler {
if uv.name == name { if uv.name == name {
let idx = self.upvalues.borrow().len() as u8; let idx = self.upvalues.borrow().len() as u8;
self.upvalues.borrow_mut().push(Upvalue { 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); return Some(idx);
} }
+3 -2
View File
@@ -78,12 +78,13 @@ pub enum OpCode {
CompoundAssignLocal = 39, // operand: u8 slot + u8 compound-op tag CompoundAssignLocal = 39, // operand: u8 slot + u8 compound-op tag
CompoundAssignProp = 40, // operand: u16 name idx + u8 compound-op tag CompoundAssignProp = 40, // operand: u16 name idx + u8 compound-op tag
CompoundAssignIndex = 41, // operand: u8 compound-op tag CompoundAssignIndex = 41, // operand: u8 compound-op tag
CompoundAssignUpvalue = 44, // operand: u8 upvalue idx + u8 compound-op tag
} }
impl OpCode { impl OpCode {
pub fn from_u8(byte: u8) -> Option<Self> { pub fn from_u8(byte: u8) -> Option<Self> {
match byte { match byte {
0..=41 => Some(unsafe { std::mem::transmute::<u8, OpCode>(byte) }), 0..=41 | 44 => Some(unsafe { std::mem::transmute::<u8, OpCode>(byte) }),
42 => Some(OpCode::LoadUpvalue), 42 => Some(OpCode::LoadUpvalue),
43 => Some(OpCode::StoreUpvalue), 43 => Some(OpCode::StoreUpvalue),
_ => None, _ => None,
@@ -91,7 +92,7 @@ impl OpCode {
} }
/// Total number of distinct opcodes /// Total number of distinct opcodes
pub const COUNT: usize = 44; pub const COUNT: usize = 45;
} }
/// Compound assignment operator tags (used as operand byte after /// Compound assignment operator tags (used as operand byte after
+45 -7
View File
@@ -48,8 +48,8 @@ pub struct Vm {
/// Call frames /// Call frames
frames: Vec<CallFrame>, frames: Vec<CallFrame>,
/// Script-level globals (top-level let bindings) /// Script-level globals (top-level let/const bindings). Value is (value, is_mutable).
pub globals: Rc<RefCell<HashMap<String, Value>>>, pub globals: Rc<RefCell<HashMap<String, (Value, bool)>>>,
/// Builtins (shared across modules) /// Builtins (shared across modules)
pub builtins: Rc<RefCell<HashMap<String, Value>>>, pub builtins: Rc<RefCell<HashMap<String, Value>>>,
@@ -246,7 +246,8 @@ impl Vm {
OpCode::LoadGlobal => { OpCode::LoadGlobal => {
let name_idx = read_u16(code, ip) as usize; let name_idx = read_u16(code, ip) as usize;
let name = proto_string(&proto, name_idx)?; 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()) .or_else(|| self.builtins.borrow().get(&name).cloned())
.ok_or_else(|| RuntimeError::RuntimeError { .ok_or_else(|| RuntimeError::RuntimeError {
message: format!("Undefined variable '{}'", name), message: format!("Undefined variable '{}'", name),
@@ -259,16 +260,31 @@ impl Vm {
let name_idx = read_u16(code, ip) as usize; let name_idx = read_u16(code, ip) as usize;
let name = proto_string(&proto,name_idx)?; let name = proto_string(&proto,name_idx)?;
let val = self.stack.last().unwrap().clone(); 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); self.advance_ip(SIZE_U16);
} }
OpCode::DefineGlobal => { OpCode::DefineGlobal => {
let name_idx = read_u16(code, ip) as usize; let name_idx = read_u16(code, ip) as usize;
let name = proto_string(&proto,name_idx)?; let name = proto_string(&proto,name_idx)?;
let mutable = code[ip + 3] != 0; // 0 = const, 1 = mutable
let val = self.stack.pop().unwrap(); 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.stack.push(val);
self.advance_ip(SIZE_U16); self.advance_ip(SIZE_U16 + 1); // 4 bytes: opcode + u16 + u8
} }
// --- Properties --- // --- Properties ---
@@ -594,6 +610,28 @@ impl Vm {
self.stack.push(result); self.stack.push(result);
self.advance_ip(SIZE_OP + 1); 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 // 10. Collect exports from module globals
if let Value::Object(exports_map) = &exports_obj { if let Value::Object(exports_map) = &exports_obj {
let mut map = exports_map.borrow_mut(); 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()); map.insert(name.clone(), val.clone());
} }
} }