use super::files::mode_index;
use super::flow::{Arrival, Exit};
use super::{Code, Halt, Lowered, Vm};
use crate::abend::{Abend, Ending};
use crate::lir::{Binding, GlobalAt, Program, RangeId, Section, Step, SymId};
use crate::unit::{Connector, Loader};
use crate::vocab::{OpenMode, Pos};
use std::rc::Rc;
#[derive(Clone)]
pub(super) struct Container<'p> {
code: &'p Lowered,
me: usize,
base: usize,
local_base: usize,
linkage: Vec<Option<usize>>,
}
impl Container<'_> {
fn id(&self) -> &str {
text(&self.code.program, self.code.program.id)
}
}
fn text(p: &Program, id: SymId) -> &str {
&p.symbols[id as usize]
}
fn declarer(p: &Program, k: usize) -> &str {
let declared_in = p.services.scope.files.iter().find(|f| usize::from(f.file) == k).and_then(|f| f.declared_in);
text(p, declared_in.unwrap_or(p.id))
}
impl<'p, L: Loader<Rc<Code>>> Vm<'p, '_, '_, L> {
pub(super) fn containers_of(&self, callee: &Program) -> Vec<Container<'p>> {
let ids = &callee.services.scope.containers;
if ids.is_empty() {
return Vec::new();
}
let me = Container { code: self.code, me: self.me, base: self.base, local_base: self.local_base, linkage: self.linkage.clone() };
let running: Vec<Container<'p>> = std::iter::once(me).chain(self.containers.iter().cloned()).collect();
ids.iter().filter_map(|&id| running.iter().find(|f| f.id() == text(callee, id)).cloned()).collect()
}
fn frame(&self, id: &str) -> Result<&Container<'p>, Abend> {
self.containers.iter().find(|f| f.id() == id).ok_or_else(|| {
Abend::ironwork(format!("{} uses the GLOBAL names of {id}, which contains it and is not running", self.sym(self.p.id)), Pos::default())
})
}
pub(super) fn bind_shared(&mut self) -> Result<(), Abend> {
let p = self.p;
let scope = &p.services.scope;
let external = |m: String| Abend::ironwork(m, Pos::default());
for (ordinal, binding) in &scope.records {
let address = match *binding {
Binding::External { name, size } => Some(self.unit.external(self.sym(name), false, size as usize).map_err(external)?),
Binding::ExternalFile(k) => {
let k = usize::from(k);
Some(self.unit.external(self.sym(p.services.files[k].name), true, p.storage.file_areas[k].1 as usize).map_err(external)?)
}
Binding::Global { program, section, name } => self.global_address(self.sym(program), section, self.sym(name))?,
};
self.linkage[usize::from(*ordinal)] = address;
}
for shared in &scope.files {
let k = usize::from(shared.file);
let file = &p.services.files[k];
let name = self.sym(file.name);
let to = match shared.declared_in {
_ if shared.external => self.unit.external_file(name),
None => continue,
Some(declarer) => {
let declarer = self.sym(declarer);
let frame = self.frame(declarer)?;
let theirs = &frame.code.program;
let own = |j: usize| !theirs.services.scope.files.iter().any(|f| usize::from(f.file) == j && f.declared_in.is_some());
let Some(j) = (0..theirs.services.files.len()).find(|&j| text(theirs, theirs.services.files[j].name) == name && own(j)) else {
return Err(Abend::ironwork(format!("{declarer} has no file {name}"), Pos::default()));
};
if file.carriage != theirs.services.files[j].carriage {
let message = format!("{name}, a GLOBAL file of {declarer}, is written as a print file in one of {declarer} and {} and not the other, which is not supported yet", self.sym(p.id));
return Err(Abend::ironwork(message, Pos::default()));
}
Connector::Program(frame.me, j)
}
};
self.unit.connect(self.me, k, to);
}
Ok(())
}
fn global_address(&self, id: &str, section: Section, name: &str) -> Result<Option<usize>, Abend> {
let frame = self.frame(id)?;
let theirs = &frame.code.program;
let Some(global) = theirs.services.scope.globals.iter().find(|g| g.section == section && text(theirs, g.name) == name) else {
let record = if section == Section::File { "" } else { name };
return Err(Abend::ironwork(format!("{id} has no GLOBAL record {record}"), Pos::default()));
};
Ok(match global.at {
GlobalAt::Program(offset) => Some(frame.base + offset as usize),
GlobalAt::Local(offset) => Some(frame.local_base + offset as usize),
GlobalAt::Linkage(record) => frame.linkage[usize::from(record)],
})
}
pub(super) fn global_procedure(&mut self, k: usize, mode: Option<OpenMode>, pos: Pos) -> Result<bool, Abend> {
let p = self.p;
let (name, declared) = (text(p, p.services.files[k].name), declarer(p, k));
let found = self.containers.iter().enumerate().find_map(|(n, frame)| {
let theirs = &frame.code.program;
let scope = &theirs.services.scope;
let file = (0..theirs.services.files.len()).find(|&j| text(theirs, theirs.services.files[j].name) == name && declarer(theirs, j) == declared);
let range = file.and_then(|j| scope.global_files.iter().find(|&&(f, _)| usize::from(f) == j)).map(|&(_, r)| r);
range.or_else(|| mode.and_then(|m| scope.global_modes[mode_index(m)])).map(|r| (n, r))
});
let Some((n, range)) = found else { return Ok(false) };
self.run_global_procedure(n, range, pos)?;
Ok(true)
}
fn run_global_procedure(&mut self, n: usize, range: RangeId, pos: Pos) -> Result<(), Abend> {
let frame = self.containers[n].clone();
let outer = self.containers[n + 1..].to_vec();
let (me, program) = (self.sym(self.p.id), text(&frame.code.program, frame.code.program.id));
let ran = {
let mut vm = Vm::over(frame.code, frame.me, frame.base, &mut *self.unit, false, outer);
(vm.linkage, vm.first) = (frame.linkage, self.first);
vm.local_base = frame.local_base;
let depth = vm.unit.depth;
let ran = match vm.unit.enter(pos) {
Ok(()) => vm.run_procedure(range, Arrival::Use),
Err(abend) => Err(abend.into()),
};
vm.unit.depth = depth;
match (ran, vm.pending.take()) {
(_, Some(what)) => Err(Halt::Unimplemented(what)),
(ran, None) => ran,
}
};
match self.lift(ran, pos)? {
Exit::Completed => Ok(()),
Exit::End(Ending::StopRun) | Exit::Left(Step::End(Ending::StopRun)) => Err(self.leave_statement(Step::End(Ending::StopRun), pos)),
Exit::End(_) | Exit::Left(_) => Err(Abend::ironwork(
format!("the GLOBAL EXCEPTION/ERROR procedure of {program}, run for {me}, left by GO TO, GOBACK or EXIT PROGRAM, which is not supported yet"),
pos,
)),
}
}
}