use std::cmp::Ordering; use gc_arena::{Gc, Mutation}; use crate::value::*; use crate::{BytecodeReader, NixNum, Step, VmContextExt, VmError}; impl<'gc> crate::Vm<'gc> { #[inline(always)] pub(crate) fn op_add( &mut self, ctx: &mut impl crate::VmContext, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, ) -> Step { let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?; if let (Some(ls), Some(rs)) = (ctx.get_string(lhs), ctx.get_string(rhs)) { let ns = Gc::new(mc, crate::NixString::new(format!("{ls}{rs}"))); self.push(Value::new_gc(ns)); return Step::Continue(()); } let res = numeric_binop(lhs, rhs, mc, i64::wrapping_add, |a, b| a + b); match res { Ok(val) => { self.push(val); Step::Continue(()) } Err(e) => self.finish_vm_err(e), } } #[inline(always)] pub(crate) fn op_sub(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step { self.op_arith(reader, mc, i64::wrapping_sub, |a, b| a - b) } #[inline(always)] pub(crate) fn op_mul(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step { self.op_arith(reader, mc, i64::wrapping_mul, |a, b| a * b) } #[inline(always)] fn op_arith( &mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, int_op: fn(i64, i64) -> i64, float_op: fn(f64, f64) -> f64, ) -> Step { let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?; let res = numeric_binop(lhs, rhs, mc, int_op, float_op); match res { Ok(val) => { self.push(val); Step::Continue(()) } Err(e) => self.finish_vm_err(e), } } #[inline(always)] pub(crate) fn op_div(&mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>) -> Step { let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?; match (get_num(rhs), get_num(lhs)) { (_, Some(NixNum::Int(0))) | (_, Some(NixNum::Float(0.))) => { return self.finish_vm_err(VmError::Uncatchable(fix_error::Error::eval_error( "division by zero", ))); } _ => {} } let res = numeric_binop(lhs, rhs, mc, |a, b| a / b, |a, b| a / b); match res { Ok(val) => { self.push(val); Step::Continue(()) } Err(e) => self.finish_vm_err(e), } } #[inline(always)] pub(crate) fn op_eq( &mut self, ctx: &mut impl crate::VmContext, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, ) -> Step { let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?; let eq = match self.values_equal(ctx, lhs, rhs) { Ok(eq) => eq, Err(e) => return self.finish_vm_err(e), }; self.push(Value::new_inline(eq)); Step::Continue(()) } #[inline(always)] pub(crate) fn op_neq( &mut self, ctx: &mut impl crate::VmContext, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, ) -> Step { let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?; let eq = match self.values_equal(ctx, lhs, rhs) { Ok(eq) => eq, Err(e) => return self.finish_vm_err(e), }; self.push(Value::new_inline(!eq)); Step::Continue(()) } #[inline(always)] pub(crate) fn op_lt( &mut self, ctx: &mut impl crate::VmContext, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, ) -> Step { self.compare_values(ctx, reader, mc, Ordering::is_lt) } #[inline(always)] pub(crate) fn op_gt( &mut self, ctx: &mut impl crate::VmContext, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, ) -> Step { self.compare_values(ctx, reader, mc, Ordering::is_gt) } #[inline(always)] pub(crate) fn op_leq( &mut self, ctx: &mut impl crate::VmContext, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, ) -> Step { self.compare_values(ctx, reader, mc, Ordering::is_le) } #[inline(always)] pub(crate) fn op_geq( &mut self, ctx: &mut impl crate::VmContext, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, ) -> Step { self.compare_values(ctx, reader, mc, Ordering::is_ge) } fn compare_values( &mut self, ctx: &impl crate::VmContext, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, pred: fn(Ordering) -> bool, ) -> Step { let (lhs, rhs) = self.try_force::<(StrictValue, StrictValue)>(reader, mc)?; match self.compare_values_inner(ctx, pred, lhs, rhs) { Ok(()) => Step::Continue(()), Err(e) => self.finish_vm_err(e), } } #[inline(always)] pub(crate) fn op_concat( &mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, ) -> Step { let (l, r) = self.try_force::<(Gc, Gc)>(reader, mc)?; let mut items = smallvec::SmallVec::new(); items.extend_from_slice(&l); items.extend_from_slice(&r); self.push(Value::new_gc(Gc::new(mc, crate::List { inner: items }))); Step::Continue(()) } #[inline(always)] pub(crate) fn op_update( &mut self, reader: &mut BytecodeReader<'_>, mc: &Mutation<'gc>, ) -> Step { let (l, r) = self.try_force::<(Gc, Gc)>(reader, mc)?; self.push(Value::new_gc(l.merge(&r, mc))); Step::Continue(()) } #[inline(always)] pub(crate) fn op_neg(&mut self) -> Step { todo!("implement unary operation"); } #[inline(always)] pub(crate) fn op_not(&mut self) -> Step { todo!("implement unary operation"); } pub(crate) fn values_equal( &mut self, ctx: &impl crate::VmContext, lhs: StrictValue<'gc>, rhs: StrictValue<'gc>, ) -> crate::VmResult { if let (Some(a), Some(b)) = (get_num(lhs), get_num(rhs)) { return Ok(match (a, b) { (NixNum::Int(a), NixNum::Int(b)) => a == b, (NixNum::Float(a), NixNum::Float(b)) => a == b, (NixNum::Int(a), NixNum::Float(b)) => a as f64 == b, (NixNum::Float(a), NixNum::Int(b)) => a == b as f64, }); } if let (Some(a), Some(b)) = (lhs.as_inline::(), rhs.as_inline::()) { return Ok(a == b); } if lhs.is::() && rhs.is::() { return Ok(true); } if let (Some(a), Some(b)) = (ctx.get_string(lhs), ctx.get_string(rhs)) { return Ok(a == b); } if let (Some(a), Some(b)) = (lhs.as_gc::(), rhs.as_gc::()) { if a.inner.len() != b.inner.len() { return Ok(false); } for (x, y) in a.inner.iter().zip(b.inner.iter()) { let lx = x.restrict().expect("forced"); let ly = y.restrict().expect("forced"); if !self.values_equal(ctx, lx, ly)? { return Ok(false); } } return Ok(true); } if let (Some(a), Some(b)) = (lhs.as_gc::(), rhs.as_gc::()) { if a.len() != b.len() { return Ok(false); } for ((k1, v1), (k2, v2)) in a.iter().zip(b.iter()) { if k1 != k2 { return Ok(false); } let lv1 = v1.restrict().expect("forced"); let lv2 = v2.restrict().expect("forced"); if !self.values_equal(ctx, lv1, lv2)? { return Ok(false); } } return Ok(true); } Ok(false) } fn compare_values_inner( &mut self, ctx: &impl crate::VmContext, pred: fn(Ordering) -> bool, lhs: StrictValue<'gc>, rhs: StrictValue<'gc>, ) -> crate::VmResult<()> { if let (Some(a), Some(b)) = (get_num(lhs), get_num(rhs)) { let ord = match (a, b) { (NixNum::Int(a), NixNum::Int(b)) => a.cmp(&b), (NixNum::Float(a), NixNum::Float(b)) => a.partial_cmp(&b).unwrap_or(Ordering::Less), (NixNum::Int(a), NixNum::Float(b)) => { (a as f64).partial_cmp(&b).unwrap_or(Ordering::Less) } (NixNum::Float(a), NixNum::Int(b)) => { a.partial_cmp(&(b as f64)).unwrap_or(Ordering::Less) } }; self.push(Value::new_inline(pred(ord))); return Ok(()); } if let (Some(a), Some(b)) = ( VmContextExt::get_string(ctx, lhs), VmContextExt::get_string(ctx, rhs), ) { self.push(Value::new_inline(pred(a.cmp(b)))); return Ok(()); } // TODO: compare other types Err(crate::vm_err("cannot compare these types")) } } pub(crate) fn get_num(val: StrictValue<'_>) -> Option { if let Some(i) = val.as_inline::() { Some(NixNum::Int(i as i64)) } else if let Some(gc_i) = val.as_gc::() { Some(NixNum::Int(*gc_i)) } else { val.as_float().map(NixNum::Float) } } #[inline] fn numeric_binop<'gc>( lhs: StrictValue<'gc>, rhs: StrictValue<'gc>, mc: &Mutation<'gc>, int_op: fn(i64, i64) -> i64, float_op: fn(f64, f64) -> f64, ) -> crate::VmResult> { match (get_num(lhs), get_num(rhs)) { (Some(NixNum::Int(a)), Some(NixNum::Int(b))) => Ok(Value::make_int(int_op(a, b), mc)), (Some(NixNum::Float(a)), Some(NixNum::Float(b))) => Ok(Value::new_float(float_op(a, b))), (Some(NixNum::Int(a)), Some(NixNum::Float(b))) => { Ok(Value::new_float(float_op(a as f64, b))) } (Some(NixNum::Float(a)), Some(NixNum::Int(b))) => { Ok(Value::new_float(float_op(a, b as f64))) } _ => Err(crate::vm_err("cannot perform arithmetic on non-numbers")), } }