feat: 支持成员自增自减与复合运算
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -108,6 +108,44 @@ impl Vm {
|
||||
None
|
||||
}
|
||||
|
||||
fn set_member_prop(
|
||||
&mut self,
|
||||
obj: &mut JsObject,
|
||||
prop_name_si: u32,
|
||||
val: JsValue,
|
||||
) -> Result<(), String> {
|
||||
if let Some(offset) = self
|
||||
.kernel
|
||||
.shape_forge()
|
||||
.lookup_offset(obj.shape_id(), prop_name_si)
|
||||
{
|
||||
let new_ext = (obj.shape_id() & 0x00FF_FFFF) | ((offset as u32) << 24);
|
||||
self.bytecode[self.pc - 1] = new_ext;
|
||||
obj.set_prop(offset, val);
|
||||
} else {
|
||||
let new_offset = obj.prop_count();
|
||||
let new_shape_id = self
|
||||
.kernel
|
||||
.shape_forge()
|
||||
.make_shape(obj.shape_id(), prop_name_si);
|
||||
let new_ext = (new_shape_id & 0x00FF_FFFF) | ((new_offset as u32) << 24);
|
||||
self.bytecode[self.pc - 1] = new_ext;
|
||||
obj.set_shape_id(new_shape_id);
|
||||
obj.set_prop_count(new_offset + 1);
|
||||
obj.set_prop_expand(new_offset, val, self.epoch.bump());
|
||||
obj.bump_generation();
|
||||
self.kernel.prop_forge().upsert(
|
||||
new_shape_id,
|
||||
PropTemplate {
|
||||
shape_id: new_shape_id,
|
||||
offset: new_offset,
|
||||
generation: obj.generation(),
|
||||
},
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn rerun(&mut self) -> Result<JsValue, String> {
|
||||
self.pc = 0;
|
||||
self.regs = [JsValue::undefined(); 256];
|
||||
@@ -661,6 +699,531 @@ impl Vm {
|
||||
self.regs[rd] = JsValue::float(n - 1.0);
|
||||
}
|
||||
|
||||
OpCode::MEMBER_INC => {
|
||||
let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject;
|
||||
if obj_ptr.is_null() {
|
||||
return Err("MEMBER_INC on non-object".into());
|
||||
}
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
let prop_name_si = self.regs[b].as_string_index();
|
||||
let ext = self.bytecode[self.pc];
|
||||
self.pc += 1;
|
||||
let cached_shape_id = ext & 0x00FF_FFFF;
|
||||
let cached_offset = ((ext >> 24) & 0xFF) as u8;
|
||||
|
||||
let prop_val = if cached_shape_id != 0 && cached_shape_id == obj.shape_id() {
|
||||
obj.get_prop(cached_offset)
|
||||
} else if let Some(template) =
|
||||
self.kernel.prop_forge().get_template(obj.shape_id())
|
||||
{
|
||||
let new_ext =
|
||||
(obj.shape_id() & 0x00FF_FFFF) | ((template.offset as u32) << 24);
|
||||
self.bytecode[self.pc - 1] = new_ext;
|
||||
obj.get_prop(template.offset)
|
||||
} else if let Some(val) = self.resolve_property(obj, prop_name_si) {
|
||||
let offset = self
|
||||
.kernel
|
||||
.shape_forge()
|
||||
.lookup_offset(obj.shape_id(), prop_name_si)
|
||||
.unwrap_or(0);
|
||||
let new_ext = (obj.shape_id() & 0x00FF_FFFF) | ((offset as u32) << 24);
|
||||
self.bytecode[self.pc - 1] = new_ext;
|
||||
val
|
||||
} else {
|
||||
JsValue::undefined()
|
||||
};
|
||||
|
||||
let n = coercion::to_number(prop_val, self.kernel.string_forge().as_ref());
|
||||
let new_val = JsValue::float(n + 1.0);
|
||||
|
||||
let offset = if cached_shape_id != 0 && cached_shape_id == obj.shape_id() {
|
||||
cached_offset
|
||||
} else if let Some(off) = self
|
||||
.kernel
|
||||
.shape_forge()
|
||||
.lookup_offset(obj.shape_id(), prop_name_si)
|
||||
{
|
||||
off
|
||||
} else {
|
||||
let new_offset = obj.prop_count();
|
||||
let new_shape_id = self
|
||||
.kernel
|
||||
.shape_forge()
|
||||
.make_shape(obj.shape_id(), prop_name_si);
|
||||
obj.set_shape_id(new_shape_id);
|
||||
obj.set_prop_count(new_offset + 1);
|
||||
obj.set_prop_expand(new_offset, new_val, self.epoch.bump());
|
||||
obj.bump_generation();
|
||||
let new_ext = (new_shape_id & 0x00FF_FFFF) | ((new_offset as u32) << 24);
|
||||
self.bytecode[self.pc - 1] = new_ext;
|
||||
self.kernel.prop_forge().upsert(
|
||||
new_shape_id,
|
||||
PropTemplate {
|
||||
shape_id: new_shape_id,
|
||||
offset: new_offset,
|
||||
generation: obj.generation(),
|
||||
},
|
||||
);
|
||||
self.regs[a] = new_val;
|
||||
continue;
|
||||
};
|
||||
obj.set_prop(offset, new_val);
|
||||
self.regs[a] = new_val;
|
||||
}
|
||||
|
||||
OpCode::MEMBER_DEC => {
|
||||
let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject;
|
||||
if obj_ptr.is_null() {
|
||||
return Err("MEMBER_DEC on non-object".into());
|
||||
}
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
let prop_name_si = self.regs[b].as_string_index();
|
||||
let ext = self.bytecode[self.pc];
|
||||
self.pc += 1;
|
||||
let cached_shape_id = ext & 0x00FF_FFFF;
|
||||
let cached_offset = ((ext >> 24) & 0xFF) as u8;
|
||||
|
||||
let prop_val = if cached_shape_id != 0 && cached_shape_id == obj.shape_id() {
|
||||
obj.get_prop(cached_offset)
|
||||
} else if let Some(template) =
|
||||
self.kernel.prop_forge().get_template(obj.shape_id())
|
||||
{
|
||||
let new_ext =
|
||||
(obj.shape_id() & 0x00FF_FFFF) | ((template.offset as u32) << 24);
|
||||
self.bytecode[self.pc - 1] = new_ext;
|
||||
obj.get_prop(template.offset)
|
||||
} else if let Some(val) = self.resolve_property(obj, prop_name_si) {
|
||||
let offset = self
|
||||
.kernel
|
||||
.shape_forge()
|
||||
.lookup_offset(obj.shape_id(), prop_name_si)
|
||||
.unwrap_or(0);
|
||||
let new_ext = (obj.shape_id() & 0x00FF_FFFF) | ((offset as u32) << 24);
|
||||
self.bytecode[self.pc - 1] = new_ext;
|
||||
val
|
||||
} else {
|
||||
JsValue::undefined()
|
||||
};
|
||||
|
||||
let n = coercion::to_number(prop_val, self.kernel.string_forge().as_ref());
|
||||
let new_val = JsValue::float(n - 1.0);
|
||||
|
||||
let offset = if cached_shape_id != 0 && cached_shape_id == obj.shape_id() {
|
||||
cached_offset
|
||||
} else if let Some(off) = self
|
||||
.kernel
|
||||
.shape_forge()
|
||||
.lookup_offset(obj.shape_id(), prop_name_si)
|
||||
{
|
||||
off
|
||||
} else {
|
||||
let new_offset = obj.prop_count();
|
||||
let new_shape_id = self
|
||||
.kernel
|
||||
.shape_forge()
|
||||
.make_shape(obj.shape_id(), prop_name_si);
|
||||
obj.set_shape_id(new_shape_id);
|
||||
obj.set_prop_count(new_offset + 1);
|
||||
obj.set_prop_expand(new_offset, new_val, self.epoch.bump());
|
||||
obj.bump_generation();
|
||||
let new_ext = (new_shape_id & 0x00FF_FFFF) | ((new_offset as u32) << 24);
|
||||
self.bytecode[self.pc - 1] = new_ext;
|
||||
self.kernel.prop_forge().upsert(
|
||||
new_shape_id,
|
||||
PropTemplate {
|
||||
shape_id: new_shape_id,
|
||||
offset: new_offset,
|
||||
generation: obj.generation(),
|
||||
},
|
||||
);
|
||||
self.regs[a] = new_val;
|
||||
continue;
|
||||
};
|
||||
obj.set_prop(offset, new_val);
|
||||
self.regs[a] = new_val;
|
||||
}
|
||||
|
||||
OpCode::DYN_MEMBER_INC => {
|
||||
let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject;
|
||||
if obj_ptr.is_null() {
|
||||
return Err("DYN_MEMBER_INC on non-object".into());
|
||||
}
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
let key_val = self.regs[a];
|
||||
if !key_val.is_string() {
|
||||
return Err("DYN_MEMBER_INC key not a string".into());
|
||||
}
|
||||
let prop_name_si = key_val.as_string_index();
|
||||
let prop_val = self
|
||||
.resolve_property(obj, prop_name_si)
|
||||
.unwrap_or(JsValue::undefined());
|
||||
let n = coercion::to_number(prop_val, self.kernel.string_forge().as_ref());
|
||||
let new_val = JsValue::float(n + 1.0);
|
||||
|
||||
if let Some(offset) = self
|
||||
.kernel
|
||||
.shape_forge()
|
||||
.lookup_offset(obj.shape_id(), prop_name_si)
|
||||
{
|
||||
obj.set_prop(offset, new_val);
|
||||
} else {
|
||||
let new_offset = obj.prop_count();
|
||||
let new_shape_id = self
|
||||
.kernel
|
||||
.shape_forge()
|
||||
.make_shape(obj.shape_id(), prop_name_si);
|
||||
obj.set_shape_id(new_shape_id);
|
||||
obj.set_prop_count(new_offset + 1);
|
||||
obj.set_prop_expand(new_offset, new_val, self.epoch.bump());
|
||||
obj.bump_generation();
|
||||
};
|
||||
self.regs[b] = new_val;
|
||||
}
|
||||
|
||||
OpCode::DYN_MEMBER_DEC => {
|
||||
let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject;
|
||||
if obj_ptr.is_null() {
|
||||
return Err("DYN_MEMBER_DEC on non-object".into());
|
||||
}
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
let key_val = self.regs[a];
|
||||
if !key_val.is_string() {
|
||||
return Err("DYN_MEMBER_DEC key not a string".into());
|
||||
}
|
||||
let prop_name_si = key_val.as_string_index();
|
||||
let prop_val = self
|
||||
.resolve_property(obj, prop_name_si)
|
||||
.unwrap_or(JsValue::undefined());
|
||||
let n = coercion::to_number(prop_val, self.kernel.string_forge().as_ref());
|
||||
let new_val = JsValue::float(n - 1.0);
|
||||
|
||||
if let Some(offset) = self
|
||||
.kernel
|
||||
.shape_forge()
|
||||
.lookup_offset(obj.shape_id(), prop_name_si)
|
||||
{
|
||||
obj.set_prop(offset, new_val);
|
||||
} else {
|
||||
let new_offset = obj.prop_count();
|
||||
let new_shape_id = self
|
||||
.kernel
|
||||
.shape_forge()
|
||||
.make_shape(obj.shape_id(), prop_name_si);
|
||||
obj.set_shape_id(new_shape_id);
|
||||
obj.set_prop_count(new_offset + 1);
|
||||
obj.set_prop_expand(new_offset, new_val, self.epoch.bump());
|
||||
obj.bump_generation();
|
||||
};
|
||||
self.regs[b] = new_val;
|
||||
}
|
||||
|
||||
OpCode::COMPOUND_MEMBER_ADD => {
|
||||
let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject;
|
||||
if obj_ptr.is_null() {
|
||||
return Err("COMPOUND_MEMBER_ADD on non-object".into());
|
||||
}
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
let prop_name_si = self.regs[b].as_string_index();
|
||||
let ext = self.bytecode[self.pc];
|
||||
self.pc += 1;
|
||||
let cached_shape_id = ext & 0x00FF_FFFF;
|
||||
let cached_offset = ((ext >> 24) & 0xFF) as u8;
|
||||
|
||||
let prop_val = if cached_shape_id != 0 && cached_shape_id == obj.shape_id() {
|
||||
obj.get_prop(cached_offset)
|
||||
} else if let Some(template) =
|
||||
self.kernel.prop_forge().get_template(obj.shape_id())
|
||||
{
|
||||
let new_ext =
|
||||
(obj.shape_id() & 0x00FF_FFFF) | ((template.offset as u32) << 24);
|
||||
self.bytecode[self.pc - 1] = new_ext;
|
||||
obj.get_prop(template.offset)
|
||||
} else if let Some(val) = self.resolve_property(obj, prop_name_si) {
|
||||
let offset = self
|
||||
.kernel
|
||||
.shape_forge()
|
||||
.lookup_offset(obj.shape_id(), prop_name_si)
|
||||
.unwrap_or(0);
|
||||
let new_ext = (obj.shape_id() & 0x00FF_FFFF) | ((offset as u32) << 24);
|
||||
self.bytecode[self.pc - 1] = new_ext;
|
||||
val
|
||||
} else {
|
||||
JsValue::undefined()
|
||||
};
|
||||
|
||||
let rhs = self.regs[a];
|
||||
let new_val = if prop_val.is_string() || rhs.is_string() {
|
||||
let ls = coercion::to_string(self.kernel.string_forge().as_ref(), prop_val);
|
||||
let rs = coercion::to_string(self.kernel.string_forge().as_ref(), rhs);
|
||||
let concat = format!("{ls}{rs}");
|
||||
self.intern(&concat)
|
||||
} else {
|
||||
let ln = coercion::to_number(prop_val, self.kernel.string_forge().as_ref());
|
||||
let rn = coercion::to_number(rhs, self.kernel.string_forge().as_ref());
|
||||
JsValue::float(ln + rn)
|
||||
};
|
||||
|
||||
if let Some(offset) = self
|
||||
.kernel
|
||||
.shape_forge()
|
||||
.lookup_offset(obj.shape_id(), prop_name_si)
|
||||
{
|
||||
let new_ext = (obj.shape_id() & 0x00FF_FFFF) | ((offset as u32) << 24);
|
||||
self.bytecode[self.pc - 1] = new_ext;
|
||||
obj.set_prop(offset, new_val);
|
||||
} else {
|
||||
let new_offset = obj.prop_count();
|
||||
let new_shape_id = self
|
||||
.kernel
|
||||
.shape_forge()
|
||||
.make_shape(obj.shape_id(), prop_name_si);
|
||||
let new_ext = (new_shape_id & 0x00FF_FFFF) | ((new_offset as u32) << 24);
|
||||
self.bytecode[self.pc - 1] = new_ext;
|
||||
obj.set_shape_id(new_shape_id);
|
||||
obj.set_prop_count(new_offset + 1);
|
||||
obj.set_prop_expand(new_offset, new_val, self.epoch.bump());
|
||||
obj.bump_generation();
|
||||
self.kernel.prop_forge().upsert(
|
||||
new_shape_id,
|
||||
PropTemplate {
|
||||
shape_id: new_shape_id,
|
||||
offset: new_offset,
|
||||
generation: obj.generation(),
|
||||
},
|
||||
);
|
||||
}
|
||||
self.regs[a] = new_val;
|
||||
}
|
||||
|
||||
OpCode::COMPOUND_MEMBER_SUB => {
|
||||
let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject;
|
||||
if obj_ptr.is_null() {
|
||||
return Err("COMPOUND_MEMBER_SUB on non-object".into());
|
||||
}
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
let prop_name_si = self.regs[b].as_string_index();
|
||||
let ext = self.bytecode[self.pc];
|
||||
self.pc += 1;
|
||||
let cached_shape_id = ext & 0x00FF_FFFF;
|
||||
let cached_offset = ((ext >> 24) & 0xFF) as u8;
|
||||
|
||||
let prop_val = if cached_shape_id != 0 && cached_shape_id == obj.shape_id() {
|
||||
obj.get_prop(cached_offset)
|
||||
} else if let Some(template) =
|
||||
self.kernel.prop_forge().get_template(obj.shape_id())
|
||||
{
|
||||
let new_ext =
|
||||
(obj.shape_id() & 0x00FF_FFFF) | ((template.offset as u32) << 24);
|
||||
self.bytecode[self.pc - 1] = new_ext;
|
||||
obj.get_prop(template.offset)
|
||||
} else if let Some(val) = self.resolve_property(obj, prop_name_si) {
|
||||
let offset = self
|
||||
.kernel
|
||||
.shape_forge()
|
||||
.lookup_offset(obj.shape_id(), prop_name_si)
|
||||
.unwrap_or(0);
|
||||
let new_ext = (obj.shape_id() & 0x00FF_FFFF) | ((offset as u32) << 24);
|
||||
self.bytecode[self.pc - 1] = new_ext;
|
||||
val
|
||||
} else {
|
||||
JsValue::undefined()
|
||||
};
|
||||
|
||||
let ln = coercion::to_number(prop_val, self.kernel.string_forge().as_ref());
|
||||
let rn = coercion::to_number(self.regs[a], self.kernel.string_forge().as_ref());
|
||||
let new_val = JsValue::float(ln - rn);
|
||||
self.regs[a] = new_val;
|
||||
if let Some(offset) = self
|
||||
.kernel
|
||||
.shape_forge()
|
||||
.lookup_offset(obj.shape_id(), prop_name_si)
|
||||
{
|
||||
let new_ext = (obj.shape_id() & 0x00FF_FFFF) | ((offset as u32) << 24);
|
||||
self.bytecode[self.pc - 1] = new_ext;
|
||||
obj.set_prop(offset, new_val);
|
||||
} else {
|
||||
let new_offset = obj.prop_count();
|
||||
let new_shape_id = self
|
||||
.kernel
|
||||
.shape_forge()
|
||||
.make_shape(obj.shape_id(), prop_name_si);
|
||||
let new_ext = (new_shape_id & 0x00FF_FFFF) | ((new_offset as u32) << 24);
|
||||
self.bytecode[self.pc - 1] = new_ext;
|
||||
obj.set_shape_id(new_shape_id);
|
||||
obj.set_prop_count(new_offset + 1);
|
||||
obj.set_prop_expand(new_offset, new_val, self.epoch.bump());
|
||||
obj.bump_generation();
|
||||
self.kernel.prop_forge().upsert(
|
||||
new_shape_id,
|
||||
PropTemplate {
|
||||
shape_id: new_shape_id,
|
||||
offset: new_offset,
|
||||
generation: obj.generation(),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
OpCode::COMPOUND_MEMBER_MUL => {
|
||||
let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject;
|
||||
if obj_ptr.is_null() {
|
||||
return Err("COMPOUND_MEMBER_MUL on non-object".into());
|
||||
}
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
let prop_name_si = self.regs[b].as_string_index();
|
||||
let ext = self.bytecode[self.pc];
|
||||
self.pc += 1;
|
||||
let cached_shape_id = ext & 0x00FF_FFFF;
|
||||
|
||||
let prop_val = if cached_shape_id != 0 && cached_shape_id == obj.shape_id() {
|
||||
obj.get_prop(((ext >> 24) & 0xFF) as u8)
|
||||
} else if let Some(template) =
|
||||
self.kernel.prop_forge().get_template(obj.shape_id())
|
||||
{
|
||||
let new_ext =
|
||||
(obj.shape_id() & 0x00FF_FFFF) | ((template.offset as u32) << 24);
|
||||
self.bytecode[self.pc - 1] = new_ext;
|
||||
obj.get_prop(template.offset)
|
||||
} else if let Some(val) = self.resolve_property(obj, prop_name_si) {
|
||||
let offset = self
|
||||
.kernel
|
||||
.shape_forge()
|
||||
.lookup_offset(obj.shape_id(), prop_name_si)
|
||||
.unwrap_or(0);
|
||||
let new_ext = (obj.shape_id() & 0x00FF_FFFF) | ((offset as u32) << 24);
|
||||
self.bytecode[self.pc - 1] = new_ext;
|
||||
val
|
||||
} else {
|
||||
JsValue::undefined()
|
||||
};
|
||||
|
||||
let ln = coercion::to_number(prop_val, self.kernel.string_forge().as_ref());
|
||||
let rn = coercion::to_number(self.regs[a], self.kernel.string_forge().as_ref());
|
||||
let new_val = JsValue::float(ln * rn);
|
||||
self.regs[a] = new_val;
|
||||
self.set_member_prop(obj, prop_name_si, new_val)?;
|
||||
}
|
||||
|
||||
OpCode::COMPOUND_MEMBER_DIV => {
|
||||
let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject;
|
||||
if obj_ptr.is_null() {
|
||||
return Err("COMPOUND_MEMBER_DIV on non-object".into());
|
||||
}
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
let prop_name_si = self.regs[b].as_string_index();
|
||||
let ext = self.bytecode[self.pc];
|
||||
self.pc += 1;
|
||||
let cached_shape_id = ext & 0x00FF_FFFF;
|
||||
|
||||
let prop_val = if cached_shape_id != 0 && cached_shape_id == obj.shape_id() {
|
||||
obj.get_prop(((ext >> 24) & 0xFF) as u8)
|
||||
} else if let Some(template) =
|
||||
self.kernel.prop_forge().get_template(obj.shape_id())
|
||||
{
|
||||
let new_ext =
|
||||
(obj.shape_id() & 0x00FF_FFFF) | ((template.offset as u32) << 24);
|
||||
self.bytecode[self.pc - 1] = new_ext;
|
||||
obj.get_prop(template.offset)
|
||||
} else if let Some(val) = self.resolve_property(obj, prop_name_si) {
|
||||
let offset = self
|
||||
.kernel
|
||||
.shape_forge()
|
||||
.lookup_offset(obj.shape_id(), prop_name_si)
|
||||
.unwrap_or(0);
|
||||
let new_ext = (obj.shape_id() & 0x00FF_FFFF) | ((offset as u32) << 24);
|
||||
self.bytecode[self.pc - 1] = new_ext;
|
||||
val
|
||||
} else {
|
||||
JsValue::undefined()
|
||||
};
|
||||
|
||||
let ln = coercion::to_number(prop_val, self.kernel.string_forge().as_ref());
|
||||
let rn = coercion::to_number(self.regs[a], self.kernel.string_forge().as_ref());
|
||||
let new_val = JsValue::float(ln / rn);
|
||||
self.regs[a] = new_val;
|
||||
self.set_member_prop(obj, prop_name_si, new_val)?;
|
||||
}
|
||||
|
||||
OpCode::COMPOUND_MEMBER_MOD => {
|
||||
let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject;
|
||||
if obj_ptr.is_null() {
|
||||
return Err("COMPOUND_MEMBER_MOD on non-object".into());
|
||||
}
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
let prop_name_si = self.regs[b].as_string_index();
|
||||
let ext = self.bytecode[self.pc];
|
||||
self.pc += 1;
|
||||
let cached_shape_id = ext & 0x00FF_FFFF;
|
||||
|
||||
let prop_val = if cached_shape_id != 0 && cached_shape_id == obj.shape_id() {
|
||||
obj.get_prop(((ext >> 24) & 0xFF) as u8)
|
||||
} else if let Some(template) =
|
||||
self.kernel.prop_forge().get_template(obj.shape_id())
|
||||
{
|
||||
let new_ext =
|
||||
(obj.shape_id() & 0x00FF_FFFF) | ((template.offset as u32) << 24);
|
||||
self.bytecode[self.pc - 1] = new_ext;
|
||||
obj.get_prop(template.offset)
|
||||
} else if let Some(val) = self.resolve_property(obj, prop_name_si) {
|
||||
let offset = self
|
||||
.kernel
|
||||
.shape_forge()
|
||||
.lookup_offset(obj.shape_id(), prop_name_si)
|
||||
.unwrap_or(0);
|
||||
let new_ext = (obj.shape_id() & 0x00FF_FFFF) | ((offset as u32) << 24);
|
||||
self.bytecode[self.pc - 1] = new_ext;
|
||||
val
|
||||
} else {
|
||||
JsValue::undefined()
|
||||
};
|
||||
|
||||
let ln = coercion::to_number(prop_val, self.kernel.string_forge().as_ref());
|
||||
let rn = coercion::to_number(self.regs[a], self.kernel.string_forge().as_ref());
|
||||
let new_val = JsValue::float(ln % rn);
|
||||
self.regs[a] = new_val;
|
||||
self.set_member_prop(obj, prop_name_si, new_val)?;
|
||||
}
|
||||
|
||||
OpCode::COMPOUND_MEMBER_EXP => {
|
||||
let obj_ptr = self.regs[rd].as_object_ptr() as *mut JsObject;
|
||||
if obj_ptr.is_null() {
|
||||
return Err("COMPOUND_MEMBER_EXP on non-object".into());
|
||||
}
|
||||
let obj = unsafe { &mut *obj_ptr };
|
||||
let prop_name_si = self.regs[b].as_string_index();
|
||||
let ext = self.bytecode[self.pc];
|
||||
self.pc += 1;
|
||||
let cached_shape_id = ext & 0x00FF_FFFF;
|
||||
|
||||
let prop_val = if cached_shape_id != 0 && cached_shape_id == obj.shape_id() {
|
||||
obj.get_prop(((ext >> 24) & 0xFF) as u8)
|
||||
} else if let Some(template) =
|
||||
self.kernel.prop_forge().get_template(obj.shape_id())
|
||||
{
|
||||
let new_ext =
|
||||
(obj.shape_id() & 0x00FF_FFFF) | ((template.offset as u32) << 24);
|
||||
self.bytecode[self.pc - 1] = new_ext;
|
||||
obj.get_prop(template.offset)
|
||||
} else if let Some(val) = self.resolve_property(obj, prop_name_si) {
|
||||
let offset = self
|
||||
.kernel
|
||||
.shape_forge()
|
||||
.lookup_offset(obj.shape_id(), prop_name_si)
|
||||
.unwrap_or(0);
|
||||
let new_ext = (obj.shape_id() & 0x00FF_FFFF) | ((offset as u32) << 24);
|
||||
self.bytecode[self.pc - 1] = new_ext;
|
||||
val
|
||||
} else {
|
||||
JsValue::undefined()
|
||||
};
|
||||
|
||||
let ln = coercion::to_number(prop_val, self.kernel.string_forge().as_ref());
|
||||
let rn = coercion::to_number(self.regs[a], self.kernel.string_forge().as_ref());
|
||||
let new_val = JsValue::float(ln.powf(rn));
|
||||
self.regs[a] = new_val;
|
||||
self.set_member_prop(obj, prop_name_si, new_val)?;
|
||||
}
|
||||
|
||||
OpCode::FOR_IN_INIT => {
|
||||
let obj_val = self.regs[a];
|
||||
if !obj_val.is_object() {
|
||||
|
||||
@@ -42,3 +42,78 @@ fn eval_object_property_read() {
|
||||
fn eval_object_missing_property() {
|
||||
assert_eq!(eval("({a:1}).b"), "undefined");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_member_inc_post() {
|
||||
assert_eq!(eval("var obj={x:1}; obj.x++; obj.x"), "2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_member_inc_post_expr() {
|
||||
assert_eq!(eval("var obj={x:1}; obj.x++"), "2");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_member_dec() {
|
||||
assert_eq!(eval("var obj={x:5}; obj.x--; obj.x"), "4");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_member_dec_expr() {
|
||||
assert_eq!(eval("var obj={x:5}; obj.x--"), "4");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_member_inc_pre() {
|
||||
assert_eq!(eval("var obj={x:5}; ++obj.x; obj.x"), "6");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_dyn_member_inc() {
|
||||
assert_eq!(eval("var obj={a:3}; var k='a'; obj[k]++"), "4");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_dyn_member_inc_var() {
|
||||
assert_eq!(eval("var obj={a:3}; var k='a'; obj[k]++; obj.a"), "4");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_compound_member_add() {
|
||||
assert_eq!(eval("var obj={x:5}; obj.x+=3; obj.x"), "8");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_compound_member_sub() {
|
||||
assert_eq!(eval("var obj={x:10}; obj.x-=2; obj.x"), "8");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_compound_member_mul() {
|
||||
assert_eq!(eval("var obj={x:2}; obj.x*=3; obj.x"), "6");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_compound_member_div() {
|
||||
assert_eq!(eval("var obj={x:10}; obj.x/=2; obj.x"), "5");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_compound_member_mod() {
|
||||
assert_eq!(eval("var obj={x:7}; obj.x%=3; obj.x"), "1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_compound_member_exp() {
|
||||
assert_eq!(eval("var obj={x:2}; obj.x**=3; obj.x"), "8");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_compound_member_expr_val() {
|
||||
assert_eq!(eval("var obj={x:5}; var y=obj.x+=3; y"), "8");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn eval_member_multi_inc() {
|
||||
assert_eq!(eval("var obj={}; obj.x=0; obj.x++; obj.x++; obj.x"), "2");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user