use super::*;
#[derive(Clone, Copy, Debug)]
pub(super) struct Return {
frame: u64,
resume: Option<(usize, (usize, usize))>,
}
#[derive(Default)]
pub(super) struct Returns {
armed: Vec<Option<Return>>,
saved: HashMap<usize, Option<Return>>,
active: Vec<u64>,
frames: u64,
running: usize,
}
impl Returns {
pub(super) fn new(paragraphs: usize) -> Self {
Self { armed: vec![None; paragraphs], ..Self::default() }
}
}
impl<'p> Machine<'p, '_, '_> {
pub(super) fn run_from(&mut self, at: Option<(usize, usize)>) -> R<Ending> {
let (start, skip) = at.unwrap_or((self.program.report_writer.procedure_start, 0));
if self.program.paragraphs.len() <= start {
return Ok(Ending::EndOfProgram);
}
self.segment = self.program.paragraphs[start].priority;
self.uses.arrival = declaratives::Arrival::Start;
match self.run_region((start, skip), (0, self.program.paragraphs.len() - 1), 0)? {
Flow::End(e) => Ok(e),
_ => Ok(Ending::EndOfProgram),
}
}
pub(super) fn run_paragraphs(&mut self, from: usize, to: usize) -> R<Flow> {
self.perform_range(from, to, None, None)
}
pub(super) fn perform_range(&mut self, from: usize, to: usize, region: Option<(usize, usize)>, statement: Option<(usize, (usize, usize))>) -> R<Flow> {
let last = self.program.paragraphs.len() - 1;
let region = region.unwrap_or(if from <= to { (from, to) } else { (from, last) });
self.returns.frames += 1;
let frame = self.returns.frames;
let saved = self.returns.armed[to].replace(Return { frame, resume: statement });
self.returns.active.push(frame);
let flow = self.run_region((from, 0), region, frame);
self.returns.active.pop();
match flow? {
Flow::Next => {
self.returns.armed[to] = saved;
Ok(Flow::Next)
}
Flow::Return(f) if f == frame => {
self.returns.armed[to] = saved;
Ok(Flow::Next)
}
left => {
if let Some((address, _)) = statement {
self.returns.saved.insert(address, saved);
}
Ok(left)
}
}
}
pub(super) fn after(&self, s: &Stmt) -> Option<(usize, (usize, usize))> {
let i = self.returns.running;
let stmts = &self.program.paragraphs.get(i)?.statements;
let address = std::ptr::from_ref(s) as usize;
let k = address.checked_sub(stmts.as_ptr() as usize)? / std::mem::size_of::<Stmt>();
(k < stmts.len() && std::ptr::eq(&stmts[k], s)).then_some((address, (i, k + 1)))
}
fn run_region(&mut self, (mut i, mut skip): (usize, usize), (lo, hi): (usize, usize), frame: u64) -> R<Flow> {
let program = self.program;
let (segment, running) = (self.segment, self.returns.running);
let mut arrival = std::mem::take(&mut self.uses.arrival);
let within = |t: usize| (lo..=hi).contains(&t);
let flow = loop {
if i > hi {
break if i < program.paragraphs.len() { Flow::GoTo(i) } else { Flow::End(Ending::EndOfProgram) };
}
self.returns.running = i;
self.enter_segment(program.paragraphs[i].priority);
if !self.declaratives.triggers.is_empty() {
if skip == 0
&& let Some(flow) = self.debug_before(i, arrival)?
{
break flow;
}
if program.paragraphs[i].is_section {
self.uses.line = program.paragraphs[i].pos;
}
arrival = declaratives::Arrival::FallThrough;
}
let altered = self.unit.programs[self.me].altered.get(i).copied().flatten();
let statements = &program.paragraphs[i].statements;
let flow = match altered {
Some(t) => Flow::GoTo(t),
None => self.run_sentences(&statements[skip.min(statements.len())..])?,
};
skip = 0;
let end = match flow {
Flow::Next | Flow::ExitParagraph | Flow::ExitPerform | Flow::ExitPerformCycle => i,
Flow::ExitSection => crate::section_end(program, i),
Flow::GoTo(t) if within(t) => {
i = t;
arrival = declaratives::Arrival::GoTo;
continue;
}
Flow::Resume(p, s) if within(p) => {
(i, skip) = (p, s);
self.segment = program.paragraphs[p].priority;
continue;
}
other => break other,
};
match self.returns.armed[end] {
None => i = end + 1,
Some(r) if r.frame == frame => break Flow::Next,
Some(r) if self.returns.active.binary_search(&r.frame).is_ok() => break Flow::Return(r.frame),
Some(Return { resume: Some((address, (p, s))), .. }) => {
self.returns.armed[end] = self.returns.saved.get(&address).copied().flatten();
if !within(p) {
break Flow::Resume(p, s);
}
(i, skip) = (p, s);
self.segment = program.paragraphs[p].priority;
}
Some(_) => {
return Err(Abend::ironwork(
format!(
"control passed the end of {}, which is armed to return to a PERFORM that control left by GO TO; ironwork returns there only to a PERFORM that runs once and is not inside another statement",
program.paragraphs[end].name
),
program.paragraphs[end].pos,
));
}
}
};
if matches!(flow, Flow::Next | Flow::Return(_)) {
self.segment = segment;
}
self.returns.running = running;
Ok(flow)
}
}