use super::value::{Number, int_binop};
use super::{Code, Halt, R, Stop, Vm};
use crate::abend::Abend;
use crate::arith;
use crate::fixed::places_of;
use crate::lir::{ArithPlan, ArithStep, Expr, ExprId, Mode, Operand, Step};
use crate::storage::{Loc, Val};
use crate::store;
use crate::unit::Loader;
use crate::vocab::{BinOp, Pos};
use std::rc::Rc;
type Own = Option<(BinOp, bool, ExprId)>;
type Evaluated<'s> = (&'s ArithStep, ExprId, Own, Result<Val, Abend>);
impl<L: Loader<Rc<Code>>> Vm<'_, '_, '_, L> {
pub(super) fn arith(&mut self, plan: &ArithPlan, pos: Pos) -> R<Step> {
for &q in &plan.prepass {
self.loc(q)?;
}
if let [step] = plan.steps.as_slice() {
let evaluated = self.evaluated(plan, step, pos)?;
return self.stored(plan, [evaluated], pos);
}
let mut results = Vec::with_capacity(plan.steps.len());
for step in &plan.steps {
results.push(self.evaluated(plan, step, pos)?);
}
self.stored(plan, results, pos)
}
fn evaluated<'s>(&mut self, plan: &ArithPlan, step: &'s ArithStep, pos: Pos) -> R<Evaluated<'s>> {
for &q in &step.probe {
self.loc(q)?;
}
let (shared, own) = self.shared(plan, step);
let outcome = match step.mode {
Mode::Float(p) => self.eval_float(shared, p, pos).map(Val::Float),
Mode::Fixed => {
let last = if own.is_some() { plan.inner_dmax } else { plan.dmax };
self.eval_fixed_at(shared, last, plan.inner_dmax, pos).map(Val::Num)
}
};
let outcome = match outcome.map_err(Stop::halt) {
Err(Halt::Unimplemented(what)) => return Err(Halt::Unimplemented(what).into()),
Err(Halt::Abend(a)) => Err(a),
Ok(v) => Ok(v),
};
Ok((step, shared, own, outcome))
}
fn stored<'s>(&mut self, plan: &ArithPlan, results: impl IntoIterator<Item = Evaluated<'s>>, pos: Pos) -> R<Step> {
let operands = match &plan.remainder {
Some(r) => Some((self.eval_fixed(r.dividend, plan.dmax, pos)?, self.eval_fixed(r.divisor, plan.dmax, pos)?)),
None => None,
};
let mut size_error = false;
let mut quotient: Option<Loc> = None;
for (step, shared, own, outcome) in results {
let loc = self.loc(step.target)?;
quotient.get_or_insert(loc);
let outcome = match (own, outcome) {
(Some((op, receiver_first, receiver)), Ok(Val::Num(value))) => {
let current = self.operand_number(Operand::Load(step.target), plan.dmax, pos)?;
let value = Number::of(value);
let (x, y) = if receiver_first { (current, value) } else { (value, current) };
match int_binop(x, op, y, plan.dmax, plan.arith) {
Some(r) => Ok(Val::Num(r.fixed())),
None => {
let (x, y) = (x.fixed(), y.fixed());
if arith::divides_by_zero(op, &y) {
let binary = self.binary_division(receiver, shared)?;
Err(arith::zero_divide(binary, pos))
} else {
arith::fixed_binop(x, op, y, plan.dmax, plan.arith, pos).map(Val::Num)
}
}
}
}
(Some((op, receiver_first, _)), Ok(Val::Float(value))) => {
let p = plan.arith.float_intermediate();
let current = self.value(Operand::Load(step.target))?;
let current = arith::float_operand(current, p, pos)?;
let (x, y) = if receiver_first { (current, value) } else { (value, current) };
arith::float_binop(x, op, y, p, pos).map(Val::Float)
}
(_, outcome) => outcome,
};
let Some(value) = arith::size_error(outcome, plan.handled)? else {
size_error = true;
continue;
};
size_error |= store::store_value(&self.facts(), self.unit, loc, value, step.rounded, plan.handled, pos)?;
}
if let (Some(r), Some((x, y)), Some(q)) = (&plan.remainder, operands, quotient)
&& let Some(rest) = arith::remainder(x, y, places_of(q.kind).dec, plan.dmax, plan.arith, pos)?
{
let loc = self.loc(r.target)?;
size_error |= store::store_value(&self.facts(), self.unit, loc, Val::Num(rest), false, plan.handled, pos)?;
}
Ok(if plan.handled { Step::Arm(u8::from(size_error)) } else { Step::Next })
}
fn shared(&self, plan: &ArithPlan, step: &ArithStep) -> (ExprId, Own) {
let own = |e: ExprId| plan.per_receiver && self.p.exprs[e as usize] == Expr::Operand(Operand::Load(step.target));
match self.p.exprs[step.expr as usize] {
Expr::Bin(a, op, b) if own(a) => (b, Some((op, true, a))),
Expr::Bin(a, op, b) if own(b) => (a, Some((op, false, b))),
_ => (step.expr, None),
}
}
}