use super::{Code, Facts, R, Vm, not_yet};
use crate::abend::Abend;
use crate::accept;
use crate::display;
use crate::host::{Host, Values};
use crate::lir::{DisplayItem, InitPlan, Inspected, MovePlan, NumericFrom, Op, Operand, PlaceId, SearchAllPlan, Step, StorePlan};
use crate::set;
use crate::storage::{Kind, Loc, Val};
use crate::store;
use crate::text::{self, UnstringField};
use crate::unit::Loader;
use crate::vocab::{Figurative, Pos};
use numeric::precision::Fixed;
use std::cmp::Ordering;
use std::rc::Rc;
fn sender_kept(sender: Kind, dest: Kind, plan: &MovePlan) -> bool {
if !matches!(dest, Kind::Zoned { .. } | Kind::Packed { .. } | Kind::Binary { .. }) {
return true;
}
match (sender, plan) {
(Kind::Group, MovePlan::Numeric { .. }) => false,
(Kind::NumericEdited { .. }, MovePlan::Numeric { from: NumericFrom::Zoned, .. }) => false,
(Kind::Packed { .. }, MovePlan::Numeric { from: NumericFrom::Value, .. }) => sender != dest,
_ => true,
}
}
fn field_kind(plan: &MovePlan) -> Option<Kind> {
Some(match *plan {
MovePlan::Alnum { justified, .. } => Kind::Alnum { justified },
MovePlan::AlnumEdited { edit, .. } => Kind::AlnumEdited { edit },
MovePlan::National(_) => Kind::National,
MovePlan::Float { precision, .. } => Kind::Float(precision),
MovePlan::Address => Kind::Pointer,
MovePlan::Index => Kind::Index,
MovePlan::Numeric { store, .. } => match store {
StorePlan::Zoned { digits, scale, signed, sign } => Kind::Zoned { digits, scale, signed, sign },
StorePlan::Packed { digits, scale, signed } => Kind::Packed { digits, scale, signed },
StorePlan::Binary { digits, scale, signed, native, .. } => Kind::Binary { digits, scale, signed, native },
StorePlan::NumericEdited { edit, digits, scale, blank_when_zero } => Kind::NumericEdited { edit, digits, scale, blank_when_zero },
StorePlan::Float(p) => Kind::Float(p),
StorePlan::Index => Kind::Index,
StorePlan::Refused(_) => return None,
},
MovePlan::Refused(_) => return None,
})
}
impl<'p, L: Loader<Rc<Code>>> Vm<'p, '_, '_, L> {
pub(super) fn op(&mut self, op: &Op, at: u32) -> R<Step> {
let p = self.p;
let pos = self.pos(at);
match op {
Op::Move { from, to, plan } | Op::Set { from, to, plan } => {
let dest = self.loc(*to)?;
let (val, src) = match (op, from) {
(Op::Move { .. }, Operand::Load(p)) => {
let src = self.loc(*p)?;
(store::move_sender(&self.facts(), &self.unit.mem, src, dest, self.pos(self.p.places[*p as usize].at))?, Some(src))
}
_ => self.value_with_loc(*from)?,
};
if let MovePlan::Refused(abend) = plan {
return Err(self.abend(*abend, Some(at)).into());
}
let src = src.filter(|s| sender_kept(s.kind, dest.kind, plan));
store::assign(&self.facts(), self.unit, dest, val, src, pos)?;
}
Op::Initialize { target, plan } => self.initialize(*target, &p.plans.init[*plan as usize], pos)?,
Op::Arith(id) => return self.arith(&p.plans.arith[*id as usize], pos),
Op::SetAddress { records, address } => {
let val = self.value(*address)?;
let offset = set::address(val, self.unit.mem.len(), pos)?;
for &r in records {
self.linkage[r as usize] = offset;
}
}
Op::SetUpDown { by, down, targets } => {
let by = self.int(by, pos)?;
let places: Vec<PlaceId> = targets.iter().map(|(place, _)| *place).collect();
let result = set::up_down(self, by, *down, &places, pos);
self.settle(result)?;
}
Op::Step { var, by, plan, prepass } => {
let dest = self.loc(*var)?;
for &q in prepass {
self.loc(q)?;
}
let current = self.value(Operand::Load(*var))?;
let x = crate::arith::fixed_operand(current, plan.dmax, pos)?;
let y = self.eval_fixed(*by, plan.dmax, pos)?;
let next = crate::arith::fixed_binop(x, crate::vocab::BinOp::Add, y, plan.dmax, p.options.options.arith, pos)?;
store::store_fixed(&self.facts(), self.unit, dest, &next, false, pos)?;
}
Op::SetInt { target, value } => {
let n = self.int(value, pos)?;
let dest = self.loc(*target)?;
store::set_integer(&self.facts(), self.unit, dest, n, pos)?;
}
Op::Inspect(id) => self.inspect(*id, pos)?,
Op::String(id) => {
let plan = &p.plans.string[*id as usize];
let result = text::string(self, plan.into, plan.pointer.map(|(q, _)| q), &plan.sources, pos);
return Ok(Step::Arm(u8::from(self.settle(result)?)));
}
Op::Unstring(id) => {
let plan = &p.plans.unstring[*id as usize];
let into: Vec<UnstringField<PlaceId>> =
plan.into.iter().map(|i| UnstringField { target: i.target, delimiter: i.delimiter.map(|d| d.target), count: i.count.map(|(q, _)| q) }).collect();
let result = text::unstring(self, plan.source, plan.pointer.map(|(q, _)| q), &plan.delimiters, &into, plan.tallying.map(|(q, _)| q), pos);
return Ok(Step::Arm(u8::from(self.settle(result)?)));
}
Op::SearchAll(id) => return self.search_all(&p.plans.search_all[*id as usize], pos),
Op::Nest => self.unit.enter(pos)?,
Op::Unnest(n) => self.unit.depth = self.unit.depth.saturating_sub(usize::from(*n)),
Op::SetTemp(t, value) => {
let n = self.int(value, pos)?.max(0);
let Some(frame) = self.returns.frames.last_mut() else { return Err(not_yet("a TIMES counter outside a frame")) };
let t = usize::from(*t);
if frame.temps.len() <= t {
frame.temps.resize(t + 1, 0);
}
frame.temps[t] = n;
}
Op::DecTemp(t) => {
if let Some(n) = self.returns.frames.last_mut().and_then(|f| f.temps.get_mut(usize::from(*t))) {
*n -= 1;
}
}
Op::Display(id) => self.display(*id, pos)?,
Op::Accept { target, from, .. } => {
let dest = self.loc(*target)?;
let name = self.sym(p.places[*target as usize].name);
accept::accept(&self.facts(), self.unit, dest, *from, name, pos)?;
}
Op::File(_) => return Err(not_yet("file I/O")),
Op::Call(id) => return self.call(&p.services.calls[*id as usize], pos),
Op::Cancel(name) => self.cancel(*name, pos)?,
Op::Sort(_) | Op::Release(_) | Op::Return(_) => return Err(not_yet("SORT and MERGE")),
Op::Report(_) => return Err(not_yet("Report Writer")),
Op::Invoke(_) => return Err(not_yet("object-oriented COBOL")),
Op::Cics(_) => return Err(not_yet("EXEC CICS")),
Op::Sql(_) => return Err(not_yet("EXEC SQL")),
Op::Markup(_) => return Err(not_yet("JSON and XML statements")),
Op::Alter { para, to } => {
let paragraphs = p.paragraphs.len();
let altered = &mut self.unit.programs[self.me].altered;
altered.resize(paragraphs, None);
altered[*para as usize] = Some(*to as usize);
}
Op::EnterSegment(priority) => self.enter_segment(*priority),
Op::DebugLine(line) => self.line = *line,
Op::DebugAlter { range, name, contents } => {
if self.debugging {
return Ok(Step::Next);
}
return Ok(self.run_debugging(*range, self.sym(*name), pos.line, self.sym(*contents), pos)?.unwrap_or(Step::Next));
}
}
Ok(Step::Next)
}
pub(super) fn enter_segment(&mut self, priority: u8) {
if priority == self.segment {
return;
}
self.segment = priority;
if priority >= 50 {
let p = self.p;
for (i, target) in self.unit.programs[self.me].altered.iter_mut().enumerate() {
if p.paragraphs[i].priority == priority {
*target = None;
}
}
}
}
fn initialize(&mut self, target: PlaceId, plan: &InitPlan, pos: Pos) -> R<()> {
let loc = self.loc(target)?;
for field in &plan.fields {
let Some(kind) = field_kind(&field.store) else { return Err(not_yet("an INITIALIZE field with no MOVE plan")) };
let at = Loc { offset: loc.offset + field.offset as usize, len: field.len as usize, kind, item: usize::MAX };
let val = match field.value {
Figurative::Null => Val::Address(0),
other => Val::Fig(other),
};
store::assign(&self.facts(), self.unit, at, val, None, pos)?;
}
Ok(())
}
fn inspect(&mut self, id: u32, pos: Pos) -> R<()> {
let plan = &self.p.plans.inspect[id as usize];
let phrase = |p: &crate::lir::InspectPhrase| text::InspectPhrase {
mode: p.mode,
pattern: p.pattern.clone(),
by: p.by.clone(),
counter: p.counter.map(|(q, _)| q),
bounds: p.bounds.clone(),
};
let tallying: Vec<_> = plan.tallying.iter().map(phrase).collect();
let result = match &plan.target {
Inspected::Item(target) => {
let replacing: Vec<_> = plan.replacing.iter().map(phrase).collect();
text::inspect(self, *target, &tallying, &replacing, plan.converting.as_ref(), pos)
}
Inspected::Value(subject) => text::tally(self, subject, &tallying, pos),
};
self.settle(result)
}
fn display(&mut self, id: u32, pos: Pos) -> R<()> {
let p = self.p;
let plan = &p.plans.display[id as usize];
let mut shown = String::new();
for item in &plan.items {
shown.push_str(&match item {
DisplayItem::Bytes(place) | DisplayItem::National(place) | DisplayItem::Digits { place, .. } | DisplayItem::Refused { place, .. } => {
let loc = self.loc(*place)?;
display::place(&self.facts(), &self.unit.mem, loc, self.pos(p.places[*place as usize].at))?
}
DisplayItem::Text(text) => self.sym(*text).to_owned(),
DisplayItem::Value(o) => {
let val = self.value(*o)?;
display::value(&self.facts(), val, pos)?
}
});
}
if self.unit.observed() {
self.sink("log", pos, &shown);
}
Ok(display::write(&mut *self.unit.out, &shown, plan.no_advancing, pos)?)
}
fn search_all(&mut self, plan: &SearchAllPlan, pos: Pos) -> R<Step> {
let count = i64::from(self.count(&plan.count, pos)?);
let (mut low, mut high) = (1i64, count);
while low <= high {
let mid = (low + high) / 2;
let dest = self.loc(plan.index)?;
store::set_integer(&self.facts(), self.unit, dest, mid, pos)?;
let mut order = Ordering::Equal;
for key in &plan.keys {
let o = self.compare(&key.key, &key.value, key.how, pos)?;
order = if key.ascending { o } else { o.reverse() };
if order != Ordering::Equal {
break;
}
}
match order {
Ordering::Less => low = mid + 1,
Ordering::Greater => high = mid - 1,
Ordering::Equal => return Ok(Step::Arm(0)),
}
}
Ok(Step::Arm(1))
}
}
impl<'p, L: Loader<Rc<Code>>> Host<PlaceId> for Vm<'p, '_, '_, L> {
type Facts = Facts<'p>;
fn facts(&self) -> Facts<'p> {
Vm::facts(self)
}
fn mem(&mut self) -> &mut [u8] {
&mut self.unit.mem
}
fn locate(&mut self, place: PlaceId, _receiving: bool) -> Result<Loc, Abend> {
let pos = self.pos(self.p.places[place as usize].at);
let loc = self.loc(place);
self.lift(loc, pos)
}
fn integer(&mut self, place: PlaceId, pos: Pos) -> Result<i64, Abend> {
let n = self.int_place(place, pos);
self.lift(n, pos)
}
fn assign(&mut self, dest: Loc, val: Val, src: Option<Loc>, pos: Pos) -> Result<(), Abend> {
store::assign(&Vm::facts(self), self.unit, dest, val, src, pos)
}
fn store_fixed(&mut self, dest: Loc, value: &Fixed, pos: Pos) -> Result<(), Abend> {
store::store_fixed(&Vm::facts(self), self.unit, dest, value, false, pos)
}
}
impl<L: Loader<Rc<Code>>> Values<PlaceId, Operand> for Vm<'_, '_, '_, L> {
fn value(&mut self, operand: &Operand, pos: Pos) -> Result<Val, Abend> {
let val = Vm::value(self, *operand);
self.lift(val, pos)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::lir::Image;
#[test]
fn only_varying_from_moves_without_the_sender_s_storage() {
let zoned = Kind::Zoned { digits: 3, scale: 0, signed: false, sign: None };
let packed = Kind::Packed { digits: 5, scale: 0, signed: true };
let store = StorePlan::Packed { digits: 5, scale: 0, signed: true };
let bytes = MovePlan::Alnum { image: Image::Bytes, justified: false };
assert!(sender_kept(Kind::Group, packed, &bytes));
assert!(!sender_kept(Kind::Group, packed, &MovePlan::Numeric { from: NumericFrom::Zoned, store }));
assert!(sender_kept(packed, packed, &MovePlan::Numeric { from: NumericFrom::PackedCopy, store }));
assert!(!sender_kept(packed, packed, &MovePlan::Numeric { from: NumericFrom::Value, store }));
assert!(sender_kept(zoned, packed, &MovePlan::Numeric { from: NumericFrom::Value, store }));
assert!(sender_kept(Kind::Group, Kind::Alnum { justified: false }, &bytes));
}
}