mod arith;
mod call;
mod cond;
mod flow;
mod ops;
mod place;
mod value;
use crate::abend::{Abend, Ending};
use crate::lir::{AbendId, Block, Collating, DebugId, Frame, FrameKind, MovePlan, Op, PlaceId, Program, Returns, StorePlan, SymId, UpDown};
use crate::picture::Sym;
use crate::store::ProgramFacts;
use crate::unit::{Loader, RunUnit};
use crate::vocab::{Figurative, Pos};
use numeric::Options;
use std::collections::{BTreeMap, HashMap};
use std::rc::Rc;
use zarch::ebcdic::{self, CodePage, Collation};
pub(crate) use flow::Arrival;
type R<T> = Result<T, Halt>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Halt {
Abend(Abend),
Unimplemented(String),
}
impl From<Abend> for Halt {
fn from(a: Abend) -> Self {
Self::Abend(a)
}
}
fn not_yet(what: impl Into<String>) -> Halt {
Halt::Unimplemented(what.into())
}
pub struct Code {
lowered: Result<Lowered, String>,
entries: Vec<String>,
files: usize,
size: usize,
nested: Vec<String>,
}
struct Lowered {
program: Program,
collation: Collation,
ordinals: Vec<u8>,
high_value: u8,
low_value: u8,
entry_of: Vec<Option<u32>>,
receivers: HashMap<PlaceId, SymId>,
}
impl Code {
pub fn new(program: Result<Program, String>, entries: Vec<String>, files: usize, size: usize, nested: Vec<String>) -> Self {
Self { lowered: program.map(Lowered::new), entries, files, size, nested }
}
pub fn program(&self) -> Option<&Program> {
self.lowered.as_ref().ok().map(|l| &l.program)
}
pub fn entry(&self, name: &str) -> Option<usize> {
self.entries.iter().position(|e| e == name)
}
pub fn shape(&self) -> (usize, usize) {
(self.files, self.size)
}
}
impl Lowered {
fn new(program: Program) -> Self {
let (collation, ordinals, high_value, low_value) = match &program.options.collating {
Collating::Native => (Collation::Native, (0..=255).collect(), ebcdic::HIGH_VALUE, ebcdic::LOW_VALUE),
Collating::Sequence(s) => {
let weights = Box::new(std::array::from_fn(|b| u16::from(s.positions[b])));
(Collation::Weights(weights), s.characters.clone(), s.high_value, s.low_value)
}
};
let mut entry_of = vec![None; program.blocks.len()];
for (i, paragraph) in program.paragraphs.iter().enumerate() {
if let Some(slot) = entry_of.get_mut(paragraph.entry as usize) {
*slot = Some(i as u32);
}
}
let receivers = receivers(&program);
Self { program, collation, ordinals, high_value, low_value, entry_of, receivers }
}
}
fn receivers(p: &Program) -> HashMap<PlaceId, SymId> {
let mut names = HashMap::new();
let mut store = |place: PlaceId, plan: &StorePlan| {
if let StorePlan::Binary { name, .. } = plan {
names.entry(place).or_insert(*name);
}
};
let moved = |plan: &MovePlan| match plan {
MovePlan::Numeric { store, .. } => Some(*store),
_ => None,
};
for a in &p.plans.arith {
a.steps.iter().for_each(|s| store(s.target, &s.store));
a.remainder.iter().for_each(|r| store(r.target, &r.store));
}
for Block { ops, .. } in &p.blocks {
for op in ops {
match op {
Op::Move { to, plan, .. } | Op::Set { to, plan, .. } | Op::Accept { target: to, plan, .. } => moved(plan).iter().for_each(|s| store(*to, s)),
Op::Step { var, plan, .. } => store(*var, &plan.store),
Op::SetUpDown { targets, .. } => targets.iter().for_each(|(place, how)| {
if let UpDown::Number(plan) = how {
store(*place, &plan.store);
}
}),
_ => {}
}
}
}
for s in &p.plans.string {
s.pointer.iter().for_each(|(place, plan)| store(*place, plan));
}
for u in &p.plans.unstring {
u.pointer.iter().for_each(|(place, plan)| store(*place, plan));
u.tallying.iter().for_each(|(place, plan)| store(*place, &plan.store));
for into in &u.into {
moved(&into.plan).iter().for_each(|s| store(into.target, s));
into.count.iter().for_each(|(place, plan)| store(*place, plan));
}
}
for i in &p.plans.inspect {
i.tallying.iter().filter_map(|t| t.counter.as_ref()).for_each(|(place, plan)| store(*place, &plan.store));
}
for s in &p.plans.search_all {
store(s.index, &s.store);
}
names
}
pub fn run<L: Loader<Rc<Code>>>(code: &Code, me: usize, unit: &mut RunUnit<'_, Rc<Code>, L>, arguments: &[Option<usize>]) -> Result<Ending, Halt> {
let lowered = code.lowered.as_ref().map_err(|why| not_yet(format!("a program that does not lower ({why})")))?;
let mut vm = Vm::activation(lowered, me, unit, true)?;
for (&record, &address) in lowered.program.storage.using.iter().zip(arguments) {
vm.linkage[usize::from(record)] = address;
}
vm.run_from(None)
}
#[derive(Clone, Copy)]
struct Facts<'p> {
code: &'p Lowered,
}
impl ProgramFacts for Facts<'_> {
fn options(&self) -> Options {
self.code.program.options.options
}
fn page(&self) -> &'static CodePage {
self.code.program.options.options.code_page()
}
fn figurative(&self, f: Figurative) -> u8 {
match f {
Figurative::HighValue => self.code.high_value,
Figurative::LowValue => self.code.low_value,
Figurative::Space => ebcdic::SPACE,
Figurative::Zero => ebcdic::ZERO,
Figurative::Quote => self.options().quote.byte(),
Figurative::Null => 0,
}
}
fn collation(&self) -> &Collation {
&self.code.collation
}
fn ordinal(&self, byte: u8) -> u16 {
self.code.collation.weight(byte) + 1
}
fn character(&self, ordinal: i64) -> Option<u8> {
usize::try_from(ordinal).ok().and_then(|n| n.checked_sub(1)).and_then(|i| self.code.ordinals.get(i)).copied()
}
fn characters(&self) -> usize {
self.code.ordinals.len()
}
fn decimal_point(&self) -> char {
if self.code.program.options.decimal_point_comma { ',' } else { '.' }
}
fn edit(&self, edit: u32) -> (&[Sym], &str) {
let e = &self.code.program.edits[edit as usize];
(&e.syms, &e.currency)
}
fn scaling(&self, item: usize) -> u32 {
self.code.program.places.get(item).map_or(0, |p| p.scaling)
}
fn item_name(&self, item: usize) -> String {
let p = &self.code.program;
let name = match (u32::try_from(item).ok().and_then(|i| self.code.receivers.get(&i)), p.places.get(item)) {
(Some(&name), _) => name,
(None, Some(place)) => place.name,
(None, None) => return "RETURN-CODE".into(),
};
p.symbols[name as usize].clone()
}
}
struct Vm<'p, 'u, 'w, L: Loader<Rc<Code>>> {
code: &'p Lowered,
p: &'p Program,
me: usize,
base: usize,
local_base: usize,
linkage: Vec<Option<usize>>,
main: bool,
returns: Returns,
segment: u8,
line: u32,
arrival: Arrival,
debugging: bool,
locating: u32,
pending: Option<String>,
unit: &'u mut RunUnit<'w, Rc<Code>, L>,
}
impl<'p, 'u, 'w, L: Loader<Rc<Code>>> Vm<'p, 'u, 'w, L> {
fn activation(code: &'p Lowered, me: usize, unit: &'u mut RunUnit<'w, Rc<Code>, L>, main: bool) -> R<Self> {
let p = &code.program;
let storage = &p.storage;
if !storage.local_image.is_empty() && (storage.init_abend.is_some() || !storage.init_reports.is_empty()) {
return Err(not_yet("VALUE initialization that reports or abends in a program with LOCAL-STORAGE"));
}
let (base, fresh) = unit.activate(me, p.initial);
let main_frame = Frame { id: 0, kind: FrameKind::Main, displaced: None, segment: 0, depth: unit.depth as u32, temps: Vec::new() };
let mut vm = Self {
code,
p,
me,
base,
local_base: 0,
linkage: vec![None; storage.linkage.len()],
main,
returns: Returns { armed: vec![None; p.paragraphs.len()], saved: BTreeMap::new(), frames: vec![main_frame], next_frame: 1 },
segment: 0,
line: 0,
arrival: Arrival::Start,
debugging: false,
locating: 0,
pending: None,
unit,
};
if !storage.local_image.is_empty() {
vm.local_base = vm.unit.push_temporary(&storage.local_image);
}
if fresh {
vm.unit.mem[base..base + storage.image.len()].copy_from_slice(&storage.image);
for &report in &storage.init_reports {
let _ = writeln!(vm.unit.err, "{}", vm.sym(report));
}
if let Some(abend) = storage.init_abend {
return Err(vm.abend(abend, None).into());
}
vm.unit.initialized(me);
}
Ok(vm)
}
fn facts(&self) -> Facts<'p> {
Facts { code: self.code }
}
fn sym(&self, id: SymId) -> &'p str {
&self.p.symbols[id as usize]
}
fn pos(&self, at: DebugId) -> Pos {
self.p.debug.positions[at as usize]
}
fn abend(&self, id: AbendId, at: Option<DebugId>) -> Abend {
let text = &self.p.abends[id as usize];
let pos = text.at.or(at).map(|d| self.pos(d)).unwrap_or_default();
Abend { code: text.code.clone(), message: self.sym(text.message).to_owned(), pos, file: None }
}
fn lift<T>(&mut self, result: R<T>, pos: Pos) -> Result<T, Abend> {
result.map_err(|halt| match halt {
Halt::Abend(a) => a,
Halt::Unimplemented(what) => {
self.pending.get_or_insert(what);
Abend::ironwork("the VM stopped inside a library call", pos)
}
})
}
fn settle<T>(&mut self, result: Result<T, Abend>) -> R<T> {
match (result, self.pending.take()) {
(_, Some(what)) => Err(Halt::Unimplemented(what)),
(result, None) => result.map_err(Halt::Abend),
}
}
}