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