use super::flow::{Arrival, Exit};
use super::{Code, Facts, Halt, R, Vm, not_yet};
use crate::abend::{Abend, AbendCode, Signal};
use crate::fileio::{self, File, Files, Outcome, Read};
use crate::files::{Dd, FileStatus, KeySpan, Keying, Open};
use crate::host::Host;
use crate::lir::{Advance, DebugId, FileOp, FileVerb, FromMove, IntExpr, Organization, Phrase, PlaceId, RangeId, RecordSpan, RelativeKey, Spacing, StartKey, Step};
use crate::sort::Active;
use crate::storage::{Loc, Val};
use crate::store;
use crate::unit::{Event, Loader};
use crate::vocab::{OpenMode, Pos};
use numeric::precision::Fixed;
use std::convert::Infallible;
use std::rc::Rc;
#[derive(Default)]
pub(super) struct State {
pub(super) sort: Option<Active>,
pub(super) failed: Option<usize>,
pub(super) leaving: Option<Step>,
pub(super) stale: Vec<u64>,
}
#[derive(Clone, Copy)]
pub(super) enum Handle<'p> {
Place(PlaceId),
Relative(&'p RelativeKey),
Key(usize, RecordSpan),
Int(&'p IntExpr),
}
pub(super) type FileOf<'p> = File<'p, Handle<'p>, &'p IntExpr>;
pub(super) struct Io<'a, 'p, 'u, 'w, L: Loader<Rc<Code>>> {
pub(super) vm: &'a mut Vm<'p, 'u, 'w, L>,
}
pub(super) fn mode_index(mode: OpenMode) -> usize {
match mode {
OpenMode::Input => 0,
OpenMode::Output => 1,
OpenMode::InputOutput => 2,
OpenMode::Extend => 3,
}
}
fn key_span(span: RecordSpan) -> KeySpan {
KeySpan { offset: span.offset as usize, len: span.len as usize }
}
fn advance(a: &Advance) -> fileio::Advance<'static, &IntExpr> {
match a {
Advance::Lines { before, count } => fileio::Advance::Lines { before: *before, count },
Advance::Page { before } => fileio::Advance::Page { before: *before },
Advance::Mnemonic { before, space } => fileio::Advance::Mnemonic { before: *before, space: Some(*space), name: "", environment: "" },
}
}
impl<'p, L: Loader<Rc<Code>>> Vm<'p, '_, '_, L> {
pub(super) fn file_desc(&self, k: usize) -> FileOf<'p> {
let p = self.p;
let d = &p.services.files[k];
let (offset, size) = p.storage.file_areas[k];
let base = match p.services.scope.areas.iter().find(|&&(f, _)| usize::from(f) == k) {
Some(&(_, record)) => self.linkage[usize::from(record)].unwrap_or_default(),
None => self.base,
};
File {
index: k,
name: self.sym(d.name),
assign: self.sym(d.assign),
organization: d.organization,
access: d.access,
optional: d.optional,
format: d.format,
status: d.status.map(|(q, _)| Handle::Place(q)),
relative: d.relative.as_ref().map(Handle::Relative),
linage: d.linage.as_ref().map(|l| fileio::Linage {
lines: &l.lines,
footing: l.footing.as_ref(),
top: l.top.as_ref(),
bottom: l.bottom.as_ref(),
counter: l.counter.map(|(q, _)| self.static_loc(q)),
}),
carriage: d.carriage,
area: (base + offset as usize, size as usize),
read_lengths: (d.read_lengths.0 as usize, d.read_lengths.1 as usize),
depending: d.depending.map(|r| fileio::Depending { item: Handle::Place(r.item), lengths: (r.lengths.0 as usize, r.lengths.1 as usize) }),
}
}
pub(super) fn static_loc(&self, place: PlaceId) -> Loc {
let q = &self.p.places[place as usize];
Loc { offset: self.base + q.offset as usize, len: q.len as usize, kind: q.kind, item: place as usize }
}
pub(super) fn error_procedure(&self, k: usize, mode: Option<OpenMode>) -> Option<RangeId> {
let services = &self.p.services;
services.files[k].error.or_else(|| mode.and_then(|m| services.declaratives.modes[mode_index(m)]))
}
pub(super) fn file(&mut self, op: &'p FileOp, at: DebugId) -> R<Step> {
let arm = Io { vm: self }.statement(op, at);
self.concluded(arm.map(|arm| if op.arms() == 0 { Step::Next } else { Step::Arm(arm.unwrap_or(0)) }))
}
pub(super) fn concluded(&mut self, result: Result<Step, Abend>) -> R<Step> {
match self.settle(result) {
Err(Halt::Abend(a)) if a.code == AbendCode::Signal(Signal::DeclarativeExit) => Ok(self.io.leaving.take().unwrap_or(Step::Next)),
other => other,
}
}
pub(super) fn procedure(&mut self, range: RangeId, arrival: Arrival, pos: Pos) -> Result<Exit, Abend> {
let depth = self.unit.depth;
self.unit.enter(pos)?;
let exit = self.run_procedure(range, arrival);
self.unit.depth = depth;
self.lift(exit, pos)
}
pub(super) fn move_from(&mut self, from: &FromMove, dest: Loc, at: DebugId) -> R<()> {
self.move_to(Some(from.check), from.from, dest, &from.plan, at)
}
pub(super) fn leave_statement(&mut self, step: Step, pos: Pos) -> Abend {
self.io.leaving = Some(step);
Abend { code: AbendCode::Signal(Signal::DeclarativeExit), message: String::new(), pos, file: None }
}
fn run_error_procedure(&mut self, range: RangeId, pos: Pos) -> Result<(), Abend> {
match self.procedure(range, Arrival::Use, pos)? {
Exit::Completed => Ok(()),
Exit::Left(step) => Err(self.leave_statement(step, pos)),
Exit::End(e) => Err(self.leave_statement(Step::End(e), pos)),
}
}
}
impl<'p, L: Loader<Rc<Code>>> Io<'_, 'p, '_, '_, L> {
fn statement(&mut self, op: &'p FileOp, at: DebugId) -> Result<Option<u8>, Abend> {
let k = usize::from(op.file);
let pos = self.vm.pos(at);
let file = self.vm.file_desc(k);
match &op.verb {
FileVerb::Open(mode) => {
let outcome = fileio::open(self, &file, *mode, pos)?;
self.settle(&file, outcome, None, pos)
}
FileVerb::Close => {
let outcome = fileio::close(self, &file, None, pos)?;
self.settle(&file, outcome, None, pos)
}
FileVerb::CloseWith(closing) => {
let outcome = fileio::close(self, &file, Some(*closing), pos)?;
self.settle(&file, outcome, None, pos)
}
FileVerb::Read { sequential, previous, into, key } => {
let keys = self.vm.p.services.files[k].keys.as_ref();
let alternate = usize::from(*key).checked_sub(1).and_then(|n| keys?.alternates.get(n));
let key = alternate.map(|&(span, _)| Handle::Key(usize::from(*key), span));
let read = Read { sequential: *sequential, previous: *previous, into: into.map(|(q, _)| Handle::Place(q)), key };
match fileio::read(self, &file, read, pos)? {
Outcome::Status { status, at_end } => {
let phrase = op.phrase.filter(|_| at_end == *sequential);
self.conclude(&file, status, phrase, if at_end { '1' } else { '2' }, "READ", pos)
}
outcome => self.settle(&file, outcome, None, pos),
}
}
FileVerb::Write { record, from, advancing } => {
let loc = self.record(*record, from.as_ref(), at)?;
match fileio::write(self, &file, loc, advancing.as_ref().map(advance), pos)? {
Outcome::Status { status, .. } => self.conclude(&file, status, op.phrase, '2', "WRITE", pos),
outcome => self.settle(&file, outcome, op.end_of_page, pos),
}
}
FileVerb::Rewrite { record, from } => {
let loc = self.record(*record, from.as_ref(), at)?;
let status = fileio::rewrite(self, &file, loc, pos)?;
self.conclude(&file, status, op.phrase, '2', "REWRITE", pos)
}
FileVerb::Delete => {
let status = fileio::delete(self, &file, pos)?;
self.conclude(&file, status, op.phrase, '2', "DELETE", pos)
}
FileVerb::Start { rel, key } => {
let key = match key {
StartKey::Prime | StartKey::RelativeKey => None,
StartKey::Named { key, span } => Some(Handle::Key(usize::from(*key), *span)),
StartKey::Relative(value) => Some(Handle::Int(value)),
};
let status = fileio::start(self, &file, *rel, key, pos)?;
self.conclude(&file, status, op.phrase, '2', "START", pos)
}
}
}
fn record(&mut self, record: PlaceId, from: Option<&FromMove>, at: DebugId) -> Result<Loc, Abend> {
let pos = self.vm.pos(at);
let located = match from {
Some(f) => self.vm.loc(f.to).and_then(|dest| self.vm.move_from(f, dest, at)).and_then(|()| self.vm.loc(record)),
None => self.vm.loc(record),
};
self.vm.lift(located, pos)
}
pub(super) fn conclude(&mut self, file: &FileOf<'p>, status: FileStatus, phrase: Option<Phrase>, class: char, verb: &str, pos: Pos) -> Result<Option<u8>, Abend> {
let arm = match phrase {
Some(p) if status.covers('0') => p.not_on.then_some(2),
Some(p) if status.covers(class) => p.on.then_some(1),
_ => None,
};
if arm.is_some() {
fileio::set_status(self, file, status, pos)?;
return Ok(arm);
}
let message = format!("{verb} {}: file status {}: {}", file.name, status.as_str(), status.meaning());
self.io_status(file, status, message, pos)?;
Ok(None)
}
pub(super) fn settle(&mut self, file: &FileOf<'p>, outcome: Outcome, end_of_page: Option<Phrase>, pos: Pos) -> Result<Option<u8>, Abend> {
Ok(match outcome {
Outcome::Failed(f) => {
self.io_failure(file, f.status, f.mode, f.message, pos)?;
None
}
Outcome::Page { end_of_page: true } => end_of_page.filter(|p| p.on).map(|_| 3),
Outcome::Page { end_of_page: false } => end_of_page.filter(|p| p.not_on).map(|_| 4),
Outcome::Done | Outcome::Status { .. } => None,
})
}
fn io_status(&mut self, file: &FileOf<'p>, status: FileStatus, message: String, pos: Pos) -> Result<(), Abend> {
let mode = self.slot(file.index).as_ref().map(|f| f.mode);
self.io_failure(file, status, mode, message, pos)
}
pub(super) fn io_failure(&mut self, file: &FileOf<'p>, status: FileStatus, mode: Option<OpenMode>, message: String, pos: Pos) -> Result<(), Abend> {
fileio::set_status(self, file, status, pos)?;
if status.covers('0') {
return Ok(());
}
let k = file.index;
self.vm.io.failed = Some(k);
if let Some(range) = self.vm.error_procedure(k, mode) {
return self.vm.run_error_procedure(range, pos);
}
if self.vm.global_procedure(k, mode, pos)? {
return Ok(());
}
if file.status.is_none() && status.ends_the_run() {
return Err(Abend { code: AbendCode::Io(status), message, pos, file: None });
}
Ok(())
}
pub(super) fn write_line(&mut self, k: usize, loc: Loc, space: Spacing, pos: Pos) -> Result<(), Abend> {
let file = self.vm.file_desc(k);
let outcome = fileio::write_stream(self, &file, loc, false, space, pos)?;
self.settle(&file, outcome, None, pos).map(drop)
}
fn not_a_place(&mut self, pos: Pos) -> Abend {
let Err(stopped) = self.vm.lift::<Infallible>(Err(not_yet("a file key or integer used as a data item")), pos);
stopped
}
}
impl<'p, L: Loader<Rc<Code>>> Host<Handle<'p>> for Io<'_, 'p, '_, '_, L> {
type Facts = Facts<'p>;
fn facts(&self) -> Facts<'p> {
self.vm.facts()
}
fn mem(&mut self) -> &mut [u8] {
&mut self.vm.unit.mem
}
fn taint(&mut self) -> Option<&mut crate::taint::Taint> {
self.vm.unit.taint.as_mut()
}
fn locate(&mut self, handle: Handle<'p>, receiving: bool) -> Result<Loc, Abend> {
match handle {
Handle::Place(place) | Handle::Relative(&RelativeKey { place, .. }) => Host::<PlaceId>::locate(&mut *self.vm, place, receiving),
Handle::Key(..) | Handle::Int(_) => Err(self.not_a_place(Pos::default())),
}
}
fn integer(&mut self, handle: Handle<'p>, pos: Pos) -> Result<i64, Abend> {
let n = match handle {
Handle::Place(place) => self.vm.int_place(place, pos),
Handle::Relative(r) => self.vm.int(&r.value, pos),
Handle::Int(value) => self.vm.int(value, pos),
Handle::Key(..) => return Err(self.not_a_place(pos)),
};
self.vm.lift(n, pos)
}
fn assign(&mut self, dest: Loc, val: Val, src: Option<Loc>, pos: Pos) -> Result<(), Abend> {
store::assign(&self.vm.facts(), self.vm.unit, dest, val, src, pos)
}
fn store_fixed(&mut self, dest: Loc, value: &Fixed, pos: Pos) -> Result<(), Abend> {
store::store_fixed(&self.vm.facts(), self.vm.unit, dest, value, false, pos)
}
}
impl<'p, L: Loader<Rc<Code>>> Files<Handle<'p>, &'p IntExpr> for Io<'_, 'p, '_, '_, L> {
fn slot(&mut self, k: usize) -> &mut Option<Open> {
self.vm.unit.file(self.vm.me, k)
}
fn locked(&mut self, k: usize) -> &mut bool {
self.vm.unit.locked(self.vm.me, k)
}
fn dd(&self, assign: &str) -> Option<Dd> {
self.vm.unit.dds.get(assign)
}
fn notify(&mut self, event: Event<'_>) {
self.vm.unit.notify(event);
}
fn int(&mut self, value: &'p IntExpr, pos: Pos) -> Result<i64, Abend> {
let n = self.vm.int(value, pos);
self.vm.lift(n, pos)
}
fn keying(&mut self, k: usize, _pos: Pos) -> Result<Keying, Abend> {
let d = &self.vm.p.services.files[k];
Ok(match (d.organization, &d.keys) {
(Organization::Relative, _) => Keying::Relative,
(Organization::Indexed, Some(keys)) => Keying::Indexed { prime: key_span(keys.prime), alternates: keys.alternates.iter().map(|&(span, duplicates)| (key_span(span), duplicates)).collect() },
_ => Keying::Position,
})
}
fn key_value(&mut self, k: usize, _keying: &Keying, key: Handle<'p>, _partial: bool, pos: Pos) -> Result<(usize, Vec<u8>), Abend> {
let Handle::Key(which, span) = key else { return Err(self.not_a_place(pos)) };
let (offset, size) = self.vm.file_desc(k).area;
Ok((which, key_span(span).of(&self.vm.unit.mem[offset..offset + size])))
}
}