318 lines
10 KiB
Rust
318 lines
10 KiB
Rust
use std::cmp::Ordering;
|
|
|
|
use gc_arena::{Gc, Mutation};
|
|
|
|
use crate::value::*;
|
|
use crate::{BytecodeReader, NixNum, Step, VmError, VmRuntimeCtx, VmRuntimeCtxExt as _};
|
|
|
|
impl<'gc> crate::Vm<'gc> {
|
|
#[inline(always)]
|
|
pub(crate) fn op_add(
|
|
&mut self,
|
|
ctx: &mut impl VmRuntimeCtx,
|
|
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(lhs), get_num(rhs)) {
|
|
(_, 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 VmRuntimeCtx,
|
|
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 VmRuntimeCtx,
|
|
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 VmRuntimeCtx,
|
|
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 VmRuntimeCtx,
|
|
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 VmRuntimeCtx,
|
|
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 VmRuntimeCtx,
|
|
reader: &mut BytecodeReader<'_>,
|
|
mc: &Mutation<'gc>,
|
|
) -> Step {
|
|
self.compare_values(ctx, reader, mc, Ordering::is_ge)
|
|
}
|
|
|
|
fn compare_values(
|
|
&mut self,
|
|
ctx: &impl VmRuntimeCtx,
|
|
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<List>, Gc<List>)>(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<AttrSet>, Gc<AttrSet>)>(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 VmRuntimeCtx,
|
|
lhs: StrictValue<'gc>,
|
|
rhs: StrictValue<'gc>,
|
|
) -> crate::VmResult<bool> {
|
|
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::<bool>(), rhs.as_inline::<bool>()) {
|
|
return Ok(a == b);
|
|
}
|
|
if lhs.is::<crate::Null>() && rhs.is::<crate::Null>() {
|
|
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::<crate::List>(), rhs.as_gc::<crate::List>()) {
|
|
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::<crate::AttrSet>(), rhs.as_gc::<crate::AttrSet>()) {
|
|
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 VmRuntimeCtx,
|
|
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)) = (ctx.get_string(lhs), ctx.get_string(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<NixNum> {
|
|
if let Some(i) = val.as_inline::<i32>() {
|
|
Some(NixNum::Int(i as i64))
|
|
} else if let Some(gc_i) = val.as_gc::<i64>() {
|
|
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<Value<'gc>> {
|
|
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")),
|
|
}
|
|
}
|