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