fix: CompoundAssignUpvalue支持 + const变量赋值检查
- 新增 CompoundAssignUpvalue 字节码,闭包中 x+=5 不再报错 - Local/Upvalue 上的 is_const 编译期检查 - globals 改为 HashMap<String, (Value, bool)> 追踪 const,StoreGlobal 运行时拒绝 - DefineGlobal 指令新增 1 字节 mutable 标志 - 更新 README:双执行模式、for-in、require()、VM 模块说明 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user