BankLang

Reports

Report Writer: control breaks, totals, and pagination.

Part of the BankTS language reference.

Reports

page ... footing ... on a file paginates, but the program still writes every line itself and counts nothing. A report declares the shape and lets COBOL run it:

file statementFile sequential output record StatementLine status reportStatus;

report branchSummary on statementFile control branch
  page 20 heading 1 firstDetail 4 lastDetail 15 {
  pageHeading {
    line 1 {
      column 1 "BRANCH SUMMARY";
      column 40 "PAGE ";
      column 46 pageNumber;
    }
  }
  detail lineDetail {
    line next {
      column 1 branch;
      column 10 amount;
    }
  }
  controlFooting branch {
    line next {
      column 1 "SUBTOTAL:";
      column 10 sum amount;
    }
  }
  controlFooting {
    line next {
      column 1 "TOTAL:";
      column 10 sum amount;
    }
  }
}

Driven by three statements:

initiate branchSummary;
generate lineDetail;
terminate branchSummary;

generate names the detail group, because that is the thing being printed; initiate and terminate name the report. Everything between the two is the compiler's: it turns the page, repeats the heading, and breaks the totals.

Printing the source above three times — twice for LONDON, once for LEEDS — produces:

BRANCH SUMMARY                         PAGE     1

LONDON          42.50
LONDON          42.50
SUBTOTAL:                   85.00
LEEDS           42.50
SUBTOTAL:                   42.50
TOTAL:                     127.50

Nothing in the source adds anything up. That is the reason to have it: a hand-written subtotal reset in the wrong place is a report that is wrong and still balances, which is the kind of defect that survives review.

A total is printed wider than the rows above it, which is why the figures do not line up under the detail column. Report Writer sizes the accumulator from the picture on the sum entry rather than from the field being totalled, so a total given the row's own picture is an accumulator sized for one row: two postings of 9,999,999.99 would subtotal 9,999,999.98. The compiler gives every total all eighteen digits ARITH(COMPAT) carries, since how large a total gets depends on how many rows arrive and that is not known until the job runs.

A column prints a literal, a field, sum of a field, or pageNumber. A field is named bare and resolved against the record the report's file holds — a report is declared at the top level, where no transaction's variables are in scope, and that record is the only thing it reads. An amount is printed in its edited form, with the picture taken from the field's own precision and scale, which is what lets a COMP-3 balance reach a page at all.

Groups are pageHeading, pageFooting, detail, controlHeading, and controlFooting. A heading or footing may name a control field or leave it off, which means FINAL — the total over everything. Lines are placed with line <n> for an absolute line, or line next / line plus <n> to space.

The checks are BANK-FILE-008: a control field has to be in the record, a control heading or footing has to name a control the report breaks on, a sum has to total a numeric field and has to sit in a footing, and there has to be a detail group for generate to name. Confining sum to a footing is stricter than COBOL, which allows one in any group; the reason is in docs/diagnostics.md. A report's file is sequential output and may not also carry a page ... clause, since both decide where the page ends (BANK-FILE-007).

What a report costs on z/OS

Report Writer is not part of Enterprise COBOL. The Language Reference says so: the Report Writer module of the standard "is supported with the optional IBM COBOL Report Writer Precompiler and Libraries (5798-DYR)", and RD, PAGE LIMIT, CONTROL HEADING, PAGE FOOTING, SUM, COLUMN and report description entries are all listed as features that precompiler supplies. A REPORT SECTION handed straight to IGYCRCTL does not compile.

The generated job therefore runs the stand-alone precompiler first — SPCRWCOB, reading SYSIN, writing the expanded COBOL to SYSINS, with RWWORK as working space — and the compile step reads what it wrote. It runs before the CICS translator and the Db2 precompiler, because Report Writer passes EXEC ... END-EXEC through unchanged and neither of the others understands a REPORT SECTION. The link-edit step picks up the Report Writer run time library, since the expansion leaves external references to it.

If your installation does not license 5798-DYR, do not use report: page on the file paginates with LINAGE, which is in the base compiler.

Verifying one locally

GnuCOBOL implements Report Writer and the tests execute one — headings, control breaks, and totals all check out. One local wrinkle is worth knowing: GnuCOBOL's default assign_clause resolves an unquoted ASSIGN TO <name> on a file carrying REPORT IS to report-section storage rather than to the DD name, so the output lands in a file named after a printed value. Compile with -fassign-clause=external to bind it. On z/OS the DD comes from the JCL and the question does not arise; zos/README.md records it.

Paginating a report

file statementReport sequential output record ReportLine
  page 60 footing 55 top 3 bottom 3 status reportStatus;

write statementReport from heading advancing page;
write statementReport from line advancing 1 on page {
  write statementReport from heading advancing page;
};

page emits LINAGE. It is what makes a report paginate: COBOL counts the lines written and signals end of page at the footing, which is where a program writes its carried-forward total and the next page's heading. Without it a statement run is one unbroken column of text. footing, top, and bottom are optional; a depth alone is a page.

advancing <n> and advancing page emit AFTER ADVANCING, so a line is written after spacing rather than on top of the last one.

on page { ... } is AT END-OF-PAGE. It needs the file to declare a depth, since otherwise there is no page for a write to reach the end of (BANK-FILE-007), and a page depth belongs to a sequential output file — a keyed file has records, not lines to space.

What the copybook contains

A generated copybook is the record's own COBOL declaration, not a summary of it: every clause the program's inline record carries, the copybook carries too — REDEFINES, OCCURS with its index, SYNCHRONIZED, JUSTIFIED, BLANK WHEN ZERO, the nested groups, and the 88-levels of an enum.

That matters under copybookMode: "copy", where the program's storage is the copybook. A clause the copybook omitted was a clause the program did not have: a redefining field took storage of its own and pushed every later field along, a table collapsed to a single element, and an aligned field lost the slack bytes. bankc copybook inspect reads the same structure, so its offsets and the layout report's agree.


Read this page as Markdown on GitHub →