Skip to main content

ironwork_exec/
machine.rs

1//! The interpreter: one activation of one program, over the run unit's memory in EBCDIC. A
2//! reference can reach anywhere in that memory, as a program compiled without SSRANGE can on
3//! z/OS, but never outside it.
4
5pub use rt::abend::{Abend, Ending};
6use crate::abend::{AbendCode, Signal};
7use crate::layout::{Item, Kind, Layout, Resolved};
8use rt::storage::{Loc, Val};
9pub(crate) use rt::storage::literal_fixed;
10use crate::unit::{ADDRESS_BASE, Event, LoadError, OS_COMMAND_ROUTINES, RETURN_CODE, RunUnit};
11use crate::Compiled;
12use numeric::precision::{self, Fixed, Places};
13use numeric::{Options, Trunc};
14use rt::fixed::{align, places_of, zoned_digits};
15use rt::arith;
16use rt::display::utf16_text;
17use rt::lir::{ByteClass, ConvertTable, Converting, SignTest, StringSource, TrimSide};
18use rt::loc;
19use rt::store;
20use rt::text::UnstringField;
21use std::cmp::Ordering;
22use std::collections::HashMap;
23use syntax::Pos;
24use syntax::ast::*;
25use zarch::decimal;
26use zarch::ebcdic::{self, CodePage, Collation};
27use zarch::hfp::{Hfp, Precision};
28
29mod cics;
30pub(crate) mod cics_bind;
31mod declaratives;
32mod facts;
33mod file_io;
34mod intrinsic;
35mod json;
36mod le_services;
37mod oo;
38mod parmcheck;
39mod perform;
40mod report;
41mod scope;
42mod sort;
43pub(crate) mod sql;
44mod xml;
45
46type R<T> = Result<T, Abend>;
47
48enum Flow {
49    Next,
50    End(Ending),
51    GoTo(usize),
52    ExitParagraph,
53    ExitSection,
54    ExitPerform,
55    ExitPerformCycle,
56    NextSentence,
57    /// Returning, at this paragraph and statement, after a PERFORM whose range control left.
58    Resume(usize, usize),
59    /// Returning to the active PERFORM with this frame number.
60    Return(u64),
61}
62
63pub struct Machine<'p, 'u, 'w> {
64    compiled: &'p Compiled,
65    program: &'p Program,
66    layout: &'p Layout,
67    options: Options,
68    ssrange: bool,
69    page: &'static CodePage,
70    collating: &'p crate::collating::Sequence,
71    when_compiled: rt::lir::CompileTime,
72    resolved: HashMap<(String, Vec<String>), Resolved>,
73    /// This program's place in the run unit, and where its storage starts.
74    me: usize,
75    base: usize,
76    /// Where each LINKAGE record is, once an argument or SET ADDRESS OF has given it an address.
77    linkage: Vec<Option<usize>>,
78    /// Where this activation's LOCAL-STORAGE starts.
79    local_base: usize,
80    /// The first program of the run unit, where EXIT PROGRAM does nothing.
81    main: bool,
82    /// HANDLE CONDITION, IGNORE CONDITION and HANDLE ABEND, which belong to the program level.
83    cics_handlers: cics::Handlers,
84    report_writer: &'p crate::report::Writer,
85    /// Each file's printer control character, when it is a print file.
86    carriage: &'p [Option<crate::printer::Carriage>],
87    /// The method this activation runs, if it is one: its class and SELF.
88    oo: oo::Frame,
89    /// The SORT or MERGE whose input or output procedure is running.
90    sort: Option<sort::Active>,
91    /// The priority-number of the segment the running paragraph is in.
92    segment: u8,
93    declaratives: &'p crate::declaratives::Table,
94    uses: declaratives::State,
95    returns: perform::Returns,
96    /// XML-TEXT and the other XML registers of the event being processed.
97    xml: xml::Registers,
98    /// The programs containing this one, innermost first, as they are running.
99    containers: Vec<scope::Frame<'p>>,
100    unit: &'u mut RunUnit<'w>,
101}
102
103enum Step {
104    Again,
105    Leave,
106    Out(Flow),
107}
108
109impl<'p, 'u, 'w> Machine<'p, 'u, 'w> {
110    /// An activation of loaded program `me`. Its storage is initialized on its first activation,
111    /// after a CANCEL, and on every activation of an INITIAL program.
112    pub fn activation(compiled: &'p Compiled, me: usize, unit: &'u mut RunUnit<'w>, main: bool) -> R<Self> {
113        Self::activation_within(compiled, me, unit, main, Vec::new())
114    }
115
116    /// An activation of a contained program, called with the programs containing it running.
117    fn activation_within(compiled: &'p Compiled, me: usize, unit: &'u mut RunUnit<'w>, main: bool, containers: Vec<scope::Frame<'p>>) -> R<Self> {
118        let (base, fresh) = unit.activate(me, compiled.program.initial);
119        let mut m = Self::over(compiled, me, base, unit, main);
120        m.containers = containers;
121        m.bind_shared()?;
122        if compiled.layout.local_size > 0 {
123            m.local_base = m.unit.push_temporary(&vec![0; compiled.layout.local_size as usize]);
124            m.initialize_values(true)?;
125        }
126        if fresh {
127            m.unit.mem[base..base + compiled.layout.size as usize].fill(0);
128            m.initialize_values(false)?;
129            m.unit.initialized(me);
130        }
131        Ok(m)
132    }
133
134    /// Program `me` over its storage at `base`, with nothing bound or initialized.
135    fn over(compiled: &'p Compiled, me: usize, base: usize, unit: &'u mut RunUnit<'w>, main: bool) -> Self {
136        Self {
137            compiled,
138            program: &compiled.program,
139            layout: &compiled.layout,
140            options: compiled.options,
141            ssrange: compiled.ssrange,
142            page: compiled.options.code_page(),
143            collating: &compiled.collating,
144            when_compiled: compiled.when_compiled,
145            resolved: HashMap::new(),
146            me,
147            base,
148            linkage: vec![None; compiled.layout.linkage_roots.len()],
149            local_base: 0,
150            main,
151            cics_handlers: cics::Handlers::default(),
152            report_writer: &compiled.report_writer,
153            carriage: &compiled.carriage,
154            oo: oo::Frame::default(),
155            sort: None,
156            segment: 0,
157            declaratives: &compiled.declaratives,
158            uses: declaratives::State::default(),
159            returns: perform::Returns::new(compiled.program.paragraphs.len()),
160            xml: xml::Registers::default(),
161            containers: Vec::new(),
162            unit,
163        }
164    }
165
166    /// Applies VALUE clauses: to WORKING-STORAGE and file records, or to LOCAL-STORAGE.
167    fn initialize_values(&mut self, local: bool) -> R<()> {
168        let base = if local { self.local_base } else { self.base };
169        for index in 0..self.layout.items.len() {
170            let item = &self.layout.items[index];
171            let Some(value) = item.value.clone().filter(|_| item.linkage.is_none() && item.local == local) else { continue };
172            let occurrences: u32 = item.dims.iter().map(|&(_, n)| n).product::<u32>().max(1);
173            let kind = value_kind(item.kind, &value);
174            for k in 0..occurrences {
175                let offset = base + item.offset as usize + self.occurrence_offset(item, k);
176                let loc = Loc { offset, len: item.size as usize, kind, item: index };
177                let val = self.literal_value(&value, item.pos)?;
178                self.assign(loc, val, None, item.pos)?;
179            }
180        }
181        Ok(())
182    }
183
184    fn occurrence_offset(&self, item: &Item, k: u32) -> usize {
185        loc::occurrence_offset(&item.dims, k)
186    }
187
188    pub fn run_procedure(&mut self) -> R<Ending> {
189        self.run_from(None)
190    }
191
192    /// Control reaching a paragraph of segment `priority`: an independent segment entered from
193    /// another is in its initial state, so its altered GO TOs are as written (assumption C52).
194    fn enter_segment(&mut self, priority: u8) {
195        if priority == self.segment {
196            return;
197        }
198        self.segment = priority;
199        if priority >= 50 {
200            let program = self.program;
201            for (i, target) in self.unit.programs[self.me].altered.iter_mut().enumerate() {
202                if program.paragraphs[i].priority == priority {
203                    *target = None;
204                }
205            }
206        }
207    }
208
209    /// A paragraph's statements: NEXT SENTENCE resumes after the next separator period.
210    fn run_sentences(&mut self, stmts: &'p [Stmt]) -> R<Flow> {
211        let mut i = 0;
212        while i < stmts.len() {
213            match self.exec(&stmts[i])? {
214                Flow::Next => i += 1,
215                Flow::NextSentence => i = stmts[i..].iter().position(|s| *s == Stmt::SentenceEnd).map_or(stmts.len(), |j| i + j + 1),
216                other => return Ok(other),
217            }
218        }
219        Ok(Flow::Next)
220    }
221
222    fn run_block(&mut self, stmts: &'p [Stmt]) -> R<Flow> {
223        for s in stmts {
224            match self.exec(s)? {
225                Flow::Next => {}
226                other => return Ok(other),
227            }
228        }
229        Ok(Flow::Next)
230    }
231
232    fn procedure(&self, p: &ProcName, pos: Pos) -> R<(usize, usize)> {
233        crate::procedure(self.program, p).map_err(|m| Abend::ironwork(m, pos))
234    }
235
236    /// One statement. An EXCEPTION/ERROR procedure it ran may have sent control elsewhere.
237    fn exec(&mut self, s: &'p Stmt) -> R<Flow> {
238        if !self.declaratives.triggers.is_empty()
239            && let Some(pos) = declaratives::statement_pos(s)
240        {
241            self.uses.line = pos;
242        }
243        if self.unit.statements.is_some()
244            && let Some(pos) = declaratives::statement_pos(s)
245            && self.unit.traces(pos.line)
246        {
247            let file = self.event_file(pos);
248            self.unit.notify(Event::Statement { file: &file, line: pos.line });
249        }
250        match self.statement(s) {
251            Err(Abend { code: AbendCode::Signal(Signal::DeclarativeExit), .. }) => Ok(self.declarative_exit()),
252            flow => flow,
253        }
254    }
255
256    fn statement(&mut self, s: &'p Stmt) -> R<Flow> {
257        match s {
258            Stmt::Move { from, to, pos } => {
259                for r in to {
260                    let dest = self.locate_receiving(r)?;
261                    let (val, src) = self.move_source(from, dest, *pos)?;
262                    self.assign(dest, val, src, *pos)?;
263                }
264            }
265            Stmt::Compute { targets, expr, size_error, pos } => {
266                let computations: Vec<(Target, Expr)> = targets.iter().map(|t| (t.clone(), expr.clone())).collect();
267                return self.arithmetic(&computations, None, size_error.as_ref(), false, *pos);
268            }
269            Stmt::Arith(a) => return self.arithmetic(&a.computations, a.remainder.as_ref(), a.size_error.as_ref(), true, a.pos),
270            Stmt::Corresponding(c) => return Err(Abend::ironwork("CORRESPONDING reached the interpreter unexpanded", c.pos)),
271            Stmt::If { cond, then, otherwise, pos } => {
272                let branch = if self.condition(cond, *pos)? { then } else { otherwise };
273                return self.run_block(branch);
274            }
275            Stmt::Evaluate { subjects, whens, other, pos } => {
276                for w in whens {
277                    for alternative in &w.alternatives {
278                        if self.alternative_matches(subjects, alternative, *pos)? {
279                            return self.run_block(&w.body);
280                        }
281                    }
282                }
283                return self.run_block(other);
284            }
285            Stmt::PerformProc { from, thru, repeat, pos } => {
286                let (start, first_end) = self.procedure(from, *pos)?;
287                let end = match thru {
288                    Some(t) => self.procedure(t, *pos)?.1,
289                    None => first_end,
290                };
291                let statement = if matches!(repeat, Loop::Once) { self.after(s) } else { None };
292                return self.repeat(repeat, *pos, &mut |m: &mut Self| {
293                    m.uses.line = *pos;
294                    m.perform_range(start, end, None, statement)
295                });
296            }
297            Stmt::PerformInline { body, repeat, pos } => return self.repeat(repeat, *pos, &mut |m: &mut Self| m.run_block(body)),
298            Stmt::Display { items, no_advancing, pos } => self.display(items, *no_advancing, *pos)?,
299            Stmt::Open { files, pos } => {
300                for (mode, name) in files {
301                    self.open_file(*mode, name, *pos)?;
302                }
303            }
304            Stmt::Close { files, pos } => {
305                for (name, closing) in files {
306                    self.close_file_with(name, *closing, *pos)?;
307                }
308            }
309            Stmt::Read(r) => return self.read_stmt(r),
310            Stmt::Write { record, from, advancing, invalid, end_of_page, pos } => return self.write_stmt(record, from.as_ref(), advancing.as_ref(), invalid, end_of_page, *pos),
311            Stmt::Rewrite { record, from, invalid, pos } => return self.rewrite_stmt(record, from.as_ref(), invalid, *pos),
312            Stmt::Delete { file, invalid, pos } => return self.delete_stmt(file, invalid, *pos),
313            Stmt::Start { file, key, invalid, pos } => return self.start_stmt(file, key.as_ref(), invalid, *pos),
314            Stmt::Initialize { targets, with, pos } => {
315                let with = with.as_deref().unwrap_or(&NO_PHRASES);
316                for r in targets {
317                    let loc = self.locate(r)?;
318                    if loc.item != usize::MAX {
319                        self.initialize(loc.item, loc.offset, with, *pos)?;
320                        continue;
321                    }
322                    match with.initial_value(Some(DataCategory::Numeric), false) {
323                        Some(InitialValue::Replacing(by)) => {
324                            let (val, src) = self.operand_with_loc(by, *pos)?;
325                            self.assign(loc, val, src, *pos)?;
326                        }
327                        Some(_) => self.write(loc, &vec![0; loc.len]),
328                        None => {}
329                    }
330                }
331            }
332            Stmt::GoTo { target: Some(target), pos } => return Ok(Flow::GoTo(self.procedure(target, *pos)?.0)),
333            Stmt::GoTo { target: None, .. } | Stmt::Entry { .. } => {}
334            Stmt::GoToDepending { targets, on, pos } => {
335                let n = self.integer(&Expr::Operand(Operand::Ref(on.clone())), *pos)?;
336                if let Some(target) = usize::try_from(n).ok().and_then(|n| targets.get(n.wrapping_sub(1))) {
337                    return Ok(Flow::GoTo(self.procedure(target, *pos)?.0));
338                }
339            }
340            Stmt::Alter { pairs, pos } => {
341                for (paragraph, target) in pairs {
342                    let (at, to) = (self.procedure(paragraph, *pos)?.0, self.procedure(target, *pos)?.0);
343                    let paragraphs = self.program.paragraphs.len();
344                    let altered = &mut self.unit.programs[self.me].altered;
345                    altered.resize(paragraphs, None);
346                    altered[at] = Some(to);
347                }
348                if let Some(flow) = self.debug_alter(pairs, *pos)? {
349                    return Ok(flow);
350                }
351            }
352            Stmt::Goback { .. } => return Ok(Flow::End(Ending::Goback)),
353            Stmt::ExitProgram { .. } if self.main => {}
354            Stmt::ExitProgram { .. } => return Ok(Flow::End(Ending::Goback)),
355            Stmt::Call(c) => return self.call(c),
356            Stmt::Cancel { targets, pos } => {
357                for t in targets {
358                    let name = self.program_name(t, *pos)?;
359                    self.cancel(&name, *pos)?;
360                }
361            }
362            Stmt::Set { set, pos } => self.set(set, *pos)?,
363            Stmt::Accept { target, from, pos } => self.accept(target, *from, *pos)?,
364            Stmt::String(st) => return self.string_stmt(st),
365            Stmt::Unstring(u) => return self.unstring(u),
366            Stmt::Inspect(i) => self.inspect(i)?,
367            Stmt::Search(se) => return self.search(se),
368            Stmt::Sorting(s) => return self.sorting(s),
369            Stmt::NextSentence => return Ok(Flow::NextSentence),
370            Stmt::Exec(block) if block.declarative() => {}
371            Stmt::Exec(block) if block.kind == ExecKind::Cics => return self.cics(block),
372            Stmt::Exec(block) if block.kind == ExecKind::Sql => return self.sql(block),
373            Stmt::Report(r) => return self.report_statement(r),
374            Stmt::Exec(block) => {
375                let kind = match block.kind {
376                    ExecKind::Sql => "SQL",
377                    ExecKind::Cics => "CICS",
378                    ExecKind::Dli => "DLI",
379                    ExecKind::Other => "",
380                };
381                return Err(Abend {
382                    code: AbendCode::Exec,
383                    message: format!("EXEC {kind} {} was reached: ironwork for COBOL checks EXEC statements but does not run them yet", block.command),
384                    pos: block.pos,
385                    file: None,
386                });
387            }
388            Stmt::Invoke(i) => return self.invoke(i),
389            Stmt::JsonGenerate(g) => return self.json_generate(g),
390            Stmt::XmlParse(x) => return self.xml_parse(x),
391            Stmt::XmlGenerate(x) => return self.xml_generate(x),
392            Stmt::JsonParse(j) => return self.json_parse(j),
393            Stmt::ExitMethod { .. } => return Ok(Flow::End(Ending::Goback)),
394            Stmt::SentenceEnd => {}
395            Stmt::StopRun { .. } => return Ok(Flow::End(Ending::StopRun)),
396            Stmt::Exit { kind: ExitKind::Paragraph, .. } => return Ok(Flow::ExitParagraph),
397            Stmt::Exit { kind: ExitKind::Section, .. } => return Ok(Flow::ExitSection),
398            Stmt::Exit { kind: ExitKind::Perform, .. } => return Ok(Flow::ExitPerform),
399            Stmt::Exit { kind: ExitKind::PerformCycle, .. } => return Ok(Flow::ExitPerformCycle),
400            Stmt::Continue | Stmt::Exit { kind: ExitKind::Plain, .. } => {}
401        }
402        Ok(Flow::Next)
403    }
404
405    fn alternative_matches(&mut self, subjects: &[Subject], objects: &[Object], pos: Pos) -> R<bool> {
406        for (subject, object) in subjects.iter().zip(objects) {
407            let hit = match (subject, object) {
408                (_, Object::Any) => true,
409                (Subject::Bool(b), Object::Bool(o)) => b == o,
410                (Subject::Bool(b), Object::Cond(c)) => self.condition(c, pos)? == *b,
411                (Subject::Cond(c), Object::Bool(o)) => self.condition(c, pos)? == *o,
412                (Subject::Cond(c), Object::Cond(d)) => self.condition(c, pos)? == self.condition(d, pos)?,
413                (Subject::Expr(e), Object::Value { not, from, thru }) => {
414                    let inside = match thru {
415                        None => self.compare(e, from, pos)? == Ordering::Equal,
416                        Some(t) => self.compare(e, from, pos)? != Ordering::Less && self.compare(e, t, pos)? != Ordering::Greater,
417                    };
418                    inside != *not
419                }
420                _ => return Err(Abend::ironwork("a WHEN object of a different kind from its subject", pos)),
421            };
422            if !hit {
423                return Ok(false);
424            }
425        }
426        Ok(true)
427    }
428
429    /// Runs a PERFORM's body as its phrase says. EXIT PERFORM leaves the loop; EXIT PERFORM CYCLE
430    /// ends one iteration.
431    fn repeat(&mut self, repeat: &'p Loop, pos: Pos, body: &mut dyn FnMut(&mut Self) -> R<Flow>) -> R<Flow> {
432        self.nest(pos)?;
433        let flow = self.repeat_nested(repeat, pos, body);
434        self.unit.depth -= 1;
435        flow
436    }
437
438    fn nest(&mut self, pos: Pos) -> R<()> {
439        self.unit.enter(pos)
440    }
441
442    fn repeat_nested(&mut self, repeat: &'p Loop, pos: Pos, body: &mut dyn FnMut(&mut Self) -> R<Flow>) -> R<Flow> {
443        let mut run = |m: &mut Self| -> R<Step> {
444            Ok(match body(m)? {
445                Flow::Next | Flow::ExitPerformCycle => Step::Again,
446                Flow::ExitPerform => Step::Leave,
447                other => Step::Out(other),
448            })
449        };
450        match repeat {
451            Loop::Once => match run(self)? {
452                Step::Out(f) => return Ok(f),
453                Step::Again | Step::Leave => {}
454            },
455            Loop::Times(count) => {
456                for _ in 0..self.integer(count, pos)?.max(0) {
457                    match run(self)? {
458                        Step::Again => {}
459                        Step::Leave => break,
460                        Step::Out(f) => return Ok(f),
461                    }
462                }
463            }
464            Loop::Until { cond, test_after } => loop {
465                if !test_after && self.condition(cond, pos)? {
466                    break;
467                }
468                match run(self)? {
469                    Step::Again => {}
470                    Step::Leave => break,
471                    Step::Out(f) => return Ok(f),
472                }
473                if *test_after && self.condition(cond, pos)? {
474                    break;
475                }
476            },
477            Loop::Varying { varying, after, test_after } => {
478                let levels: Vec<&'p Varying> = std::iter::once(&**varying).chain(after).collect();
479                let count = if *test_after { 1 } else { levels.len() };
480                for v in &levels[..count] {
481                    self.vary_from(v, pos)?;
482                }
483                if let Step::Out(f) = self.vary(&levels, *test_after, pos, &mut run)? {
484                    return Ok(f);
485                }
486            }
487        }
488        Ok(Flow::Next)
489    }
490
491    /// The loop of `levels[0]`, each pass running the loops of the levels inside it, in the order of
492    /// the Language Reference's figures for TEST BEFORE and TEST AFTER (SC27-8713-03, pp. 425-428):
493    /// an outer variable is augmented before the one inside it is set to its FROM value again.
494    fn vary(&mut self, levels: &[&'p Varying], test_after: bool, pos: Pos, run: &mut dyn FnMut(&mut Self) -> R<Step>) -> R<Step> {
495        let Some((level, inner)) = levels.split_first() else { return run(self) };
496        loop {
497            if !test_after && self.condition(&level.until, pos)? {
498                return Ok(Step::Again);
499            }
500            if test_after && let Some(next) = inner.first() {
501                self.vary_from(next, pos)?;
502            }
503            match self.vary(inner, test_after, pos, run)? {
504                Step::Again => {}
505                other => return Ok(other),
506            }
507            if test_after && self.condition(&level.until, pos)? {
508                return Ok(Step::Again);
509            }
510            self.vary_by(level, pos)?;
511            if !test_after && let Some(next) = inner.first() {
512                self.vary_from(next, pos)?;
513            }
514        }
515    }
516
517    fn vary_from(&mut self, v: &Varying, pos: Pos) -> R<()> {
518        let var = self.locate(&v.var)?;
519        let start = self.expr_value(&v.from, pos)?;
520        self.assign(var, start, None, pos)
521    }
522
523    fn vary_by(&mut self, v: &Varying, pos: Pos) -> R<()> {
524        let var = self.locate(&v.var)?;
525        let step = Expr::Bin(Box::new(Expr::Operand(Operand::Ref(v.var.clone()))), BinOp::Add, Box::new(v.by.clone()));
526        let dmax = var.kind.digits_scale().map_or(0, |(_, s)| s).max(self.dmax(&step)?);
527        let next = self.eval_fixed(&step, dmax, pos)?;
528        self.store_fixed(var, &next, false, pos)
529    }
530
531    fn resolve(&mut self, r: &Ref) -> R<Resolved> {
532        let key = (r.name.clone(), r.qualifiers.clone());
533        if let Some(&hit) = self.resolved.get(&key) {
534            return Ok(hit);
535        }
536        let found = self.layout.resolve(&r.name, &r.qualifiers, r.pos).map_err(|e| Abend::ironwork(e.message, r.pos))?;
537        self.resolved.insert(key, found);
538        Ok(found)
539    }
540
541    fn locate(&mut self, r: &Ref) -> R<Loc> {
542        self.locate_as(r, false)
543    }
544
545    /// The receiving item of MOVE, ACCEPT, STRING, UNSTRING, READ and RETURN INTO, and WRITE,
546    /// REWRITE and RELEASE FROM: a group holding the object of its own OCCURS DEPENDING ON is its
547    /// maximum length (Language Reference SC27-8713-03, pp. 205-206).
548    fn locate_receiving(&mut self, r: &Ref) -> R<Loc> {
549        self.locate_as(r, true)
550    }
551
552    fn locate_as(&mut self, r: &Ref, receiving: bool) -> R<Loc> {
553        if let Some(loc) = self.oo_register(r)?.or(self.xml_register(r)?) {
554            return Ok(loc);
555        }
556        if r.name == "RETURN-CODE" && r.qualifiers.is_empty() && !self.layout.items.iter().any(|i| i.name.as_deref() == Some("RETURN-CODE")) {
557            return Ok(Loc { offset: RETURN_CODE, len: 2, kind: Kind::Binary { digits: 4, scale: 0, signed: true, native: false }, item: usize::MAX });
558        }
559        let Resolved::Item(index) = self.resolve(r)? else {
560            return Err(Abend::ironwork(format!("{} is a condition-name, not a data item", r.name), r.pos));
561        };
562        self.locate_item(index, r, receiving)
563    }
564
565    /// Item `index` with `r`'s subscripts and reference modification, `r` naming it in messages:
566    /// how a condition-name reaches its conditional variable, which may be FILLER or share its
567    /// name with other items.
568    fn locate_item(&mut self, index: usize, r: &Ref, receiving: bool) -> R<Loc> {
569        let layout = self.layout;
570        let item = &layout.items[index];
571        if r.subscripts.len() != item.dims.len() {
572            return Err(Abend::ironwork(format!("{} takes {} subscripts, not {}", r.name, item.dims.len(), r.subscripts.len()), r.pos));
573        }
574        let base = match item.linkage {
575            Some(l) => loc::linkage_base(self.linkage[l as usize], &r.name, r.pos)?,
576            None if item.local => self.local_base,
577            None => self.base,
578        };
579        let mut offset = (base + item.offset as usize) as i64;
580        for &t in &item.moved_by {
581            offset -= self.unused(t, r.pos)?;
582        }
583        for (&(stride, count), sub) in item.dims.iter().zip(&r.subscripts) {
584            let s = self.integer(sub, r.pos)?;
585            offset += loc::subscript(s, stride, self.ssrange.then_some(count), &r.name, r.pos)?;
586        }
587        let (mut len, mut kind) = (item.size as i64, item.kind);
588        if !item.odo.is_empty() && !(receiving && r.refmod.is_none() && !item.followed && self.objects_within(&item.odo, index)?) {
589            for &t in &item.odo {
590                len -= self.unused(t, r.pos)?;
591            }
592        }
593        if let Some(rm) = &r.refmod {
594            let start = self.integer(&rm.start, r.pos)?;
595            let length = match &rm.length {
596                Some(l) => Some(self.integer(l, r.pos)?),
597                None => None,
598            };
599            // A national item's character positions are two bytes, and a part of it is national.
600            let unit = if kind == Kind::National { 2 } else { 1 };
601            let (from, length) = loc::refmod(len / unit, start, length, self.ssrange, &r.name, r.pos)?;
602            offset += from * unit;
603            len = length * unit;
604            if kind != Kind::National {
605                kind = Kind::Alnum { justified: false };
606            }
607        }
608        let (offset, len) = loc::within(offset, len, self.unit.mem.len(), &r.name, r.pos)?;
609        Ok(Loc { offset, len, kind, item: index })
610    }
611
612    /// Whether the objects of these tables' OCCURS DEPENDING ON all lie within item `group`.
613    fn objects_within(&mut self, tables: &[usize], group: usize) -> R<bool> {
614        for &t in tables {
615            if !self.object_within(t, group)? {
616                return Ok(false);
617            }
618        }
619        Ok(true)
620    }
621
622    /// The bytes of OCCURS DEPENDING ON table `t` past its current count of occurrences.
623    fn unused(&mut self, t: usize, pos: Pos) -> R<i64> {
624        let table = &self.layout.items[t];
625        let (max, element) = (table.occurs, table.size);
626        let current = self.occurrences(t, pos)?;
627        Ok(-loc::odo_len(0, max, current, element))
628    }
629
630    /// The bytes the OCCURS DEPENDING ON tables ahead of item `member` in its record, and not ahead
631    /// of the group `holder` it is in, move it back from where the group's layout puts it.
632    fn moved_within(&mut self, member: usize, holder: usize, pos: Pos) -> R<usize> {
633        let layout = self.layout;
634        let mut moved = 0;
635        for &t in layout.items[member].moved_by.iter().filter(|t| !layout.items[holder].moved_by.contains(t)) {
636            moved += self.unused(t, pos)? as usize;
637        }
638        Ok(moved)
639    }
640
641    /// Whether the object of table `t`'s OCCURS DEPENDING ON lies within item `group`.
642    fn object_within(&mut self, t: usize, group: usize) -> R<bool> {
643        let layout = self.layout;
644        let Some(object) = &layout.items[t].depending_on else { return Ok(false) };
645        let Resolved::Item(mut at) = self.resolve(object)? else { return Ok(false) };
646        loop {
647            if at == group {
648                return Ok(true);
649            }
650            match layout.items[at].parent {
651                Some(p) => at = p,
652                None => return Ok(false),
653            }
654        }
655    }
656
657    /// The current count of an OCCURS DEPENDING ON table, kept within its declared maximum so that
658    /// a bad count never reaches past the table's storage.
659    fn occurrences(&mut self, table: usize, pos: Pos) -> R<u32> {
660        let layout = self.layout;
661        let item = &layout.items[table];
662        let Some(object) = &item.depending_on else { return Ok(item.occurs) };
663        let count = self.integer(&Expr::Operand(Operand::Ref(object.clone())), pos)?;
664        loc::occurrences(count, item.occurs, self.ssrange, &object.name, pos)
665    }
666
667    fn bytes(&self, loc: Loc) -> &[u8] {
668        store::bytes(&self.unit.mem, loc)
669    }
670
671    fn write(&mut self, loc: Loc, bytes: &[u8]) {
672        store::write(&mut self.unit.mem, loc, bytes);
673    }
674
675    fn integer(&mut self, e: &Expr, pos: Pos) -> R<i64> {
676        let dmax = self.dmax(e)?;
677        let v = self.eval_fixed(e, dmax, pos)?;
678        let whole = align(&v, 0, false).and_then(|m| m.to_u128()).and_then(|m| i64::try_from(m).ok());
679        let whole = whole.ok_or_else(|| Abend::ironwork("an integer operand beyond 64 bits", pos))?;
680        Ok(if v.negative { -whole } else { whole })
681    }
682
683    fn literal_value(&self, lit: &Literal, pos: Pos) -> R<Val> {
684        Ok(match lit {
685            Literal::Alnum(s) => Val::Bytes(self.page.encode(s).map_err(|e| Abend::ironwork(e.to_string(), pos))?),
686            Literal::Hex(b) => Val::Bytes(b.clone()),
687            Literal::National(s) => Val::National(s.encode_utf16().flat_map(u16::to_be_bytes).collect()),
688            Literal::Number(t) => Val::Num(literal_fixed(t).ok_or_else(|| Abend::ironwork(format!("the literal {t} has more than 31 digits"), pos))?),
689            Literal::Figurative(f) => Val::Fig(*f),
690            Literal::All(inner) => match self.literal_value(inner, pos)? {
691                Val::Bytes(b) => Val::All(b),
692                Val::Fig(f) => Val::Fig(f),
693                _ => return Err(Abend::ironwork("ALL takes an alphanumeric literal", pos)),
694            },
695        })
696    }
697
698    /// What a DECIMAL-POINT IS COMMA program shows for a decimal point.
699    fn decimal_point(&self) -> char {
700        if self.program.environment.decimal_point_comma { ',' } else { '.' }
701    }
702
703    fn default_currency(&self) -> String {
704        numval_currency(&self.program.environment.currency)
705    }
706
707    fn read(&self, loc: Loc, pos: Pos) -> R<Val> {
708        store::read(&self.facts(), &self.unit.mem, loc, pos)
709    }
710
711    fn operand_with_loc(&mut self, op: &Operand, pos: Pos) -> R<(Val, Option<Loc>)> {
712        if let Operand::Ref(r) = op {
713            let loc = self.locate(r)?;
714            self.numcheck(loc, false, r.pos)?;
715            return Ok((self.read(loc, r.pos)?, Some(loc)));
716        }
717        Ok((self.operand(op, pos)?, None))
718    }
719
720    /// NUMCHECK's test of a sending item, under the option (`rt::store::numcheck`).
721    fn numcheck(&mut self, loc: Loc, as_integer: bool, pos: Pos) -> R<()> {
722        if self.options.numcheck.is_none() {
723            return Ok(());
724        }
725        let facts = self.facts();
726        rt::store::numcheck(&facts, self.unit, loc, as_integer, &self.program.id, pos)
727    }
728
729    /// A MOVE's sender: NUMCHECK tests a zoned or packed sender, and an alphanumeric one moved to a
730    /// numeric receiver as an integer; under ZON(LAX) a zoned sender moved to a zoned or
731    /// alphanumeric receiver is not tested (Programming Guide SC27-8714-03, pp. 388-391).
732    fn move_source(&mut self, from: &Operand, dest: Loc, pos: Pos) -> R<(Val, Option<Loc>)> {
733        let Operand::Ref(r) = from else { return self.operand_with_loc(from, pos) };
734        let loc = self.locate(r)?;
735        let receiver_numeric = matches!(dest.kind, Kind::Zoned { .. } | Kind::Packed { .. } | Kind::Binary { .. } | Kind::Float(_) | Kind::NumericEdited { .. });
736        let lax = self.options.numcheck.and_then(|c| c.zon).is_some_and(|z| z.lax);
737        let exempt = lax && matches!(loc.kind, Kind::Zoned { .. }) && matches!(dest.kind, Kind::Zoned { .. } | Kind::Alnum { .. } | Kind::Group);
738        if !exempt {
739            self.numcheck(loc, receiver_numeric && matches!(loc.kind, Kind::Alnum { .. } | Kind::Group), r.pos)?;
740        }
741        Ok((store::move_sender(&self.facts(), &self.unit.mem, loc, dest, r.pos)?, Some(loc)))
742    }
743
744    fn operand(&mut self, op: &Operand, pos: Pos) -> R<Val> {
745        match op {
746            Operand::Ref(r) => {
747                let loc = self.locate(r)?;
748                self.numcheck(loc, false, r.pos)?;
749                self.read(loc, r.pos)
750            }
751            Operand::Literal(lit) => self.literal_value(lit, pos),
752            Operand::LengthOf(r) => {
753                let layout = self.layout;
754                let loc = self.locate(&layout.length_of_ref(r))?;
755                Ok(Val::Num(Fixed::new(loc.len as i128, Places::new(9, 0))))
756            }
757            Operand::Function(f) => self.function(f),
758            Operand::AddressOf(r) => Ok(Val::Address(self.address_of(r)?)),
759        }
760    }
761
762    fn set_integer(&mut self, r: &Ref, value: i64, pos: Pos) -> R<()> {
763        let dest = self.locate(r)?;
764        store::set_integer(&self.facts(), self.unit, dest, value, pos)
765    }
766
767    fn overflow_branch(&mut self, overflow: bool, on: &'p Option<Vec<Stmt>>, not_on: &'p Option<Vec<Stmt>>) -> R<Flow> {
768        match (overflow, on, not_on) {
769            (true, Some(body), _) | (false, _, Some(body)) => self.run_block(body),
770            _ => Ok(Flow::Next),
771        }
772    }
773
774    fn string_stmt(&mut self, st: &'p StringStmt) -> R<Flow> {
775        let sources: Vec<_> = st.sources.iter().map(|(op, d)| StringSource { chars: facts::chars(op), delimiter: match d {
776            Delimiter::Size => None,
777            Delimiter::By(d) => Some(facts::chars(d)),
778        } }).collect();
779        let overflow = rt::text::string(self, &st.into, st.pointer.as_ref(), &sources, st.pos)?;
780        self.overflow_branch(overflow, &st.on_overflow, &st.not_on_overflow)
781    }
782
783    fn unstring(&mut self, u: &'p Unstring) -> R<Flow> {
784        let delimiters: Vec<_> = u.delimiters.iter().map(|(all, d)| (*all, facts::chars(d))).collect();
785        let into: Vec<_> = u.into.iter().map(|i| UnstringField { target: &i.target, delimiter: i.delimiter_in.as_ref(), count: i.count_in.as_ref() }).collect();
786        let overflow = rt::text::unstring(self, &u.source, u.pointer.as_ref(), &delimiters, &into, u.tallying.as_ref(), u.pos)?;
787        self.overflow_branch(overflow, &u.on_overflow, &u.not_on_overflow)
788    }
789
790    fn inspect(&mut self, i: &Inspect) -> R<()> {
791        let tallying: Vec<_> = i.tallying.iter().map(|p| self.inspect_phrase(p)).collect();
792        let Operand::Ref(target) = &i.target else {
793            return rt::text::tally(self, &&i.target, &tallying, i.pos);
794        };
795        let replacing: Vec<_> = i.replacing.iter().map(|p| self.inspect_phrase(p)).collect();
796        let converting = i.converting.as_ref().map(|(from, to, bounds)| Converting { table: ConvertTable::Operands { from: facts::chars(from), to: facts::chars(to) }, bounds: facts::bounds(bounds) });
797        rt::text::inspect(self, target, &tallying, &replacing, converting.as_ref(), i.pos)
798    }
799
800    fn search(&mut self, se: &'p Search) -> R<Flow> {
801        let pos = se.pos;
802        let Resolved::Item(t) = self.resolve(&se.table)? else {
803            return Err(Abend::ironwork(format!("SEARCH {}: not a table", se.table.name), pos));
804        };
805        let layout = self.layout;
806        let table = &layout.items[t];
807        let count = self.occurrences(t, pos)? as i64;
808        let index = match (&se.varying, table.index_names.first()) {
809            (_, Some(name)) => Ref { name: name.clone(), qualifiers: Vec::new(), subscripts: Vec::new(), refmod: None, pos },
810            (Some(v), None) => v.clone(),
811            (None, None) => return Err(Abend::ironwork(format!("SEARCH {}: the table has no INDEXED BY", se.table.name), pos)),
812        };
813        let index_expr = Expr::Operand(Operand::Ref(index.clone()));
814        if !se.all {
815            loop {
816                let i = self.integer(&index_expr, pos)?;
817                if i < 1 || i > count {
818                    return match &se.at_end {
819                        Some(body) => self.run_block(body),
820                        None => Ok(Flow::Next),
821                    };
822                }
823                for (cond, body) in &se.whens {
824                    if self.condition(cond, pos)? {
825                        return self.run_block(body);
826                    }
827                }
828                self.set_integer(&index, i + 1, pos)?;
829                if let Some(v) = &se.varying
830                    && v.name != index.name
831                {
832                    let current = self.integer(&Expr::Operand(Operand::Ref(v.clone())), pos)?;
833                    self.set_integer(v, current + 1, pos)?;
834                }
835            }
836        }
837        let (cond, body) = &se.whens[0];
838        let mut terms = Vec::new();
839        flatten_and(cond, &mut terms);
840        let (mut low, mut high) = (1i64, count);
841        while low <= high {
842            let mid = (low + high) / 2;
843            self.set_integer(&index, mid, pos)?;
844            let mut order = Ordering::Equal;
845            for (ascending, key) in &table.keys {
846                let Some((subject, value)) = key_term(&terms, &key.name) else { continue };
847                let o = self.compare(subject, value, pos)?;
848                order = if *ascending { o } else { o.reverse() };
849                if order != Ordering::Equal {
850                    break;
851                }
852            }
853            match order {
854                Ordering::Less => low = mid + 1,
855                Ordering::Greater => high = mid - 1,
856                Ordering::Equal => {
857                    return if self.condition(cond, pos)? {
858                        self.run_block(body)
859                    } else {
860                        match &se.at_end {
861                            Some(b) => self.run_block(b),
862                            None => Ok(Flow::Next),
863                        }
864                    };
865                }
866            }
867        }
868        match &se.at_end {
869            Some(b) => self.run_block(b),
870            None => Ok(Flow::Next),
871        }
872    }
873
874    /// ADDRESS OF: the item's address, or NULL for a LINKAGE record with none yet.
875    fn address_of(&mut self, r: &Ref) -> R<u32> {
876        if let Ok(Resolved::Item(i)) = self.resolve(r)
877            && let Some(l) = self.layout.items[i].linkage
878            && self.linkage[l as usize].is_none()
879        {
880            return Ok(0);
881        }
882        let loc = self.locate(r)?;
883        Ok(ADDRESS_BASE + loc.offset as u32)
884    }
885
886    fn program_name(&mut self, op: &Operand, pos: Pos) -> R<String> {
887        Ok(match self.operand(op, pos)? {
888            Val::Bytes(b) => self.page.decode(&b).trim().to_ascii_uppercase(),
889            _ => return Err(Abend::ironwork("a program name must be alphanumeric", pos)),
890        })
891    }
892
893    /// Tells the observer, for the input trace, the operand of an operation an input could steer.
894    pub(crate) fn sink(&mut self, kind: &'static str, pos: Pos, operand: &str) {
895        let file = self.event_file(pos);
896        self.unit.notify(Event::Sink { kind, file: &file, line: pos.line, operand });
897    }
898
899    /// The file an event names for `pos`: a library program's own source by its path, a COPY
900    /// member by the program's file table, and the first program's own source as empty.
901    fn event_file(&self, pos: Pos) -> String {
902        match (pos.file, &self.unit.programs[self.me].source) {
903            (0, Some(path)) => path.to_str().unwrap_or_default().to_owned(),
904            (i, _) => self.program.sources.get(i as usize).cloned().unwrap_or_default(),
905        }
906    }
907
908    /// An abend from a program CALL or LINK loaded from a library, named by that program's files,
909    /// which the caller's file table would misname.
910    pub(crate) fn in_loaded(&self, index: usize, compiled: &Compiled, mut abend: Abend) -> Abend {
911        if abend.file.is_none()
912            && let Some(source) = &self.unit.programs[index].source
913        {
914            abend.file = Some(match abend.pos.file {
915                0 => source.display().to_string(),
916                i => compiled.program.sources.get(i as usize).cloned().unwrap_or_default(),
917            });
918        }
919        abend
920    }
921
922    /// The bytes a CALL passes, one argument after another, as the code page reads them.
923    fn arguments_text(&mut self, c: &Call) -> R<String> {
924        let mut text = String::new();
925        for op in c.using.iter().filter_map(|a| a.value.as_ref()) {
926            let bytes = match op {
927                Operand::Ref(r) => {
928                    let loc = self.locate(r)?;
929                    self.bytes(loc).to_vec()
930                }
931                _ => self.content_argument(op, c.pos)?,
932            };
933            text.push_str(&self.page.decode(&bytes));
934        }
935        Ok(text)
936    }
937
938    fn call(&mut self, c: &'p Call) -> R<Flow> {
939        let pos = c.pos;
940        let (name, dynamic) = match self.entry_pointer(&c.target)? {
941            Some(entry) => (entry.name, entry.dynamic),
942            None => {
943                if let Some(flow) = self.call_through_pointer(c)? {
944                    return Ok(flow);
945                }
946                let name = self.program_name(&c.target, pos)?;
947                let variable = !matches!(c.target, Operand::Literal(_));
948                if variable && self.unit.observed() {
949                    self.sink("dynamic-program-load", pos, &name);
950                }
951                (name, self.options.dynam || variable)
952            }
953        };
954        // ironwork runs no operating-system command: the CALL loads a program of that name or fails.
955        if self.unit.observed()
956            && OS_COMMAND_ROUTINES.contains(&name.as_str())
957            && let Ok(text) = self.arguments_text(c)
958        {
959            self.sink("os-command", pos, &text);
960        }
961        let (index, entry) = match self.unit.load_entry(&name, dynamic) {
962            Ok(i) => i,
963            Err(LoadError::NotFound) if crate::le::provides(&name) => return self.le_call(c, &name),
964            Err(LoadError::NotFound) => {
965                if let Some(flow) = self.virtual_print(c, &name)? {
966                    return Ok(flow);
967                }
968                return match &c.on_exception {
969                    Some(body) => self.run_block(body),
970                    None => Err(Abend { code: AbendCode::ModuleNotFound, message: crate::le::missing(&name), pos, file: None }),
971                };
972            }
973            Err(LoadError::Compile(message)) => return Err(Abend::ironwork(format!("CALL {name}: {message}"), pos)),
974        };
975        let Some(compiled) = self.unit.programs[index].compiled.clone() else {
976            return Err(Abend::ironwork(format!("CALL {name}: the first program of the run unit is already active"), pos));
977        };
978        self.unit.programs[index].dynamic |= dynamic;
979        if self.unit.programs[index].active && !compiled.program.recursive {
980            return Err(Abend::ironwork(format!("CALL {name}: the program is already active and is not RECURSIVE"), pos));
981        }
982        self.nest(pos)?;
983        let result = self.call_nested(c, index, entry, compiled);
984        self.unit.depth -= 1;
985        result
986    }
987
988    /// A CALL no library answers that the virtual printer serves: SYSTEM or C$SYSTEM with an lp or
989    /// lpr command, in a run given DD PRINTER. It returns lp's status, 0 printed or 1 not, through
990    /// RETURNING or else RETURN-CODE.
991    fn virtual_print(&mut self, c: &'p Call, name: &str) -> R<Option<Flow>> {
992        use rt::virtual_printer::{self, Job};
993        if !virtual_printer::ROUTINES.contains(&name) {
994            return Ok(None);
995        }
996        let Some(printer) = self.unit.dds.get(virtual_printer::DD) else { return Ok(None) };
997        let Some(job) = self.arguments_text(c).ok().as_deref().and_then(Job::parse) else { return Ok(None) };
998        let dds = self.unit.dds.clone();
999        let status: i16 = match virtual_printer::print(&dds, &printer, &job, &mut |event| self.unit.notify(event)) {
1000            Ok(()) => 0,
1001            Err(why) => {
1002                let _ = writeln!(self.unit.err, "ironwork: {}: CALL {name}: the virtual printer printed nothing: {why}", c.pos);
1003                1
1004            }
1005        };
1006        match &c.returning {
1007            Some(target) => {
1008                let dest = self.locate(target)?;
1009                self.assign(dest, Val::Num(Fixed::new(i128::from(status), Places::new(9, 0))), None, c.pos)?;
1010            }
1011            None => self.unit.mem[RETURN_CODE..RETURN_CODE + 2].copy_from_slice(&status.to_be_bytes()),
1012        }
1013        Ok(Some(match &c.not_on_exception {
1014            Some(body) => self.run_block(body)?,
1015            None => Flow::Next,
1016        }))
1017    }
1018
1019    /// The entry a CALL's function-pointer or procedure-pointer holds, when SET TO ENTRY set it.
1020    fn entry_pointer(&mut self, target: &Operand) -> R<Option<rt::set::Entry>> {
1021        let Operand::Ref(r) = target else { return Ok(None) };
1022        let Ok(Resolved::Item(item)) = self.resolve(r) else { return Ok(None) };
1023        if self.layout.items[item].kind != Kind::ProgramPointer {
1024            return Ok(None);
1025        }
1026        let loc = self.locate(r)?;
1027        let Ok(value) = <[u8; 4]>::try_from(self.bytes(loc)).map(u32::from_be_bytes) else { return Ok(None) };
1028        Ok(rt::set::entry_of(&self.unit.entries, value).cloned())
1029    }
1030
1031    /// SET TO ENTRY's entry: its name and whether a CALL through it is dynamic, loaded when the
1032    /// SET runs (C140).
1033    fn entry_named(&mut self, entry: &Operand, pos: Pos) -> R<(String, bool)> {
1034        let name = self.program_name(entry, pos)?;
1035        let variable = !matches!(entry, Operand::Literal(_));
1036        if variable && self.unit.observed() {
1037            self.sink("dynamic-program-load", pos, &name);
1038        }
1039        let dynamic = self.options.dynam || variable;
1040        match self.unit.load_entry(&name, dynamic) {
1041            Ok(_) => Ok((name, dynamic)),
1042            Err(LoadError::NotFound) if crate::le::provides(&name) => Ok((name, dynamic)),
1043            Err(LoadError::NotFound) => Err(Abend { code: AbendCode::ModuleNotFound, message: crate::le::missing(&name), pos, file: None }),
1044            Err(LoadError::Compile(message)) => Err(Abend::ironwork(format!("SET TO ENTRY {name}: {message}"), pos)),
1045        }
1046    }
1047
1048    fn call_nested(&mut self, c: &'p Call, index: usize, entry: Option<usize>, compiled: std::rc::Rc<Compiled>) -> R<Flow> {
1049        let pos = c.pos;
1050        let mark = self.unit.mem.len();
1051        let mut addresses = Vec::new();
1052        for arg in &c.using {
1053            let Some(op) = &arg.value else {
1054                addresses.push(None);
1055                continue;
1056            };
1057            let at = match (arg.mode, op) {
1058                (ArgMode::Reference, Operand::Ref(r)) => {
1059                    let loc = self.locate(r)?;
1060                    loc.offset
1061                }
1062                (ArgMode::Value, _) => {
1063                    let bytes = self.value_argument(op, pos)?;
1064                    self.unit.push_temporary(&bytes)
1065                }
1066                (_, _) => {
1067                    let bytes = self.content_argument(op, pos)?;
1068                    self.unit.push_temporary(&bytes)
1069                }
1070            };
1071            addresses.push(Some(at));
1072        }
1073        self.parmcheck_set();
1074        let outcome = {
1075            let containers = self.containers_of(&compiled.program);
1076            let mut callee = Machine::activation_within(&compiled, index, &mut *self.unit, false, containers)?;
1077            let entry = entry.and_then(|k| compiled.entries.get(k));
1078            callee.bind_using(entry.map_or(&compiled.program.using, |e| &e.using), &addresses);
1079            callee.bind_returning();
1080            let ending = callee.run_from(entry.map(|e| (e.paragraph, e.statement)));
1081            let returned = match (&compiled.program.returning, &ending) {
1082                (Some(item), Ok(_)) => Some(callee.returned(item, pos)?),
1083                _ => None,
1084            };
1085            (ending, returned)
1086        };
1087        self.unit.programs[index].active = false;
1088        // Leaving an INITIAL program is a CANCEL of it (Language Reference SC27-8713-03, p. 349).
1089        if compiled.program.initial {
1090            self.cancel_program(index, pos)?;
1091        }
1092        self.unit.release_temporaries(mark);
1093        let (ending, returned) = outcome;
1094        let ending = ending.map_err(|a| self.in_loaded(index, &compiled, a));
1095        if ending? == Ending::StopRun {
1096            return Ok(Flow::End(Ending::StopRun));
1097        }
1098        self.parmcheck_test(c, &addresses, |m| m.unit.programs[index].name.clone())?;
1099        if let (Some(target), Some(val)) = (&c.returning, returned) {
1100            let dest = self.locate(target)?;
1101            self.assign(dest, val, None, pos)?;
1102        }
1103        match &c.not_on_exception {
1104            Some(body) => self.run_block(body),
1105            None => Ok(Flow::Next),
1106        }
1107    }
1108
1109    /// Gives each PROCEDURE DIVISION USING item the address of the argument in its position.
1110    pub(crate) fn bind(&mut self, addresses: &[Option<usize>]) {
1111        let program = self.program;
1112        self.bind_using(&program.using, addresses);
1113    }
1114
1115    /// Gives each item of a PROCEDURE DIVISION or ENTRY USING list the address of the argument in
1116    /// its position.
1117    fn bind_using(&mut self, using: &[Param], addresses: &[Option<usize>]) {
1118        for (param, address) in using.iter().zip(addresses) {
1119            if let Some(ordinal) = self.layout.linkage_roots.iter().position(|&i| self.layout.items[i].name.as_deref() == Some(param.name.as_str())) {
1120                self.linkage[ordinal] = *address;
1121            }
1122        }
1123    }
1124
1125    /// The RETURNING item is in the LINKAGE SECTION, but no argument addresses it: the runtime
1126    /// gives it storage of its own for the call.
1127    fn bind_returning(&mut self) {
1128        let Some(name) = &self.program.returning else { return };
1129        let Some(ordinal) = self.layout.linkage_roots.iter().position(|&i| self.layout.items[i].name.as_deref() == Some(name.as_str())) else { return };
1130        let size = self.layout.items[self.layout.linkage_roots[ordinal]].size as usize;
1131        self.linkage[ordinal] = Some(self.unit.push_temporary(&vec![0; size]));
1132    }
1133
1134    fn returned(&mut self, name: &str, pos: Pos) -> R<Val> {
1135        let r = Ref { name: name.to_owned(), qualifiers: Vec::new(), subscripts: Vec::new(), refmod: None, pos };
1136        let loc = self.locate(&r)?;
1137        self.read(loc, pos)
1138    }
1139
1140    /// A BY CONTENT argument: a copy of the item, or of the literal as its own data item would hold it.
1141    fn content_argument(&mut self, op: &Operand, pos: Pos) -> R<Vec<u8>> {
1142        if let Operand::Ref(r) = op {
1143            let loc = self.locate(r)?;
1144            self.numcheck(loc, false, r.pos)?;
1145            return Ok(self.bytes(loc).to_vec());
1146        }
1147        Ok(match self.operand(op, pos)? {
1148            Val::Bytes(b) | Val::All(b) | Val::National(b) => b,
1149            Val::Fig(f) => vec![self.collating.figurative(f)],
1150            Val::Address(a) => a.to_be_bytes().to_vec(),
1151            Val::Num(f) if matches!(op, Operand::LengthOf(_)) => (align(&f, 0, false).and_then(|m| m.to_u128()).unwrap_or(0) as u32).to_be_bytes().to_vec(),
1152            Val::Num(f) => {
1153                let digits = f.places.total().max(1);
1154                let magnitude = align(&f, f.places.dec, false).and_then(|m| m.to_u128()).unwrap_or(0);
1155                zoned_digits(magnitude, digits as usize, if f.negative { decimal::MINUS } else { decimal::UNSIGNED })
1156            }
1157            Val::Float(h) => h.to_bytes(),
1158        })
1159    }
1160
1161    /// A BY VALUE argument: an integer as a binary fullword, an address, or the bytes of a one-character item.
1162    fn value_argument(&mut self, op: &Operand, pos: Pos) -> R<Vec<u8>> {
1163        Ok(match self.operand(op, pos)? {
1164            Val::Num(f) => {
1165                let whole = align(&f, 0, false).and_then(|m| m.to_u128()).and_then(|m| i32::try_from(m).ok()).ok_or_else(|| Abend::ironwork("a BY VALUE integer beyond a fullword", pos))?;
1166                (if f.negative { -whole } else { whole }).to_be_bytes().to_vec()
1167            }
1168            Val::Address(a) => a.to_be_bytes().to_vec(),
1169            Val::Fig(Figurative::Null) => vec![0; 4],
1170            Val::Bytes(b) => b,
1171            _ => return Err(Abend::ironwork("this BY VALUE argument is not supported", pos)),
1172        })
1173    }
1174
1175    /// CANCEL of a program a dynamic CALL entered, or of a contained program; a program only ever
1176    /// called statically is left as it is (Language Reference SC27-8713-03, p. 327; Programming
1177    /// Guide SC27-8714-03, pp. 399, 548).
1178    fn cancel(&mut self, name: &str, pos: Pos) -> R<()> {
1179        let Some(index) = self.unit.find(name) else { return Ok(()) };
1180        let target = &self.unit.programs[index].name;
1181        let contained = self.unit.programs.iter().any(|p| p.compiled.as_ref().is_some_and(|c| c.program.nested.contains(target)));
1182        if !self.unit.programs[index].dynamic && !contained {
1183            return Ok(());
1184        }
1185        if self.unit.programs[index].active {
1186            return Err(Abend::ironwork(format!("CANCEL {name}: the program is active"), pos));
1187        }
1188        self.cancel_program(index, pos)
1189    }
1190
1191    /// Closes the files of program `index` and of the programs it contains, each of which next
1192    /// starts in its initial state (pp. 103, 349).
1193    fn cancel_program(&mut self, index: usize, pos: Pos) -> R<()> {
1194        let files: Vec<_> = self.unit.programs[index].files.iter_mut().filter_map(Option::take).collect();
1195        for f in files {
1196            f.close().map_err(|e| Abend::ironwork(format!("CANCEL {}: {e}", self.unit.programs[index].name), pos))?;
1197        }
1198        self.unit.programs[index].initialized = false;
1199        let nested = self.unit.programs[index].compiled.as_ref().map(|c| c.program.nested.clone()).unwrap_or_default();
1200        for name in nested {
1201            if let Some(contained) = self.unit.find(&name) {
1202                self.cancel_program(contained, pos)?;
1203            }
1204        }
1205        Ok(())
1206    }
1207
1208    fn set(&mut self, set: &SetStmt, pos: Pos) -> R<()> {
1209        match set {
1210            SetStmt::ConditionTrue(targets) | SetStmt::ConditionFalse(targets) => {
1211                let truth = matches!(set, SetStmt::ConditionTrue(_));
1212                for r in targets {
1213                    let Resolved::Condition(index) = self.resolve(r)? else {
1214                        return Err(Abend::ironwork(format!("SET {} TO {}: not a condition-name", r.name, if truth { "TRUE" } else { "FALSE" }), pos));
1215                    };
1216                    let condition = &self.layout.conditions[index];
1217                    let value = if truth { condition.values.first().map(|(v, _)| v) } else { condition.false_value.as_ref() };
1218                    let Some(value) = value else { continue };
1219                    let dest = self.locate_item(condition.item, r, false)?;
1220                    let val = self.literal_value(value, pos)?;
1221                    self.assign(dest, val, None, pos)?;
1222                }
1223            }
1224            SetStmt::To { targets, value } => {
1225                for r in targets {
1226                    let dest = self.locate(r)?;
1227                    let (val, src) = self.operand_with_loc(value, pos)?;
1228                    let (val, src) = rt::set::to(dest, val, src, pos)?;
1229                    self.assign(dest, val, src, pos)?;
1230                }
1231            }
1232            SetStmt::Entry { targets, entry } => {
1233                let (name, dynamic) = self.entry_named(entry, pos)?;
1234                let value = rt::set::entry(&mut self.unit.entries, &name, dynamic, pos)?;
1235                for r in targets {
1236                    let dest = self.locate(r)?;
1237                    self.assign(dest, Val::Address(value), None, pos)?;
1238                }
1239            }
1240            SetStmt::AddressOf { targets, value } => {
1241                let val = self.operand(value, pos)?;
1242                let offset = rt::set::address(val, self.unit.mem.len(), pos)?;
1243                for r in targets {
1244                    let Resolved::Item(i) = self.resolve(r)? else {
1245                        return Err(Abend::ironwork(format!("SET ADDRESS OF {}: not a data item", r.name), pos));
1246                    };
1247                    let Some(ordinal) = self.layout.items[i].linkage.filter(|_| self.layout.items[i].parent.is_none()) else {
1248                        return Err(Abend::ironwork(format!("SET ADDRESS OF {}: only a LINKAGE record can be given an address", r.name), pos));
1249                    };
1250                    self.linkage[ordinal as usize] = offset;
1251                }
1252            }
1253            SetStmt::UpDown { targets, down, by } => {
1254                let by = self.integer(by, pos)?;
1255                let targets: Vec<&Ref> = targets.iter().collect();
1256                rt::set::up_down(self, by, *down, &targets, pos)?;
1257            }
1258        }
1259        Ok(())
1260    }
1261
1262    fn accept(&mut self, target: &Ref, from: AcceptFrom, pos: Pos) -> R<()> {
1263        let dest = self.locate_receiving(target)?;
1264        rt::accept::accept(&self.facts(), self.unit, dest, from, &target.name, pos)
1265    }
1266
1267    fn function(&mut self, f: &FunctionCall) -> R<Val> {
1268        if let Some(value) = self.storage_function(f)? {
1269            return self.function_refmod(f, value);
1270        }
1271        let args = self.function_arguments(f)?;
1272        let side = match f.modifier.as_deref() {
1273            Some("LEADING") => Some(TrimSide::Leading),
1274            Some("TRAILING") => Some(TrimSide::Trailing),
1275            _ => None,
1276        };
1277        let value = rt::intrinsic::function::evaluate(&mut intrinsic::Call { machine: self, f }, &f.name, side, args, f.pos)?;
1278        self.function_refmod(f, value)
1279    }
1280
1281    fn function_refmod(&mut self, f: &FunctionCall, value: Val) -> R<Val> {
1282        let pos = f.pos;
1283        let Some(rm) = &f.refmod else { return Ok(value) };
1284        rt::intrinsic::function::refmod(value, pos, || {
1285            let start = self.integer(&rm.start, pos)?;
1286            let length = match &rm.length {
1287                Some(l) => Some(self.integer(l, pos)?),
1288                None => None,
1289            };
1290            Ok((start, length))
1291        })
1292    }
1293
1294    fn expr_value(&mut self, e: &Expr, pos: Pos) -> R<Val> {
1295        match e {
1296            Expr::Operand(op) => self.operand(op, pos),
1297            _ if self.uses_float(e)? => Ok(Val::Float(self.eval_float(e, self.options.arith.float_intermediate(), pos)?)),
1298            _ => {
1299                let dmax = self.dmax(e)?;
1300                Ok(Val::Num(self.eval_fixed(e, dmax, pos)?))
1301            }
1302        }
1303    }
1304
1305    fn operand_kind(&mut self, op: &Operand) -> R<Option<Kind>> {
1306        Ok(match op {
1307            Operand::Ref(r) => Some(self.locate(r)?.kind),
1308            _ => None,
1309        })
1310    }
1311
1312    /// Fixed at lowering as `ArithStep.mode` (lower/plans.rs); the walker decides it on each execution.
1313    fn uses_float(&mut self, e: &Expr) -> R<bool> {
1314        Ok(match e {
1315            Expr::Operand(Operand::Function(f)) => self.is_floating_point(f)?,
1316            Expr::Operand(op) => matches!(self.operand_kind(op)?, Some(Kind::Float(_))),
1317            Expr::Neg(inner) => self.uses_float(inner)?,
1318            Expr::Bin(a, _, b) => self.uses_float(a)? || self.uses_float(b)?,
1319        })
1320    }
1321
1322    /// The most decimal places among an expression's operands, divisors and exponents aside. Fixed at
1323    /// lowering as `ArithPlan.dmax` (lower/plans.rs); the walker works it out on each execution.
1324    fn dmax(&mut self, e: &Expr) -> R<u32> {
1325        Ok(match e {
1326            Expr::Operand(Operand::Literal(Literal::Number(t))) => literal_fixed(t).map_or(0, |f| f.places.dec),
1327            Expr::Operand(op) => self.operand_kind(op)?.and_then(Kind::digits_scale).map_or(0, |(_, s)| s),
1328            Expr::Neg(inner) => self.dmax(inner)?,
1329            Expr::Bin(a, BinOp::Div | BinOp::Pow, _) => self.dmax(a)?,
1330            Expr::Bin(a, _, b) => self.dmax(a)?.max(self.dmax(b)?),
1331        })
1332    }
1333
1334    fn eval_fixed(&mut self, e: &Expr, dmax: u32, pos: Pos) -> R<Fixed> {
1335        let arith = self.options.arith;
1336        match e {
1337            Expr::Operand(op) => {
1338                let val = self.operand(op, pos)?;
1339                arith::fixed_operand(val, dmax, pos)
1340            }
1341            Expr::Neg(inner) => Ok(arith::fixed_neg(self.eval_fixed(inner, dmax, pos)?)),
1342            Expr::Bin(a, op, b) => {
1343                let x = self.eval_fixed(a, dmax, pos)?;
1344                if *op == BinOp::Pow {
1345                    let n = self.integer(b, pos)?;
1346                    return arith::pow(x, n, dmax, arith, pos);
1347                }
1348                let y = self.eval_fixed(b, dmax, pos)?;
1349                if arith::divides_by_zero(*op, &y) {
1350                    let binary = self.binary_division(a, b)?;
1351                    return Err(arith::zero_divide(binary, pos));
1352                }
1353                arith::fixed_binop(x, *op, y, dmax, arith, pos)
1354            }
1355        }
1356    }
1357
1358    /// Whether the compiler divides `a` by `b` with the fixed-point divide instruction: every
1359    /// operand of both an integer binary item or an integer literal, and one of them an item
1360    /// (assumption C55). Otherwise a fixed-point division is decimal.
1361    fn binary_division(&mut self, a: &Expr, b: &Expr) -> R<bool> {
1362        let mut items = 0;
1363        Ok(self.binary_operands(a, &mut items)? && self.binary_operands(b, &mut items)? && items > 0)
1364    }
1365
1366    fn binary_operands(&mut self, e: &Expr, items: &mut usize) -> R<bool> {
1367        Ok(match e {
1368            Expr::Operand(Operand::Literal(Literal::Number(t))) => !t.contains('.'),
1369            Expr::Operand(Operand::Literal(Literal::Figurative(Figurative::Zero))) => true,
1370            Expr::Operand(Operand::LengthOf(_)) => {
1371                *items += 1;
1372                true
1373            }
1374            Expr::Operand(op @ Operand::Ref(_)) => {
1375                let binary = matches!(self.operand_kind(op)?, Some(Kind::Binary { scale: 0, .. } | Kind::Index));
1376                *items += usize::from(binary);
1377                binary
1378            }
1379            Expr::Operand(_) => false,
1380            Expr::Neg(inner) => self.binary_operands(inner, items)?,
1381            Expr::Bin(x, _, y) => self.binary_operands(x, items)? && self.binary_operands(y, items)?,
1382        })
1383    }
1384
1385    fn eval_float(&mut self, e: &Expr, p: Precision, pos: Pos) -> R<Hfp> {
1386        match e {
1387            Expr::Operand(op) => {
1388                let val = self.operand(op, pos)?;
1389                arith::float_operand(val, p, pos)
1390            }
1391            Expr::Neg(inner) => Ok(arith::float_neg(self.eval_float(inner, p, pos)?)),
1392            Expr::Bin(a, op, b) => {
1393                let (x, y) = (self.eval_float(a, p, pos)?, self.eval_float(b, p, pos)?);
1394                arith::float_binop(x, *op, y, p, pos)
1395            }
1396        }
1397    }
1398
1399    /// COMPUTE, ADD, SUBTRACT, MULTIPLY, DIVIDE: what the receivers share is computed before any is
1400    /// stored, then each receiver in turn gets it, or with `per_receiver` combines it with its own
1401    /// current value (Language Reference SC27-8713-03, p. 298, multiple results). A size error
1402    /// leaves the target unchanged when the statement handles it.
1403    fn arithmetic(&mut self, computations: &[(Target, Expr)], remainder: Option<&(Target, Expr, Expr)>, handler: Option<&'p SizeError>, per_receiver: bool, pos: Pos) -> R<Flow> {
1404        let mut size_error = false;
1405        let mut dmax = 0;
1406        // A COMP-1 or COMP-2 receiver makes the statement's arithmetic floating point (Programming
1407        // Guide SC27-8714-03, p. 800).
1408        let mut float_receiver = false;
1409        for (t, e) in computations {
1410            let loc = self.locate(&t.r)?;
1411            float_receiver |= matches!(loc.kind, Kind::Float(_));
1412            dmax = dmax.max(precision::receiver_dec(loc.kind.digits_scale().map_or(0, |(_, s)| s), t.rounded)).max(self.dmax(e)?);
1413        }
1414        if let Some((t, dividend, _)) = remainder {
1415            let loc = self.locate(&t.r)?;
1416            dmax = dmax.max(loc.kind.digits_scale().map_or(0, |(_, s)| s)).max(self.dmax(dividend)?);
1417        }
1418        let mut quotient_target: Option<Loc> = None;
1419        let mut results = Vec::with_capacity(computations.len());
1420        for (t, e) in computations {
1421            let own = |x: &Expr| per_receiver && matches!(x, Expr::Operand(Operand::Ref(r)) if *r == t.r);
1422            let float = float_receiver || self.uses_float(e)?;
1423            let (shared, with) = match e {
1424                Expr::Bin(a, op, b) if *op != BinOp::Pow && own(a) => (b.as_ref(), Some((*op, true))),
1425                Expr::Bin(a, op, b) if *op != BinOp::Pow && own(b) => (a.as_ref(), Some((*op, false))),
1426                _ => (e, None),
1427            };
1428            let outcome = if float {
1429                self.eval_float(shared, self.options.arith.float_intermediate(), pos).map(Val::Float)
1430            } else {
1431                self.eval_fixed(shared, dmax, pos).map(Val::Num)
1432            };
1433            results.push((t, shared, with, outcome));
1434        }
1435        let operands = match remainder {
1436            Some((_, dividend, divisor)) => Some((self.eval_fixed(dividend, dmax, pos)?, self.eval_fixed(divisor, dmax, pos)?)),
1437            None => None,
1438        };
1439        for (t, shared, with, outcome) in results {
1440            let loc = self.locate(&t.r)?;
1441            quotient_target.get_or_insert(loc);
1442            let outcome = match (with, outcome) {
1443                (Some((op, receiver_first)), Ok(Val::Num(value))) => {
1444                    let current = self.eval_fixed(&Expr::Operand(Operand::Ref(t.r.clone())), dmax, pos)?;
1445                    let (x, y) = if receiver_first { (current, value) } else { (value, current) };
1446                    if arith::divides_by_zero(op, &y) {
1447                        let receiver = Expr::Operand(Operand::Ref(t.r.clone()));
1448                        let binary = self.binary_division(&receiver, shared)?;
1449                        Err(arith::zero_divide(binary, pos))
1450                    } else {
1451                        arith::fixed_binop(x, op, y, dmax, self.options.arith, pos).map(Val::Num)
1452                    }
1453                }
1454                (Some((op, receiver_first)), Ok(Val::Float(value))) => {
1455                    let p = self.options.arith.float_intermediate();
1456                    let current = self.eval_float(&Expr::Operand(Operand::Ref(t.r.clone())), p, pos)?;
1457                    let (x, y) = if receiver_first { (current, value) } else { (value, current) };
1458                    arith::float_binop(x, op, y, p, pos).map(Val::Float)
1459                }
1460                (_, outcome) => outcome,
1461            };
1462            let Some(value) = arith::size_error(outcome, handler.is_some())? else {
1463                size_error = true;
1464                continue;
1465            };
1466            size_error |= self.store_value(loc, value, t.rounded, handler.is_some(), pos)?;
1467        }
1468        if let (Some((t, _, _)), Some((x, y)), Some(q_loc)) = (remainder, operands, quotient_target)
1469            && let Some(r) = arith::remainder(x, y, places_of(q_loc.kind).dec, dmax, self.options.arith, pos)?
1470        {
1471            let r_loc = self.locate(&t.r)?;
1472            size_error |= self.store_value(r_loc, Val::Num(r), false, handler.is_some(), pos)?;
1473        }
1474        if let Some(h) = handler {
1475            return self.run_block(if size_error { &h.on } else { &h.not_on });
1476        }
1477        Ok(Flow::Next)
1478    }
1479
1480    /// Stores an arithmetic result; returns whether it was a size error.
1481    fn store_value(&mut self, loc: Loc, value: Val, rounded: bool, keep_on_size_error: bool, pos: Pos) -> R<bool> {
1482        store::store_value(&self.facts(), self.unit, loc, value, rounded, keep_on_size_error, pos)
1483    }
1484
1485    fn store_fixed(&mut self, loc: Loc, value: &Fixed, rounded: bool, pos: Pos) -> R<()> {
1486        store::store_fixed(&self.facts(), self.unit, loc, value, rounded, pos)
1487    }
1488
1489    fn store_fixed_checked(&mut self, loc: Loc, value: &Fixed, rounded: bool, keep_on_size_error: bool, pos: Pos) -> R<bool> {
1490        store::store_fixed_checked(&self.facts(), self.unit, loc, value, rounded, keep_on_size_error, pos)
1491    }
1492
1493    /// MOVE, and VALUE at start-up, into one receiving item.
1494    fn assign(&mut self, dest: Loc, val: Val, src: Option<Loc>, pos: Pos) -> R<()> {
1495        store::assign(&self.facts(), self.unit, dest, val, src, pos)
1496    }
1497
1498    fn condition(&mut self, c: &Cond, pos: Pos) -> R<bool> {
1499        Ok(match c {
1500            Cond::Rel(a, op, b) => {
1501                let o = self.compare(a, b, pos)?;
1502                match op {
1503                    RelOp::Eq => o == Ordering::Equal,
1504                    RelOp::Ne => o != Ordering::Equal,
1505                    RelOp::Lt => o == Ordering::Less,
1506                    RelOp::Le => o != Ordering::Greater,
1507                    RelOp::Gt => o == Ordering::Greater,
1508                    RelOp::Ge => o != Ordering::Less,
1509                }
1510            }
1511            Cond::Not(inner) => !self.condition(inner, pos)?,
1512            Cond::And(a, b) => self.condition(a, pos)? && self.condition(b, pos)?,
1513            Cond::Or(a, b) => self.condition(a, pos)? || self.condition(b, pos)?,
1514            Cond::Class(e, class) => self.class(e, *class, pos)?,
1515            Cond::NameOrRel { subject, op, negated, name } => match self.resolve(name)? {
1516                Resolved::Condition(_) => self.condition(&Cond::Name(name.clone()), pos)?,
1517                Resolved::Item(_) => self.condition(&Cond::Rel(subject.clone(), *op, Expr::Operand(Operand::Ref(name.clone()))), pos)? != *negated,
1518            },
1519            Cond::Name(r) => {
1520                let Resolved::Condition(index) = self.resolve(r)? else {
1521                    return Err(Abend::ironwork(format!("{} is a data item, not a condition", r.name), r.pos));
1522                };
1523                let condition = &self.layout.conditions[index];
1524                let loc = self.locate_item(condition.item, r, false)?;
1525                self.numcheck(loc, false, r.pos)?;
1526                let subject = (self.read(loc, r.pos)?, Some(loc));
1527                for (low, high) in &condition.values {
1528                    let hit = match high {
1529                        None => self.compare_literal(&subject, low, pos)? == Ordering::Equal,
1530                        Some(high) => self.compare_literal(&subject, low, pos)? != Ordering::Less && self.compare_literal(&subject, high, pos)? != Ordering::Greater,
1531                    };
1532                    if hit {
1533                        return Ok(true);
1534                    }
1535                }
1536                false
1537            }
1538        })
1539    }
1540
1541    fn class(&mut self, e: &Expr, class: Class, pos: Pos) -> R<bool> {
1542        if let (Class::Numeric | Class::Alphabetic | Class::AlphabeticLower | Class::AlphabeticUpper, Expr::Operand(Operand::Ref(r))) = (class, e) {
1543            let loc = self.locate(r)?;
1544            let test = match (class, loc.kind) {
1545                (Class::Numeric, Kind::Packed { signed, .. }) => ByteClass::Packed { signed },
1546                (Class::Numeric, Kind::Zoned { signed, sign: None, .. }) => ByteClass::Zoned { signed },
1547                (Class::Numeric, _) => ByteClass::Digits,
1548                (Class::AlphabeticLower, _) => ByteClass::AlphabeticLower,
1549                (Class::AlphabeticUpper, _) => ByteClass::AlphabeticUpper,
1550                (_, _) => ByteClass::Alphabetic,
1551            };
1552            return Ok(store::byte_class(&self.facts(), &self.unit.mem, loc, test));
1553        }
1554        let test = match class {
1555            Class::Positive => SignTest::Positive,
1556            Class::Negative => SignTest::Negative,
1557            _ => SignTest::Zero,
1558        };
1559        store::sign_test(self.expr_value(e, pos)?, test, pos)
1560    }
1561
1562    /// Object references are compared here; `rt::store::compare` compares everything else.
1563    fn compare(&mut self, a: &Expr, b: &Expr, pos: Pos) -> R<Ordering> {
1564        for (zoned, other, zoned_first) in [(a, b, true), (b, a, false)] {
1565            if let Some(image) = self.zoned_bytes_against(zoned, other)? {
1566                if let Expr::Operand(Operand::Ref(r)) = zoned
1567                    && self.checks_against(other)?
1568                {
1569                    let loc = self.locate(r)?;
1570                    self.numcheck(loc, false, r.pos)?;
1571                }
1572                let other = match other {
1573                    Expr::Operand(Operand::Ref(r)) if self.zone_sensitive(other)? => {
1574                        let loc = self.locate(r)?;
1575                        self.numcheck(loc, false, r.pos)?;
1576                        (Val::Bytes(Vec::new()), Some(loc))
1577                    }
1578                    _ => self.comparand_against(other, zoned, pos)?,
1579                };
1580                return store::compare_zoned_bytes(&self.facts(), &self.unit.mem, &image, other, zoned_first, pos);
1581            }
1582        }
1583        let (va, la) = self.comparand_against(a, b, pos)?;
1584        let (vb, lb) = self.comparand_against(b, a, pos)?;
1585        if let Some(o) = self.compare_references(a, b, (&va, la), (&vb, lb), pos)? {
1586            return Ok(o);
1587        }
1588        store::compare(&self.facts(), &self.unit.mem, (va, la), (vb, lb), pos)
1589    }
1590
1591    /// Whether `e` is an alphanumeric item, literal or figurative constant other than ZERO.
1592    fn nonnumeric(&mut self, e: &Expr) -> R<bool> {
1593        Ok(match e {
1594            Expr::Operand(Operand::Literal(l)) => {
1595                matches!(l, Literal::Alnum(_) | Literal::Hex(_) | Literal::All(_)) || matches!(l, Literal::Figurative(f) if !matches!(f, Figurative::Zero | Figurative::Null))
1596            }
1597            Expr::Operand(Operand::Ref(o)) => matches!(self.locate(o)?.kind, Kind::Group | Kind::Alnum { .. } | Kind::AlnumEdited { .. } | Kind::NumericEdited { .. }),
1598            _ => false,
1599        })
1600    }
1601
1602    /// Whether NUMCHECK tests a zoned item compared with `other`: always, but under ZON(NOALPHNUM)
1603    /// not against an alphanumeric operand (Programming Guide SC27-8714-03, pp. 389-390).
1604    fn checks_against(&mut self, other: &Expr) -> R<bool> {
1605        Ok(self.options.numcheck.and_then(|c| c.zon).is_none_or(|z| z.alphnum) || !self.nonnumeric(other)?)
1606    }
1607
1608    /// A comparand, NUMCHECK testing an item unless ZON(NOALPHNUM) exempts it against `other`.
1609    fn comparand_against(&mut self, e: &Expr, other: &Expr, pos: Pos) -> R<(Val, Option<Loc>)> {
1610        match e {
1611            Expr::Operand(Operand::Ref(r)) if !self.checks_against(other)? => {
1612                let loc = self.locate(r)?;
1613                Ok((self.read(loc, r.pos)?, Some(loc)))
1614            }
1615            _ => self.comparand(e, pos),
1616        }
1617    }
1618
1619    /// The bytes of `e`, a zoned integer item, when `other` is nonnumeric: that comparison reads the
1620    /// item's bytes, never its value, so invalid data compares rather than abends.
1621    fn zoned_bytes_against(&mut self, e: &Expr, other: &Expr) -> R<Option<Vec<u8>>> {
1622        let Expr::Operand(Operand::Ref(r)) = e else { return Ok(None) };
1623        let nonnumeric = self.nonnumeric(other)?;
1624        // INVDATA(NOFORCENUMCMP): an unsigned zoned integer against ZERO or one of its own length
1625        // compares its zones too (assumption C223).
1626        let zones_count = self.options.invdata.is_some_and(|i| !i.forcenumcmp)
1627            && self.zone_sensitive(e)?
1628            && match other {
1629                Expr::Operand(Operand::Literal(Literal::Figurative(Figurative::Zero))) => true,
1630                Expr::Operand(Operand::Ref(o)) => self.zone_sensitive(other)? && self.locate(o)?.len == self.locate(r)?.len,
1631                _ => false,
1632            };
1633        if !nonnumeric && !zones_count {
1634            return Ok(None);
1635        }
1636        let loc = self.locate(r)?;
1637        Ok(store::compared_zoned_bytes(&self.facts(), &self.unit.mem, loc))
1638    }
1639
1640    /// Whether `e` is an unsigned, unscaled zoned integer item.
1641    fn zone_sensitive(&mut self, e: &Expr) -> R<bool> {
1642        let Expr::Operand(Operand::Ref(r)) = e else { return Ok(false) };
1643        let loc = self.locate(r)?;
1644        Ok(matches!(loc.kind, Kind::Zoned { scale: 0, signed: false, .. }) && self.layout.items.get(loc.item).is_none_or(|i| i.scaling == 0))
1645    }
1646
1647    fn compare_literal(&mut self, subject: &(Val, Option<Loc>), literal: &Literal, pos: Pos) -> R<Ordering> {
1648        let value = self.literal_value(literal, pos)?;
1649        store::compare(&self.facts(), &self.unit.mem, subject.clone(), (value, None), pos)
1650    }
1651
1652    fn comparand(&mut self, e: &Expr, pos: Pos) -> R<(Val, Option<Loc>)> {
1653        match e {
1654            Expr::Operand(op) => self.operand_with_loc(op, pos),
1655            _ => Ok((self.expr_value(e, pos)?, None)),
1656        }
1657    }
1658
1659    fn display(&mut self, items: &[Operand], no_advancing: bool, pos: Pos) -> R<()> {
1660        let mut text = String::new();
1661        for op in items {
1662            let shown = match op {
1663                Operand::Ref(r) => {
1664                    let loc = self.locate(r)?;
1665                    rt::display::place(&self.facts(), &self.unit.mem, loc, r.pos)?
1666                }
1667                Operand::Literal(Literal::Number(t)) => rt::display::number(t, &self.facts()),
1668                other => {
1669                    let val = self.operand(other, pos)?;
1670                    rt::display::value(&self.facts(), val, pos)?
1671                }
1672            };
1673            text.push_str(&shown);
1674        }
1675        if self.unit.observed() {
1676            self.sink("log", pos, &text);
1677        }
1678        rt::display::write(&mut *self.unit.out, &text, no_advancing, pos)
1679    }
1680
1681    /// The implicit MOVEs of INITIALIZE to item `index` at `offset`.
1682    fn initialize(&mut self, index: usize, offset: usize, with: &InitializeWith, pos: Pos) -> R<()> {
1683        let layout = self.layout;
1684        for (i, at) in layout.initialize_receivers(index, with.filler) {
1685            let item = &layout.items[i];
1686            let loc = Loc { offset: offset + at as usize, len: item.size as usize, kind: item.kind, item: i };
1687            match (with.initial_value(layout.category(i), item.value.is_some()), &item.value) {
1688                (Some(InitialValue::Value), Some(value)) => {
1689                    let val = self.literal_value(value, pos)?;
1690                    self.assign(Loc { kind: value_kind(item.kind, value), ..loc }, val, None, pos)?;
1691                }
1692                (Some(InitialValue::Replacing(by)), _) => {
1693                    let (val, src) = self.operand_with_loc(by, pos)?;
1694                    self.assign(loc, val, src, pos)?;
1695                }
1696                (Some(_), _) => self.assign(loc, initial_default(item.kind), None, pos)?,
1697                (None, _) => {}
1698            }
1699        }
1700        Ok(())
1701    }
1702}
1703
1704/// The cs of NUMVAL-C and TEST-NUMVAL-C without argument-2 (assumption C102).
1705pub(crate) fn numval_currency(signs: &[CurrencySign]) -> String {
1706    match signs {
1707        [only] => only.value.clone(),
1708        _ => "$".to_owned(),
1709    }
1710}
1711
1712static NO_PHRASES: InitializeWith = InitializeWith { filler: false, value: Vec::new(), replacing: Vec::new(), default: false };
1713
1714/// The kind a VALUE clause's literal is placed as: editing is ignored, so an alphanumeric VALUE fills
1715/// a numeric-edited or alphanumeric-edited item as alphanumeric data (Language Reference p. 246).
1716fn value_kind(kind: Kind, value: &Literal) -> Kind {
1717    match (kind, value) {
1718        (Kind::NumericEdited { .. } | Kind::AlnumEdited { .. }, Literal::Alnum(_) | Literal::Figurative(_) | Literal::All(_)) => Kind::Alnum { justified: false },
1719        (kind, _) => kind,
1720    }
1721}
1722
1723/// INITIALIZE's implied sending item for an elementary receiver of `kind` (Language Reference
1724/// SC27-8713-03, p. 353), NULL for a pointer.
1725fn initial_default(kind: Kind) -> Val {
1726    match kind {
1727        Kind::Pointer => Val::Address(0),
1728        Kind::Alnum { .. } | Kind::AlnumEdited { .. } | Kind::National => Val::Fig(Figurative::Space),
1729        _ => Val::Fig(Figurative::Zero),
1730    }
1731}
1732
1733pub(crate) fn flatten_and<'c>(cond: &'c Cond, out: &mut Vec<&'c Cond>) {
1734    match cond {
1735        Cond::And(a, b) => {
1736            flatten_and(a, out);
1737            flatten_and(b, out);
1738        }
1739        other => out.push(other),
1740    }
1741}
1742
1743/// In a SEARCH ALL condition, the key item and the value it must equal.
1744pub(crate) fn key_term<'c>(terms: &[&'c Cond], key: &str) -> Option<(&'c Expr, &'c Expr)> {
1745    let is_key = |e: &Expr| matches!(e, Expr::Operand(Operand::Ref(r)) if r.name == key);
1746    terms.iter().find_map(|t| match t {
1747        Cond::Rel(a, RelOp::Eq, b) if is_key(a) => Some((a, b)),
1748        Cond::Rel(a, RelOp::Eq, b) if is_key(b) => Some((b, a)),
1749        _ => None,
1750    })
1751}