use oxide_bytecode::opcode::OpCode; use oxide_types::object::JsObject; use oxide_types::value::JsValue; use crate::coercion; use crate::vm::Vm; impl Vm { pub(crate) fn dispatch_member_op(&mut self, op: OpCode, rd: usize, a: usize, b: usize) -> Result<(), String> { match op { OpCode::MEMBER_INC => self.dispatch_member_inc(rd, a, b), OpCode::MEMBER_DEC => self.dispatch_member_dec(rd, a, b), OpCode::DYN_MEMBER_INC => self.dispatch_dyn_member_inc(rd, a, b), OpCode::DYN_MEMBER_DEC => self.dispatch_dyn_member_dec(rd, a, b), OpCode::COMPOUND_MEMBER_ADD => self.dispatch_compound_member_add(rd, a, b), OpCode::COMPOUND_MEMBER_SUB => { self.dispatch_compound_member_numeric(rd, a, b, "COMPOUND_MEMBER_SUB on non-object", |l, r| l - r) } OpCode::COMPOUND_MEMBER_MUL => { self.dispatch_compound_member_numeric(rd, a, b, "COMPOUND_MEMBER_MUL on non-object", |l, r| l * r) } OpCode::COMPOUND_MEMBER_DIV => { self.dispatch_compound_member_numeric(rd, a, b, "COMPOUND_MEMBER_DIV on non-object", |l, r| l / r) } OpCode::COMPOUND_MEMBER_MOD => { self.dispatch_compound_member_numeric(rd, a, b, "COMPOUND_MEMBER_MOD on non-object", |l, r| l % r) } OpCode::COMPOUND_MEMBER_EXP => { self.dispatch_compound_member_numeric(rd, a, b, "COMPOUND_MEMBER_EXP on non-object", |l, r| l.powf(r)) } _ => unreachable!("non-member opcode passed to dispatch_member_op"), } } fn member_target(&mut self, rd: usize, b: usize, error_msg: &str) -> Result<(*mut JsObject, u32), String> { let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], error_msg)? else { return Err(String::new()); }; let prop_name_si = self.property_key_si(self.regs[b]); Ok((obj_ptr, prop_name_si)) } fn dispatch_member_inc(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> { let Ok((obj_ptr, prop_name_si)) = self.member_target(rd, b, "MEMBER_INC on non-object") else { return Ok(()); }; let receiver = self.regs[rd]; let obj = unsafe { &mut *obj_ptr }; let prop_val = self.read_member_prop(obj, prop_name_si, receiver)?; let n = self.coerce_number_bounded(prop_val)?; let new_val = JsValue::float(n + 1.0); self.set_member_prop(obj, prop_name_si, new_val, receiver)?; self.regs[a] = new_val; Ok(()) } fn dispatch_member_dec(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> { let Ok((obj_ptr, prop_name_si)) = self.member_target(rd, b, "MEMBER_DEC on non-object") else { return Ok(()); }; let receiver = self.regs[rd]; let obj = unsafe { &mut *obj_ptr }; let prop_val = self.read_member_prop(obj, prop_name_si, receiver)?; let n = self.coerce_number_bounded(prop_val)?; let new_val = JsValue::float(n - 1.0); self.set_member_prop(obj, prop_name_si, new_val, receiver)?; self.regs[a] = new_val; Ok(()) } fn dispatch_dyn_member_inc(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> { let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "DYN_MEMBER_INC on non-object")? else { return Ok(()); }; let receiver = self.regs[rd]; let obj = unsafe { &mut *obj_ptr }; let prop_name_si = self.property_key_si(self.regs[a]); let prop_val = self.ordinary_get(obj, prop_name_si, receiver)?; let n = self.coerce_number_bounded(prop_val)?; let new_val = JsValue::float(n + 1.0); self.ordinary_set(obj, prop_name_si, new_val, receiver)?; self.regs[b] = new_val; Ok(()) } fn dispatch_dyn_member_dec(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> { let Some(obj_ptr) = self.checked_object_ptr(self.regs[rd], "DYN_MEMBER_DEC on non-object")? else { return Ok(()); }; let receiver = self.regs[rd]; let obj = unsafe { &mut *obj_ptr }; let prop_name_si = self.property_key_si(self.regs[a]); let prop_val = self.ordinary_get(obj, prop_name_si, receiver)?; let n = self.coerce_number_bounded(prop_val)?; let new_val = JsValue::float(n - 1.0); self.ordinary_set(obj, prop_name_si, new_val, receiver)?; self.regs[b] = new_val; Ok(()) } fn dispatch_compound_member_add(&mut self, rd: usize, a: usize, b: usize) -> Result<(), String> { let Ok((obj_ptr, prop_name_si)) = self.member_target(rd, b, "COMPOUND_MEMBER_ADD on non-object") else { return Ok(()); }; let receiver = self.regs[rd]; let prop_raw = { let obj = unsafe { &mut *obj_ptr }; self.read_member_prop(obj, prop_name_si, receiver)? }; let prop_val = self.coerce_primitive_bounded(prop_raw, false)?; let rhs = self.coerce_primitive_bounded(self.regs[a], false)?; let new_val = if prop_val.is_string() || rhs.is_string() { let ls = coercion::to_string(self.kernel_core.string_forge().as_ref(), prop_val); let rs = coercion::to_string(self.kernel_core.string_forge().as_ref(), rhs); self.intern(&format!("{ls}{rs}")) } else { let ln = coercion::to_number(prop_val, self.kernel_core.string_forge().as_ref()); let rn = coercion::to_number(rhs, self.kernel_core.string_forge().as_ref()); JsValue::float(ln + rn) }; let obj = unsafe { &mut *obj_ptr }; self.set_member_prop(obj, prop_name_si, new_val, receiver)?; self.regs[a] = new_val; Ok(()) } fn dispatch_compound_member_numeric( &mut self, rd: usize, a: usize, b: usize, error_msg: &str, op: F, ) -> Result<(), String> where F: FnOnce(f64, f64) -> f64, { let Ok((obj_ptr, prop_name_si)) = self.member_target(rd, b, error_msg) else { return Ok(()); }; let receiver = self.regs[rd]; let ln = { let obj = unsafe { &mut *obj_ptr }; let prop_val = self.read_member_prop(obj, prop_name_si, receiver)?; self.coerce_number_bounded(prop_val)? }; let rn = self.coerce_number_bounded(self.regs[a])?; let new_val = JsValue::float(op(ln, rn)); self.regs[a] = new_val; let obj = unsafe { &mut *obj_ptr }; self.set_member_prop(obj, prop_name_si, new_val, receiver)?; Ok(()) } }