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