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