Skip to main content

ironwork_rt/report/
mod.rs

1//! The report writer (lir.md ยง9.6): the reports `compile::report` resolves, as Enterprise COBOL's
2//! Report Writer Precompiler lays them out, and INITIATE, GENERATE, TERMINATE and SUPPRESS PRINTING
3//! over them ([`run`]). The model is generic over the executor's handles: `X` a SOURCE or SUM
4//! operand's expression, `C` a CONTROL item, `V` a VALUE or CODE literal, `U` a USE BEFORE
5//! REPORTING procedure: the LIR's comparands and ids by default, the walker's own AST and paragraph
6//! spans in the interpreter.
7
8mod run;
9
10pub use run::{ReportFile, ReportHost, UseEnd, run};
11
12use crate::lir::{Comparand, ConstId, PlaceId, RangeId};
13use crate::vocab::Pos;
14use crate::{codec_enum, codec_struct};
15
16/// Offsets in a report's state item. The item starts as X'00', so every flag starts false.
17pub mod state {
18    pub const INITIATED: usize = 0;
19    pub const GENERATED: usize = 1;
20    /// A page has begun: output has gone to the file since INITIATE.
21    pub const STARTED: usize = 2;
22    /// The PAGE HEADING is still to come on the current page, below a REPORT HEADING.
23    pub const HEADING_DUE: usize = 3;
24    pub const BODY_ON_PAGE: usize = 4;
25    /// The current page holds the REPORT HEADING alone.
26    pub const HEADING_ONLY: usize = 5;
27    /// The line the file is at, 0 before the page's first line: a fullword.
28    pub const VERTICAL: usize = 8;
29    /// NEXT GROUP's absolute line, held until the next page: a fullword.
30    pub const SAVED_NEXT_GROUP: usize = 12;
31    /// One GROUP INDICATE flag per report group, then each control's value at the last GENERATE.
32    pub const FLAGS: usize = 16;
33}
34
35/// FOOTING, or LAST CONTROL FOOTING: a line, or lines below LAST DETAIL.
36#[derive(Clone, Copy, Debug, PartialEq, Eq)]
37pub enum Footing {
38    Line(u32),
39    Plus(u32),
40}
41
42#[derive(Clone, Copy, Debug, PartialEq, Eq)]
43pub enum NextGroup {
44    Line(u32),
45    Plus(u32),
46    NextPage,
47}
48
49#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50pub enum LineNumber {
51    Line(u32),
52    Plus(u32),
53    /// NEXT PAGE, with the absolute line when one is written.
54    NextPage(Option<u32>),
55}
56
57#[derive(Clone, Copy, Debug, PartialEq, Eq)]
58pub enum ColumnNumber {
59    Left(u32),
60    Plus(u32),
61    Right(u32),
62    Center(u32),
63}
64
65/// INITIATE, GENERATE, TERMINATE or SUPPRESS PRINTING, with the report and DETAIL group by index.
66/// GENERATE of a report alone, for summary reporting, has no `detail`.
67#[derive(Clone, Copy, Debug, PartialEq, Eq)]
68pub enum ReportOp {
69    Initiate(u32),
70    Generate { report: u32, detail: Option<u32> },
71    Terminate(u32),
72    Suppress,
73}
74
75/// The reports of one program. Items (`usize`) are data items: by their index in the interpreter,
76/// and once lowered by the id of a static place in the slab.
77#[derive(Clone, Debug, PartialEq, Eq)]
78pub struct Writer<X = Comparand, C = PlaceId, V = ConstId, U = RangeId> {
79    pub reports: Vec<Report<X, C, V, U>>,
80    /// PRINT-SWITCH, which SUPPRESS PRINTING sets.
81    pub print_switch: Option<usize>,
82}
83
84impl<X, C, V, U> Default for Writer<X, C, V, U> {
85    fn default() -> Self {
86        Writer { reports: Vec::new(), print_switch: None }
87    }
88}
89
90#[derive(Clone, Debug, PartialEq, Eq)]
91pub struct Report<X = Comparand, C = PlaceId, V = ConstId, U = RangeId> {
92    pub name: String,
93    pub file: usize,
94    pub code: Option<V>,
95    /// Bytes of a line: the record less the CODE, and under NOADV the control character.
96    pub width: usize,
97    pub page: Option<Page>,
98    /// Level 1 is the most major control; level 0 is FINAL.
99    pub controls: Vec<Control<C>>,
100    pub groups: Vec<Group<X, V, U>>,
101    pub sums: Vec<Sum>,
102    /// SUM operands outside the REPORT SECTION, added by GENERATE.
103    pub subtotals: Vec<Subtotal<X>>,
104    pub page_counter: usize,
105    pub line_counter: usize,
106    pub state: usize,
107    pub report_heading: Option<usize>,
108    pub page_heading: Option<usize>,
109    pub page_footing: Option<usize>,
110    pub report_footing: Option<usize>,
111    /// The CONTROL HEADING and CONTROL FOOTING of each level, FINAL first.
112    pub control_headings: Vec<Option<usize>>,
113    pub control_footings: Vec<Option<usize>>,
114    /// FIRST DETAIL when written, so a PAGE HEADING below a REPORT HEADING can be seen not to fit.
115    pub first_detail_written: Option<i64>,
116}
117
118/// The page regions, with the precompiler's defaults applied.
119#[derive(Clone, Copy, Debug, PartialEq, Eq)]
120pub struct Page {
121    pub limit: i64,
122    pub heading: i64,
123    pub first_detail: i64,
124    pub last_detail: i64,
125    pub footing: i64,
126}
127
128#[derive(Clone, Debug, PartialEq, Eq)]
129pub struct Control<C = PlaceId> {
130    pub reference: C,
131    /// Where its value at the last GENERATE is kept in the state item.
132    pub saved: usize,
133    pub len: usize,
134}
135
136#[derive(Clone, Copy, Debug, PartialEq, Eq)]
137pub enum GroupKind {
138    ReportHeading,
139    PageHeading,
140    ControlHeading,
141    Detail,
142    ControlFooting,
143    PageFooting,
144    ReportFooting,
145}
146
147impl GroupKind {
148    pub fn is_body(self) -> bool {
149        matches!(self, Self::ControlHeading | Self::Detail | Self::ControlFooting)
150    }
151}
152
153#[derive(Clone, Debug, PartialEq, Eq)]
154pub struct Group<X = Comparand, V = ConstId, U = RangeId> {
155    pub name: Option<String>,
156    pub kind: GroupKind,
157    /// The control level of a CONTROL HEADING or FOOTING.
158    pub level: usize,
159    pub next_group: Option<NextGroup>,
160    pub lines: Vec<Line<X, V>>,
161    /// Fields with no COLUMN, which are set but not printed.
162    pub unprinted: Vec<Field<X, V>>,
163    /// Cross-footing: SUM entries of this group adding entries of this group, in dependency order.
164    pub cross: Vec<(usize, Origin<X, V>)>,
165    /// Rolling forward: SUM entries elsewhere adding entries of this group.
166    pub rolls: Vec<(usize, Origin<X, V>)>,
167    /// The SUM entries defined in this group, reset after it unless RESET defers them.
168    pub totals: Vec<usize>,
169    pub indicate: Option<usize>,
170    /// The USE BEFORE REPORTING section: its first and last paragraph.
171    pub declarative: Option<U>,
172}
173
174#[derive(Clone, Debug, PartialEq, Eq)]
175pub struct Line<X = Comparand, V = ConstId> {
176    pub number: LineNumber,
177    pub fields: Vec<Field<X, V>>,
178}
179
180#[derive(Clone, Debug, PartialEq, Eq)]
181pub struct Field<X = Comparand, V = ConstId> {
182    pub item: usize,
183    /// First byte in the line.
184    pub column: usize,
185    pub content: FieldContent<X, V>,
186    pub group_indicate: bool,
187    /// BLANK WHEN ZERO on an unedited numeric PICTURE, which the field applies after the MOVE.
188    pub blank_when_zero: bool,
189    pub rounded: bool,
190    pub pos: Pos,
191}
192
193#[derive(Clone, Debug, PartialEq, Eq)]
194pub enum FieldContent<X = Comparand, V = ConstId> {
195    Source(X),
196    Value(V),
197    Sum(usize),
198    /// No SOURCE, VALUE or SUM: the program's own statements fill the field.
199    Program,
200}
201
202#[derive(Clone, Debug, PartialEq, Eq)]
203pub struct Sum {
204    pub total: usize,
205    /// RESET ON: the control level whose break resets the total instead.
206    pub reset: Option<usize>,
207}
208
209/// What an entry adds to a total when its group is produced.
210#[derive(Clone, Debug, PartialEq, Eq)]
211pub enum Origin<X = Comparand, V = ConstId> {
212    Source(X),
213    Value(V),
214    Total(usize),
215}
216
217#[derive(Clone, Debug, PartialEq, Eq)]
218pub struct Subtotal<X = Comparand> {
219    pub sum: usize,
220    pub operand: X,
221    pub adding: Adding,
222}
223
224#[derive(Clone, Debug, PartialEq, Eq)]
225pub enum Adding {
226    EveryGenerate,
227    /// UPON: only a GENERATE of one of these DETAIL groups adds.
228    Upon(Vec<usize>),
229    /// SOURCE SUM correlation: the DETAIL groups that have the operand as a SOURCE.
230    Correlated(Vec<usize>),
231}
232
233/// The rows of a relative group from the line before its first to its last.
234pub fn span<X, V, U>(g: &Group<X, V, U>) -> i64 {
235    g.lines.iter().map(|l| if let LineNumber::Plus(k) = l.number { k as i64 } else { 0 }).sum()
236}
237
238/// The report and DETAIL group a GENERATE names: a report alone for summary reporting.
239pub fn generate_target<X, C, V, U>(reports: &[Report<X, C, V, U>], name: &str, qualifier: Option<&str>) -> Option<(usize, Option<usize>)> {
240    if qualifier.is_none()
241        && let Some(ri) = reports.iter().position(|r| r.name == name)
242    {
243        return Some((ri, None));
244    }
245    let mut found = reports
246        .iter()
247        .enumerate()
248        .filter(|(_, r)| qualifier.is_none_or(|q| r.name == q))
249        .flat_map(|(ri, r)| r.groups.iter().enumerate().filter(|(_, g)| g.kind == GroupKind::Detail && g.name.as_deref() == Some(name)).map(move |(gi, _)| (ri, Some(gi))));
250    let first = found.next()?;
251    found.next().is_none().then_some(first)
252}
253
254codec_enum!(ReportOp { Initiate(report) = 0, Generate { report, detail } = 1, Terminate(report) = 2, Suppress = 3 });
255codec_enum!(NextGroup { Line(line) = 0, Plus(lines) = 1, NextPage = 2 });
256codec_enum!(LineNumber { Line(line) = 0, Plus(lines) = 1, NextPage(line) = 2 });
257codec_struct!(Writer { reports, print_switch });
258codec_struct!(Report {
259    name, file, code, width, page, controls, groups, sums, subtotals, page_counter, line_counter, state, report_heading,
260    page_heading, page_footing, report_footing, control_headings, control_footings, first_detail_written,
261});
262codec_struct!(Page { limit, heading, first_detail, last_detail, footing });
263codec_struct!(Control { reference, saved, len });
264codec_enum!(GroupKind {
265    ReportHeading = 0,
266    PageHeading = 1,
267    ControlHeading = 2,
268    Detail = 3,
269    ControlFooting = 4,
270    PageFooting = 5,
271    ReportFooting = 6,
272});
273codec_struct!(Group { name, kind, level, next_group, lines, unprinted, cross, rolls, totals, indicate, declarative });
274codec_struct!(Line { number, fields });
275codec_struct!(Field { item, column, content, group_indicate, blank_when_zero, rounded, pos });
276codec_enum!(FieldContent { Source(expr) = 0, Value(value) = 1, Sum(sum) = 2, Program = 3 });
277codec_struct!(Sum { total, reset });
278codec_enum!(Origin { Source(expr) = 0, Value(value) = 1, Total(sum) = 2 });
279codec_struct!(Subtotal { sum, operand, adding });
280codec_enum!(Adding { EveryGenerate = 0, Upon(details) = 1, Correlated(details) = 2 });