feat: 支持成员自增自减与复合运算

This commit is contained in:
爱特每羊队
2026-06-06 20:48:44 +08:00
parent b812cfae01
commit c8996dc438
6 changed files with 814 additions and 19 deletions
+21 -2
View File
@@ -1,4 +1,6 @@
use oxide_parser::{Expression, ForStatementInit, SimpleAssignmentTarget, Statement};
use oxide_parser::{
AssignmentOperator, Expression, ForStatementInit, SimpleAssignmentTarget, Statement,
};
use crate::compiler::{is_side_effect_free, CompileCtx, Compiler, Label};
@@ -210,8 +212,12 @@ impl Compiler {
{
self.count_expression(&member.object, ctx);
self.count_expression(&assign.right, ctx);
if assign.operator != AssignmentOperator::Assign {
ctx.alloc_reg();
ctx.projected_pc += 1;
}
ctx.alloc_reg();
ctx.projected_pc += 1; // IC_SET_PROP
ctx.projected_pc += 1; // IC_SET_PROP or COMPOUND_MEMBER_*
} else if let oxide_parser::AssignmentTarget::ComputedMemberExpression(member) =
&assign.left
{
@@ -319,6 +325,19 @@ impl Compiler {
ctx.alloc_reg();
ctx.projected_pc += 1;
}
SimpleAssignmentTarget::StaticMemberExpression(member) => {
self.count_expression(&member.object, ctx);
ctx.alloc_reg();
ctx.projected_pc += 1;
ctx.alloc_reg();
ctx.projected_pc += 1;
}
SimpleAssignmentTarget::ComputedMemberExpression(member) => {
self.count_expression(&member.object, ctx);
self.count_expression(&member.expression, ctx);
ctx.alloc_reg();
ctx.projected_pc += 1;
}
_ => {
ctx.alloc_reg();
}
+54 -8
View File
@@ -606,14 +606,29 @@ impl Compiler {
(idx & 0xFF) as u8,
((idx >> 8) & 0xFF) as u8,
));
ctx.emit(opcode::encode(
OpCode::IC_SET_PROP,
obj_reg,
val_reg,
key_reg,
));
ctx.emit(0);
Ok(val_reg)
if assign.operator != AssignmentOperator::Assign {
let op = match assign.operator {
AssignmentOperator::Addition => OpCode::COMPOUND_MEMBER_ADD,
AssignmentOperator::Subtraction => OpCode::COMPOUND_MEMBER_SUB,
AssignmentOperator::Multiplication => OpCode::COMPOUND_MEMBER_MUL,
AssignmentOperator::Division => OpCode::COMPOUND_MEMBER_DIV,
AssignmentOperator::Remainder => OpCode::COMPOUND_MEMBER_MOD,
AssignmentOperator::Exponential => OpCode::COMPOUND_MEMBER_EXP,
_ => unreachable!(),
};
ctx.emit(opcode::encode(op, obj_reg, val_reg, key_reg));
ctx.emit(0);
Ok(val_reg)
} else {
ctx.emit(opcode::encode(
OpCode::IC_SET_PROP,
obj_reg,
val_reg,
key_reg,
));
ctx.emit(0);
Ok(val_reg)
}
} else if let oxide_parser::AssignmentTarget::ComputedMemberExpression(member) =
&assign.left
{
@@ -670,6 +685,37 @@ impl Compiler {
ctx.emit(opcode::encode(op, var_reg, result_reg, result_reg));
Ok(result_reg)
}
SimpleAssignmentTarget::StaticMemberExpression(member) => {
let obj_reg = self.emit_expression(&member.object, ctx)?;
let prop_name = member.property.name.as_str();
let key_idx = ctx.add_constant(Constant::String(prop_name.to_string()));
let key_reg = ctx.alloc_reg();
ctx.emit(opcode::encode(
OpCode::LOAD_CONST,
key_reg,
(key_idx & 0xFF) as u8,
((key_idx >> 8) & 0xFF) as u8,
));
let val_reg = ctx.alloc_reg();
let op = match update.operator {
UpdateOperator::Increment => OpCode::MEMBER_INC,
UpdateOperator::Decrement => OpCode::MEMBER_DEC,
};
ctx.emit(opcode::encode(op, obj_reg, val_reg, key_reg));
ctx.emit(0);
Ok(val_reg)
}
SimpleAssignmentTarget::ComputedMemberExpression(member) => {
let obj_reg = self.emit_expression(&member.object, ctx)?;
let key_reg = self.emit_expression(&member.expression, ctx)?;
let val_reg = ctx.alloc_reg();
let op = match update.operator {
UpdateOperator::Increment => OpCode::DYN_MEMBER_INC,
UpdateOperator::Decrement => OpCode::DYN_MEMBER_DEC,
};
ctx.emit(opcode::encode(op, obj_reg, key_reg, val_reg));
Ok(val_reg)
}
_ => Err("member update not yet supported".into()),
},
Expression::Identifier(ident) => {
+41 -9
View File
@@ -75,11 +75,23 @@ pub enum OpCode {
NEW_ARRAY = 0x57,
SET_ELEM = 0x58,
// ── Profiling — placeholders (0x60-0x6F) ──
PROFILE_TYPE = 0x60,
PROFILE_SHAPE = 0x61,
PROFILE_BRANCH = 0x62,
PROFILE_CALL = 0x63,
// -- Member Update (0x59-0x62) --
MEMBER_INC = 0x59,
MEMBER_DEC = 0x5A,
DYN_MEMBER_INC = 0x5B,
DYN_MEMBER_DEC = 0x5C,
COMPOUND_MEMBER_ADD = 0x5D,
COMPOUND_MEMBER_SUB = 0x5E,
COMPOUND_MEMBER_MUL = 0x5F,
COMPOUND_MEMBER_DIV = 0x60,
COMPOUND_MEMBER_MOD = 0x61,
COMPOUND_MEMBER_EXP = 0x62,
// -- Profiling - placeholders (0x63-0x6F) --
PROFILE_TYPE = 0x63,
PROFILE_SHAPE = 0x64,
PROFILE_BRANCH = 0x65,
PROFILE_CALL = 0x66,
// ── Parallel — placeholders (0x70-0x7F) ──
FORK = 0x70,
@@ -162,10 +174,20 @@ impl TryFrom<u8> for OpCode {
0x56 => Ok(OpCode::NEW_OBJECT),
0x57 => Ok(OpCode::NEW_ARRAY),
0x58 => Ok(OpCode::SET_ELEM),
0x60 => Ok(OpCode::PROFILE_TYPE),
0x61 => Ok(OpCode::PROFILE_SHAPE),
0x62 => Ok(OpCode::PROFILE_BRANCH),
0x63 => Ok(OpCode::PROFILE_CALL),
0x59 => Ok(OpCode::MEMBER_INC),
0x5A => Ok(OpCode::MEMBER_DEC),
0x5B => Ok(OpCode::DYN_MEMBER_INC),
0x5C => Ok(OpCode::DYN_MEMBER_DEC),
0x5D => Ok(OpCode::COMPOUND_MEMBER_ADD),
0x5E => Ok(OpCode::COMPOUND_MEMBER_SUB),
0x5F => Ok(OpCode::COMPOUND_MEMBER_MUL),
0x60 => Ok(OpCode::COMPOUND_MEMBER_DIV),
0x61 => Ok(OpCode::COMPOUND_MEMBER_MOD),
0x62 => Ok(OpCode::COMPOUND_MEMBER_EXP),
0x63 => Ok(OpCode::PROFILE_TYPE),
0x64 => Ok(OpCode::PROFILE_SHAPE),
0x65 => Ok(OpCode::PROFILE_BRANCH),
0x66 => Ok(OpCode::PROFILE_CALL),
0x70 => Ok(OpCode::FORK),
0x71 => Ok(OpCode::JOIN),
0xF0 => Ok(OpCode::NOP),
@@ -231,6 +253,16 @@ impl fmt::Display for OpCode {
OpCode::NEW_OBJECT => "NEW_OBJECT",
OpCode::NEW_ARRAY => "NEW_ARRAY",
OpCode::SET_ELEM => "SET_ELEM",
OpCode::MEMBER_INC => "MEMBER_INC",
OpCode::MEMBER_DEC => "MEMBER_DEC",
OpCode::DYN_MEMBER_INC => "DYN_MEMBER_INC",
OpCode::DYN_MEMBER_DEC => "DYN_MEMBER_DEC",
OpCode::COMPOUND_MEMBER_ADD => "COMPOUND_MEMBER_ADD",
OpCode::COMPOUND_MEMBER_SUB => "COMPOUND_MEMBER_SUB",
OpCode::COMPOUND_MEMBER_MUL => "COMPOUND_MEMBER_MUL",
OpCode::COMPOUND_MEMBER_DIV => "COMPOUND_MEMBER_DIV",
OpCode::COMPOUND_MEMBER_MOD => "COMPOUND_MEMBER_MOD",
OpCode::COMPOUND_MEMBER_EXP => "COMPOUND_MEMBER_EXP",
OpCode::PROFILE_TYPE => "PROFILE_TYPE",
OpCode::PROFILE_SHAPE => "PROFILE_SHAPE",
OpCode::PROFILE_BRANCH => "PROFILE_BRANCH",
@@ -298,3 +298,63 @@ fn compile_inc_dec_diff_opcodes() {
assert!(has_inc_pre, "++x should emit INC_PRE");
assert!(has_dec_pre, "--x should emit DEC_PRE");
}
#[test]
fn compile_member_inc() {
let module = compile_source("var obj={x:1}; obj.x++");
assert!(
module
.bytecode
.iter()
.any(|&i| opcode::opcode(i) == OpCode::MEMBER_INC),
"obj.x++ should emit MEMBER_INC"
);
}
#[test]
fn compile_dyn_member_inc() {
let module = compile_source("var obj={a:3}; var k='a'; obj[k]++");
assert!(
module
.bytecode
.iter()
.any(|&i| opcode::opcode(i) == OpCode::DYN_MEMBER_INC),
"obj[k]++ should emit DYN_MEMBER_INC"
);
}
#[test]
fn compile_compound_member_add() {
let module = compile_source("var obj={x:1}; obj.x+=1");
assert!(
module
.bytecode
.iter()
.any(|&i| opcode::opcode(i) == OpCode::COMPOUND_MEMBER_ADD),
"obj.x+=1 should emit COMPOUND_MEMBER_ADD"
);
}
#[test]
fn compile_compound_member_assign_ok() {
let result = std::panic::catch_unwind(|| {
compile_source("var obj={x:1}; obj.x+=1");
});
assert!(result.is_ok(), "obj.x+=1 should compile without error");
}
#[test]
fn compile_prefix_member_inc_ok() {
let result = std::panic::catch_unwind(|| {
compile_source("var obj={x:1}; ++obj.x");
});
assert!(result.is_ok(), "++obj.x should compile without error");
}
#[test]
fn compile_compound_member_exp_ok() {
let result = std::panic::catch_unwind(|| {
compile_source("var obj={x:1}; obj.x**=2");
});
assert!(result.is_ok(), "obj.x**=2 should compile without error");
}