use super::cond::Test;
use super::data::{Side, Value, Within, scale};
use super::{Lower, LowerError, R, push, unsupported};
use crate::declaratives::{Span, debug_name};
use rt::abend::{AbendCode, Ending};
use rt::lir::{self, BlockId, DebugId, Op, RangeId, RangeKind, Terminator};
use rt::storage::Kind;
use rt::vocab::AcceptFrom;
use syntax::Pos;
use syntax::ast::{Arith, BinOp, ExecBlock, ExecKind, ExitKind, Expr, InitializeWith, Loop, Object, Operand, ProcName, RelOp, SizeError, Sorting, Stmt, Subject, Target, Varying, When};
#[derive(Default)]
pub(super) struct Blocks {
open: Vec<Open>,
current: Option<BlockId>,
}
#[derive(Default)]
struct Open {
ops: Vec<Op>,
at: Vec<DebugId>,
starts: Vec<(u32, DebugId)>,
end: Option<(Terminator, DebugId)>,
}
pub(super) struct BlockDebug {
pub ops: Vec<Vec<DebugId>>,
pub statements: Vec<Vec<(u32, DebugId)>>,
}
impl Blocks {
pub(super) fn finish(self) -> R<(Vec<lir::Block>, BlockDebug)> {
let mut blocks = Vec::with_capacity(self.open.len());
let mut debug = BlockDebug { ops: Vec::with_capacity(self.open.len()), statements: Vec::with_capacity(self.open.len()) };
for (b, open) in self.open.into_iter().enumerate() {
let Some((end, at)) = open.end else { return Err(LowerError::Invalid(format!("block {b} has no terminator"))) };
let mut ids = open.at;
ids.push(at);
blocks.push(lir::Block { ops: open.ops, end });
debug.ops.push(ids);
debug.statements.push(open.starts);
}
Ok((blocks, debug))
}
}
#[derive(Clone)]
pub(super) struct Ctx {
pub(super) para: usize,
top: usize,
pos: Pos,
loops: Vec<Inline>,
}
#[derive(Clone, Copy)]
struct Inline {
exit: BlockId,
cont: BlockId,
}
enum Body<'a> {
Range { range: RangeId, resumes: bool },
Inline(&'a [Stmt]),
}
struct VaryLevel {
from: Op,
step: Op,
until: Test,
}
impl Lower<'_> {
pub(super) fn new_block(&mut self) -> R<BlockId> {
push(&mut self.blocks.open, Open::default(), "blocks")
}
fn current(&mut self) -> R<BlockId> {
match self.blocks.current {
Some(b) => Ok(b),
None => {
let b = self.new_block()?;
self.blocks.current = Some(b);
Ok(b)
}
}
}
pub(super) fn op(&mut self, op: Op, pos: Pos) -> R<()> {
let at = self.at(pos);
let b = self.current()? as usize;
self.blocks.open[b].ops.push(op);
self.blocks.open[b].at.push(at);
Ok(())
}
pub(super) fn end(&mut self, end: Terminator, pos: Pos) -> R<()> {
let at = self.at(pos);
let b = self.current()? as usize;
self.blocks.open[b].end = Some((end, at));
self.blocks.current = None;
Ok(())
}
pub(super) fn jump(&mut self, to: BlockId, pos: Pos) -> R<()> {
match self.blocks.current {
Some(_) => self.end(Terminator::Jump(to), pos),
None => Ok(()),
}
}
pub(super) fn switch(&mut self, b: BlockId) -> R<()> {
if let Some(open) = self.blocks.current {
return Err(LowerError::Invalid(format!("block {open} left open for block {b}")));
}
self.blocks.current = Some(b);
Ok(())
}
fn sentence(&mut self, para: usize, k: usize) -> R<BlockId> {
if let Some(&b) = self.sentences.get(&(para, k)) {
return Ok(b);
}
let b = self.new_block()?;
self.sentences.insert((para, k), b);
Ok(b)
}
pub(super) fn procedure(&mut self) -> R<Vec<lir::Paragraph>> {
let program = self.program;
let n = program.paragraphs.len();
if u32::try_from(n + 1).is_err() {
return Err(LowerError::Exceeds("paragraphs", Pos::default()));
}
for p in &program.paragraphs {
self.collect(&p.statements)?;
}
self.segments = self.altered.iter().any(|&p| program.paragraphs[p].priority >= 50);
self.entries = (0..n).map(|_| self.new_block()).collect::<R<_>>()?;
let collected = self.ranges.len();
let mut paragraphs = Vec::with_capacity(n);
for (p, para) in program.paragraphs.iter().enumerate() {
self.switch(self.entries[p])?;
self.paragraph(p)?;
paragraphs.push(lir::Paragraph {
name: self.sym(¶.name),
is_section: para.is_section,
entry: self.entries[p],
section_end: crate::section_end(program, p) as u32,
priority: para.priority,
at: self.at(para.pos),
abandoned: None,
});
}
if self.ranges.len() != collected {
return Err(LowerError::Invalid("a range `collect` missed, which the paragraph ends were decided without".into()));
}
for (p, para) in program.paragraphs.iter().enumerate() {
if self.ranges.iter().any(|r| r.last as usize == p) {
let message = 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",
para.name
);
paragraphs[p].abandoned = Some(self.abend(AbendCode::Ironwork, &message, Some(para.pos))?);
}
}
Ok(paragraphs)
}
pub(super) fn declaratives(&mut self) -> R<lir::Declaratives> {
let table = &self.c.declaratives;
let mut modes = [None; 4];
for (mode, span) in modes.iter_mut().zip(table.modes) {
*mode = span.map(|s| self.span_range(s, RangeKind::UseProcedure)).transpose()?;
}
for (span, _) in table.triggers.iter().flatten() {
self.span_range(*span, RangeKind::Debugging)?;
}
let debug_item = table.debug_item.filter(|_| self.debugging).map(|i| (self.layout.items[i].offset, self.layout.items[i].size));
Ok(lir::Declaratives { modes, debug_item })
}
pub(super) fn span_range(&mut self, (first, last): Span, kind: RangeKind) -> R<RangeId> {
self.intern_range(first as u32, last as u32, kind)
}
fn intern_range(&mut self, first: u32, last: u32, kind: RangeKind) -> R<RangeId> {
if let Some(&r) = self.range_ids.get(&(first, last, kind)) {
return Ok(r);
}
let r = push(&mut self.ranges, lir::Range { first, last, kind }, "ranges")?;
self.range_ids.insert((first, last, kind), r);
Ok(r)
}
fn trigger(&self, p: usize) -> Option<(Span, String)> {
self.c.declaratives.triggers.get(p).cloned().flatten()
}
fn collect(&mut self, stmts: &[Stmt]) -> R<()> {
for s in stmts {
match s {
Stmt::PerformProc { from, thru, repeat: _, pos } => {
self.range(from, thru.as_ref(), *pos)?;
}
Stmt::Alter { pairs, pos: _ } => {
for (from, _) in pairs {
if let Ok((p, _)) = crate::procedure(self.program, from) {
self.altered.insert(p);
}
}
}
Stmt::XmlParse(x) => {
self.processing(x)?;
}
Stmt::Sorting(so) => {
if let Sorting::Sort(st) = &**so {
self.collect_sort(st)?;
}
}
_ => {}
}
for body in crate::oo::bodies(s) {
self.collect(body)?;
}
}
Ok(())
}
fn range(&mut self, from: &ProcName, thru: Option<&ProcName>, pos: Pos) -> R<RangeId> {
let program = self.program;
let Ok((first, first_end)) = crate::procedure(program, from) else { return unsupported("a PERFORM of a procedure the walker cannot find", pos) };
let last = match thru {
None => first_end,
Some(t) => match crate::procedure(program, t) {
Ok((_, last)) => last,
Err(_) => return unsupported("a PERFORM of a procedure the walker cannot find", pos),
},
};
self.intern_range(first as u32, last as u32, RangeKind::Perform)
}
fn paragraph(&mut self, p: usize) -> R<()> {
let para = &self.program.paragraphs[p];
if self.segments {
self.op(Op::EnterSegment(para.priority), para.pos)?;
}
if let Some((span, name)) = self.trigger(p) {
let name = self.sym(&name);
let range = self.span_range(span, RangeKind::Debugging)?;
let next = self.new_block()?;
self.end(Terminator::Debug { range, name, next }, para.pos)?;
self.switch(next)?;
}
if self.debugging && para.is_section {
self.op(Op::DebugLine(para.pos.line), para.pos)?;
}
if self.altered.contains(&p) {
let body = self.new_block()?;
self.end(Terminator::AlteredGoTo { para: p as u32, otherwise: body }, para.pos)?;
self.switch(body)?;
}
for (i, s) in para.statements.iter().enumerate() {
let pos = stmt_pos(s).unwrap_or(para.pos);
match s {
Stmt::SentenceEnd => {
let b = self.sentence(p, i + 1)?;
self.jump(b, pos)?;
self.switch(b)?;
}
Stmt::Entry { name: _, using: _, pos: _ } => {
self.statement_start(pos)?;
let b = self.new_block()?;
self.jump(b, pos)?;
self.switch(b)?;
self.entry_blocks.insert((p, i + 1), b);
}
_ => self.statement(s, &Ctx { para: p, top: i, pos, loops: Vec::new() })?,
}
}
if self.blocks.current.is_some() {
let end = self.paragraph_end(p, p + 1);
self.end(end, para.pos)?;
}
Ok(())
}
fn paragraph_end(&self, p: usize, next: usize) -> Terminator {
let completes = self.ranges.iter().any(|r| p <= r.last as usize && (r.last as usize) < next);
if next < self.entries.len() && !completes && self.trigger(next).is_none() {
Terminator::Jump(self.entries[next])
} else {
Terminator::ParagraphEnd { next: next as u32 }
}
}
fn debug_line(&mut self, pos: Pos) -> R<()> {
if self.debugging { self.op(Op::DebugLine(pos.line), pos) } else { Ok(()) }
}
fn statement_start(&mut self, pos: Pos) -> R<()> {
let at = self.at(pos);
let b = self.current()? as usize;
let open = &mut self.blocks.open[b];
open.starts.push((open.ops.len() as u32, at));
self.debug_line(pos)
}
pub(super) fn statements(&mut self, stmts: &[Stmt], ctx: &Ctx) -> R<()> {
for s in stmts {
self.statement(s, ctx)?;
}
Ok(())
}
fn statement(&mut self, s: &Stmt, ctx: &Ctx) -> R<()> {
if let Some(at) = stmt_pos(s) {
self.statement_start(at)?;
}
let pos = stmt_pos(s).unwrap_or(ctx.pos);
let inner = Ctx { pos, ..ctx.clone() };
match s {
Stmt::Move { from, to, pos: _ } => {
for r in to {
let dest = self.place(r, true)?;
let sender = self.operand(from, pos)?;
let plan = self.move_plan(&sender.side, self.kind_of(dest), self.place_items[dest as usize])?;
let check = self.move_check(sender.operand, dest);
self.op(Op::Move { from: sender.operand, to: dest, plan, check }, pos)?;
}
}
Stmt::Compute { targets, expr, size_error, pos: _ } => {
let computations: Vec<(&Target, &Expr)> = targets.iter().map(|t| (t, expr)).collect();
self.arithmetic(&computations, None, size_error.as_ref(), false, pos, &inner)?;
}
Stmt::Arith(a) => {
let Arith { verb: _, computations, remainder, size_error, pos: _ } = &**a;
let computations: Vec<(&Target, &Expr)> = computations.iter().map(|(t, e)| (t, e)).collect();
self.arithmetic(&computations, remainder.as_ref(), size_error.as_ref(), true, pos, &inner)?;
}
Stmt::Corresponding(c) => return unsupported("a CORRESPONDING statement the compiler did not expand", c.pos),
Stmt::If { cond, then, otherwise, pos: _ } => {
let test = self.test(cond, pos)?;
let (yes, no, join) = (self.new_block()?, self.new_block()?, self.new_block()?);
self.branch(test, yes, no, pos)?;
self.switch(yes)?;
self.statements(then, &inner)?;
self.jump(join, pos)?;
self.switch(no)?;
self.statements(otherwise, &inner)?;
self.jump(join, pos)?;
self.switch(join)?;
}
Stmt::Evaluate { subjects, whens, other, pos: _ } => self.evaluate(subjects, whens, other, pos, &inner)?,
Stmt::PerformProc { from, thru, repeat, pos: _ } => {
let range = self.range(from, thru.as_ref(), pos)?;
let own = self.program.paragraphs[ctx.para].statements.get(ctx.top).is_some_and(|top| std::ptr::eq(top, s));
let body = Body::Range { range, resumes: own && matches!(repeat, Loop::Once) };
self.perform(repeat, body, pos, &inner)?;
}
Stmt::PerformInline { body, repeat, pos: _ } => self.perform(repeat, Body::Inline(body), pos, &inner)?,
Stmt::Display { items, no_advancing, pos: _ } => {
let plan = self.display_plan(items, *no_advancing, pos)?;
self.op(Op::Display(plan), pos)?;
}
Stmt::Initialize { targets, with, pos: _ } => {
let none = InitializeWith::default();
let with = with.as_deref().unwrap_or(&none);
for r in targets {
let target = self.place(r, false)?;
let plan = self.init_plan(target, with, pos)?;
self.op(Op::Initialize { target, plan }, pos)?;
}
}
Stmt::GoTo { target: None, pos: _ } | Stmt::Entry { name: _, using: _, pos: _ } => {}
Stmt::GoToDepending { targets, on, pos: _ } => {
let value = self.int_expr(&Expr::Operand(Operand::Ref(on.clone())), pos)?;
let mut paragraphs = Vec::with_capacity(targets.len());
for target in targets {
let Ok((t, _)) = crate::procedure(self.program, target) else { return unsupported("a GO TO DEPENDING ON target the walker cannot resolve", pos) };
paragraphs.push(t as u32);
}
let next = self.new_block()?;
self.end(Terminator::Switch { value, targets: paragraphs, otherwise: next }, pos)?;
self.switch(next)?;
}
Stmt::Alter { pairs, pos: _ } => {
let mut altered = Vec::with_capacity(pairs.len());
for (from, to) in pairs {
let (Ok((para, _)), Ok((target, _))) = (crate::procedure(self.program, from), crate::procedure(self.program, to)) else {
return unsupported("an ALTER the walker cannot resolve", pos);
};
self.op(Op::Alter { para: para as u32, to: target as u32 }, pos)?;
altered.push((para, to));
}
if !self.c.declaratives.declarative_alters.contains(&pos) {
for (para, to) in altered {
let Some((span, name)) = self.trigger(para) else { continue };
let name = self.sym(&name);
let contents = self.sym(&debug_name(to));
let range = self.span_range(span, RangeKind::Debugging)?;
self.op(Op::DebugAlter { range, name, contents }, pos)?;
}
}
}
Stmt::Open { .. } | Stmt::Close { .. } | Stmt::Read(_) | Stmt::Write { .. } | Stmt::Rewrite { .. } | Stmt::Delete { .. } | Stmt::Start { .. } => {
self.file_statement(s, pos, &inner)?
}
Stmt::Set { set, pos: _ } => self.set(set, pos)?,
Stmt::String(st) => self.string(st, pos, &inner)?,
Stmt::Unstring(u) => self.unstring(u, pos, &inner)?,
Stmt::Inspect(i) => {
let plan = self.inspect_plan(i, pos)?;
self.op(Op::Inspect(plan), pos)?;
}
Stmt::Search(se) => self.search(se, pos, &inner)?,
Stmt::Accept { target, from, pos: _ } => {
let place = self.place(target, true)?;
let value = match from {
AcceptFrom::Sysin => Side { value: Value::Bytes, src: None, digits: 0 },
AcceptFrom::Date { four_digit_year } => Side { value: Value::Num(Some(0)), src: None, digits: if *four_digit_year { 8 } else { 6 } },
AcceptFrom::Day { four_digit_year } => Side { value: Value::Num(Some(0)), src: None, digits: if *four_digit_year { 7 } else { 5 } },
AcceptFrom::DayOfWeek => Side { value: Value::Num(Some(0)), src: None, digits: 1 },
AcceptFrom::Time => Side { value: Value::Num(Some(0)), src: None, digits: 8 },
};
let plan = self.move_plan(&value, self.kind_of(place), self.place_items[place as usize])?;
self.op(Op::Accept { target: place, from: *from, plan }, pos)?;
}
Stmt::Call(c) => self.call(c, pos, &inner)?,
Stmt::Cancel { targets, pos: _ } => {
for t in targets {
let name = self.operand(t, pos)?.operand;
self.op(Op::Cancel(name), pos)?;
}
}
Stmt::Invoke(i) => self.invoke(i, pos, &inner)?,
Stmt::GoTo { target: Some(target), pos: _ } => {
let Ok((t, _)) = crate::procedure(self.program, target) else { return unsupported("a GO TO the walker cannot resolve", pos) };
self.go_to(t, ctx, pos)?;
}
Stmt::Exec(block) => {
let ExecBlock { kind, command, options: _, host_variables: _, sql: _, text: _, pos: _ } = &**block;
match kind {
ExecKind::Sql if block.declarative() => {}
ExecKind::Cics => self.cics(block, pos, ctx.para)?,
ExecKind::Sql => self.sql(block, pos, &inner)?,
ExecKind::Dli | ExecKind::Other => {
let kind = if *kind == ExecKind::Dli { "DLI" } else { "" };
let message = format!("EXEC {kind} {command} was reached: ironwork for COBOL checks EXEC statements but does not run them yet");
let abend = self.abend(AbendCode::Exec, &message, None)?;
self.end(Terminator::Abend(abend), pos)?;
}
}
}
Stmt::JsonGenerate(_) | Stmt::JsonParse(_) | Stmt::XmlGenerate(_) | Stmt::XmlParse(_) => self.markup(s, pos, &inner)?,
Stmt::Sorting(so) => self.sorting(so, pos, &inner)?,
Stmt::Report(r) => self.report_statement(r, pos)?,
Stmt::Goback { pos: _ } | Stmt::ExitMethod { pos: _ } => self.end(Terminator::End(Ending::Goback), pos)?,
Stmt::StopRun { pos: _ } => self.end(Terminator::End(Ending::StopRun), pos)?,
Stmt::ExitProgram { pos: _ } => {
let next = self.new_block()?;
self.end(Terminator::ExitProgram { next }, pos)?;
self.switch(next)?;
}
Stmt::Continue | Stmt::SentenceEnd | Stmt::Exit { kind: ExitKind::Plain, pos: _ } => {}
Stmt::Exit { kind: ExitKind::Paragraph, pos: _ } => self.leave(ctx.para + 1, ctx, pos)?,
Stmt::Exit { kind: ExitKind::Section, pos: _ } => self.leave(crate::section_end(self.program, ctx.para) + 1, ctx, pos)?,
Stmt::Exit { kind: ExitKind::Perform, pos: _ } => match ctx.loops.last() {
Some(l) => self.end(Terminator::Jump(l.exit), pos)?,
None => self.leave(ctx.para + 1, ctx, pos)?,
},
Stmt::Exit { kind: ExitKind::PerformCycle, pos: _ } => match ctx.loops.last() {
Some(l) => self.end(Terminator::Jump(l.cont), pos)?,
None => self.leave(ctx.para + 1, ctx, pos)?,
},
Stmt::NextSentence => {
let stmts = &self.program.paragraphs[ctx.para].statements;
let after = stmts[ctx.top..].iter().position(|s| *s == Stmt::SentenceEnd).map(|j| ctx.top + j + 1);
self.unnest(ctx.loops.len(), pos)?;
let end = match after {
Some(k) => Terminator::Jump(self.sentence(ctx.para, k)?),
None => self.paragraph_end(ctx.para, ctx.para + 1),
};
self.end(end, pos)?;
}
}
Ok(())
}
fn unnest(&mut self, loops: usize, pos: Pos) -> R<()> {
if loops == 0 {
return Ok(());
}
let n = u8::try_from(loops).map_err(|_| LowerError::Exceeds("inline PERFORMs around one statement", pos))?;
self.op(Op::Unnest(n), pos)
}
pub(super) fn go_to(&mut self, t: usize, ctx: &Ctx, pos: Pos) -> R<()> {
self.unnest(ctx.loops.len(), pos)?;
let (p, t32, n) = (ctx.para as u32, t as u32, self.entries.len() as u32);
let holds = |r: &lir::Range, q: u32| {
let (lo, hi) = r.region(n);
lo <= q && q <= hi
};
let stays = self.ranges.iter().all(|r| !holds(r, p) || holds(r, t32)) && self.trigger(t).is_none();
self.end(if stays { Terminator::Jump(self.entries[t]) } else { Terminator::GoTo(t32) }, pos)
}
fn leave(&mut self, next: usize, ctx: &Ctx, pos: Pos) -> R<()> {
self.unnest(ctx.loops.len(), pos)?;
let end = self.paragraph_end(ctx.para, next);
self.end(end, pos)
}
pub(super) fn branch(&mut self, test: Test, then: BlockId, otherwise: BlockId, pos: Pos) -> R<()> {
if !test.abends() {
let cond = self.fold(&test)?;
return self.end(Terminator::Branch { cond, then, otherwise }, pos);
}
match test {
Test::Cond(cond) => self.end(Terminator::Branch { cond, then, otherwise }, pos),
Test::Abend(abend, at) => self.end(Terminator::Abend(abend), at),
Test::Not(a) => self.branch(*a, otherwise, then, pos),
Test::And(a, b) => {
let mid = self.new_block()?;
self.branch(*a, mid, otherwise, pos)?;
self.switch(mid)?;
self.branch(*b, then, otherwise, pos)
}
Test::Or(a, b) => {
let mid = self.new_block()?;
self.branch(*a, then, mid, pos)?;
self.switch(mid)?;
self.branch(*b, then, otherwise, pos)
}
}
}
pub(super) fn phrases(&mut self, on: Option<&[Stmt]>, not_on: Option<&[Stmt]>, pos: Pos, ctx: &Ctx) -> R<()> {
if on.is_none() && not_on.is_none() {
return Ok(());
}
self.select(on, not_on, pos, ctx)
}
pub(super) fn select(&mut self, on: Option<&[Stmt]>, not_on: Option<&[Stmt]>, pos: Pos, ctx: &Ctx) -> R<()> {
let (normal, exception, join) = (self.new_block()?, self.new_block()?, self.new_block()?);
self.end(Terminator::Select(vec![normal, exception]), pos)?;
self.switch(normal)?;
self.statements(not_on.unwrap_or_default(), ctx)?;
self.jump(join, pos)?;
self.switch(exception)?;
self.statements(on.unwrap_or_default(), ctx)?;
self.jump(join, pos)?;
self.switch(join)
}
fn arithmetic(&mut self, computations: &[(&Target, &Expr)], remainder: Option<&(Target, Expr, Expr)>, handler: Option<&SizeError>, per_receiver: bool, pos: Pos, ctx: &Ctx) -> R<()> {
let plan = self.arith_plan(computations, remainder, handler.is_some(), per_receiver, pos)?;
self.op(Op::Arith(plan), pos)?;
let Some(SizeError { on: on_size_error, not_on: not_on_size_error }) = handler else { return Ok(()) };
let (not_on, on, join) = (self.new_block()?, self.new_block()?, self.new_block()?);
self.end(Terminator::Select(vec![not_on, on]), pos)?;
self.switch(not_on)?;
self.statements(not_on_size_error, ctx)?;
self.jump(join, pos)?;
self.switch(on)?;
self.statements(on_size_error, ctx)?;
self.jump(join, pos)?;
self.switch(join)
}
fn evaluate(&mut self, subjects: &[Subject], whens: &[When], other: &[Stmt], pos: Pos, ctx: &Ctx) -> R<()> {
let join = self.new_block()?;
for When { alternatives, body: statements } in whens.iter().filter(|w| !w.alternatives.is_empty()) {
let (body, fail) = (self.new_block()?, self.new_block()?);
for (k, alternative) in alternatives.iter().enumerate() {
let next = if k + 1 == alternatives.len() { fail } else { self.new_block()? };
for (subject, object) in subjects.iter().zip(alternative) {
if self.blocks.current.is_none() {
break;
}
self.pair(subject, object, next, pos)?;
}
self.jump(body, pos)?;
if next != fail {
self.switch(next)?;
}
}
self.switch(body)?;
self.statements(statements, ctx)?;
self.jump(join, pos)?;
self.switch(fail)?;
}
self.statements(other, ctx)?;
self.jump(join, pos)?;
self.switch(join)
}
fn pair(&mut self, subject: &Subject, object: &Object, fail: BlockId, pos: Pos) -> R<()> {
let test = match (subject, object) {
(_, Object::Any) => return Ok(()),
(Subject::Bool(b), Object::Bool(o)) => return if b == o { Ok(()) } else { self.jump(fail, pos) },
(Subject::Bool(b), Object::Cond(c)) | (Subject::Cond(c), Object::Bool(b)) => {
let t = self.test(c, pos)?;
if *b { t } else { t.not() }
}
(Subject::Cond(c), Object::Cond(d)) => {
let tc = self.test(c, pos)?;
let (yes, no, pass) = (self.new_block()?, self.new_block()?, self.new_block()?);
self.branch(tc, yes, no, pos)?;
self.switch(yes)?;
let td = self.test(d, pos)?;
self.branch(td, pass, fail, pos)?;
self.switch(no)?;
let td = self.test(d, pos)?;
self.branch(td, fail, pass, pos)?;
return self.switch(pass);
}
(Subject::Expr(e), Object::Value { not, from, thru }) => {
let t = match thru {
None => self.relation(e, RelOp::Eq, from, pos)?,
Some(thru) => Test::And(Box::new(self.relation(e, RelOp::Ge, from, pos)?), Box::new(self.relation(e, RelOp::Le, thru, pos)?)),
};
if *not { t.not() } else { t }
}
_ => Test::Abend(self.ironwork("a WHEN object of a different kind from its subject")?, pos),
};
let pass = self.new_block()?;
self.branch(test, pass, fail, pos)?;
self.switch(pass)
}
pub(super) fn temp(&mut self, pos: Pos) -> R<lir::TempId> {
let t = self.temps;
self.temps = t.checked_add(1).ok_or(LowerError::Exceeds("PERFORM TIMES counters", pos))?;
Ok(t)
}
fn perform(&mut self, repeat: &Loop, body: Body<'_>, pos: Pos, ctx: &Ctx) -> R<()> {
self.op(Op::Nest, pos)?;
let exit = self.new_block()?;
let after = match body {
Body::Range { resumes: true, .. } => Some(lir::Resume { para: ctx.para as u32, block: self.new_block()? }),
_ => None,
};
match repeat {
Loop::Once => self.run_body(&body, exit, exit, after, pos, ctx)?,
Loop::Times(count) => {
let temp = self.temp(pos)?;
let n = self.int_expr(count, pos)?;
self.op(Op::SetTemp(temp, n), pos)?;
let (head, run) = (self.new_block()?, self.new_block()?);
self.jump(head, pos)?;
self.switch(head)?;
let counter = self.cond(lir::Cond::Counter(temp))?;
self.end(Terminator::Branch { cond: counter, then: run, otherwise: exit }, pos)?;
self.switch(run)?;
self.op(Op::DecTemp(temp), pos)?;
self.run_body(&body, head, exit, None, pos, ctx)?;
}
Loop::Until { cond, test_after } => {
let until = self.test(cond, pos)?;
let run = self.new_block()?;
if *test_after {
let cont = self.new_block()?;
self.jump(run, pos)?;
self.switch(run)?;
self.run_body(&body, cont, exit, None, pos, ctx)?;
self.switch(cont)?;
self.branch(until, exit, run, pos)?;
} else {
let head = self.new_block()?;
self.jump(head, pos)?;
self.switch(head)?;
self.branch(until, exit, run, pos)?;
self.switch(run)?;
self.run_body(&body, head, exit, None, pos, ctx)?;
}
}
Loop::Varying { varying, after, test_after } => {
let levels: Vec<&Varying> = std::iter::once(&**varying).chain(after).collect();
self.varying(&levels, *test_after, &body, exit, pos, ctx)?;
}
}
self.switch(exit)?;
self.op(Op::Unnest(1), pos)?;
match after {
Some(resume) => {
self.jump(resume.block, pos)?;
self.switch(resume.block)
}
None => Ok(()),
}
}
fn varying(&mut self, levels: &[&Varying], test_after: bool, body: &Body<'_>, exit: BlockId, pos: Pos, ctx: &Ctx) -> R<()> {
let mut vary = Vec::with_capacity(levels.len());
for v in levels {
vary.push(self.vary_level(v, pos)?);
}
let n = vary.len() - 1;
let tests: Vec<BlockId> = (0..=n).map(|_| self.new_block()).collect::<R<_>>()?;
let steps: Vec<BlockId> = (0..=n).map(|_| self.new_block()).collect::<R<_>>()?;
let run = self.new_block()?;
if test_after {
let tops: Vec<BlockId> = (0..n).map(|_| self.new_block()).collect::<R<_>>()?;
let top = |k: usize| if k < n { tops[k] } else { run };
self.op(vary[0].from.clone(), pos)?;
self.jump(top(0), pos)?;
for k in 0..n {
self.switch(tops[k])?;
self.op(vary[k + 1].from.clone(), pos)?;
self.jump(top(k + 1), pos)?;
}
self.switch(run)?;
self.run_body(body, tests[n], exit, None, pos, ctx)?;
for (k, level) in vary.into_iter().enumerate() {
self.switch(tests[k])?;
let then = if k == 0 { exit } else { tests[k - 1] };
self.branch(level.until, then, steps[k], pos)?;
self.switch(steps[k])?;
self.op(level.step, pos)?;
self.jump(top(k), pos)?;
}
} else {
for level in &vary {
self.op(level.from.clone(), pos)?;
}
self.jump(tests[0], pos)?;
let froms: Vec<Op> = vary.iter().map(|level| level.from.clone()).collect();
for (k, level) in vary.into_iter().enumerate() {
self.switch(tests[k])?;
let then = if k == 0 { exit } else { steps[k - 1] };
let inner = if k == n { run } else { tests[k + 1] };
self.branch(level.until, then, inner, pos)?;
self.switch(steps[k])?;
self.op(level.step, pos)?;
if k < n {
self.op(froms[k + 1].clone(), pos)?;
}
self.jump(tests[k], pos)?;
}
self.switch(run)?;
self.run_body(body, steps[n], exit, None, pos, ctx)?;
}
Ok(())
}
fn vary_level(&mut self, v: &Varying, pos: Pos) -> R<VaryLevel> {
let Varying { var: _, from: _, by: _, until: _ } = v;
let var = self.place(&v.var, false)?;
let kind = self.kind_of(var);
if !matches!(kind, Kind::Zoned { .. } | Kind::Packed { .. } | Kind::Binary { .. } | Kind::Index) {
return unsupported("a PERFORM VARYING variable that is not a fixed-point numeric item", v.var.pos);
}
let Expr::Operand(from) = &v.from else { return unsupported("PERFORM VARYING FROM an arithmetic expression", pos) };
let from = self.operand(from, pos)?;
let item = self.place_items[var as usize];
let plan = self.move_plan(&Side { src: None, ..from.side }, kind, item)?;
let from = Op::Set { from: from.operand, to: var, plan };
let sum = Expr::Bin(Box::new(Expr::Operand(Operand::Ref(v.var.clone()))), BinOp::Add, Box::new(v.by.clone()));
let dmax = scale(kind).max(self.dmax(&sum)?);
let prepass = self.dmax_places(&sum)?;
let by = self.expr_within(&v.by, pos, Within::Fixed(dmax))?;
let store = self.store_plan(kind, item)?;
let step = Op::Step { var, by, plan: lir::StepPlan { dmax, store }, prepass };
let until = self.test(&v.until, pos)?;
Ok(VaryLevel { from, step, until })
}
fn run_body(&mut self, body: &Body<'_>, cont: BlockId, exit: BlockId, resume: Option<lir::Resume>, pos: Pos, ctx: &Ctx) -> R<()> {
match body {
Body::Range { range, .. } => {
self.debug_line(pos)?;
self.end(Terminator::PerformEnter { range: *range, ret: cont, resume }, pos)
}
Body::Inline(stmts) => {
let mut inner = Ctx { pos, ..ctx.clone() };
inner.loops.push(Inline { exit, cont });
self.statements(stmts, &inner)?;
self.jump(cont, pos)
}
}
}
}
fn stmt_pos(s: &Stmt) -> Option<Pos> {
use syntax::report::ReportStmt;
Some(match s {
Stmt::Move { pos, .. }
| Stmt::Compute { pos, .. }
| Stmt::If { pos, .. }
| Stmt::PerformInline { pos, .. }
| Stmt::PerformProc { pos, .. }
| Stmt::Evaluate { pos, .. }
| Stmt::Display { pos, .. }
| Stmt::Open { pos, .. }
| Stmt::Close { pos, .. }
| Stmt::Write { pos, .. }
| Stmt::Rewrite { pos, .. }
| Stmt::Delete { pos, .. }
| Stmt::Start { pos, .. }
| Stmt::Initialize { pos, .. }
| Stmt::GoTo { pos, .. }
| Stmt::Goback { pos }
| Stmt::ExitProgram { pos }
| Stmt::Cancel { pos, .. }
| Stmt::Set { pos, .. }
| Stmt::Accept { pos, .. }
| Stmt::ExitMethod { pos }
| Stmt::StopRun { pos }
| Stmt::GoToDepending { pos, .. }
| Stmt::Alter { pos, .. }
| Stmt::Entry { pos, .. } => *pos,
Stmt::Arith(a) => a.pos,
Stmt::Corresponding(c) => c.pos,
Stmt::Read(r) => r.pos,
Stmt::Call(c) => c.pos,
Stmt::String(st) => st.pos,
Stmt::Unstring(u) => u.pos,
Stmt::Inspect(i) => i.pos,
Stmt::Search(se) => se.pos,
Stmt::Exec(block) => block.pos,
Stmt::Invoke(i) => i.pos,
Stmt::JsonGenerate(g) => g.pos,
Stmt::XmlParse(x) => x.pos,
Stmt::XmlGenerate(x) => x.pos,
Stmt::JsonParse(j) => j.pos,
Stmt::Report(r) => match &**r {
ReportStmt::Initiate { pos, .. } | ReportStmt::Generate { pos, .. } | ReportStmt::Terminate { pos, .. } | ReportStmt::Suppress { pos } => *pos,
},
Stmt::Sorting(so) => match &**so {
Sorting::Sort(st) => st.pos,
Sorting::Release { pos, .. } | Sorting::Return { pos, .. } => *pos,
},
Stmt::NextSentence | Stmt::SentenceEnd | Stmt::Continue | Stmt::Exit { .. } => return None,
})
}