Skip to main content

ironwork_rt/report/
run.rs

1//! The report writer at run time: INITIATE, GENERATE, TERMINATE and SUPPRESS PRINTING over a
2//! report control area in WORKING-STORAGE. Each line is written with WRITE ... AFTER ADVANCING
3//! through the report file, as the Report Writer Precompiler's generated code writes it, so each
4//! record carries a printer control character
5//! ([`numeric::assumptions::REPORT_LINE_WRITES`], [`numeric::assumptions::REPORT_CARRIAGE_CONTROL`]).
6
7use super::{Adding, Field, FieldContent, Group, GroupKind, Line, LineNumber, NextGroup, Origin, Report, ReportOp, Writer, span, state};
8use crate::abend::{Abend, AbendCode, Ending, Signal};
9use crate::fixed::{align, places_of};
10use crate::host::{self, Host};
11use crate::lir::Spacing;
12use crate::storage::{Kind, Loc, Val};
13use crate::store::{self, ProgramFacts};
14use crate::vocab::{Figurative, Pos};
15use numeric::float;
16use numeric::precision::{Fixed, Places};
17use std::io::Write;
18use zarch::ebcdic;
19
20type R<T> = Result<T, Abend>;
21
22/// How a USE BEFORE REPORTING procedure ended.
23#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub enum UseEnd {
25    Completed,
26    /// STOP RUN or GOBACK.
27    End(Ending),
28    GoTo,
29    /// Control left for an active PERFORM's return point or resumed a statement; the executor
30    /// carries that out once the report statement is abandoned.
31    Left,
32}
33
34/// A report file as the report writer writes it.
35#[derive(Clone, Copy, Debug)]
36pub struct ReportFile {
37    /// The record area in run-unit memory, and its length.
38    pub area: (usize, usize),
39    /// Open with variable-length records.
40    pub variable: bool,
41    /// NOADV: the record's first byte is the printer control character's.
42    pub reserved: bool,
43    pub record_min: Option<u32>,
44}
45
46/// What the report writer asks of the executor beyond [`Host`]: the values of its SOURCE, SUM and
47/// VALUE operands, its data items, the stores of a SUM and a SOURCE, the report file, and running
48/// a USE BEFORE REPORTING procedure.
49pub trait ReportHost<'w, X: 'w, C: 'w, V: 'w>: Host<&'w C> {
50    fn value(&mut self, expr: &X, pos: Pos) -> R<Val>;
51    /// A lone operand's value and its storage, when it has any; None for any other expression.
52    fn operand(&mut self, expr: &X, pos: Pos) -> Option<R<(Val, Option<Loc>)>>;
53    fn literal(&mut self, value: &V, pos: Pos) -> R<Val>;
54    fn item(&self, item: usize) -> Loc;
55    /// Stores an arithmetic result; true when it was a size error.
56    fn store_value(&mut self, dest: Loc, value: Val, rounded: bool, keep_on_size_error: bool, pos: Pos) -> R<bool>;
57    /// A numeric store, not ROUNDED, that keeps the receiver on a size error; true when it was one.
58    fn store_checked(&mut self, dest: Loc, value: &Fixed, pos: Pos) -> R<bool>;
59    fn report_file(&mut self, k: usize) -> ReportFile;
60    /// WRITE ... AFTER ADVANCING `space` of the record at `loc` to file `k`.
61    fn write_line(&mut self, k: usize, loc: Loc, space: Spacing, pos: Pos) -> R<()>;
62    /// Performs the USE BEFORE REPORTING section of paragraphs `first..=last`.
63    fn use_before_reporting(&mut self, range: (usize, usize), pos: Pos) -> R<UseEnd>;
64    fn err(&mut self) -> &mut dyn Write;
65}
66
67/// One report statement. A STOP RUN or GOBACK in a USE BEFORE REPORTING procedure ends the run.
68pub fn run<'w, X, C, V, H: ReportHost<'w, X, C, V>>(x: &mut H, writer: &'w Writer<X, C, V>, op: ReportOp, pos: Pos) -> R<Option<Ending>> {
69    let mut r = Reporting { x, w: writer };
70    let done = match op {
71        ReportOp::Initiate(ri) => r.initiate(ri as usize, pos),
72        ReportOp::Terminate(ri) => r.terminate(ri as usize, pos),
73        ReportOp::Generate { report, detail } => r.generate(report as usize, detail.map(|d| d as usize), pos),
74        ReportOp::Suppress => r.set_print_switch(1, pos),
75    };
76    match done {
77        Ok(()) => Ok(None),
78        Err(Abend { code: AbendCode::Signal(Signal::StopRun), .. }) => Ok(Some(Ending::StopRun)),
79        Err(Abend { code: AbendCode::Signal(Signal::GoBack), .. }) => Ok(Some(Ending::Goback)),
80        Err(a) => Err(a),
81    }
82}
83
84/// Why a report group is being produced, for NEXT GROUP and subtotalling.
85#[derive(Clone, Copy, PartialEq, Eq)]
86enum Trigger {
87    Detail,
88    /// A CONTROL FOOTING of a control break at this level (0 at TERMINATE).
89    Footing(usize),
90    Other,
91}
92
93fn lines(n: i64) -> Spacing {
94    Spacing::Lines(n.max(0) as u64)
95}
96
97fn signal(signal: Signal, pos: Pos) -> Abend {
98    Abend { code: AbendCode::Signal(signal), message: String::new(), pos, file: None }
99}
100
101struct Reporting<'w, 'h, H, X, C, V> {
102    x: &'h mut H,
103    w: &'w Writer<X, C, V>,
104}
105
106impl<'w, X, C, V, H: ReportHost<'w, X, C, V>> Reporting<'w, '_, H, X, C, V> {
107    fn report(&self, ri: usize) -> &'w Report<X, C, V> {
108        let w = self.w;
109        &w.reports[ri]
110    }
111
112    fn read(&mut self, loc: Loc, pos: Pos) -> R<Val> {
113        host::read::<&'w C>(self.x, loc, pos)
114    }
115
116    fn state_offset(&self, ri: usize, at: usize) -> usize {
117        self.x.item(self.report(ri).state).offset + at
118    }
119
120    fn flag(&mut self, ri: usize, at: usize) -> bool {
121        let offset = self.state_offset(ri, at);
122        self.x.mem()[offset] != 0
123    }
124
125    fn set_flag(&mut self, ri: usize, at: usize, on: bool) {
126        let offset = self.state_offset(ri, at);
127        self.x.mem()[offset] = on as u8;
128    }
129
130    fn fullword(&mut self, ri: usize, at: usize) -> i64 {
131        let offset = self.state_offset(ri, at);
132        i32::from_be_bytes(self.x.mem()[offset..offset + 4].try_into().unwrap()) as i64
133    }
134
135    fn set_fullword(&mut self, ri: usize, at: usize, value: i64) {
136        let offset = self.state_offset(ri, at);
137        self.x.mem()[offset..offset + 4].copy_from_slice(&(value.clamp(i32::MIN as i64, i32::MAX as i64) as i32).to_be_bytes());
138    }
139
140    fn counter(&mut self, item: usize, pos: Pos) -> R<i64> {
141        Ok(match self.read(self.x.item(item), pos)? {
142            Val::Num(f) => align(&f, 0, false).and_then(|m| m.to_u128()).map_or(0, |m| if f.negative { -(m as i64) } else { m as i64 }),
143            _ => 0,
144        })
145    }
146
147    fn set_counter(&mut self, item: usize, value: i64, pos: Pos) -> R<()> {
148        let loc = self.x.item(item);
149        self.x.store_fixed(loc, &Fixed::new(value as i128, Places::new(18, 0)), pos)
150    }
151
152    fn line_counter(&mut self, ri: usize, pos: Pos) -> R<i64> {
153        self.counter(self.report(ri).line_counter, pos)
154    }
155
156    fn set_line_counter(&mut self, ri: usize, value: i64, pos: Pos) -> R<()> {
157        self.set_counter(self.report(ri).line_counter, value, pos)
158    }
159
160    fn set_print_switch(&mut self, value: i64, pos: Pos) -> R<()> {
161        match self.w.print_switch {
162            Some(item) => self.set_counter(item, value, pos),
163            None => Ok(()),
164        }
165    }
166
167    fn arm_indicators(&mut self, ri: usize) {
168        for g in &self.report(ri).groups {
169            if let Some(flag) = g.indicate {
170                self.set_flag(ri, state::FLAGS + flag, true);
171            }
172        }
173    }
174
175    fn zero_total(&mut self, ri: usize, sum: usize, pos: Pos) -> R<()> {
176        let loc = self.x.item(self.report(ri).sums[sum].total);
177        self.x.store_fixed(loc, &Fixed::new(0, Places::new(1, 0)), pos)
178    }
179
180    fn initiate(&mut self, ri: usize, pos: Pos) -> R<()> {
181        let r = self.report(ri);
182        let at = self.state_offset(ri, 0);
183        let len = self.x.item(r.state).len;
184        self.x.mem()[at..at + len].fill(0);
185        self.set_counter(r.page_counter, 1, pos)?;
186        self.set_counter(r.line_counter, 0, pos)?;
187        for s in 0..r.sums.len() {
188            self.zero_total(ri, s, pos)?;
189        }
190        self.set_flag(ri, state::INITIATED, true);
191        self.arm_indicators(ri);
192        Ok(())
193    }
194
195    /// See [`numeric::assumptions::REPORT_OUT_OF_ORDER`] for a GENERATE before INITIATE.
196    fn generate(&mut self, ri: usize, detail: Option<usize>, pos: Pos) -> R<()> {
197        let r = self.report(ri);
198        if !self.flag(ri, state::INITIATED) {
199            let _ = writeln!(self.x.err(), "ironwork: {pos}: report writer run-time error 14: GENERATE for report {} before its INITIATE; it is initiated now", r.name);
200            self.initiate(ri, pos)?;
201        }
202        if !self.flag(ri, state::GENERATED) {
203            self.set_flag(ri, state::GENERATED, true);
204            if let Some(g) = r.report_heading {
205                self.produce(ri, g, Trigger::Other, pos)?;
206            }
207            for g in r.control_headings.iter().flatten() {
208                self.produce(ri, *g, Trigger::Other, pos)?;
209            }
210        } else if let Some(level) = self.control_break(ri)? {
211            self.break_at(ri, level, pos)?;
212        }
213        match detail {
214            Some(g) => self.produce(ri, g, Trigger::Detail, pos)?,
215            None => self.subtotal(ri, None, pos)?,
216        }
217        self.save_controls(ri)
218    }
219
220    fn terminate(&mut self, ri: usize, pos: Pos) -> R<()> {
221        let r = self.report(ri);
222        if !self.flag(ri, state::INITIATED) {
223            return Ok(());
224        }
225        if self.flag(ri, state::GENERATED) {
226            let current = self.swap_in_saved_controls(ri)?;
227            for level in (0..r.control_footings.len()).rev() {
228                if let Some(g) = r.control_footings[level] {
229                    self.produce(ri, g, Trigger::Footing(0), pos)?;
230                }
231                if level > 0 {
232                    self.reset_totals_on(ri, level, pos)?;
233                }
234            }
235            self.restore_controls(&current);
236            if r.page.is_some() && self.flag(ri, state::STARTED) && !self.flag(ri, state::HEADING_ONLY)
237                && let Some(g) = r.page_footing
238            {
239                self.produce(ri, g, Trigger::Other, pos)?;
240            }
241            if let Some(g) = r.report_footing {
242                self.produce(ri, g, Trigger::Other, pos)?;
243            }
244        }
245        self.set_flag(ri, state::INITIATED, false);
246        Ok(())
247    }
248
249    fn control_values(&mut self, ri: usize) -> R<Vec<(Loc, Vec<u8>)>> {
250        let mut out = Vec::new();
251        for c in &self.report(ri).controls {
252            let loc = self.x.locate(&c.reference, false)?;
253            out.push((loc, store::bytes(self.x.mem(), loc).to_vec()));
254        }
255        Ok(out)
256    }
257
258    /// The most major control whose value differs from the last GENERATE's, by its level.
259    fn control_break(&mut self, ri: usize) -> R<Option<usize>> {
260        let values = self.control_values(ri)?;
261        for (i, ((_, value), c)) in values.iter().zip(&self.report(ri).controls).enumerate() {
262            let saved = self.state_offset(ri, c.saved);
263            if self.x.mem()[saved..saved + c.len.min(value.len())] != value[..c.len.min(value.len())] {
264                return Ok(Some(i + 1));
265            }
266        }
267        Ok(None)
268    }
269
270    fn save_controls(&mut self, ri: usize) -> R<()> {
271        let values = self.control_values(ri)?;
272        for ((_, value), c) in values.iter().zip(&self.report(ri).controls) {
273            let saved = self.state_offset(ri, c.saved);
274            let n = c.len.min(value.len());
275            self.x.mem()[saved..saved + n].copy_from_slice(&value[..n]);
276        }
277        Ok(())
278    }
279
280    /// Puts each control's value at the last GENERATE back in the control, for CONTROL FOOTING
281    /// time; returns the values it replaced.
282    fn swap_in_saved_controls(&mut self, ri: usize) -> R<Vec<(Loc, Vec<u8>)>> {
283        let current = self.control_values(ri)?;
284        for ((loc, value), c) in current.iter().zip(&self.report(ri).controls) {
285            let saved = self.state_offset(ri, c.saved);
286            let n = c.len.min(value.len());
287            let mem = self.x.mem();
288            let before = mem[saved..saved + n].to_vec();
289            mem[loc.offset..loc.offset + n].copy_from_slice(&before);
290        }
291        Ok(current)
292    }
293
294    fn restore_controls(&mut self, values: &[(Loc, Vec<u8>)]) {
295        for (loc, value) in values {
296            store::write(self.x.mem(), *loc, value);
297        }
298    }
299
300    /// CONTROL FOOTINGs minor to major up to the break's level under the values before the break,
301    /// then CONTROL HEADINGs major to minor under the new ones.
302    fn break_at(&mut self, ri: usize, level: usize, pos: Pos) -> R<()> {
303        let r = self.report(ri);
304        let current = self.swap_in_saved_controls(ri)?;
305        for l in (level..r.control_footings.len()).rev() {
306            if let Some(g) = r.control_footings[l] {
307                self.produce(ri, g, Trigger::Footing(level), pos)?;
308            }
309            self.reset_totals_on(ri, l, pos)?;
310        }
311        self.restore_controls(&current);
312        self.arm_indicators(ri);
313        for l in level..r.control_headings.len() {
314            if let Some(g) = r.control_headings[l] {
315                self.produce(ri, g, Trigger::Other, pos)?;
316            }
317        }
318        Ok(())
319    }
320
321    fn reset_totals_on(&mut self, ri: usize, level: usize, pos: Pos) -> R<()> {
322        for (s, sum) in self.report(ri).sums.iter().enumerate() {
323            if sum.reset == Some(level) {
324                self.zero_total(ri, s, pos)?;
325            }
326        }
327        Ok(())
328    }
329
330    /// Adds SUM operands from outside the REPORT SECTION: on a GENERATE of `detail`, or of the
331    /// report alone for summary reporting ([`numeric::assumptions::REPORT_SOURCE_SUM_CORRELATION`]).
332    fn subtotal(&mut self, ri: usize, detail: Option<usize>, pos: Pos) -> R<()> {
333        for st in &self.report(ri).subtotals {
334            let times = match (&st.adding, detail) {
335                (Adding::EveryGenerate, _) => 1,
336                (Adding::Upon(ds) | Adding::Correlated(ds), Some(d)) => ds.contains(&d) as usize,
337                (Adding::Upon(_), None) => 0,
338                (Adding::Correlated(ds), None) => ds.len(),
339            };
340            for _ in 0..times {
341                let value = self.x.value(&st.operand, pos)?;
342                self.add_to_total(ri, st.sum, value, pos)?;
343            }
344        }
345        Ok(())
346    }
347
348    fn accumulate(&mut self, ri: usize, sum: usize, origin: &Origin<X, V>, pos: Pos) -> R<()> {
349        let value = match origin {
350            Origin::Source(e) => match self.x.value(e, pos) {
351                Err(a) if a.code.zero_divisor() => {
352                    let _ = writeln!(self.x.err(), "ironwork: {pos}: report writer run-time error 10: a SOURCE expression divided by zero; nothing was added to the total");
353                    return Ok(());
354                }
355                other => other?,
356            },
357            Origin::Value(lit) => self.x.literal(lit, pos)?,
358            Origin::Total(t) => self.read(self.x.item(self.report(ri).sums[*t].total), pos)?,
359        };
360        self.add_to_total(ri, sum, value, pos)
361    }
362
363    /// ADD with ON SIZE ERROR, as the precompiler generates it: a total that would overflow is
364    /// left as it was, and the run-time error is logged ([`numeric::assumptions::REPORT_SUM_OVERFLOW`]).
365    fn add_to_total(&mut self, ri: usize, sum: usize, value: Val, pos: Pos) -> R<()> {
366        let loc = self.x.item(self.report(ri).sums[sum].total);
367        let addend = match value {
368            Val::Num(f) => f,
369            Val::Float(h) => float::to_receiver(h, places_of(loc.kind)).0,
370            Val::Fig(Figurative::Zero) => Fixed::new(0, Places::new(1, 0)),
371            _ => return Err(Abend::ironwork("a SUM operand that is not numeric", pos)),
372        };
373        let Val::Num(current) = self.read(loc, pos)? else { return Err(Abend::ironwork("a SUM total that is not numeric", pos)) };
374        let dmax = places_of(loc.kind).dec.max(addend.places.dec);
375        let total = current.add(addend, dmax, self.x.facts().options().arith).map_err(|_| Abend::ironwork("a SUM wider than 256 bits", pos))?;
376        if self.x.store_checked(loc, &total, pos)? {
377            let name = &self.report(ri).name;
378            let _ = writeln!(self.x.err(), "ironwork: {pos}: report writer run-time error 11: a SUM total of report {name} overflowed; the value was not added");
379        }
380        Ok(())
381    }
382
383    /// One report group, as the precompiler's GENERATE processing cycle produces it: totals first
384    /// ([`numeric::assumptions::REPORT_TOTALS_BEFORE_PAGE_FIT`]), then USE BEFORE REPORTING, then
385    /// the lines unless it suppressed them ([`numeric::assumptions::REPORT_SUPPRESS_PRINTING`]).
386    fn produce(&mut self, ri: usize, gi: usize, trigger: Trigger, pos: Pos) -> R<()> {
387        let r = self.report(ri);
388        let g = &r.groups[gi];
389        for (s, origin) in &g.cross {
390            self.accumulate(ri, *s, origin, pos)?;
391        }
392        if trigger == Trigger::Detail {
393            self.subtotal(ri, Some(gi), pos)?;
394        }
395        for (s, origin) in &g.rolls {
396            self.accumulate(ri, *s, origin, pos)?;
397        }
398        let suppressed = match g.declarative {
399            Some(range) => self.use_before_reporting(range, pos)?,
400            None => false,
401        };
402        if !suppressed {
403            for f in &g.unprinted {
404                self.fill_field(ri, f)?;
405            }
406            if !g.lines.is_empty() {
407                match g.kind {
408                    GroupKind::ReportHeading => self.place_report_heading(ri, g, pos)?,
409                    GroupKind::PageHeading => self.place_page_heading(ri, g, pos)?,
410                    GroupKind::PageFooting => self.place_page_footing(ri, g, pos)?,
411                    GroupKind::ReportFooting => self.place_report_footing(ri, g, pos)?,
412                    _ => self.place_body(ri, g, pos)?,
413                }
414            }
415            self.next_group(ri, g, trigger, pos)?;
416        }
417        for &s in &g.totals {
418            if r.sums[s].reset.is_none() {
419                self.zero_total(ri, s, pos)?;
420            }
421        }
422        if !suppressed && let Some(flag) = g.indicate {
423            self.set_flag(ri, state::FLAGS + flag, false);
424        }
425        Ok(())
426    }
427
428    /// Performs a USE BEFORE REPORTING section; true when it suppressed the group's printing.
429    fn use_before_reporting(&mut self, range: (usize, usize), pos: Pos) -> R<bool> {
430        match self.x.use_before_reporting(range, pos)? {
431            UseEnd::End(Ending::StopRun) => return Err(signal(Signal::StopRun, pos)),
432            UseEnd::End(_) => return Err(signal(Signal::GoBack, pos)),
433            UseEnd::GoTo => return Err(Abend::ironwork("GO TO out of a USE BEFORE REPORTING procedure", pos)),
434            UseEnd::Left => return Err(signal(Signal::DeclarativeExit, pos)),
435            UseEnd::Completed => {}
436        }
437        let Some(item) = self.w.print_switch else { return Ok(false) };
438        let suppressed = self.counter(item, pos)? != 0;
439        self.set_counter(item, 0, pos)?;
440        Ok(suppressed)
441    }
442
443    fn first_increment(g: &Group<X, V>) -> i64 {
444        match g.lines.first().map(|l| l.number) {
445            Some(LineNumber::Plus(k)) => k as i64,
446            _ => 1,
447        }
448    }
449
450    /// Prints a group's lines, the first on line `first` and each other where its LINE puts it.
451    fn print_lines(&mut self, ri: usize, g: &'w Group<X, V>, first: i64, pos: Pos) -> R<()> {
452        let mut target = first;
453        for (i, line) in g.lines.iter().enumerate() {
454            if i > 0 {
455                target = match line.number {
456                    LineNumber::Line(n) => n as i64,
457                    LineNumber::Plus(k) => target + k as i64,
458                    LineNumber::NextPage(_) => target + 1,
459                };
460            }
461            self.print_line(ri, g, line, target, pos)?;
462        }
463        Ok(())
464    }
465
466    fn place_body(&mut self, ri: usize, g: &'w Group<X, V>, pos: Pos) -> R<()> {
467        let r = self.report(ri);
468        let Some(page) = r.page else {
469            let lc = self.line_counter(ri, pos)?;
470            return self.print_lines(ri, g, lc + Self::first_increment(g), pos);
471        };
472        self.open_page(ri, pos)?;
473        let limit = if g.kind == GroupKind::ControlFooting { page.footing } else { page.last_detail };
474        let lc = self.line_counter(ri, pos)?;
475        let body = self.flag(ri, state::BODY_ON_PAGE);
476        let fits = match g.lines[0].number {
477            LineNumber::NextPage(_) => !body,
478            LineNumber::Line(n) => lc < n as i64,
479            LineNumber::Plus(k) => {
480                let start = if body { lc + k as i64 } else { page.first_detail.max(lc + 1) };
481                start + span(g) - k as i64 <= limit
482            }
483        };
484        if !fits {
485            self.advance_page(ri, pos)?;
486        }
487        let lc = self.line_counter(ri, pos)?;
488        let first = match g.lines[0].number {
489            LineNumber::Line(n) | LineNumber::NextPage(Some(n)) => n as i64,
490            LineNumber::Plus(k) if self.flag(ri, state::BODY_ON_PAGE) => lc + k as i64,
491            _ => page.first_detail.max(lc + 1),
492        };
493        self.print_lines(ri, g, first, pos)?;
494        self.set_flag(ri, state::BODY_ON_PAGE, true);
495        Ok(())
496    }
497
498    /// Before a body group: the first page begins, or the PAGE HEADING below a REPORT HEADING
499    /// comes out, or a REPORT HEADING alone on its page gives way to the next page.
500    fn open_page(&mut self, ri: usize, pos: Pos) -> R<()> {
501        if self.flag(ri, state::HEADING_ONLY) {
502            return self.advance_page(ri, pos);
503        }
504        if !self.flag(ri, state::STARTED) {
505            self.set_flag(ri, state::STARTED, true);
506            self.set_fullword(ri, state::VERTICAL, 0);
507            self.set_flag(ri, state::HEADING_DUE, true);
508        }
509        if self.flag(ri, state::HEADING_DUE) {
510            self.set_flag(ri, state::HEADING_DUE, false);
511            if let Some(g) = self.report(ri).page_heading {
512                self.produce(ri, g, Trigger::Other, pos)?;
513            }
514        }
515        Ok(())
516    }
517
518    /// PAGE FOOTING, the next page, PAGE HEADING.
519    fn advance_page(&mut self, ri: usize, pos: Pos) -> R<()> {
520        let r = self.report(ri);
521        if self.flag(ri, state::STARTED) && !self.flag(ri, state::HEADING_ONLY)
522            && let Some(g) = r.page_footing
523        {
524            self.produce(ri, g, Trigger::Other, pos)?;
525        }
526        self.new_page(ri, pos)?;
527        self.arm_indicators(ri);
528        if let Some(g) = r.page_heading {
529            self.produce(ri, g, Trigger::Other, pos)?;
530        }
531        let saved = self.fullword(ri, state::SAVED_NEXT_GROUP);
532        if saved != 0 {
533            self.set_line_counter(ri, saved, pos)?;
534            self.set_fullword(ri, state::SAVED_NEXT_GROUP, 0);
535        }
536        Ok(())
537    }
538
539    fn new_page(&mut self, ri: usize, pos: Pos) -> R<()> {
540        let r = self.report(ri);
541        let page = self.counter(r.page_counter, pos)?;
542        self.set_counter(r.page_counter, page + 1, pos)?;
543        self.set_fullword(ri, state::VERTICAL, 0);
544        self.set_line_counter(ri, 0, pos)?;
545        for at in [state::HEADING_DUE, state::BODY_ON_PAGE, state::HEADING_ONLY] {
546            self.set_flag(ri, at, false);
547        }
548        self.set_flag(ri, state::STARTED, true);
549        Ok(())
550    }
551
552    fn place_report_heading(&mut self, ri: usize, g: &'w Group<X, V>, pos: Pos) -> R<()> {
553        let r = self.report(ri);
554        let Some(page) = r.page else {
555            let lc = self.line_counter(ri, pos)?;
556            return self.print_lines(ri, g, lc + Self::first_increment(g), pos);
557        };
558        self.set_flag(ri, state::STARTED, true);
559        self.set_fullword(ri, state::VERTICAL, 0);
560        self.set_flag(ri, state::HEADING_DUE, true);
561        let first = match g.lines[0].number {
562            LineNumber::Line(n) | LineNumber::NextPage(Some(n)) => n as i64,
563            LineNumber::Plus(k) => page.heading - 1 + k as i64,
564            LineNumber::NextPage(None) => page.heading,
565        };
566        self.print_lines(ri, g, first, pos)?;
567        let lc = self.line_counter(ri, pos)?;
568        let alone = matches!(g.next_group, Some(NextGroup::NextPage))
569            || match r.page_heading.map(|h| &r.groups[h]) {
570                Some(h) => match h.lines[0].number {
571                    LineNumber::Line(n) => lc >= n as i64,
572                    _ => r.first_detail_written.is_some_and(|fd| lc + span(h) >= fd),
573                },
574                None => false,
575            };
576        self.set_flag(ri, state::HEADING_ONLY, alone);
577        Ok(())
578    }
579
580    fn place_page_heading(&mut self, ri: usize, g: &'w Group<X, V>, pos: Pos) -> R<()> {
581        let Some(page) = self.report(ri).page else { return Ok(()) };
582        let lc = self.line_counter(ri, pos)?;
583        let first = match g.lines[0].number {
584            LineNumber::Line(n) | LineNumber::NextPage(Some(n)) => n as i64,
585            LineNumber::Plus(k) => (if lc > 0 { lc } else { page.heading - 1 }) + k as i64,
586            LineNumber::NextPage(None) => page.heading,
587        };
588        self.print_lines(ri, g, first, pos)
589    }
590
591    fn place_page_footing(&mut self, ri: usize, g: &'w Group<X, V>, pos: Pos) -> R<()> {
592        let Some(page) = self.report(ri).page else { return Ok(()) };
593        let first = match g.lines[0].number {
594            LineNumber::Line(n) | LineNumber::NextPage(Some(n)) => n as i64,
595            LineNumber::Plus(k) => page.footing + k as i64,
596            LineNumber::NextPage(None) => page.footing + 1,
597        };
598        self.print_lines(ri, g, first, pos)
599    }
600
601    /// The REPORT FOOTING below the last PAGE FOOTING, or on a page of its own when it will
602    /// not fit there or says NEXT PAGE ([`numeric::assumptions::REPORT_NEW_PAGES`]).
603    fn place_report_footing(&mut self, ri: usize, g: &'w Group<X, V>, pos: Pos) -> R<()> {
604        let r = self.report(ri);
605        let lc = self.line_counter(ri, pos)?;
606        let Some(page) = r.page else {
607            return self.print_lines(ri, g, lc + Self::first_increment(g), pos);
608        };
609        let started = self.flag(ri, state::STARTED);
610        if !started {
611            self.set_flag(ri, state::STARTED, true);
612            self.set_fullword(ri, state::VERTICAL, 0);
613        }
614        let own_page = |m: &mut Self| if started { m.new_page(ri, pos) } else { Ok(()) };
615        let first = match g.lines[0].number {
616            LineNumber::NextPage(n) => {
617                own_page(self)?;
618                n.map_or(page.heading, |n| n as i64)
619            }
620            LineNumber::Line(n) => {
621                if started && n as i64 <= lc {
622                    own_page(self)?;
623                }
624                n as i64
625            }
626            LineNumber::Plus(k) => {
627                let base = if !started { page.heading - 1 } else if r.page_footing.is_some() { lc } else { page.footing };
628                if started && base + span(g) > page.limit {
629                    own_page(self)?;
630                    page.heading - 1 + k as i64
631                } else {
632                    base + k as i64
633                }
634            }
635        };
636        self.print_lines(ri, g, first, pos)
637    }
638
639    fn next_group(&mut self, ri: usize, g: &'w Group<X, V>, trigger: Trigger, pos: Pos) -> R<()> {
640        let Some(next) = g.next_group else { return Ok(()) };
641        if let (GroupKind::ControlFooting, Trigger::Footing(level)) = (g.kind, trigger)
642            && g.level != level
643        {
644            return Ok(());
645        }
646        let lc = self.line_counter(ri, pos)?;
647        let Some(page) = self.report(ri).page else {
648            if let NextGroup::Plus(n) = next {
649                self.set_line_counter(ri, lc + n as i64, pos)?;
650            }
651            return Ok(());
652        };
653        match (g.kind.is_body(), next) {
654            (true, NextGroup::Plus(n)) => self.set_line_counter(ri, (lc + n as i64).min(page.footing), pos),
655            (true, NextGroup::Line(n)) if lc < n as i64 => self.set_line_counter(ri, n as i64, pos),
656            (true, NextGroup::Line(n)) => {
657                self.set_fullword(ri, state::SAVED_NEXT_GROUP, n as i64);
658                self.set_line_counter(ri, page.footing, pos)
659            }
660            (true, NextGroup::NextPage) => self.set_line_counter(ri, page.footing, pos),
661            (false, NextGroup::Plus(n)) => self.set_line_counter(ri, lc + n as i64, pos),
662            (false, NextGroup::Line(n)) if lc < n as i64 => self.set_line_counter(ri, n as i64, pos),
663            (false, _) => Ok(()),
664        }
665    }
666
667    /// Sets LINE-COUNTER to the line, fills its fields and writes it.
668    fn print_line(&mut self, ri: usize, g: &'w Group<X, V>, line: &'w Line<X, V>, target: i64, pos: Pos) -> R<()> {
669        let r = self.report(ri);
670        self.set_line_counter(ri, target, pos)?;
671        let indicate = g.indicate.is_none_or(|flag| self.flag(ri, state::FLAGS + flag));
672        let mut text = vec![ebcdic::SPACE; r.width];
673        let mut end = 0;
674        for f in &line.fields {
675            if f.group_indicate && !indicate {
676                continue;
677            }
678            self.fill_field(ri, f)?;
679            let loc = self.x.item(f.item);
680            let n = loc.len.min(r.width.saturating_sub(f.column));
681            text[f.column..f.column + n].copy_from_slice(&self.x.mem()[loc.offset..loc.offset + n]);
682            end = end.max(f.column + n);
683        }
684        self.write_report_line(ri, &text, end, target, pos)
685    }
686
687    fn blank(&mut self, loc: Loc) {
688        store::write(self.x.mem(), loc, &vec![ebcdic::SPACE; loc.len]);
689    }
690
691    /// Stores a field's SOURCE, VALUE or SUM, as MOVE (or COMPUTE, for an expression or ROUNDED)
692    /// would ([`numeric::assumptions::REPORT_SOURCE_OVERFLOW`]).
693    fn fill_field(&mut self, ri: usize, f: &'w Field<X, V>) -> R<()> {
694        let dest = self.x.item(f.item);
695        let pos = f.pos;
696        match &f.content {
697            FieldContent::Program => return Ok(()),
698            FieldContent::Value(lit) => {
699                let v = self.x.literal(lit, pos)?;
700                self.x.assign(dest, v, None, pos)?;
701            }
702            FieldContent::Source(e) => match self.x.operand(e, pos) {
703                Some(read) if !f.rounded => {
704                    let (v, src) = read?;
705                    self.x.assign(dest, v, src, pos)?;
706                }
707                Some(read) => {
708                    let (v, _) = read?;
709                    self.x.store_value(dest, v, true, false, pos)?;
710                }
711                None => {
712                    let overflow = match self.x.value(e, pos) {
713                        Err(a) if a.code.zero_divisor() => true,
714                        Err(a) => return Err(a),
715                        Ok(v) => self.x.store_value(dest, v, f.rounded, true, pos)?,
716                    };
717                    if overflow {
718                        self.blank(dest);
719                        let _ = writeln!(self.x.err(), "ironwork: {pos}: report writer run-time error 10: a SOURCE expression overflowed or divided by zero; the field is left blank");
720                    }
721                }
722            },
723            FieldContent::Sum(s) => {
724                let total = self.x.item(self.report(ri).sums[*s].total);
725                let v = self.read(total, pos)?;
726                if f.rounded {
727                    self.x.store_value(dest, v, true, false, pos)?;
728                } else {
729                    self.x.assign(dest, v, Some(total), pos)?;
730                }
731            }
732        }
733        if f.blank_when_zero && matches!(self.read(dest, pos)?, Val::Num(v) if v.magnitude.is_zero()) {
734            self.blank(dest);
735        }
736        Ok(())
737    }
738
739    /// The WRITEs for one line: at the top of a page, a line 1 goes out AFTER ADVANCING PAGE, and
740    /// a lower line after a blank record written AFTER ADVANCING PAGE.
741    fn write_report_line(&mut self, ri: usize, text: &[u8], end: usize, target: i64, pos: Pos) -> R<()> {
742        let paged = self.report(ri).page.is_some();
743        let vertical = self.fullword(ri, state::VERTICAL);
744        if paged && vertical == 0 {
745            if target > 1 {
746                self.write_report_record(ri, None, Spacing::Channel(1), pos)?;
747                self.write_report_record(ri, Some((text, end)), lines(target - 1), pos)?;
748            } else {
749                self.write_report_record(ri, Some((text, end)), Spacing::Channel(1), pos)?;
750            }
751        } else {
752            self.write_report_record(ri, Some((text, end)), lines(target - vertical), pos)?;
753        }
754        self.set_fullword(ri, state::VERTICAL, target.max(1));
755        Ok(())
756    }
757
758    /// One record through the report file's record area: the CODE, then the line; all spaces for
759    /// the blank record at the top of a page. Under NOADV both follow the control character's byte.
760    /// A variable-length record ends after its last field.
761    fn write_report_record(&mut self, ri: usize, line: Option<(&[u8], usize)>, space: Spacing, pos: Pos) -> R<()> {
762        let r = self.report(ri);
763        let k = r.file;
764        let file = self.x.report_file(k);
765        let (offset, size) = file.area;
766        let mut record = vec![ebcdic::SPACE; size];
767        let reserved = usize::from(file.reserved).min(size);
768        let shortest = (file.record_min.unwrap_or(1) as usize).max(reserved);
769        let mut len = size;
770        if let Some((text, end)) = line {
771            let code = match &r.code {
772                Some(lit) => match self.x.literal(lit, pos)? {
773                    Val::Bytes(b) => b,
774                    _ => Vec::new(),
775                },
776                None => Vec::new(),
777            };
778            let c = reserved + code.len().min(size - reserved);
779            record[reserved..c].copy_from_slice(&code[..c - reserved]);
780            let n = text.len().min(size - c);
781            record[c..c + n].copy_from_slice(&text[..n]);
782            if file.variable {
783                len = (c + end.min(n)).max(shortest).min(size);
784            }
785        } else if file.variable {
786            len = shortest.min(size);
787        }
788        self.x.mem()[offset..offset + size].copy_from_slice(&record);
789        let loc = Loc { offset, len, kind: Kind::Alnum { justified: false }, item: usize::MAX };
790        self.x.write_line(k, loc, space, pos)
791    }
792}