HLASM Capstone Project: Build a z/OS Record Processing Utility

HLASM Capstone Project: z/OS Record Processing Utility
This capstone project integrates every major topic from the HLASM Mastery course into a single, complete program. It is not a toy — it is the kind of utility that real z/OS shops write and maintain.
You will build EMPRPT — an employee record processing program that reads a sequential input file, validates each record, accumulates departmental salary totals in packed decimal, and writes a formatted summary report to an output file.
What You Will Build
EMPRPT performs the following tasks:
- Opens an input sequential file containing fixed-length employee records.
- Reads each record in a loop using the GET macro.
- Validates each record (department code in range, salary not zero, name not blank).
- Accumulates total salary per department using packed decimal arithmetic.
- Counts valid and invalid records.
- Closes the input file.
- Writes a formatted report to an output sequential file using PUT.
- Closes the output file and returns with a return code:
- RC=0: all records valid
- RC=4: some records were invalid but processing completed
- RC=8: I/O error encountered
Record Layout and DSECT
The input file has RECFM=FB (fixed block), LRECL=80. Each record has the following layout:
Offset Length Type Field
0 8 CHAR Employee ID (e.g., "EMP00042")
8 30 CHAR Employee Name (last, first)
38 4 CHAR Department Code (e.g., "ACCT", "ENGG", "SALE")
42 7 PACKED Annual Salary (PIC 9(13) packed = 7 bytes)
49 31 CHAR Filler / reservedDefine this layout as a DSECT so you can reference fields by name:
EMPREC DSECT
EMPID DS CL8 Employee ID
EMPNAME DS CL30 Employee Name
EMPDEPT DS CL4 Department Code
EMPSALRY DS PL7 Annual Salary (packed decimal)
EMPFILL DS CL31 Filler
EMPRECLN EQU *-EMPREC Record length = 80Program Structure
EMPRPT is organised into a main routine and five subroutines. Each subroutine has its own save area and follows standard z/OS linkage conventions.
EMPRPT (main)
├── INITIO — Open input and output files
├── PROCREC — Process one record (called in a loop)
│ ├── VALIDREC — Validate a single record
│ └── ACCUMSAL — Add salary to department total
├── WRTRPT — Write the summary report
└── CLOSEIO — Close both filesComplete Source Code
Main Program — EMPRPT
* EMPRPT — Employee Report Generator
* Reads employee records, validates, accumulates salary totals,
* writes department summary report.
*
EMPRPT CSECT
STM R14,R12,12(R13) Save caller's registers
BALR R12,0 Establish base register
USING *,R12 Base register is R12
ST R13,SAVE+4 Chain save areas (forward)
LA R13,SAVE Set R13 to our save area
ST R13,8(,R13) Chain save areas (backward — for dumps)
*
* Initialise counters and accumulators
ZAP TOTACCT,=P'0' Zero ACCT department total
ZAP TOTENGG,=P'0' Zero ENGG department total
ZAP TOTSALE,=P'0' Zero SALE department total
SR R6,R6 R6 = valid record count
SR R7,R7 R7 = invalid record count
*
* Open files
BAL R14,INITIO
LTR R15,R15 Check return code
BNZ IOERROR
*
* Main processing loop
READLOOP GET INFILE,INREC Read next record
USING EMPREC,INREC_ADDR Map DSECT over input buffer
*
BAL R14,PROCREC Process this record
LTR R15,R15
BZ VALIDCNT
LA R7,1(,R7) Increment invalid count
B READLOOP
VALIDCNT LA R6,1(,R6) Increment valid count
B READLOOP
*
EOFFILE DS 0H GET issues B to here at end-of-file
*
* Write report
BAL R14,WRTRPT
*
* Close files
BAL R14,CLOSEIO
*
* Set return code
LTR R7,R7 Any invalid records?
BZ ALLGOOD
LA R15,4 RC=4: some invalids
B EXIT
ALLGOOD SR R15,R15 RC=0: all clean
EXIT L R13,SAVE+4
L R14,12(,R13)
LM R0,R12,20(R13)
BR R14
*
IOERROR WTO 'EMPRPT: I/O INITIALISATION ERROR'
LA R15,8
B EXIT
*
* --------------------------------------------------------
* INITIO — Open INFILE and OUTFILE
* --------------------------------------------------------
INITIO DS 0H
OPEN (INFILE,(INPUT),OUTFILE,(OUTPUT))
TM INFILE+48,X'10' Test OPEN successfully bit
BNO INITFAIL
BR R14
INITFAIL LA R15,8
BR R14
*
* --------------------------------------------------------
* PROCREC — Process one record
* Input: INREC contains current record
* Output: R15=0 valid, R15=4 invalid
* --------------------------------------------------------
PROCREC STM R14,R12,12(R13PROC)
... (Full linkage as in INITIO above)
* (abbreviated for readability — full source in the lab kit)
*
* Validate
BAL R14,VALIDREC
LTR R15,R15
BNZ PROCINV
* Accumulate
BAL R14,ACCUMSAL
SR R15,R15
B PROCRET
PROCINV LA R15,4
PROCRET ... Restore and return
*
* --------------------------------------------------------
* VALIDREC — Validate one record
* Returns R15=0 (valid) or R15=4 (invalid)
* --------------------------------------------------------
VALIDREC DS 0H
* Check department code is one of ACCT, ENGG, SALE
CLC EMPDEPT,=CL4'ACCT'
BE DEPTVLD
CLC EMPDEPT,=CL4'ENGG'
BE DEPTVLD
CLC EMPDEPT,=CL4'SALE'
BE DEPTVLD
LA R15,4 Invalid department
BR R14
DEPTVLD DS 0H
* Check salary is not zero
CP EMPSALRY,=P'0'
BE INVALREC
* Check name is not blank
CLC EMPNAME,=CL30' '
BE INVALREC
SR R15,R15 Valid
BR R14
INVALREC LA R15,4
BR R14
*
* --------------------------------------------------------
* ACCUMSAL — Add EMPSALRY to the correct department total
* --------------------------------------------------------
ACCUMSAL DS 0H
CLC EMPDEPT,=CL4'ACCT'
BNE TRYENGG
AP TOTACCT,EMPSALRY Add to ACCT total
BR R14
TRYENGG CLC EMPDEPT,=CL4'ENGG'
BNE TRYSALE
AP TOTENGG,EMPSALRY
BR R14
TRYSALE AP TOTSALE,EMPSALRY
BR R14
*
* --------------------------------------------------------
* WRTRPT — Format and write summary report lines
* --------------------------------------------------------
WRTRPT DS 0H
* Format header
MVC OUTREC,HEADER1
PUT OUTFILE,OUTREC
* Format ACCT line
MVC OUTREC,DEPTLINE Copy template
MVC OUTREC+2,=CL4'ACCT'
ED OUTREC+10,TOTACCT Edit packed decimal to printable
PUT OUTFILE,OUTREC
* (repeat for ENGG and SALE)
...
BR R14
*
* --------------------------------------------------------
* CLOSEIO — Close both files
* --------------------------------------------------------
CLOSEIO DS 0H
CLOSE (INFILE,,OUTFILE)
BR R14
*
* --------------------------------------------------------
* Data areas
* --------------------------------------------------------
SAVE DS 18F Main program save area
INREC DS CL80 Input record buffer
INREC_ADDR EQU INREC
OUTREC DS CL133 Output record buffer (RECFM=FBA)
*
* Department salary accumulators (15 digits packed = 8 bytes)
TOTACCT DS PL8
TOTENGG DS PL8
TOTSALE DS PL8
*
* Report line templates
HEADER1 DC CL133' DEPARTMENT SALARY SUMMARY REPORT'
DEPTLINE DC CL133' XXXX $9,999,999,999,999'
*
* File DCBs
INFILE DCB DSORG=PS,MACRF=GM,DDNAME=INFILE,EODAD=EOFFILE, X
RECFM=FB,LRECL=80
OUTFILE DCB DSORG=PS,MACRF=PM,DDNAME=OUTFILE, X
RECFM=FBA,LRECL=133
*
LTORG
EMPREC DSECT
EMPID DS CL8
EMPNAME DS CL30
EMPDEPT DS CL4
EMPSALRY DS PL7
EMPFILL DS CL31
END EMPRPTJCL to Run EMPRPT
//EMPRPTJB JOB (ACCT),'CAPSTONE',CLASS=A,MSGCLASS=X
//*
//ASMSTEP EXEC PGM=ASMA90,PARM='OBJECT,NODECK,LIST,XREF(FULL)'
//SYSPRINT DD SYSOUT=*
//SYSLIN DD DSN=&&OBJ,DISP=(NEW,PASS),UNIT=SYSDA,SPACE=(CYL,(1,1))
//SYSUT1 DD UNIT=SYSDA,SPACE=(CYL,(2,1))
//SYSLIB DD DSN=SYS1.MACLIB,DISP=SHR
// DD DSN=SYS1.MODGEN,DISP=SHR
//SYSIN DD DSN=MY.HLASM.SOURCE(EMPRPT),DISP=SHR
//*
//LNKSTEP EXEC PGM=IEWBLINK,PARM='MAP,LIST',COND=(8,LT,ASMSTEP)
//SYSPRINT DD SYSOUT=*
//SYSLIN DD DSN=&&OBJ,DISP=(OLD,DELETE)
//SYSLMOD DD DSN=MY.LOAD.LIBRARY(EMPRPT),DISP=SHR
//SYSUT1 DD UNIT=SYSDA,SPACE=(CYL,(1,1))
//*
//RUNJOB EXEC PGM=EMPRPT,COND=(8,LT)
//STEPLIB DD DSN=MY.LOAD.LIBRARY,DISP=SHR
//INFILE DD DSN=MY.TEST.EMPDATA,DISP=SHR
//OUTFILE DD DSN=MY.EMPRPT.OUTPUT,DISP=(NEW,CATLG),
// SPACE=(TRK,(5,1)),RECFM=FBA,LRECL=133
//SYSOUT DD SYSOUT=*Test Data
Create a test dataset MY.TEST.EMPDATA with RECFM=FB, LRECL=80 containing records like:
EMP00001SMITH JOHN ACCT 000000007500000
EMP00002JONES MARY ENGG 000000012000000
EMP00003BROWN ROBERT SALE 000000009500000
EMP00004DAVIS LINDA XXXX 000000008000000 ← invalid dept
EMP00005WILSON JAMES ACCT 000000000000000 ← zero salaryWith these records, your program should produce RC=4 (invalid records found) and a report showing:
- ACCT total: $75,000.00
- ENGG total: $120,000.00
- SALE total: $95,000.00
Extension Tasks
Once the core program works, tackle these additions to deepen your skills:
- Error report — Write rejected records to a third output file (ERRFILE DCB) with the rejection reason appended to each record.
- Grand total — Add a grand total across all departments using a fourth accumulator.
- Record count lines — Add a footer line showing total records read, valid, and invalid counts.
- Multiple base registers — If your program grows beyond 4096 bytes, implement a second base register using the
USINGandDROPpattern covered in the addressing modes module. - ESTAE error recovery — Wrap the processing loop in an ESTAE macro to catch unexpected ABENDs, log the failing record, and continue processing rather than terminating.
Skills Checklist
Use this to verify you have applied the key course concepts:
- Standard linkage in main program and all subroutines (STM, BALR, USING, RETURN)
- DSECT used to map the input record layout
- GET/PUT/OPEN/CLOSE MVS macros for file I/O
- Packed decimal arithmetic (ZAP, AP, CP, ED)
- CLC/CLI for character validation
- Structured subroutine design with meaningful return codes
- LTORG for literal pool placement
- DCB definitions with correct MACRF, DSORG, RECFM, LRECL
- EODAD for end-of-file handling
- Conditional return code (RC=0, RC=4, RC=8)
Conclusion
Completing EMPRPT means you can write a production-grade HLASM program from scratch — one that opens files, processes structured records, performs packed decimal arithmetic, validates input, writes formatted reports, and returns meaningful return codes to JCL.
That is a real, marketable skill. z/OS shops that maintain legacy assembler code — and there are many of them — need developers who can read, modify, and extend programs exactly like this one.
You have completed the HLASM Mastery course. Review the full curriculum at HLASM Mastery Course and explore the 50 HLASM Interview Questions to prepare for mainframe assembler roles.
