1use 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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
24pub enum UseEnd {
25 Completed,
26 End(Ending),
28 GoTo,
29 Left,
32}
33
34#[derive(Clone, Copy, Debug)]
36pub struct ReportFile {
37 pub area: (usize, usize),
39 pub variable: bool,
41 pub reserved: bool,
43 pub record_min: Option<u32>,
44}
45
46pub trait ReportHost<'w, X: 'w, C: 'w, V: 'w>: Host<&'w C> {
50 fn value(&mut self, expr: &X, pos: Pos) -> R<Val>;
51 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 fn store_value(&mut self, dest: Loc, value: Val, rounded: bool, keep_on_size_error: bool, pos: Pos) -> R<bool>;
57 fn store_checked(&mut self, dest: Loc, value: &Fixed, pos: Pos) -> R<bool>;
59 fn report_file(&mut self, k: usize) -> ReportFile;
60 fn write_line(&mut self, k: usize, loc: Loc, space: Spacing, pos: Pos) -> R<()>;
62 fn use_before_reporting(&mut self, range: (usize, usize), pos: Pos) -> R<UseEnd>;
64 fn err(&mut self) -> &mut dyn Write;
65}
66
67pub 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#[derive(Clone, Copy, PartialEq, Eq)]
86enum Trigger {
87 Detail,
88 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 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(¤t);
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 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 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 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(¤t);
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 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 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 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 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 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 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 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 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 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 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 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 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}