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
5use crate::layout::{Item, Kind, Layout, Resolved};
6use crate::unit::{ADDRESS_BASE, LoadError, RETURN_CODE, RunUnit};
7use crate::Compiled;
8use numeric::binary::{self, Binary};
9use numeric::precision::{ArithError, Fixed, Places};
10use numeric::{Numproc, Options, Trunc, float, sign};
11use std::cmp::Ordering;
12use std::collections::HashMap;
13use syntax::Pos;
14use syntax::ast::*;
15use zarch::check::{ProgramCheck, ProgramMask};
16use zarch::decimal::{self, Decimal};
17use zarch::ebcdic::{self, CodePage, Collation};
18use zarch::hfp::{Hfp, Precision};
19use zarch::wide::U256;
20
21mod cics;
22mod cics_bms;
23mod cics_files;
24mod cics_services;
25mod file_io;
26
27#[derive(Clone, Debug, PartialEq, Eq)]
28pub struct Abend {
29    pub code: String,
30    pub message: String,
31    pub pos: Pos,
32}
33
34impl Abend {
35    fn check(c: ProgramCheck, pos: Pos) -> Self {
36        Self { code: c.abend(), message: format!("{c:?} exception"), pos }
37    }
38
39    fn ironwork(message: impl Into<String>, pos: Pos) -> Self {
40        Self { code: "IRONWORK".into(), message: message.into(), pos }
41    }
42}
43
44type R<T> = Result<T, Abend>;
45
46const DIVIDE_BY_ZERO: &str = "DIVIDE-BY-ZERO";
47/// The reader of DISPLAY output went away, as `head` does: not the program's failure.
48pub const CLOSED_OUTPUT: &str = "CLOSED-OUTPUT";
49
50#[derive(Clone, Copy, Debug, PartialEq, Eq)]
51pub enum Ending {
52    Goback,
53    StopRun,
54    EndOfProgram,
55}
56
57enum Flow {
58    Next,
59    End(Ending),
60    GoTo(usize),
61    ExitParagraph,
62    ExitSection,
63    ExitPerform,
64    ExitPerformCycle,
65    NextSentence,
66}
67
68#[derive(Clone, Copy, Debug)]
69struct Loc {
70    offset: usize,
71    len: usize,
72    kind: Kind,
73    item: usize,
74}
75
76#[derive(Clone, Debug)]
77enum Val {
78    Bytes(Vec<u8>),
79    National(Vec<u8>),
80    Num(Fixed),
81    Float(Hfp),
82    Fig(Figurative),
83    All(Vec<u8>),
84    /// A pointer value: [`ADDRESS_BASE`] plus an offset into run-unit memory, or 0 for NULL.
85    Address(u32),
86}
87
88pub struct Machine<'p, 'u, 'w> {
89    program: &'p Program,
90    layout: &'p Layout,
91    options: Options,
92    ssrange: bool,
93    page: &'static CodePage,
94    resolved: HashMap<(String, Vec<String>), Resolved>,
95    /// This program's place in the run unit, and where its storage starts.
96    me: usize,
97    base: usize,
98    /// Where each LINKAGE record is, once an argument or SET ADDRESS OF has given it an address.
99    linkage: Vec<Option<usize>>,
100    /// Where this activation's LOCAL-STORAGE starts.
101    local_base: usize,
102    /// The first program of the run unit, where EXIT PROGRAM does nothing.
103    main: bool,
104    /// HANDLE CONDITION, IGNORE CONDITION and HANDLE ABEND, which belong to the program level.
105    cics_handlers: cics::Handlers,
106    unit: &'u mut RunUnit<'w>,
107}
108
109fn figurative_byte(f: Figurative) -> u8 {
110    match f {
111        Figurative::Zero => ebcdic::ZERO,
112        Figurative::Space => ebcdic::SPACE,
113        Figurative::HighValue => ebcdic::HIGH_VALUE,
114        Figurative::LowValue => ebcdic::LOW_VALUE,
115        Figurative::Quote => ebcdic::QUOTE,
116        Figurative::Null => 0,
117    }
118}
119
120fn figurative_unit(f: Figurative) -> u16 {
121    match f {
122        Figurative::Zero => 0x0030,
123        Figurative::Space => 0x0020,
124        Figurative::HighValue => 0xFFFF,
125        Figurative::LowValue => 0x0000,
126        Figurative::Quote => 0x0022,
127        Figurative::Null => 0,
128    }
129}
130
131fn fixed(negative: bool, magnitude: U256, places: Places) -> Fixed {
132    Fixed { negative: negative && !magnitude.is_zero(), magnitude, places }
133}
134
135pub(crate) fn literal_fixed(text: &str) -> Option<Fixed> {
136    let (negative, body) = match text.as_bytes().first() {
137        Some(b'-') => (true, &text[1..]),
138        Some(b'+') => (false, &text[1..]),
139        _ => (false, text),
140    };
141    let (int, frac) = body.split_once('.').unwrap_or((body, ""));
142    let digits = format!("{int}{frac}");
143    if digits.is_empty() || digits.len() > 31 {
144        return None;
145    }
146    let magnitude: u128 = digits.parse().ok()?;
147    Some(fixed(negative, U256::from_u128(magnitude), Places::new(int.len().max(1) as u32, frac.len() as u32)))
148}
149
150fn pow10(n: u32) -> U256 {
151    U256::pow10(n)
152}
153
154/// The value's magnitude at `scale` decimal places, truncated or rounded half away from zero.
155fn align(value: &Fixed, scale: u32, rounded: bool) -> Option<U256> {
156    let from = value.places.dec;
157    if scale >= from {
158        return value.magnitude.checked_mul(pow10(scale - from));
159    }
160    let (q, r) = value.magnitude.div_rem(pow10(from - scale));
161    let half = pow10(from - scale - 1).checked_mul(U256::from_u128(5))?;
162    Some(if rounded && r >= half { q + U256::from_u128(1) } else { q })
163}
164
165fn compare_fixed(a: &Fixed, b: &Fixed) -> Ordering {
166    let dec = a.places.dec.max(b.places.dec);
167    let (ma, mb) = (align(a, dec, false).unwrap_or_default(), align(b, dec, false).unwrap_or_default());
168    match (a.negative, b.negative) {
169        (false, false) => ma.cmp(&mb),
170        (true, true) => mb.cmp(&ma),
171        (true, false) => Ordering::Less,
172        (false, true) => Ordering::Greater,
173    }
174}
175
176fn places_of(kind: Kind) -> Places {
177    let (digits, scale) = kind.digits_scale().unwrap_or((0, 0));
178    Places::new(digits - scale, scale)
179}
180
181fn zoned_digits(magnitude: u128, digits: usize, sign_zone: u8) -> Vec<u8> {
182    let mut out = vec![0xF0u8; digits];
183    let mut m = magnitude;
184    for b in out.iter_mut().rev() {
185        *b = 0xF0 | (m % 10) as u8;
186        m /= 10;
187    }
188    if let Some(last) = out.last_mut() {
189        *last = (sign_zone << 4) | (*last & 0x0F);
190    }
191    out
192}
193
194impl<'p, 'u, 'w> Machine<'p, 'u, 'w> {
195    /// An activation of loaded program `me`. Its storage is initialized on its first activation,
196    /// after a CANCEL, and on every activation of an INITIAL program.
197    pub fn activation(compiled: &'p Compiled, me: usize, unit: &'u mut RunUnit<'w>, main: bool) -> R<Self> {
198        let base = unit.programs[me].base;
199        let fresh = !unit.programs[me].initialized || compiled.program.initial;
200        unit.programs[me].active = true;
201        let mut m = Self {
202            program: &compiled.program,
203            layout: &compiled.layout,
204            options: compiled.options,
205            ssrange: compiled.ssrange,
206            page: compiled.options.code_page(),
207            resolved: HashMap::new(),
208            me,
209            base,
210            linkage: vec![None; compiled.layout.linkage_roots.len()],
211            local_base: 0,
212            main,
213            cics_handlers: cics::Handlers::default(),
214            unit,
215        };
216        if compiled.layout.local_size > 0 {
217            m.local_base = m.unit.push_temporary(&vec![0; compiled.layout.local_size as usize]);
218            m.initialize_values(true)?;
219        }
220        if fresh {
221            m.unit.mem[base..base + compiled.layout.size as usize].fill(0);
222            m.initialize_values(false)?;
223            m.unit.programs[me].initialized = true;
224        }
225        Ok(m)
226    }
227
228    /// Applies VALUE clauses: to WORKING-STORAGE and file records, or to LOCAL-STORAGE.
229    fn initialize_values(&mut self, local: bool) -> R<()> {
230        let base = if local { self.local_base } else { self.base };
231        for index in 0..self.layout.items.len() {
232            let item = &self.layout.items[index];
233            let Some(value) = item.value.clone().filter(|_| item.linkage.is_none() && item.local == local) else { continue };
234            let occurrences: u32 = item.dims.iter().map(|&(_, n)| n).product::<u32>().max(1);
235            for k in 0..occurrences {
236                let offset = base + item.offset as usize + self.occurrence_offset(item, k);
237                let loc = Loc { offset, len: item.size as usize, kind: item.kind, item: index };
238                let val = self.literal_value(&value, item.pos)?;
239                self.assign(loc, val, None, item.pos)?;
240            }
241        }
242        Ok(())
243    }
244
245    fn occurrence_offset(&self, item: &Item, mut k: u32) -> usize {
246        let mut offset = 0usize;
247        for &(stride, count) in item.dims.iter().rev() {
248            offset += (k % count) as usize * stride as usize;
249            k /= count;
250        }
251        offset
252    }
253
254    pub fn run_procedure(&mut self) -> R<Ending> {
255        if self.program.paragraphs.is_empty() {
256            return Ok(Ending::EndOfProgram);
257        }
258        let last = self.program.paragraphs.len() - 1;
259        let mut start = 0;
260        loop {
261            match self.run_paragraphs(start, last)? {
262                Flow::End(e) => return Ok(e),
263                Flow::GoTo(t) => start = t,
264                _ => return Ok(Ending::EndOfProgram),
265            }
266        }
267    }
268
269    fn run_paragraphs(&mut self, from: usize, to: usize) -> R<Flow> {
270        let program = self.program;
271        let mut i = from;
272        while i <= to {
273            match self.run_sentences(&program.paragraphs[i].statements)? {
274                Flow::Next | Flow::ExitParagraph => i += 1,
275                Flow::ExitSection => i = crate::section_end(program, i) + 1,
276                Flow::GoTo(t) if (from..=to).contains(&t) => i = t,
277                Flow::ExitPerform | Flow::ExitPerformCycle => i += 1,
278                other => return Ok(other),
279            }
280        }
281        Ok(Flow::Next)
282    }
283
284    /// A paragraph's statements: NEXT SENTENCE resumes after the next separator period.
285    fn run_sentences(&mut self, stmts: &'p [Stmt]) -> R<Flow> {
286        let mut i = 0;
287        while i < stmts.len() {
288            match self.exec(&stmts[i])? {
289                Flow::Next => i += 1,
290                Flow::NextSentence => i = stmts[i..].iter().position(|s| *s == Stmt::SentenceEnd).map_or(stmts.len(), |j| i + j + 1),
291                other => return Ok(other),
292            }
293        }
294        Ok(Flow::Next)
295    }
296
297    fn run_block(&mut self, stmts: &'p [Stmt]) -> R<Flow> {
298        for s in stmts {
299            match self.exec(s)? {
300                Flow::Next => {}
301                other => return Ok(other),
302            }
303        }
304        Ok(Flow::Next)
305    }
306
307    fn procedure(&self, p: &ProcName, pos: Pos) -> R<(usize, usize)> {
308        crate::procedure(self.program, p).map_err(|m| Abend::ironwork(m, pos))
309    }
310
311    fn exec(&mut self, s: &'p Stmt) -> R<Flow> {
312        match s {
313            Stmt::Move { from, to, pos } => {
314                for r in to {
315                    let dest = self.locate(r)?;
316                    let (val, src) = self.operand_with_loc(from, *pos)?;
317                    self.assign(dest, val, src, *pos)?;
318                }
319            }
320            Stmt::Compute { targets, expr, size_error, pos } => {
321                let computations: Vec<(Target, Expr)> = targets.iter().map(|t| (t.clone(), expr.clone())).collect();
322                return self.arithmetic(&computations, None, size_error.as_ref(), *pos);
323            }
324            Stmt::Arith(a) => return self.arithmetic(&a.computations, a.remainder.as_ref(), a.size_error.as_ref(), a.pos),
325            Stmt::If { cond, then, otherwise, pos } => {
326                let branch = if self.condition(cond, *pos)? { then } else { otherwise };
327                return self.run_block(branch);
328            }
329            Stmt::Evaluate { subjects, whens, other, pos } => {
330                for w in whens {
331                    for alternative in &w.alternatives {
332                        if self.alternative_matches(subjects, alternative, *pos)? {
333                            return self.run_block(&w.body);
334                        }
335                    }
336                }
337                return self.run_block(other);
338            }
339            Stmt::PerformProc { from, thru, repeat, pos } => {
340                let (start, first_end) = self.procedure(from, *pos)?;
341                let end = match thru {
342                    Some(t) => self.procedure(t, *pos)?.1,
343                    None => first_end,
344                };
345                return self.repeat(repeat, *pos, &mut |m: &mut Self| m.run_paragraphs(start, end));
346            }
347            Stmt::PerformInline { body, repeat, pos } => return self.repeat(repeat, *pos, &mut |m: &mut Self| m.run_block(body)),
348            Stmt::Display { items, no_advancing, pos } => self.display(items, *no_advancing, *pos)?,
349            Stmt::Open { files, pos } => {
350                for (mode, name) in files {
351                    self.open_file(*mode, name, *pos)?;
352                }
353            }
354            Stmt::Close { files, pos } => {
355                for name in files {
356                    self.close_file(name, *pos)?;
357                }
358            }
359            Stmt::Read(r) => return self.read_stmt(r),
360            Stmt::Write { record, from, advancing, invalid, pos } => return self.write_stmt(record, from.as_ref(), advancing.as_ref(), invalid, *pos),
361            Stmt::Rewrite { record, from, invalid, pos } => return self.rewrite_stmt(record, from.as_ref(), invalid, *pos),
362            Stmt::Delete { file, invalid, pos } => return self.delete_stmt(file, invalid, *pos),
363            Stmt::Start { file, key, invalid, pos } => return self.start_stmt(file, key.as_ref(), invalid, *pos),
364            Stmt::Initialize { targets, pos } => {
365                for r in targets {
366                    let loc = self.locate(r)?;
367                    if loc.item == usize::MAX {
368                        self.write(loc, &[0, 0]);
369                    } else {
370                        self.initialize(loc.item, loc.offset, *pos)?;
371                    }
372                }
373            }
374            Stmt::GoTo { target, pos } => return Ok(Flow::GoTo(self.procedure(target, *pos)?.0)),
375            Stmt::Goback { .. } => return Ok(Flow::End(Ending::Goback)),
376            Stmt::ExitProgram { .. } if self.main => {}
377            Stmt::ExitProgram { .. } => return Ok(Flow::End(Ending::Goback)),
378            Stmt::Call(c) => return self.call(c),
379            Stmt::Cancel { targets, pos } => {
380                for t in targets {
381                    let name = self.program_name(t, *pos)?;
382                    self.cancel(&name, *pos)?;
383                }
384            }
385            Stmt::Set { set, pos } => self.set(set, *pos)?,
386            Stmt::Accept { target, from, pos } => self.accept(target, *from, *pos)?,
387            Stmt::String(st) => return self.string_stmt(st),
388            Stmt::Unstring(u) => return self.unstring(u),
389            Stmt::Inspect(i) => self.inspect(i)?,
390            Stmt::Search(se) => return self.search(se),
391            Stmt::NextSentence => return Ok(Flow::NextSentence),
392            Stmt::Exec(block) if block.declarative() => {}
393            Stmt::Exec(block) if block.kind == ExecKind::Cics => return self.cics(block),
394            Stmt::Exec(block) => {
395                let kind = match block.kind {
396                    ExecKind::Sql => "SQL",
397                    ExecKind::Cics => "CICS",
398                    ExecKind::Dli => "DLI",
399                    ExecKind::Other => "",
400                };
401                return Err(Abend {
402                    code: "EXEC".into(),
403                    message: format!("EXEC {kind} {} was reached: ironwork for COBOL checks EXEC statements but does not run them yet", block.command),
404                    pos: block.pos,
405                });
406            }
407            Stmt::SentenceEnd => {}
408            Stmt::StopRun { .. } => return Ok(Flow::End(Ending::StopRun)),
409            Stmt::Exit(ExitKind::Paragraph) => return Ok(Flow::ExitParagraph),
410            Stmt::Exit(ExitKind::Section) => return Ok(Flow::ExitSection),
411            Stmt::Exit(ExitKind::Perform) => return Ok(Flow::ExitPerform),
412            Stmt::Exit(ExitKind::PerformCycle) => return Ok(Flow::ExitPerformCycle),
413            Stmt::Continue | Stmt::Exit(ExitKind::Plain) => {}
414        }
415        Ok(Flow::Next)
416    }
417
418    fn alternative_matches(&mut self, subjects: &[Subject], objects: &[Object], pos: Pos) -> R<bool> {
419        for (subject, object) in subjects.iter().zip(objects) {
420            let hit = match (subject, object) {
421                (_, Object::Any) => true,
422                (Subject::Bool(b), Object::Bool(o)) => b == o,
423                (Subject::Bool(b), Object::Cond(c)) => self.condition(c, pos)? == *b,
424                (Subject::Cond(c), Object::Bool(o)) => self.condition(c, pos)? == *o,
425                (Subject::Cond(c), Object::Cond(d)) => self.condition(c, pos)? == self.condition(d, pos)?,
426                (Subject::Expr(e), Object::Value { not, from, thru }) => {
427                    let inside = match thru {
428                        None => self.compare(e, from, pos)? == Ordering::Equal,
429                        Some(t) => self.compare(e, from, pos)? != Ordering::Less && self.compare(e, t, pos)? != Ordering::Greater,
430                    };
431                    inside != *not
432                }
433                _ => return Err(Abend::ironwork("a WHEN object of a different kind from its subject", pos)),
434            };
435            if !hit {
436                return Ok(false);
437            }
438        }
439        Ok(true)
440    }
441
442    /// Runs a PERFORM's body as its phrase says. EXIT PERFORM leaves the loop; EXIT PERFORM CYCLE
443    /// ends one iteration.
444    fn repeat(&mut self, repeat: &'p Loop, pos: Pos, body: &mut dyn FnMut(&mut Self) -> R<Flow>) -> R<Flow> {
445        self.nest(pos)?;
446        let flow = self.repeat_nested(repeat, pos, body);
447        self.unit.depth -= 1;
448        flow
449    }
450
451    fn nest(&mut self, pos: Pos) -> R<()> {
452        if self.unit.depth >= crate::unit::MAX_DEPTH {
453            return Err(Abend::ironwork(format!("PERFORM and CALL nest deeper than {}", crate::unit::MAX_DEPTH), pos));
454        }
455        self.unit.depth += 1;
456        Ok(())
457    }
458
459    fn repeat_nested(&mut self, repeat: &'p Loop, pos: Pos, body: &mut dyn FnMut(&mut Self) -> R<Flow>) -> R<Flow> {
460        enum Step {
461            Again,
462            Leave,
463            Out(Flow),
464        }
465        let mut run = |m: &mut Self| -> R<Step> {
466            Ok(match body(m)? {
467                Flow::Next | Flow::ExitPerformCycle => Step::Again,
468                Flow::ExitPerform => Step::Leave,
469                other => Step::Out(other),
470            })
471        };
472        match repeat {
473            Loop::Once => match run(self)? {
474                Step::Out(f) => return Ok(f),
475                Step::Again | Step::Leave => {}
476            },
477            Loop::Times(count) => {
478                for _ in 0..self.integer(count, pos)?.max(0) {
479                    match run(self)? {
480                        Step::Again => {}
481                        Step::Leave => break,
482                        Step::Out(f) => return Ok(f),
483                    }
484                }
485            }
486            Loop::Until { cond, test_after } => loop {
487                if !test_after && self.condition(cond, pos)? {
488                    break;
489                }
490                match run(self)? {
491                    Step::Again => {}
492                    Step::Leave => break,
493                    Step::Out(f) => return Ok(f),
494                }
495                if *test_after && self.condition(cond, pos)? {
496                    break;
497                }
498            },
499            Loop::Varying { varying, test_after } => {
500                let var = self.locate(&varying.var)?;
501                let start = self.expr_value(&varying.from, pos)?;
502                self.assign(var, start, None, pos)?;
503                loop {
504                    if !test_after && self.condition(&varying.until, pos)? {
505                        break;
506                    }
507                    match run(self)? {
508                        Step::Again => {}
509                        Step::Leave => break,
510                        Step::Out(f) => return Ok(f),
511                    }
512                    if *test_after && self.condition(&varying.until, pos)? {
513                        break;
514                    }
515                    let var = self.locate(&varying.var)?;
516                    let step = Expr::Bin(Box::new(Expr::Operand(Operand::Ref(varying.var.clone()))), BinOp::Add, Box::new(varying.by.clone()));
517                    let dmax = var.kind.digits_scale().map_or(0, |(_, s)| s).max(self.dmax(&step)?);
518                    let next = self.eval_fixed(&step, dmax, pos)?;
519                    self.store_fixed(var, &next, false, pos)?;
520                }
521            }
522        }
523        Ok(Flow::Next)
524    }
525
526    fn resolve(&mut self, r: &Ref) -> R<Resolved> {
527        let key = (r.name.clone(), r.qualifiers.clone());
528        if let Some(&hit) = self.resolved.get(&key) {
529            return Ok(hit);
530        }
531        let found = self.layout.resolve(&r.name, &r.qualifiers, r.pos).map_err(|e| Abend::ironwork(e.message, r.pos))?;
532        self.resolved.insert(key, found);
533        Ok(found)
534    }
535
536    fn locate(&mut self, r: &Ref) -> R<Loc> {
537        if r.name == "RETURN-CODE" && r.qualifiers.is_empty() && !self.layout.items.iter().any(|i| i.name.as_deref() == Some("RETURN-CODE")) {
538            return Ok(Loc { offset: RETURN_CODE, len: 2, kind: Kind::Binary { digits: 4, scale: 0, signed: true, native: false }, item: usize::MAX });
539        }
540        let Resolved::Item(index) = self.resolve(r)? else {
541            return Err(Abend::ironwork(format!("{} is a condition-name, not a data item", r.name), r.pos));
542        };
543        let layout = self.layout;
544        let item = &layout.items[index];
545        if r.subscripts.len() != item.dims.len() {
546            return Err(Abend::ironwork(format!("{} takes {} subscripts, not {}", r.name, item.dims.len(), r.subscripts.len()), r.pos));
547        }
548        let base = match item.linkage {
549            Some(l) => self.linkage[l as usize].ok_or_else(|| Abend {
550                code: "S0C4".into(),
551                message: format!("{} is a LINKAGE item with no address: no argument was passed for it, and no SET ADDRESS OF gave it one", r.name),
552                pos: r.pos,
553            })?,
554            None if item.local => self.local_base,
555            None => self.base,
556        };
557        let mut offset = (base + item.offset as usize) as i64;
558        for (&(stride, count), sub) in item.dims.iter().zip(&r.subscripts) {
559            let s = self.integer(sub, r.pos)?;
560            if self.ssrange && (s < 1 || s > count as i64) {
561                return Err(Abend::ironwork(format!("subscript {s} of {} is out of range 1 to {count} (SSRANGE)", r.name), r.pos));
562            }
563            offset += (s - 1) * stride as i64;
564        }
565        let (mut len, mut kind) = (item.size as i64, item.kind);
566        if let Some(t) = item.odo {
567            let table = &layout.items[t];
568            let current = self.occurrences(t, r.pos)?;
569            len -= (table.occurs - current) as i64 * table.size as i64;
570        }
571        if let Some(rm) = &r.refmod {
572            let start = self.integer(&rm.start, r.pos)?;
573            let length = match &rm.length {
574                Some(l) => self.integer(l, r.pos)?,
575                None => len - start + 1,
576            };
577            if self.ssrange && (start < 1 || length < 1 || start + length - 1 > len) {
578                return Err(Abend::ironwork(format!("reference modification ({start}:{length}) of {} is out of range (SSRANGE)", r.name), r.pos));
579            }
580            offset += start - 1;
581            len = length;
582            kind = Kind::Alnum { justified: false };
583        }
584        if offset < 0 || len < 0 || offset + len > self.unit.mem.len() as i64 {
585            return Err(Abend::ironwork(format!("{} reaches outside the run unit's storage", r.name), r.pos));
586        }
587        Ok(Loc { offset: offset as usize, len: len as usize, kind, item: index })
588    }
589
590    /// The current count of an OCCURS DEPENDING ON table, kept within its declared maximum so that
591    /// a bad count never reaches past the table's storage.
592    fn occurrences(&mut self, table: usize, pos: Pos) -> R<u32> {
593        let layout = self.layout;
594        let item = &layout.items[table];
595        let Some(object) = &item.depending_on else { return Ok(item.occurs) };
596        let count = self.integer(&Expr::Operand(Operand::Ref(object.clone())), pos)?;
597        if self.ssrange && !(0..=item.occurs as i64).contains(&count) {
598            return Err(Abend::ironwork(format!("{} = {count} is outside the OCCURS DEPENDING ON range 0 to {} (SSRANGE)", object.name, item.occurs), pos));
599        }
600        Ok(count.clamp(0, item.occurs as i64) as u32)
601    }
602
603    fn bytes(&self, loc: Loc) -> &[u8] {
604        &self.unit.mem[loc.offset..loc.offset + loc.len]
605    }
606
607    fn write(&mut self, loc: Loc, bytes: &[u8]) {
608        self.unit.mem[loc.offset..loc.offset + loc.len].copy_from_slice(bytes);
609    }
610
611    fn integer(&mut self, e: &Expr, pos: Pos) -> R<i64> {
612        let dmax = self.dmax(e)?;
613        let v = self.eval_fixed(e, dmax, pos)?;
614        let whole = align(&v, 0, false).and_then(|m| m.to_u128()).and_then(|m| i64::try_from(m).ok());
615        let whole = whole.ok_or_else(|| Abend::ironwork("an integer operand beyond 64 bits", pos))?;
616        Ok(if v.negative { -whole } else { whole })
617    }
618
619    fn literal_value(&self, lit: &Literal, pos: Pos) -> R<Val> {
620        Ok(match lit {
621            Literal::Alnum(s) => Val::Bytes(self.page.encode(s).map_err(|e| Abend::ironwork(e.to_string(), pos))?),
622            Literal::Hex(b) => Val::Bytes(b.clone()),
623            Literal::National(s) => Val::National(s.encode_utf16().flat_map(u16::to_be_bytes).collect()),
624            Literal::Number(t) => Val::Num(literal_fixed(t).ok_or_else(|| Abend::ironwork(format!("the literal {t} has more than 31 digits"), pos))?),
625            Literal::Figurative(f) => Val::Fig(*f),
626            Literal::All(inner) => match self.literal_value(inner, pos)? {
627                Val::Bytes(b) => Val::All(b),
628                Val::Fig(f) => Val::Fig(f),
629                _ => return Err(Abend::ironwork("ALL takes an alphanumeric literal", pos)),
630            },
631        })
632    }
633
634    fn read(&self, loc: Loc, pos: Pos) -> R<Val> {
635        let bytes = self.bytes(loc);
636        let places = places_of(loc.kind);
637        Ok(match loc.kind {
638            Kind::Group | Kind::Alnum { .. } | Kind::NumericEdited { .. } | Kind::AlnumEdited { .. } => Val::Bytes(bytes.to_vec()),
639            Kind::National => Val::National(bytes.to_vec()),
640            Kind::Pointer => Val::Address(u32::from_be_bytes(bytes.try_into().unwrap())),
641            Kind::Index => Val::Num(Fixed::new(i32::from_be_bytes(bytes.try_into().unwrap()) as i128, Places::new(9, 0))),
642            Kind::Float(p) => Val::Float(Hfp::from_bytes(p, bytes)),
643            Kind::Binary { digits, signed, native, .. } => Val::Num(Fixed::new(Binary { digits: digits as u8, signed, native }.load(bytes), places)),
644            Kind::Packed { signed, .. } => {
645                let mut p = bytes.to_vec();
646                if !signed && self.options.numproc == Numproc::Nopfd {
647                    *p.last_mut().unwrap() |= 0x0F;
648                }
649                Val::Num(self.decode(&p, places, pos)?)
650            }
651            Kind::Zoned { digits, signed, sign, .. } => Val::Num(self.zoned_value(bytes, digits, signed, sign, places, pos)?),
652        })
653    }
654
655    fn decode(&self, packed: &[u8], places: Places, pos: Pos) -> R<Fixed> {
656        let d = decimal::decode(packed).map_err(|c| Abend::check(c, pos))?;
657        Ok(fixed(d.negative, U256::from_u128(d.magnitude), places))
658    }
659
660    /// A zoned operand enters arithmetic through PACK, which keeps only the sign's zone.
661    fn zoned_value(&self, bytes: &[u8], digits: u32, signed: bool, sign: Option<SignClause>, places: Places, pos: Pos) -> R<Fixed> {
662        let mut zoned = bytes.to_vec();
663        let mut separate_negative = None;
664        match sign {
665            Some(SignClause { separate: true, position }) => {
666                let s = if position == SignPosition::Leading { zoned.remove(0) } else { zoned.pop().unwrap() };
667                separate_negative = Some(match s {
668                    0x60 => true,
669                    0x4E => false,
670                    _ => return Err(Abend::check(ProgramCheck::Data, pos)),
671                });
672                *zoned.last_mut().unwrap() |= 0xF0;
673            }
674            Some(SignClause { separate: false, position: SignPosition::Leading }) => {
675                let zone = zoned[0] & 0xF0;
676                zoned[0] |= 0xF0;
677                let last = zoned.len() - 1;
678                zoned[last] = zone | (zoned[last] & 0x0F);
679            }
680            _ => {}
681        }
682        let mut packed = vec![0u8; digits as usize / 2 + 1];
683        decimal::pack(&mut packed, &zoned).map_err(|c| Abend::check(c, pos))?;
684        if !signed && self.options.numproc == Numproc::Nopfd {
685            *packed.last_mut().unwrap() |= 0x0F;
686        }
687        let v = self.decode(&packed, places, pos)?;
688        Ok(match separate_negative {
689            Some(negative) => fixed(negative, v.magnitude, places),
690            None => v,
691        })
692    }
693
694    fn operand_with_loc(&mut self, op: &Operand, pos: Pos) -> R<(Val, Option<Loc>)> {
695        if let Operand::Ref(r) = op {
696            let loc = self.locate(r)?;
697            return Ok((self.read(loc, r.pos)?, Some(loc)));
698        }
699        Ok((self.operand(op, pos)?, None))
700    }
701
702    fn operand(&mut self, op: &Operand, pos: Pos) -> R<Val> {
703        match op {
704            Operand::Ref(r) => {
705                let loc = self.locate(r)?;
706                self.read(loc, r.pos)
707            }
708            Operand::Literal(lit) => self.literal_value(lit, pos),
709            Operand::LengthOf(r) => {
710                let loc = self.locate(r)?;
711                Ok(Val::Num(Fixed::new(loc.len as i128, Places::new(9, 0))))
712            }
713            Operand::Function(f) => self.function(f),
714            Operand::AddressOf(r) => Ok(Val::Address(self.address_of(r)?)),
715        }
716    }
717
718    /// An operand as STRING, UNSTRING and INSPECT see it: its bytes, a figurative constant as one
719    /// character, a numeric literal as its digits.
720    fn natural_bytes(&mut self, op: &Operand, pos: Pos) -> R<Vec<u8>> {
721        if let Operand::Ref(r) = op {
722            let loc = self.locate(r)?;
723            return Ok(self.bytes(loc).to_vec());
724        }
725        Ok(match self.operand(op, pos)? {
726            Val::Bytes(b) | Val::All(b) | Val::National(b) => b,
727            Val::Fig(f) => vec![figurative_byte(f)],
728            Val::Num(f) => zoned_digits(f.magnitude.to_u128().unwrap_or(0), f.places.total() as usize, decimal::UNSIGNED),
729            _ => return Err(Abend::ironwork("this operand has no characters to work on", pos)),
730        })
731    }
732
733    fn set_integer(&mut self, r: &Ref, value: i64, pos: Pos) -> R<()> {
734        let dest = self.locate(r)?;
735        self.store_fixed(dest, &Fixed::new(value as i128, Places::new(19, 0)), false, pos)
736    }
737
738    fn overflow_branch(&mut self, overflow: bool, on: &'p Option<Vec<Stmt>>, not_on: &'p Option<Vec<Stmt>>) -> R<Flow> {
739        match (overflow, on, not_on) {
740            (true, Some(body), _) | (false, _, Some(body)) => self.run_block(body),
741            _ => Ok(Flow::Next),
742        }
743    }
744
745    fn string_stmt(&mut self, st: &'p StringStmt) -> R<Flow> {
746        let pos = st.pos;
747        let dest = self.locate(&st.into)?;
748        let mut pointer = match &st.pointer {
749            Some(r) => self.integer(&Expr::Operand(Operand::Ref(r.clone())), pos)?,
750            None => 1,
751        };
752        let len = dest.len as i64;
753        let mut overflow = pointer < 1 || pointer > len;
754        if !overflow {
755            'sources: for (op, delimiter) in &st.sources {
756                let bytes = self.natural_bytes(op, pos)?;
757                let delimiter = match delimiter {
758                    Delimiter::Size => None,
759                    Delimiter::By(d) => Some(self.natural_bytes(d, pos)?),
760                };
761                for b in crate::strings::delimited(&bytes, delimiter.as_deref()) {
762                    if pointer > len {
763                        overflow = true;
764                        break 'sources;
765                    }
766                    self.unit.mem[dest.offset + pointer as usize - 1] = b;
767                    pointer += 1;
768                }
769            }
770        }
771        if let Some(r) = &st.pointer {
772            self.set_integer(r, pointer, pos)?;
773        }
774        self.overflow_branch(overflow, &st.on_overflow, &st.not_on_overflow)
775    }
776
777    fn unstring(&mut self, u: &'p Unstring) -> R<Flow> {
778        let pos = u.pos;
779        let source_loc = self.locate(&u.source)?;
780        let source = self.bytes(source_loc).to_vec();
781        let len = source.len() as i64;
782        let mut pointer = match &u.pointer {
783            Some(r) => self.integer(&Expr::Operand(Operand::Ref(r.clone())), pos)?,
784            None => 1,
785        };
786        let mut delimiters = Vec::new();
787        for (all, d) in &u.delimiters {
788            delimiters.push((*all, self.natural_bytes(d, pos)?));
789        }
790        let mut overflow = pointer < 1 || pointer > len;
791        let mut fields = 0i64;
792        if !overflow {
793            for into in &u.into {
794                if pointer > len {
795                    break;
796                }
797                let start = pointer as usize - 1;
798                let dest = self.locate(&into.target)?;
799                let (end, matched) = if delimiters.is_empty() {
800                    ((start + dest.len).min(source.len()), None)
801                } else {
802                    match crate::strings::next_delimiter(&source, start, &delimiters) {
803                        Some((at, k)) => (at, Some(k)),
804                        None => (source.len(), None),
805                    }
806                };
807                self.assign(dest, Val::Bytes(source[start..end].to_vec()), None, pos)?;
808                let delimiter = matched.map(|k| delimiters[k].1.clone());
809                if let Some(r) = &into.delimiter_in {
810                    let d = self.locate(r)?;
811                    self.assign(d, delimiter.clone().map_or(Val::Fig(Figurative::Space), Val::Bytes), None, pos)?;
812                }
813                if let Some(r) = &into.count_in {
814                    self.set_integer(r, (end - start) as i64, pos)?;
815                }
816                let next = match matched {
817                    Some(k) => crate::strings::past_delimiter(&source, end, &delimiters[k].1, delimiters[k].0),
818                    None => end,
819                };
820                pointer = next as i64 + 1;
821                fields += 1;
822            }
823            overflow = pointer <= len;
824        }
825        if let Some(r) = &u.pointer {
826            self.set_integer(r, pointer, pos)?;
827        }
828        if let Some(r) = &u.tallying {
829            let dest = self.locate(r)?;
830            let Val::Num(current) = self.read(dest, pos)? else {
831                return Err(Abend::ironwork("TALLYING IN needs a numeric item", pos));
832            };
833            let total = current.add(Fixed::new(fields as i128, Places::new(19, 0)), 0, self.options.arith).map_err(|_| Abend::ironwork("TALLYING", pos))?;
834            self.store_fixed(dest, &total, false, pos)?;
835        }
836        self.overflow_branch(overflow, &u.on_overflow, &u.not_on_overflow)
837    }
838
839    fn inspect(&mut self, i: &Inspect) -> R<()> {
840        let pos = i.pos;
841        let loc = self.locate(&i.target)?;
842        let mut data = self.bytes(loc).to_vec();
843        let tallied = self.phrases(&data, &i.tallying, pos)?;
844        let counts = crate::strings::inspect(&mut data, &tallied);
845        for (phrase, count) in i.tallying.iter().zip(counts) {
846            let Some(counter) = &phrase.counter else { continue };
847            let dest = self.locate(counter)?;
848            let Val::Num(current) = self.read(dest, pos)? else {
849                return Err(Abend::ironwork("a TALLYING counter must be numeric", pos));
850            };
851            let total = current.add(Fixed::new(count as i128, Places::new(19, 0)), 0, self.options.arith).map_err(|_| Abend::ironwork("TALLYING", pos))?;
852            self.store_fixed(dest, &total, false, pos)?;
853        }
854        let mut replacing = self.phrases(&data, &i.replacing, pos)?;
855        if let Some((from, to, bounds)) = &i.converting {
856            let (from, to) = (self.natural_bytes(from, pos)?, self.natural_bytes(to, pos)?);
857            if from.len() != to.len() {
858                return Err(Abend::ironwork("CONVERTING needs operands of the same length", pos));
859            }
860            let (start, end) = self.region(&data, bounds, pos)?;
861            let mut seen = Vec::new();
862            for (f, t) in from.into_iter().zip(to) {
863                if !seen.contains(&f) {
864                    seen.push(f);
865                    replacing.push(crate::strings::Phrase { mode: InspectMode::All, pattern: vec![f], by: Some(vec![t]), start, end });
866                }
867            }
868        }
869        crate::strings::inspect(&mut data, &replacing);
870        self.write(loc, &data);
871        Ok(())
872    }
873
874    fn region(&mut self, data: &[u8], bounds: &[Bound], pos: Pos) -> R<(usize, usize)> {
875        let (mut before, mut after) = (None, None);
876        for b in bounds {
877            let v = self.natural_bytes(&b.value, pos)?;
878            if b.after { after = Some(v) } else { before = Some(v) }
879        }
880        Ok(crate::strings::region(data, before.as_deref(), after.as_deref()))
881    }
882
883    fn phrases(&mut self, data: &[u8], phrases: &[InspectPhrase], pos: Pos) -> R<Vec<crate::strings::Phrase>> {
884        let mut out = Vec::new();
885        for p in phrases {
886            let pattern = match &p.pattern {
887                Some(op) => self.natural_bytes(op, pos)?,
888                None => Vec::new(),
889            };
890            let len = pattern.len().max(1);
891            let by = match &p.by {
892                Some(Operand::Literal(Literal::Figurative(f))) => Some(vec![figurative_byte(*f); len]),
893                Some(op) => Some(self.natural_bytes(op, pos)?),
894                None => None,
895            };
896            if by.as_ref().is_some_and(|b| b.len() != len) {
897                return Err(Abend::ironwork("a REPLACING value must be as long as what it replaces", pos));
898            }
899            let (start, end) = self.region(data, &p.bounds, pos)?;
900            out.push(crate::strings::Phrase { mode: p.mode, pattern, by, start, end });
901        }
902        Ok(out)
903    }
904
905    fn search(&mut self, se: &'p Search) -> R<Flow> {
906        let pos = se.pos;
907        let Resolved::Item(t) = self.resolve(&se.table)? else {
908            return Err(Abend::ironwork(format!("SEARCH {}: not a table", se.table.name), pos));
909        };
910        let layout = self.layout;
911        let table = &layout.items[t];
912        let count = self.occurrences(t, pos)? as i64;
913        let index = match (&se.varying, table.index_names.first()) {
914            (_, Some(name)) => Ref { name: name.clone(), qualifiers: Vec::new(), subscripts: Vec::new(), refmod: None, pos },
915            (Some(v), None) => v.clone(),
916            (None, None) => return Err(Abend::ironwork(format!("SEARCH {}: the table has no INDEXED BY", se.table.name), pos)),
917        };
918        let index_expr = Expr::Operand(Operand::Ref(index.clone()));
919        if !se.all {
920            loop {
921                let i = self.integer(&index_expr, pos)?;
922                if i < 1 || i > count {
923                    return match &se.at_end {
924                        Some(body) => self.run_block(body),
925                        None => Ok(Flow::Next),
926                    };
927                }
928                for (cond, body) in &se.whens {
929                    if self.condition(cond, pos)? {
930                        return self.run_block(body);
931                    }
932                }
933                self.set_integer(&index, i + 1, pos)?;
934                if let Some(v) = &se.varying
935                    && v.name != index.name
936                {
937                    let current = self.integer(&Expr::Operand(Operand::Ref(v.clone())), pos)?;
938                    self.set_integer(v, current + 1, pos)?;
939                }
940            }
941        }
942        let (cond, body) = &se.whens[0];
943        let mut terms = Vec::new();
944        flatten_and(cond, &mut terms);
945        let (mut low, mut high) = (1i64, count);
946        while low <= high {
947            let mid = (low + high) / 2;
948            self.set_integer(&index, mid, pos)?;
949            let mut order = Ordering::Equal;
950            for (ascending, key) in &table.keys {
951                let Some((subject, value)) = key_term(&terms, &key.name) else { continue };
952                let o = self.compare(subject, value, pos)?;
953                order = if *ascending { o } else { o.reverse() };
954                if order != Ordering::Equal {
955                    break;
956                }
957            }
958            match order {
959                Ordering::Less => low = mid + 1,
960                Ordering::Greater => high = mid - 1,
961                Ordering::Equal => {
962                    return if self.condition(cond, pos)? {
963                        self.run_block(body)
964                    } else {
965                        match &se.at_end {
966                            Some(b) => self.run_block(b),
967                            None => Ok(Flow::Next),
968                        }
969                    };
970                }
971            }
972        }
973        match &se.at_end {
974            Some(b) => self.run_block(b),
975            None => Ok(Flow::Next),
976        }
977    }
978
979    /// ADDRESS OF: the item's address, or NULL for a LINKAGE record with none yet.
980    fn address_of(&mut self, r: &Ref) -> R<u32> {
981        if let Ok(Resolved::Item(i)) = self.resolve(r)
982            && let Some(l) = self.layout.items[i].linkage
983            && self.linkage[l as usize].is_none()
984        {
985            return Ok(0);
986        }
987        let loc = self.locate(r)?;
988        Ok(ADDRESS_BASE + loc.offset as u32)
989    }
990
991    /// An address back to an offset in run-unit memory, refusing one that is NULL or outside it.
992    fn offset_of(&self, address: u32, pos: Pos) -> R<Option<usize>> {
993        if address == 0 {
994            return Ok(None);
995        }
996        let offset = address.checked_sub(ADDRESS_BASE).map(|o| o as usize).filter(|&o| o < self.unit.mem.len());
997        offset.map(Some).ok_or_else(|| Abend { code: "S0C4".into(), message: format!("address {address:08X} is outside the run unit's storage"), pos })
998    }
999
1000    fn program_name(&mut self, op: &Operand, pos: Pos) -> R<String> {
1001        Ok(match self.operand(op, pos)? {
1002            Val::Bytes(b) => self.page.decode(&b).trim().to_ascii_uppercase(),
1003            _ => return Err(Abend::ironwork("a program name must be alphanumeric", pos)),
1004        })
1005    }
1006
1007    fn call(&mut self, c: &'p Call) -> R<Flow> {
1008        let pos = c.pos;
1009        let name = self.program_name(&c.target, pos)?;
1010        let index = match self.unit.load(&name) {
1011            Ok(i) => i,
1012            Err(LoadError::NotFound) => {
1013                return match &c.on_exception {
1014                    Some(body) => self.run_block(body),
1015                    None => Err(Abend { code: "S806".into(), message: format!("CALL {name}: no such program in the run unit or its program libraries"), pos }),
1016                };
1017            }
1018            Err(LoadError::Compile(message)) => return Err(Abend::ironwork(format!("CALL {name}: {message}"), pos)),
1019        };
1020        let Some(compiled) = self.unit.programs[index].compiled.clone() else {
1021            return Err(Abend::ironwork(format!("CALL {name}: the first program of the run unit is already active"), pos));
1022        };
1023        if self.unit.programs[index].active && !compiled.program.recursive {
1024            return Err(Abend::ironwork(format!("CALL {name}: the program is already active and is not RECURSIVE"), pos));
1025        }
1026        self.nest(pos)?;
1027        let result = self.call_nested(c, index, compiled);
1028        self.unit.depth -= 1;
1029        result
1030    }
1031
1032    fn call_nested(&mut self, c: &'p Call, index: usize, compiled: std::rc::Rc<Compiled>) -> R<Flow> {
1033        let pos = c.pos;
1034        let mark = self.unit.mem.len();
1035        let mut addresses = Vec::new();
1036        for arg in &c.using {
1037            let Some(op) = &arg.value else {
1038                addresses.push(None);
1039                continue;
1040            };
1041            let at = match (arg.mode, op) {
1042                (ArgMode::Reference, Operand::Ref(r)) => {
1043                    let loc = self.locate(r)?;
1044                    loc.offset
1045                }
1046                (ArgMode::Value, _) => {
1047                    let bytes = self.value_argument(op, pos)?;
1048                    self.unit.push_temporary(&bytes)
1049                }
1050                (_, _) => {
1051                    let bytes = self.content_argument(op, pos)?;
1052                    self.unit.push_temporary(&bytes)
1053                }
1054            };
1055            addresses.push(Some(at));
1056        }
1057        let outcome = {
1058            let mut callee = Machine::activation(&compiled, index, &mut *self.unit, false)?;
1059            callee.bind(&addresses);
1060            callee.bind_returning();
1061            let ending = callee.run_procedure();
1062            let returned = match (&compiled.program.returning, &ending) {
1063                (Some(item), Ok(_)) => Some(callee.returned(item, pos)?),
1064                _ => None,
1065            };
1066            (ending, returned)
1067        };
1068        self.unit.programs[index].active = false;
1069        if compiled.program.initial {
1070            self.unit.programs[index].initialized = false;
1071        }
1072        self.unit.release_temporaries(mark);
1073        let (ending, returned) = outcome;
1074        if ending? == Ending::StopRun {
1075            return Ok(Flow::End(Ending::StopRun));
1076        }
1077        if let (Some(target), Some(val)) = (&c.returning, returned) {
1078            let dest = self.locate(target)?;
1079            self.assign(dest, val, None, pos)?;
1080        }
1081        match &c.not_on_exception {
1082            Some(body) => self.run_block(body),
1083            None => Ok(Flow::Next),
1084        }
1085    }
1086
1087    /// Gives each PROCEDURE DIVISION USING item the address of the argument in its position.
1088    fn bind(&mut self, addresses: &[Option<usize>]) {
1089        for (param, address) in self.program.using.iter().zip(addresses) {
1090            if let Some(ordinal) = self.layout.linkage_roots.iter().position(|&i| self.layout.items[i].name.as_deref() == Some(param.name.as_str())) {
1091                self.linkage[ordinal] = *address;
1092            }
1093        }
1094    }
1095
1096    /// The RETURNING item is in the LINKAGE SECTION, but no argument addresses it: the runtime
1097    /// gives it storage of its own for the call.
1098    fn bind_returning(&mut self) {
1099        let Some(name) = &self.program.returning else { return };
1100        let Some(ordinal) = self.layout.linkage_roots.iter().position(|&i| self.layout.items[i].name.as_deref() == Some(name.as_str())) else { return };
1101        let size = self.layout.items[self.layout.linkage_roots[ordinal]].size as usize;
1102        self.linkage[ordinal] = Some(self.unit.push_temporary(&vec![0; size]));
1103    }
1104
1105    fn returned(&mut self, name: &str, pos: Pos) -> R<Val> {
1106        let r = Ref { name: name.to_owned(), qualifiers: Vec::new(), subscripts: Vec::new(), refmod: None, pos };
1107        let loc = self.locate(&r)?;
1108        self.read(loc, pos)
1109    }
1110
1111    /// A BY CONTENT argument: a copy of the item, or of the literal as its own data item would hold it.
1112    fn content_argument(&mut self, op: &Operand, pos: Pos) -> R<Vec<u8>> {
1113        if let Operand::Ref(r) = op {
1114            let loc = self.locate(r)?;
1115            return Ok(self.bytes(loc).to_vec());
1116        }
1117        Ok(match self.operand(op, pos)? {
1118            Val::Bytes(b) | Val::All(b) | Val::National(b) => b,
1119            Val::Fig(f) => vec![figurative_byte(f)],
1120            Val::Address(a) => a.to_be_bytes().to_vec(),
1121            Val::Num(f) if matches!(op, Operand::LengthOf(_)) => (align(&f, 0, false).and_then(|m| m.to_u128()).unwrap_or(0) as u32).to_be_bytes().to_vec(),
1122            Val::Num(f) => {
1123                let digits = f.places.total().max(1);
1124                let magnitude = align(&f, f.places.dec, false).and_then(|m| m.to_u128()).unwrap_or(0);
1125                zoned_digits(magnitude, digits as usize, if f.negative { decimal::MINUS } else { decimal::UNSIGNED })
1126            }
1127            Val::Float(h) => h.to_bytes(),
1128        })
1129    }
1130
1131    /// A BY VALUE argument: an integer as a binary fullword, an address, or the bytes of a one-character item.
1132    fn value_argument(&mut self, op: &Operand, pos: Pos) -> R<Vec<u8>> {
1133        Ok(match self.operand(op, pos)? {
1134            Val::Num(f) => {
1135                let whole = align(&f, 0, false).and_then(|m| m.to_u128()).and_then(|m| i32::try_from(m).ok()).ok_or_else(|| Abend::ironwork("a BY VALUE integer beyond a fullword", pos))?;
1136                (if f.negative { -whole } else { whole }).to_be_bytes().to_vec()
1137            }
1138            Val::Address(a) => a.to_be_bytes().to_vec(),
1139            Val::Fig(Figurative::Null) => vec![0; 4],
1140            Val::Bytes(b) => b,
1141            _ => return Err(Abend::ironwork("this BY VALUE argument is not supported", pos)),
1142        })
1143    }
1144
1145    fn cancel(&mut self, name: &str, pos: Pos) -> R<()> {
1146        let Some(index) = self.unit.find(name) else { return Ok(()) };
1147        if self.unit.programs[index].active {
1148            return Err(Abend::ironwork(format!("CANCEL {name}: the program is active"), pos));
1149        }
1150        let files: Vec<_> = self.unit.programs[index].files.iter_mut().filter_map(Option::take).collect();
1151        for f in files {
1152            f.close().map_err(|e| Abend::ironwork(format!("CANCEL {name}: {e}"), pos))?;
1153        }
1154        self.unit.programs[index].initialized = false;
1155        Ok(())
1156    }
1157
1158    fn set(&mut self, set: &SetStmt, pos: Pos) -> R<()> {
1159        match set {
1160            SetStmt::ConditionTrue(targets) => {
1161                for r in targets {
1162                    let Resolved::Condition(index) = self.resolve(r)? else {
1163                        return Err(Abend::ironwork(format!("SET {} TO TRUE: not a condition-name", r.name), pos));
1164                    };
1165                    let condition = &self.layout.conditions[index];
1166                    let Some((value, _)) = condition.values.first().cloned() else { continue };
1167                    let item = &self.layout.items[condition.item];
1168                    let subject = Ref { name: item.name.clone().unwrap_or_default(), qualifiers: Vec::new(), subscripts: r.subscripts.clone(), refmod: None, pos };
1169                    let dest = self.locate(&subject)?;
1170                    let val = self.literal_value(&value, pos)?;
1171                    self.assign(dest, val, None, pos)?;
1172                }
1173            }
1174            SetStmt::To { targets, value } => {
1175                for r in targets {
1176                    let dest = self.locate(r)?;
1177                    let (val, src) = self.operand_with_loc(value, pos)?;
1178                    match (dest.kind, val) {
1179                        (Kind::Pointer, v @ (Val::Address(_) | Val::Fig(Figurative::Null))) => self.assign(dest, v, None, pos)?,
1180                        (Kind::Pointer, _) => return Err(Abend::ironwork("SET a pointer TO ADDRESS OF, NULL or another pointer", pos)),
1181                        (_, v) => self.assign(dest, v, src, pos)?,
1182                    }
1183                }
1184            }
1185            SetStmt::AddressOf { targets, value } => {
1186                let address = match self.operand(value, pos)? {
1187                    Val::Address(a) => a,
1188                    Val::Fig(Figurative::Null) => 0,
1189                    _ => return Err(Abend::ironwork("SET ADDRESS OF takes a pointer, ADDRESS OF or NULL", pos)),
1190                };
1191                let offset = self.offset_of(address, pos)?;
1192                for r in targets {
1193                    let Resolved::Item(i) = self.resolve(r)? else {
1194                        return Err(Abend::ironwork(format!("SET ADDRESS OF {}: not a data item", r.name), pos));
1195                    };
1196                    let Some(ordinal) = self.layout.items[i].linkage.filter(|_| self.layout.items[i].parent.is_none()) else {
1197                        return Err(Abend::ironwork(format!("SET ADDRESS OF {}: only a LINKAGE record can be given an address", r.name), pos));
1198                    };
1199                    self.linkage[ordinal as usize] = offset;
1200                }
1201            }
1202            SetStmt::UpDown { targets, down, by } => {
1203                let step = self.integer(by, pos)?;
1204                let step = if *down { -step } else { step };
1205                for r in targets {
1206                    let dest = self.locate(r)?;
1207                    match self.read(dest, pos)? {
1208                        Val::Address(a) => {
1209                            let moved = u32::try_from(a as i64 + step).map_err(|_| Abend::ironwork("a pointer moved below zero", pos))?;
1210                            self.write(dest, &moved.to_be_bytes());
1211                        }
1212                        Val::Num(f) => {
1213                            let next = f.add(Fixed::new(step as i128, Places::new(19, 0)), 0, self.options.arith).map_err(|_| Abend::ironwork("SET UP/DOWN", pos))?;
1214                            self.store_fixed(dest, &next, false, pos)?;
1215                        }
1216                        _ => return Err(Abend::ironwork("SET UP BY and DOWN BY take an index, integer or pointer", pos)),
1217                    }
1218                }
1219            }
1220        }
1221        Ok(())
1222    }
1223
1224    fn accept(&mut self, target: &Ref, from: AcceptFrom, pos: Pos) -> R<()> {
1225        let dest = self.locate(target)?;
1226        let (seconds, hundredths) = self.unit.now();
1227        let (year, month, day, hour, minute, second, yday, wday) = crate::unit::civil(seconds);
1228        let digits = |text: String| Val::Num(literal_fixed(&text).expect("digits"));
1229        let val = match from {
1230            AcceptFrom::Date { four_digit_year: true } => digits(format!("{year:04}{month:02}{day:02}")),
1231            AcceptFrom::Date { four_digit_year: false } => digits(format!("{:02}{month:02}{day:02}", year % 100)),
1232            AcceptFrom::Day { four_digit_year: true } => digits(format!("{year:04}{yday:03}")),
1233            AcceptFrom::Day { four_digit_year: false } => digits(format!("{:02}{yday:03}", year % 100)),
1234            AcceptFrom::DayOfWeek => digits(format!("{wday}")),
1235            AcceptFrom::Time => digits(format!("{hour:02}{minute:02}{second:02}{hundredths:02}")),
1236            AcceptFrom::Sysin => {
1237                let mut line = String::new();
1238                let read = match self.unit.sysin.as_mut() {
1239                    Some(r) => r.read_line(&mut line).map_err(|e| Abend::ironwork(format!("ACCEPT: {e}"), pos))?,
1240                    None => 0,
1241                };
1242                if read == 0 {
1243                    let _ = writeln!(self.unit.err, "ironwork: {pos}: ACCEPT found SYSIN at its end; {} is unchanged", target.name);
1244                    return Ok(());
1245                }
1246                let unknown = self.page.encode_char('?').unwrap_or(0x6F);
1247                Val::Bytes(line.trim_end_matches(['\n', '\r']).chars().map(|c| self.page.encode_char(c).unwrap_or(unknown)).collect())
1248            }
1249        };
1250        self.assign(dest, val, None, pos)
1251    }
1252
1253    fn function(&mut self, f: &FunctionCall) -> R<Val> {
1254        let pos = f.pos;
1255        let mut args: Vec<Val> = Vec::new();
1256        for a in &f.args {
1257            args.push(self.expr_value(a, pos)?);
1258        }
1259        let arity = |n: std::ops::RangeInclusive<usize>| {
1260            if n.contains(&args.len()) { Ok(()) } else { Err(Abend::ironwork(format!("FUNCTION {} takes {n:?} arguments", f.name), pos)) }
1261        };
1262        let bytes_of = |v: &Val| match v {
1263            Val::Bytes(b) | Val::All(b) => Ok(b.clone()),
1264            Val::Fig(fig) => Ok(vec![figurative_byte(*fig)]),
1265            _ => Err(Abend::ironwork(format!("FUNCTION {} needs an alphanumeric argument", f.name), pos)),
1266        };
1267        let value = match f.name.as_str() {
1268            "CHAR" => {
1269                arity(1..=1)?;
1270                let n = self.integer(&f.args[0], pos)?;
1271                if !(1..=256).contains(&n) {
1272                    return Err(Abend::ironwork(format!("FUNCTION CHAR({n}) is outside 1 to 256"), pos));
1273                }
1274                Val::Bytes(vec![(n - 1) as u8])
1275            }
1276            "ORD" => {
1277                arity(1..=1)?;
1278                let b = bytes_of(&args[0])?;
1279                let first = *b.first().ok_or_else(|| Abend::ironwork("FUNCTION ORD of an empty argument", pos))?;
1280                Val::Num(Fixed::new(first as i128 + 1, Places::new(3, 0)))
1281            }
1282            "NATIONAL-OF" => {
1283                arity(1..=2)?;
1284                let ccsid = if args.len() == 2 { self.integer(&f.args[1], pos)? as u16 } else { self.options.codepage };
1285                let page = CodePage::by_ccsid(ccsid).ok_or_else(|| Abend::ironwork(format!("CCSID {ccsid} is not a code page ironwork for COBOL carries"), pos))?;
1286                Val::National(page.to_utf16be(&bytes_of(&args[0])?))
1287            }
1288            "LENGTH" => {
1289                arity(1..=1)?;
1290                let n = match &args[0] {
1291                    Val::Bytes(b) | Val::All(b) => b.len(),
1292                    Val::National(b) => b.len() / 2,
1293                    Val::Num(v) => v.places.total() as usize,
1294                    _ => return Err(Abend::ironwork("FUNCTION LENGTH of this argument is not supported yet", pos)),
1295                };
1296                Val::Num(Fixed::new(n as i128, Places::new(9, 0)))
1297            }
1298            "NUMVAL" | "NUMVAL-C" => {
1299                arity(1..=2)?;
1300                let currency = match args.get(1) {
1301                    Some(v) => self.page.decode(&bytes_of(v)?),
1302                    None => "$".to_owned(),
1303                };
1304                let text = self.page.decode(&bytes_of(&args[0])?);
1305                Val::Num(numval(&text, (f.name == "NUMVAL-C").then_some(currency.as_str())).unwrap_or_else(|| Fixed::new(0, Places::new(1, 0))))
1306            }
1307            "TRIM" => {
1308                arity(1..=1)?;
1309                let b = bytes_of(&args[0])?;
1310                let first = b.iter().position(|&c| c != ebcdic::SPACE);
1311                let last = b.iter().rposition(|&c| c != ebcdic::SPACE);
1312                let trimmed = match (first, last, f.modifier.as_deref()) {
1313                    (None, _, _) | (_, None, _) => Vec::new(),
1314                    (Some(s), _, Some("LEADING")) => b[s..].to_vec(),
1315                    (_, Some(e), Some("TRAILING")) => b[..=e].to_vec(),
1316                    (Some(s), Some(e), _) => b[s..=e].to_vec(),
1317                };
1318                Val::Bytes(trimmed)
1319            }
1320            "MOD" | "REM" | "INTEGER" | "INTEGER-PART" | "ABS" => {
1321                arity(if matches!(f.name.as_str(), "MOD" | "REM") { 2..=2 } else { 1..=1 })?;
1322                let number = |v: &Val| match v {
1323                    Val::Num(x) => Ok(*x),
1324                    _ => Err(Abend::ironwork(format!("FUNCTION {} needs numeric arguments", f.name), pos)),
1325                };
1326                let x = number(&args[0])?;
1327                let dec = if args.len() == 2 { x.places.dec.max(number(&args[1])?.places.dec) } else { x.places.dec };
1328                let scaled = |v: &Fixed| -> R<i128> {
1329                    let m = align(v, dec, false).and_then(|m| m.to_u128()).and_then(|m| i128::try_from(m).ok()).ok_or_else(|| Abend::ironwork("an argument beyond 38 digits", pos))?;
1330                    Ok(if v.negative { -m } else { m })
1331                };
1332                let unit = 10i128.pow(dec);
1333                let a = scaled(&x)?;
1334                let result = match f.name.as_str() {
1335                    "ABS" => a.abs(),
1336                    "INTEGER" => a.div_euclid(unit) * unit,
1337                    "INTEGER-PART" => a / unit * unit,
1338                    other => {
1339                        let b = scaled(&number(&args[1])?)?;
1340                        if b == 0 {
1341                            return Err(Abend::ironwork(format!("FUNCTION {other} by zero"), pos));
1342                        }
1343                        if other == "MOD" { a - b * a.div_euclid(b) } else { a - b * (a / b) }
1344                    }
1345                };
1346                Val::Num(Fixed::new(result, Places::new(31 - dec.min(31), dec)))
1347            }
1348            "MIN" | "MAX" => {
1349                if args.is_empty() {
1350                    return Err(Abend::ironwork(format!("FUNCTION {} needs arguments", f.name), pos));
1351                }
1352                let want = if f.name == "MIN" { Ordering::Less } else { Ordering::Greater };
1353                let mut best = 0;
1354                for i in 1..args.len() {
1355                    let o = match (&args[i], &args[best]) {
1356                        (Val::Num(x), Val::Num(y)) => compare_fixed(x, y),
1357                        (x, y) => bytes_of(x)?.cmp(&bytes_of(y)?),
1358                    };
1359                    if o == want {
1360                        best = i;
1361                    }
1362                }
1363                args.swap_remove(best)
1364            }
1365            "INTEGER-OF-DATE" => {
1366                arity(1..=1)?;
1367                let n = self.integer(&f.args[0], pos)?;
1368                let (y, m, d) = (n / 10000, n / 100 % 100, n % 100);
1369                if !(1601..=9999).contains(&y) || !(1..=12).contains(&m) || !(1..=days_in_month(y, m)).contains(&d) {
1370                    return Err(Abend::ironwork(format!("FUNCTION INTEGER-OF-DATE({n}): not a date from 1601 to 9999"), pos));
1371                }
1372                Val::Num(Fixed::new((days_from_civil(y, m, d) - days_from_civil(1600, 12, 31)) as i128, Places::new(7, 0)))
1373            }
1374            "DATE-OF-INTEGER" => {
1375                arity(1..=1)?;
1376                let n = self.integer(&f.args[0], pos)?;
1377                if !(1..=3_067_671).contains(&n) {
1378                    return Err(Abend::ironwork(format!("FUNCTION DATE-OF-INTEGER({n}): outside 1 to 3067671"), pos));
1379                }
1380                let (y, m, d, ..) = crate::unit::civil((days_from_civil(1600, 12, 31) + n) * 86_400);
1381                Val::Num(Fixed::new((y * 10000 + m as i64 * 100 + d as i64) as i128, Places::new(8, 0)))
1382            }
1383            "CURRENT-DATE" => {
1384                arity(0..=0)?;
1385                let (seconds, hundredths) = self.unit.now();
1386                let (y, mo, d, h, mi, s, _, _) = crate::unit::civil(seconds);
1387                let text = format!("{y:04}{mo:02}{d:02}{h:02}{mi:02}{s:02}{hundredths:02}+0000");
1388                Val::Bytes(self.page.encode(&text).map_err(|e| Abend::ironwork(e.to_string(), pos))?)
1389            }
1390            "UPPER-CASE" | "LOWER-CASE" | "REVERSE" => {
1391                arity(1..=1)?;
1392                let text = self.page.decode(&bytes_of(&args[0])?);
1393                let changed: String = match f.name.as_str() {
1394                    "UPPER-CASE" => text.to_uppercase(),
1395                    "LOWER-CASE" => text.to_lowercase(),
1396                    _ => text.chars().rev().collect(),
1397                };
1398                Val::Bytes(self.page.encode(&changed).map_err(|e| Abend::ironwork(e.to_string(), pos))?)
1399            }
1400            other => return Err(Abend::ironwork(format!("FUNCTION {other} is not supported yet"), pos)),
1401        };
1402        match (&f.refmod, value) {
1403            (None, v) => Ok(v),
1404            (Some(rm), Val::Bytes(b)) => {
1405                let start = self.integer(&rm.start, pos)? as usize;
1406                let len = match &rm.length {
1407                    Some(l) => self.integer(l, pos)? as usize,
1408                    None => b.len() + 1 - start,
1409                };
1410                b.get(start - 1..start - 1 + len).map(|s| Val::Bytes(s.to_vec())).ok_or_else(|| Abend::ironwork("reference modification past the function result", pos))
1411            }
1412            (Some(_), _) => Err(Abend::ironwork("reference modification of a non-alphanumeric function result", pos)),
1413        }
1414    }
1415
1416    fn expr_value(&mut self, e: &Expr, pos: Pos) -> R<Val> {
1417        match e {
1418            Expr::Operand(op) => self.operand(op, pos),
1419            _ if self.uses_float(e)? => Ok(Val::Float(self.eval_float(e, self.options.arith.float_intermediate(), pos)?)),
1420            _ => {
1421                let dmax = self.dmax(e)?;
1422                Ok(Val::Num(self.eval_fixed(e, dmax, pos)?))
1423            }
1424        }
1425    }
1426
1427    fn operand_kind(&mut self, op: &Operand) -> R<Option<Kind>> {
1428        Ok(match op {
1429            Operand::Ref(r) => Some(self.locate(r)?.kind),
1430            _ => None,
1431        })
1432    }
1433
1434    fn uses_float(&mut self, e: &Expr) -> R<bool> {
1435        Ok(match e {
1436            Expr::Operand(op) => matches!(self.operand_kind(op)?, Some(Kind::Float(_))),
1437            Expr::Neg(inner) => self.uses_float(inner)?,
1438            Expr::Bin(a, _, b) => self.uses_float(a)? || self.uses_float(b)?,
1439        })
1440    }
1441
1442    /// The most decimal places among an expression's operands, divisors and exponents aside.
1443    fn dmax(&mut self, e: &Expr) -> R<u32> {
1444        Ok(match e {
1445            Expr::Operand(Operand::Literal(Literal::Number(t))) => literal_fixed(t).map_or(0, |f| f.places.dec),
1446            Expr::Operand(op) => self.operand_kind(op)?.and_then(Kind::digits_scale).map_or(0, |(_, s)| s),
1447            Expr::Neg(inner) => self.dmax(inner)?,
1448            Expr::Bin(a, BinOp::Div | BinOp::Pow, _) => self.dmax(a)?,
1449            Expr::Bin(a, _, b) => self.dmax(a)?.max(self.dmax(b)?),
1450        })
1451    }
1452
1453    fn eval_fixed(&mut self, e: &Expr, dmax: u32, pos: Pos) -> R<Fixed> {
1454        let arith = self.options.arith;
1455        let wrap = |r: Result<Fixed, ArithError>| {
1456            r.map_err(|e| match e {
1457                ArithError::DivideByZero => Abend { code: DIVIDE_BY_ZERO.into(), message: "division by zero".into(), pos },
1458                ArithError::BeyondModel => Abend::ironwork("an intermediate result wider than 256 bits", pos),
1459            })
1460        };
1461        match e {
1462            Expr::Operand(op) => match self.operand(op, pos)? {
1463                Val::Num(f) => Ok(f),
1464                Val::Fig(Figurative::Zero) => Ok(Fixed::new(0, Places::new(1, 0))),
1465                _ => Err(Abend::ironwork("a non-numeric operand in arithmetic", pos)),
1466            },
1467            Expr::Neg(inner) => {
1468                let v = self.eval_fixed(inner, dmax, pos)?;
1469                Ok(fixed(!v.negative, v.magnitude, v.places))
1470            }
1471            Expr::Bin(a, op, b) => {
1472                let x = self.eval_fixed(a, dmax, pos)?;
1473                if *op == BinOp::Pow {
1474                    let n = self.integer(b, pos)?;
1475                    if !(0..=31).contains(&n) {
1476                        return Err(Abend::ironwork("exponentiation other than by an integer from 0 to 31 is not supported yet", pos));
1477                    }
1478                    let mut acc = Fixed::new(1, Places::new(1, 0));
1479                    for _ in 0..n {
1480                        acc = wrap(acc.mul(x, dmax, arith))?;
1481                    }
1482                    return Ok(acc);
1483                }
1484                let y = self.eval_fixed(b, dmax, pos)?;
1485                wrap(match op {
1486                    BinOp::Add => x.add(y, dmax, arith),
1487                    BinOp::Sub => x.sub(y, dmax, arith),
1488                    BinOp::Mul => x.mul(y, dmax, arith),
1489                    _ => x.div(y, dmax, arith),
1490                })
1491            }
1492        }
1493    }
1494
1495    fn eval_float(&mut self, e: &Expr, p: Precision, pos: Pos) -> R<Hfp> {
1496        let mask = ProgramMask::default();
1497        let check = |r: Result<Hfp, ProgramCheck>| r.map_err(|c| Abend::check(c, pos));
1498        match e {
1499            Expr::Operand(op) => match self.operand(op, pos)? {
1500                Val::Float(h) if h.precision.digits() <= p.digits() => Ok(h.lengthen(p)),
1501                Val::Float(h) => Ok(float::narrow(h, p)),
1502                Val::Num(f) => check(float::from_fixed(f, p, mask)),
1503                Val::Fig(Figurative::Zero) => Ok(Hfp::zero(p)),
1504                _ => Err(Abend::ironwork("a non-numeric operand in arithmetic", pos)),
1505            },
1506            Expr::Neg(inner) => {
1507                let v = self.eval_float(inner, p, pos)?;
1508                Ok(if v.fraction == 0 { v } else { Hfp { negative: !v.negative, ..v } })
1509            }
1510            Expr::Bin(a, op, b) => {
1511                let (x, y) = (self.eval_float(a, p, pos)?, self.eval_float(b, p, pos)?);
1512                check(match op {
1513                    BinOp::Add => x.add(y, mask),
1514                    BinOp::Sub => x.sub(y, mask),
1515                    BinOp::Mul => x.mul(y, p, mask),
1516                    BinOp::Div => x.div(y, mask),
1517                    BinOp::Pow => return Err(Abend::ironwork("floating-point exponentiation is not supported yet", pos)),
1518                })
1519            }
1520        }
1521    }
1522
1523    /// COMPUTE, ADD, SUBTRACT, MULTIPLY, DIVIDE: each target receives its expression. A size error
1524    /// leaves the target unchanged when the statement handles it.
1525    fn arithmetic(&mut self, computations: &[(Target, Expr)], remainder: Option<&(Target, Expr, Expr)>, handler: Option<&'p SizeError>, pos: Pos) -> R<Flow> {
1526        let mut size_error = false;
1527        let mut dmax = 0;
1528        for (t, e) in computations {
1529            let loc = self.locate(&t.r)?;
1530            dmax = dmax.max(loc.kind.digits_scale().map_or(0, |(_, s)| s)).max(self.dmax(e)?);
1531        }
1532        if let Some((t, dividend, _)) = remainder {
1533            let loc = self.locate(&t.r)?;
1534            dmax = dmax.max(loc.kind.digits_scale().map_or(0, |(_, s)| s)).max(self.dmax(dividend)?);
1535        }
1536        let mut quotient_target: Option<Loc> = None;
1537        for (t, e) in computations {
1538            let loc = self.locate(&t.r)?;
1539            quotient_target.get_or_insert(loc);
1540            let outcome = if self.uses_float(e)? {
1541                self.eval_float(e, self.options.arith.float_intermediate(), pos).map(Val::Float)
1542            } else {
1543                self.eval_fixed(e, dmax, pos).map(Val::Num)
1544            };
1545            let value = match outcome {
1546                Err(a) if a.code == DIVIDE_BY_ZERO => {
1547                    if handler.is_none() {
1548                        return Err(Abend::check(ProgramCheck::DecimalDivide, pos));
1549                    }
1550                    size_error = true;
1551                    continue;
1552                }
1553                other => other?,
1554            };
1555            size_error |= self.store_value(loc, value, t.rounded, handler.is_some(), pos)?;
1556        }
1557        if let (Some((t, dividend, divisor)), Some(q_loc)) = (remainder, quotient_target) {
1558            let arith = self.options.arith;
1559            let (x, y) = (self.eval_fixed(dividend, dmax, pos)?, self.eval_fixed(divisor, dmax, pos)?);
1560            if !y.magnitude.is_zero() {
1561                let q = x.div(y, dmax, arith).map_err(|_| Abend::ironwork("remainder", pos))?;
1562                let q_places = places_of(q_loc.kind);
1563                let q = fixed(q.negative, align(&q, q_places.dec, false).unwrap_or_default(), Places::new(q.places.int, q_places.dec));
1564                let r = q.mul(y, dmax, arith).and_then(|p| x.sub(p, dmax, arith)).map_err(|_| Abend::ironwork("remainder", pos))?;
1565                let r_loc = self.locate(&t.r)?;
1566                size_error |= self.store_value(r_loc, Val::Num(r), false, handler.is_some(), pos)?;
1567            }
1568        }
1569        if let Some(h) = handler {
1570            return self.run_block(if size_error { &h.on } else { &h.not_on });
1571        }
1572        Ok(Flow::Next)
1573    }
1574
1575    /// Stores an arithmetic result; returns whether it was a size error.
1576    fn store_value(&mut self, loc: Loc, value: Val, rounded: bool, keep_on_size_error: bool, pos: Pos) -> R<bool> {
1577        match (loc.kind, value) {
1578            (Kind::Float(p), Val::Float(h)) => {
1579                let h = if h.precision.digits() > p.digits() { float::narrow(h, p) } else { h.lengthen(p) };
1580                self.write(loc, &h.to_bytes());
1581                Ok(false)
1582            }
1583            (Kind::Float(p), Val::Num(f)) => {
1584                let h = float::from_fixed(f, p, ProgramMask::default()).map_err(|c| Abend::check(c, pos))?;
1585                self.write(loc, &h.to_bytes());
1586                Ok(false)
1587            }
1588            (kind, Val::Float(h)) => {
1589                let (f, overflow) = float::to_fixed(h, places_of(kind), rounded);
1590                if overflow && keep_on_size_error {
1591                    return Ok(true);
1592                }
1593                Ok(self.store_fixed_checked(loc, &f, false, keep_on_size_error, pos)? || overflow)
1594            }
1595            (_, Val::Num(f)) => self.store_fixed_checked(loc, &f, rounded, keep_on_size_error, pos),
1596            _ => Err(Abend::ironwork("a non-numeric arithmetic result", pos)),
1597        }
1598    }
1599
1600    fn store_fixed(&mut self, loc: Loc, value: &Fixed, rounded: bool, pos: Pos) -> R<()> {
1601        self.store_fixed_checked(loc, value, rounded, false, pos).map(|_| ())
1602    }
1603
1604    fn store_fixed_checked(&mut self, loc: Loc, value: &Fixed, rounded: bool, keep_on_size_error: bool, pos: Pos) -> R<bool> {
1605        let beyond = || Abend::ironwork("a value wider than 256 bits", pos);
1606        let (bytes, size_error) = match loc.kind {
1607            Kind::Zoned { digits, scale, signed, sign } => {
1608                let m = align(value, scale, rounded).ok_or_else(beyond)?;
1609                let cap = pow10(digits);
1610                let kept = m.div_rem(cap).1.to_u128().unwrap();
1611                let negative = signed && value.negative && kept != 0;
1612                (self.zoned_image(kept, digits, signed, negative, sign), m >= cap)
1613            }
1614            Kind::Packed { digits, scale, signed } => {
1615                let m = align(value, scale, rounded).ok_or_else(beyond)?;
1616                let cap = pow10(digits);
1617                let kept = m.div_rem(cap).1.to_u128().unwrap();
1618                let mut out = vec![0u8; loc.len];
1619                decimal::encode(&mut out, Decimal { negative: signed && value.negative && kept != 0, magnitude: kept }).map_err(|c| Abend::check(c, pos))?;
1620                if !signed {
1621                    *out.last_mut().unwrap() |= 0x0F;
1622                }
1623                (out, m >= cap)
1624            }
1625            Kind::Binary { digits, scale, signed, native } => {
1626                let m = align(value, scale, rounded).ok_or_else(beyond)?;
1627                let magnitude = m.to_u128().and_then(|m| i128::try_from(m).ok()).ok_or_else(beyond)?;
1628                let v = if value.negative { -magnitude } else { magnitude };
1629                let item = Binary { digits: digits as u8, signed, native };
1630                let stored = binary::store(item, v, &self.options);
1631                if let Some(d) = stored.divergence {
1632                    let name = self.layout.items.get(loc.item).map_or("RETURN-CODE".into(), |i| i.name.clone().unwrap_or_else(|| "FILLER".into()));
1633                    let _ = writeln!(
1634                        self.unit.err,
1635                        "ironwork: {pos}: TRUNC(OPT) store of {} into {name} PIC {}9({digits}) BINARY: the PICTURE keeps {}, the binary field {}; {} was stored (-silent stops these reports)",
1636                        d.value,
1637                        if signed { "S" } else { "" },
1638                        d.decimal,
1639                        d.binary,
1640                        d.binary
1641                    );
1642                }
1643                let bits = 8 * item.bytes() as u32;
1644                let binary_range = if signed { v >= -(1i128 << (bits - 1)) && v < (1i128 << (bits - 1)) } else { (0..(1i128 << bits)).contains(&v.abs()) };
1645                let exceeds = if native || self.options.trunc == Trunc::Bin { !binary_range } else { v.unsigned_abs() >= 10u128.pow(digits) };
1646                (stored.bytes, exceeds)
1647            }
1648            Kind::Float(p) => (float::from_fixed(*value, p, ProgramMask::default()).map_err(|c| Abend::check(c, pos))?.to_bytes(), false),
1649            Kind::Index => {
1650                let whole = align(value, 0, false).and_then(|m| m.to_u128()).and_then(|m| i32::try_from(m).ok()).ok_or_else(beyond)?;
1651                ((if value.negative { -whole } else { whole }).to_be_bytes().to_vec(), false)
1652            }
1653            Kind::NumericEdited { edit, digits, scale, blank_when_zero } => {
1654                let m = align(value, scale, rounded).ok_or_else(beyond)?;
1655                let cap = pow10(digits);
1656                let kept = m.div_rem(cap).1.to_u128().unwrap();
1657                let text = crate::edit::numeric(&self.layout.edits[edit as usize], digits, value.negative && kept != 0, kept, blank_when_zero);
1658                (self.page.encode(&text).map_err(|e| Abend::ironwork(e.to_string(), pos))?, m >= cap)
1659            }
1660            _ => return Err(Abend::ironwork("a numeric value stored into a non-numeric item", pos)),
1661        };
1662        if size_error && keep_on_size_error {
1663            return Ok(true);
1664        }
1665        self.write(loc, &bytes);
1666        Ok(size_error)
1667    }
1668
1669    fn zoned_image(&self, magnitude: u128, digits: u32, signed: bool, negative: bool, sign: Option<SignClause>) -> Vec<u8> {
1670        let zone = match (signed, negative) {
1671            (false, _) => decimal::UNSIGNED,
1672            (true, true) => decimal::MINUS,
1673            (true, false) => decimal::PLUS,
1674        };
1675        match sign {
1676            Some(SignClause { separate: true, position }) => {
1677                let body = zoned_digits(magnitude, digits as usize, decimal::UNSIGNED);
1678                let s = if negative { 0x60 } else { 0x4E };
1679                if position == SignPosition::Leading { [&[s][..], &body].concat() } else { [&body[..], &[s]].concat() }
1680            }
1681            Some(SignClause { separate: false, position: SignPosition::Leading }) => {
1682                let mut body = zoned_digits(magnitude, digits as usize, decimal::UNSIGNED);
1683                body[0] = (zone << 4) | (body[0] & 0x0F);
1684                body
1685            }
1686            _ => zoned_digits(magnitude, digits as usize, zone),
1687        }
1688    }
1689
1690    /// MOVE, and VALUE at start-up, into one receiving item.
1691    fn assign(&mut self, dest: Loc, val: Val, src: Option<Loc>, pos: Pos) -> R<()> {
1692        match dest.kind {
1693            Kind::Group | Kind::Alnum { .. } => {
1694                let justified = matches!(dest.kind, Kind::Alnum { justified: true });
1695                let image = self.alnum_image(&val, src, dest.len, pos)?;
1696                let mut out = vec![ebcdic::SPACE; dest.len];
1697                if justified && image.len() < dest.len {
1698                    out[dest.len - image.len()..].copy_from_slice(&image);
1699                } else if justified {
1700                    out.copy_from_slice(&image[image.len() - dest.len..]);
1701                } else {
1702                    let n = image.len().min(dest.len);
1703                    out[..n].copy_from_slice(&image[..n]);
1704                }
1705                self.write(dest, &out);
1706            }
1707            Kind::National => {
1708                let units: Vec<u16> = match val {
1709                    Val::National(b) => b.chunks(2).map(|c| u16::from_be_bytes([c[0], c[1]])).collect(),
1710                    Val::Bytes(b) => self.page.decode(&b).encode_utf16().collect(),
1711                    Val::Fig(f) => vec![figurative_unit(f); dest.len / 2],
1712                    _ => return Err(Abend::ironwork("this value cannot be moved to a national item", pos)),
1713                };
1714                let mut out: Vec<u8> = units.iter().take(dest.len / 2).flat_map(|u| u.to_be_bytes()).collect();
1715                while out.len() < dest.len {
1716                    out.extend_from_slice(&0x0020u16.to_be_bytes());
1717                }
1718                self.write(dest, &out);
1719            }
1720            Kind::Pointer => match val {
1721                Val::Address(a) => self.write(dest, &a.to_be_bytes()),
1722                Val::Fig(Figurative::Null) => self.write(dest, &[0; 4]),
1723                _ => return Err(Abend::ironwork("a pointer takes an address: use SET ... TO ADDRESS OF or NULL", pos)),
1724            },
1725            Kind::Index => match val {
1726                Val::Num(f) => self.store_fixed(dest, &f, false, pos)?,
1727                _ => return Err(Abend::ironwork("an index takes an occurrence number", pos)),
1728            },
1729            Kind::AlnumEdited { edit } => {
1730                let positions = self.layout.edits[edit as usize].iter().filter(|s| !matches!(s, crate::picture::Sym::Insert(_))).count();
1731                let image = self.alnum_image(&val, src, positions, pos)?;
1732                let page = self.page;
1733                let out = crate::edit::alphanumeric(&self.layout.edits[edit as usize], &image, ebcdic::SPACE, |c| page.encode_char(c).unwrap_or(ebcdic::SPACE));
1734                self.write(dest, &out);
1735            }
1736            Kind::Zoned { .. } | Kind::Packed { .. } | Kind::Binary { .. } | Kind::NumericEdited { .. } => match val {
1737                Val::Num(f) => {
1738                    if let (Some(s), Kind::Packed { digits, scale, signed: true }, Numproc::Pfd) = (src, dest.kind, self.options.numproc)
1739                        && s.kind == dest.kind
1740                        && digits > 0
1741                        && scale == places_of(s.kind).dec
1742                    {
1743                        let copied = sign::move_packed(self.bytes(s), true, Numproc::Pfd);
1744                        self.write(dest, &copied);
1745                    } else {
1746                        self.store_fixed(dest, &f, false, pos)?;
1747                    }
1748                }
1749                Val::Float(h) => {
1750                    let (f, _) = float::to_fixed(h, places_of(dest.kind), false);
1751                    self.store_fixed(dest, &f, false, pos)?;
1752                }
1753                Val::Fig(Figurative::Zero) => self.store_fixed(dest, &Fixed::new(0, Places::new(1, 0)), false, pos)?,
1754                Val::Fig(f) => self.write(dest, &vec![figurative_byte(f); dest.len]),
1755                Val::All(b) => {
1756                    let fill: Vec<u8> = b.iter().copied().cycle().take(dest.len).collect();
1757                    self.write(dest, &fill);
1758                }
1759                Val::Bytes(b) => {
1760                    let v = match src.map(|s| s.kind) {
1761                        Some(Kind::NumericEdited { edit, digits, scale, .. }) => {
1762                            let (negative, magnitude) = crate::edit::de_edit(&self.layout.edits[edit as usize], &self.page.decode(&b));
1763                            fixed(negative, U256::from_u128(magnitude), Places::new(digits - scale, scale))
1764                        }
1765                        _ => {
1766                            let places = Places::new(b.len() as u32, 0);
1767                            self.zoned_value(&b, b.len() as u32, false, None, places, pos)?
1768                        }
1769                    };
1770                    self.store_fixed(dest, &v, false, pos)?;
1771                }
1772                Val::National(_) => return Err(Abend::ironwork("a national value cannot be moved to a numeric item", pos)),
1773                Val::Address(_) => return Err(Abend::ironwork("a pointer cannot be moved to a numeric item", pos)),
1774            },
1775            Kind::Float(p) => {
1776                let h = match val {
1777                    Val::Float(h) if h.precision.digits() > p.digits() => float::narrow(h, p),
1778                    Val::Float(h) => h.lengthen(p),
1779                    Val::Num(f) => float::from_fixed(f, p, ProgramMask::default()).map_err(|c| Abend::check(c, pos))?,
1780                    Val::Fig(Figurative::Zero) => Hfp::zero(p),
1781                    _ => return Err(Abend::ironwork("this value cannot be moved to a floating-point item", pos)),
1782                };
1783                self.write(dest, &h.to_bytes());
1784            }
1785        }
1786        Ok(())
1787    }
1788
1789    /// The bytes an alphanumeric receiver of `len` gets from `val`.
1790    fn alnum_image(&self, val: &Val, src: Option<Loc>, len: usize, pos: Pos) -> R<Vec<u8>> {
1791        Ok(match val {
1792            Val::Bytes(b) => b.clone(),
1793            Val::All(b) => b.iter().copied().cycle().take(len.max(b.len())).collect(),
1794            Val::Fig(f) => vec![figurative_byte(*f); len],
1795            Val::Num(f) if f.places.dec == 0 => {
1796                let digits = src.and_then(|s| s.kind.digits_scale()).map_or(f.places.total(), |(d, _)| d);
1797                zoned_digits(f.magnitude.to_u128().unwrap_or(0), digits as usize, decimal::UNSIGNED)
1798            }
1799            Val::National(_) => return Err(Abend::ironwork("a national value cannot be moved to an alphanumeric item", pos)),
1800            _ => return Err(Abend::ironwork("only an integer numeric value can be moved to an alphanumeric item", pos)),
1801        })
1802    }
1803
1804    fn condition(&mut self, c: &Cond, pos: Pos) -> R<bool> {
1805        Ok(match c {
1806            Cond::Rel(a, op, b) => {
1807                let o = self.compare(a, b, pos)?;
1808                match op {
1809                    RelOp::Eq => o == Ordering::Equal,
1810                    RelOp::Ne => o != Ordering::Equal,
1811                    RelOp::Lt => o == Ordering::Less,
1812                    RelOp::Le => o != Ordering::Greater,
1813                    RelOp::Gt => o == Ordering::Greater,
1814                    RelOp::Ge => o != Ordering::Less,
1815                }
1816            }
1817            Cond::Not(inner) => !self.condition(inner, pos)?,
1818            Cond::And(a, b) => self.condition(a, pos)? && self.condition(b, pos)?,
1819            Cond::Or(a, b) => self.condition(a, pos)? || self.condition(b, pos)?,
1820            Cond::Class(e, class) => self.class(e, *class, pos)?,
1821            Cond::NameOrRel { subject, op, name } => match self.resolve(name)? {
1822                Resolved::Condition(_) => self.condition(&Cond::Name(name.clone()), pos)?,
1823                Resolved::Item(_) => self.condition(&Cond::Rel(subject.clone(), *op, Expr::Operand(Operand::Ref(name.clone()))), pos)?,
1824            },
1825            Cond::Name(r) => {
1826                let Resolved::Condition(index) = self.resolve(r)? else {
1827                    return Err(Abend::ironwork(format!("{} is a data item, not a condition", r.name), r.pos));
1828                };
1829                let condition = &self.layout.conditions[index];
1830                let item = &self.layout.items[condition.item];
1831                let subject = Ref {
1832                    name: item.name.clone().unwrap_or_default(),
1833                    qualifiers: Vec::new(),
1834                    subscripts: r.subscripts.clone(),
1835                    refmod: None,
1836                    pos: r.pos,
1837                };
1838                let subject = Expr::Operand(Operand::Ref(subject));
1839                let values = condition.values.clone();
1840                for (low, high) in values {
1841                    let low = Expr::Operand(Operand::Literal(low));
1842                    let hit = match high {
1843                        None => self.compare(&subject, &low, pos)? == Ordering::Equal,
1844                        Some(high) => {
1845                            self.compare(&subject, &low, pos)? != Ordering::Less
1846                                && self.compare(&subject, &Expr::Operand(Operand::Literal(high)), pos)? != Ordering::Greater
1847                        }
1848                    };
1849                    if hit {
1850                        return Ok(true);
1851                    }
1852                }
1853                false
1854            }
1855        })
1856    }
1857
1858    fn class(&mut self, e: &Expr, class: Class, pos: Pos) -> R<bool> {
1859        if let (Class::Numeric | Class::Alphabetic, Expr::Operand(Operand::Ref(r))) = (class, e) {
1860            let loc = self.locate(r)?;
1861            let bytes = self.bytes(loc).to_vec();
1862            return Ok(match (class, loc.kind) {
1863                (Class::Numeric, Kind::Packed { signed, .. }) => {
1864                    decimal::tp(&bytes).is_ok_and(|cc| cc.0 == 0) && (signed || bytes.last().is_some_and(|b| b & 0x0F == 0x0F))
1865                }
1866                (Class::Numeric, Kind::Zoned { signed, sign: None, .. }) => bytes.iter().enumerate().all(|(i, &b)| {
1867                    let zone_ok = if i + 1 == bytes.len() && signed { matches!(b >> 4, 0xC | 0xD | 0xF) } else { b >> 4 == 0xF };
1868                    zone_ok && b & 0x0F <= 9
1869                }),
1870                (Class::Numeric, _) => bytes.iter().all(|b| (0xF0..=0xF9).contains(b)),
1871                (_, _) => bytes.iter().all(|&b| b == ebcdic::SPACE || self.page.decode_byte(b).is_ascii_alphabetic()),
1872            });
1873        }
1874        let v = match self.expr_value(e, pos)? {
1875            Val::Num(f) => f,
1876            Val::Float(h) => float::to_fixed(h, Places::new(31, 0), false).0,
1877            _ => return Err(Abend::ironwork("a sign condition on a non-numeric operand", pos)),
1878        };
1879        Ok(match class {
1880            Class::Positive => !v.negative && !v.magnitude.is_zero(),
1881            Class::Negative => v.negative,
1882            _ => v.magnitude.is_zero(),
1883        })
1884    }
1885
1886    fn compare(&mut self, a: &Expr, b: &Expr, pos: Pos) -> R<Ordering> {
1887        let (va, la) = self.comparand(a, pos)?;
1888        let (vb, lb) = self.comparand(b, pos)?;
1889        if let (Some(x), Some(y)) = (la, lb)
1890            && let (Kind::Packed { .. }, true, Numproc::Pfd) = (x.kind, x.kind == y.kind, self.options.numproc)
1891        {
1892            return sign::compare_packed(self.bytes(x), self.bytes(y), Numproc::Pfd).map_err(|c| Abend::check(c, pos));
1893        }
1894        let address = |v: &Val| match v {
1895            Val::Address(a) => Some(*a),
1896            Val::Fig(Figurative::Null) => Some(0),
1897            _ => None,
1898        };
1899        if matches!(va, Val::Address(_)) || matches!(vb, Val::Address(_)) {
1900            return match (address(&va), address(&vb)) {
1901                (Some(x), Some(y)) => Ok(x.cmp(&y)),
1902                _ => Err(Abend::ironwork("a pointer compared with something other than a pointer or NULL", pos)),
1903            };
1904        }
1905        let numeric = |v: &Val| matches!(v, Val::Num(_) | Val::Float(_));
1906        match (&va, &vb) {
1907            (Val::Float(_), _) | (_, Val::Float(_)) if (numeric(&va) || matches!(va, Val::Fig(Figurative::Zero))) && (numeric(&vb) || matches!(vb, Val::Fig(Figurative::Zero))) => {
1908                let to_float = |v: &Val| -> R<Hfp> {
1909                    Ok(match v {
1910                        Val::Float(h) => h.lengthen(Precision::Extended),
1911                        Val::Num(f) => float::from_fixed(*f, Precision::Extended, ProgramMask::default()).map_err(|c| Abend::check(c, pos))?,
1912                        _ => Hfp::zero(Precision::Extended),
1913                    })
1914                };
1915                Ok(to_float(&va)?.compare(to_float(&vb)?))
1916            }
1917            (Val::Num(x), Val::Num(y)) => Ok(compare_fixed(x, y)),
1918            (Val::Num(x), Val::Fig(Figurative::Zero)) => Ok(compare_fixed(x, &Fixed::new(0, Places::new(1, 0)))),
1919            (Val::Fig(Figurative::Zero), Val::Num(y)) => Ok(compare_fixed(&Fixed::new(0, Places::new(1, 0)), y)),
1920            (Val::National(x), Val::National(y)) => Ok(compare_national(x, y)),
1921            _ => {
1922                let len = self.image_len(&va, la).max(self.image_len(&vb, lb));
1923                let x = self.alnum_image(&va, la, len, pos)?;
1924                let y = self.alnum_image(&vb, lb, len, pos)?;
1925                Ok(ebcdic::compare_alphanumeric(&x, &y, &Collation::Native))
1926            }
1927        }
1928    }
1929
1930    fn image_len(&self, v: &Val, loc: Option<Loc>) -> usize {
1931        match (v, loc) {
1932            (_, Some(l)) if !l.kind.is_numeric() => l.len,
1933            (Val::Bytes(b) | Val::All(b), _) => b.len(),
1934            (Val::Num(f), Some(l)) => l.kind.digits_scale().map_or(f.places.total(), |(d, _)| d) as usize,
1935            (Val::Num(f), None) => f.places.total() as usize,
1936            _ => 1,
1937        }
1938    }
1939
1940    fn comparand(&mut self, e: &Expr, pos: Pos) -> R<(Val, Option<Loc>)> {
1941        match e {
1942            Expr::Operand(op) => self.operand_with_loc(op, pos),
1943            _ => Ok((self.expr_value(e, pos)?, None)),
1944        }
1945    }
1946
1947    fn display(&mut self, items: &[Operand], no_advancing: bool, pos: Pos) -> R<()> {
1948        let mut text = String::new();
1949        for op in items {
1950            match op {
1951                Operand::Ref(r) => {
1952                    let loc = self.locate(r)?;
1953                    match loc.kind {
1954                        Kind::National => text.push_str(&utf16_text(self.bytes(loc))),
1955                        Kind::Packed { digits, signed, .. } | Kind::Binary { digits, signed, .. } => {
1956                            let Val::Num(f) = self.read(loc, r.pos)? else { unreachable!() };
1957                            let zone = match (signed, f.negative) {
1958                                (false, _) => decimal::UNSIGNED,
1959                                (true, true) => decimal::MINUS,
1960                                (true, false) => decimal::PLUS,
1961                            };
1962                            let whole = match loc.kind {
1963                                Kind::Binary { native, .. } => native || self.options.trunc == Trunc::Bin,
1964                                _ => false,
1965                            };
1966                            let shown = if whole {
1967                                let width = match loc.len {
1968                                    2 => 5,
1969                                    4 => 10,
1970                                    _ if signed => 19,
1971                                    _ => 20,
1972                                };
1973                                zoned_digits(f.magnitude.to_u128().unwrap_or(0), width, zone)
1974                            } else {
1975                                zoned_digits(f.magnitude.div_rem(pow10(digits)).1.to_u128().unwrap_or(0), digits as usize, zone)
1976                            };
1977                            text.push_str(&self.page.decode(&shown));
1978                        }
1979                        Kind::Float(_) => return Err(Abend::ironwork("DISPLAY of a floating-point item is not supported yet", r.pos)),
1980                        Kind::Pointer | Kind::Index => return Err(Abend::ironwork("DISPLAY of a pointer or index is not supported", r.pos)),
1981                        _ => text.push_str(&self.page.decode(self.bytes(loc))),
1982                    }
1983                }
1984                Operand::Literal(Literal::Number(t)) => text.push_str(t),
1985                other => match self.operand(other, pos)? {
1986                    Val::Bytes(b) | Val::All(b) => text.push_str(&self.page.decode(&b)),
1987                    Val::National(b) => text.push_str(&utf16_text(&b)),
1988                    Val::Fig(f) => text.push(self.page.decode_byte(figurative_byte(f))),
1989                    Val::Num(f) => text.push_str(&self.page.decode(&zoned_digits(f.magnitude.to_u128().unwrap_or(0), f.places.total() as usize, decimal::UNSIGNED))),
1990                    Val::Float(_) => return Err(Abend::ironwork("DISPLAY of a floating-point value is not supported yet", pos)),
1991                    Val::Address(_) => return Err(Abend::ironwork("DISPLAY of a pointer is not supported", pos)),
1992                },
1993            }
1994        }
1995        let result = if no_advancing { write!(self.unit.out, "{text}") } else { writeln!(self.unit.out, "{text}") };
1996        result.map_err(|e| match e.kind() {
1997            std::io::ErrorKind::BrokenPipe => Abend { code: CLOSED_OUTPUT.into(), message: "standard output closed".into(), pos },
1998            _ => Abend::ironwork(format!("DISPLAY: {e}"), pos),
1999        })
2000    }
2001
2002    fn initialize(&mut self, index: usize, offset: usize, pos: Pos) -> R<()> {
2003        let layout = self.layout;
2004        let item = &layout.items[index];
2005        if item.kind == Kind::Index {
2006            return Ok(());
2007        }
2008        if item.kind != Kind::Group {
2009            let loc = Loc { offset, len: item.size as usize, kind: item.kind, item: index };
2010            let val = match item.kind {
2011                Kind::Pointer => Val::Address(0),
2012                Kind::Alnum { .. } | Kind::National => Val::Fig(Figurative::Space),
2013                _ => Val::Fig(Figurative::Zero),
2014            };
2015            return self.assign(loc, val, None, pos);
2016        }
2017        for &c in &item.children {
2018            let child = &layout.items[c];
2019            if child.redefines.is_some() || child.name.is_none() {
2020                continue;
2021            }
2022            for k in 0..child.occurs {
2023                self.initialize(c, offset + (child.offset - item.offset) as usize + (k * child.size) as usize, pos)?;
2024            }
2025        }
2026        Ok(())
2027    }
2028}
2029
2030fn utf16_text(bytes: &[u8]) -> String {
2031    let units: Vec<u16> = bytes.chunks(2).map(|c| u16::from_be_bytes([c[0], *c.get(1).unwrap_or(&0)])).collect();
2032    String::from_utf16_lossy(&units)
2033}
2034
2035fn compare_national(a: &[u8], b: &[u8]) -> Ordering {
2036    let unit = |s: &[u8], i: usize| if i + 1 < s.len() { u16::from_be_bytes([s[i], s[i + 1]]) } else { 0x0020 };
2037    let len = a.len().max(b.len());
2038    (0..len).step_by(2).map(|i| unit(a, i).cmp(&unit(b, i))).find(|o| o.is_ne()).unwrap_or(Ordering::Equal)
2039}
2040
2041fn flatten_and<'c>(cond: &'c Cond, out: &mut Vec<&'c Cond>) {
2042    match cond {
2043        Cond::And(a, b) => {
2044            flatten_and(a, out);
2045            flatten_and(b, out);
2046        }
2047        other => out.push(other),
2048    }
2049}
2050
2051/// In a SEARCH ALL condition, the key item and the value it must equal.
2052fn key_term<'c>(terms: &[&'c Cond], key: &str) -> Option<(&'c Expr, &'c Expr)> {
2053    let is_key = |e: &Expr| matches!(e, Expr::Operand(Operand::Ref(r)) if r.name == key);
2054    terms.iter().find_map(|t| match t {
2055        Cond::Rel(a, RelOp::Eq, b) if is_key(a) => Some((a, b)),
2056        Cond::Rel(a, RelOp::Eq, b) if is_key(b) => Some((b, a)),
2057        _ => None,
2058    })
2059}
2060
2061/// Days since 1970-01-01 of a proleptic Gregorian date.
2062fn days_from_civil(year: i64, month: i64, day: i64) -> i64 {
2063    let (y, m) = if month <= 2 { (year - 1, month + 9) } else { (year, month - 3) };
2064    let era = y.div_euclid(400);
2065    let yoe = y.rem_euclid(400);
2066    let doe = yoe * 365 + yoe / 4 - yoe / 100 + (153 * m + 2) / 5 + day - 1;
2067    era * 146_097 + doe - 719_468
2068}
2069
2070fn days_in_month(year: i64, month: i64) -> i64 {
2071    match month {
2072        2 if (year % 4 == 0 && year % 100 != 0) || year % 400 == 0 => 29,
2073        2 => 28,
2074        4 | 6 | 9 | 11 => 30,
2075        _ => 31,
2076    }
2077}
2078
2079/// NUMVAL and NUMVAL-C: spaces, one sign (leading + or -, trailing + - CR or DB), digits with at most
2080/// one decimal point; NUMVAL-C also allows the currency sign and commas. None when the text is
2081/// anything else.
2082fn numval(text: &str, currency: Option<&str>) -> Option<Fixed> {
2083    let mut t = text.trim().to_ascii_uppercase();
2084    let mut negative = false;
2085    for (suffix, minus) in [("CR", true), ("DB", true), ("-", true), ("+", false)] {
2086        if let Some(rest) = t.strip_suffix(suffix) {
2087            negative = minus;
2088            t = rest.trim_end().to_owned();
2089            break;
2090        }
2091    }
2092    if let Some(rest) = t.strip_prefix('-') {
2093        negative = true;
2094        t = rest.trim_start().to_owned();
2095    } else if let Some(rest) = t.strip_prefix('+') {
2096        t = rest.trim_start().to_owned();
2097    }
2098    if let Some(c) = currency {
2099        t = t.trim_start_matches(c.trim()).trim_start().replace(',', "");
2100    }
2101    let (int, frac) = t.split_once('.').unwrap_or((&t, ""));
2102    if int.is_empty() && frac.is_empty() || !int.chars().chain(frac.chars()).all(|c| c.is_ascii_digit()) || int.len() + frac.len() > 31 {
2103        return None;
2104    }
2105    let digits: u128 = format!("{int}{frac}").parse().unwrap_or(0);
2106    let f = Fixed::new(digits as i128, Places::new(int.len().max(1) as u32, frac.len() as u32));
2107    Some(if negative { Fixed { negative: digits != 0, ..f } } else { f })
2108}
2109