A compiler from a small banking language to readable IBM COBOL.
You write BankTS. Its types are TypeScript's —
decimal<18, 2>, string<16> — and
its statements are its own: transaction,
file, cursor, queue. It emits
IBM Enterprise COBOL, a copybook for every record, and the JCL to build
and run it. No model decides what is generated, and the same input
always produces byte-identical output.
Validated with GnuCOBOL, not IBM. No IBM Enterprise COBOL validation has been performed, and none is claimed. What that leaves open →
It refuses to compile unsafe programs
The build fails and produces no artifact, the way a type error does.
transaction postTransfer(request: TransferRequest) {
debit(request.debitAccount, request.amount);
credit(request.creditAccount, request.fee);
}
BANK-TXN-001 Transaction postTransfer has no idempotency key.
BANK-AUD-001 Transaction postTransfer does not emit an audit event.
BANK-LED-001 Transaction postTransfer does not balance: debited request.amount against credited request.fee.
Open this program in the playground →
A retry that posts twice, money moving with no audit trail, and a ledger that does not balance. Each is normally caught by a person — a reviewer who knows to look, an auditor reconciling afterwards. There are 16 such rules, among 113 diagnostics in all — each documented with an explanation and a remediation, and each one provoked by a test that fails if the rule stops working.
The COBOL it produces
Enterprise COBOL has one rounding phrase, and ROUNDED is
half-up away from zero. Banker's rounding is arithmetic this compiler
writes out.
// Interest is balance * rate, rounded to the currency scale with banker's
// rounding. The rounding mode is required by the compiler, not optional.
function accrue(balance: MoneyBDT, rate: Rate): MoneyBDT {
return round(balance * rate, "HALF_EVEN");
}
ACCRUE.
*> HALF_EVEN is generated. COBOL has
*> only ROUNDED, which is HALF_UP.
COMPUTE BANK-RND-1-VALUE = (ACCRUE-P1 * ACCRUE-P2)
ON SIZE ERROR
DISPLAY "ARITHMETIC OVERFLOW ACCRUE-RESULT" UPON
SYSOUT
MOVE 12 TO BANK-RETURN-CODE
MOVE "ARITHMETIC-OVERFLOW" TO BANK-FAILURE-CODE
GO TO ACCRUE-EXIT
END-COMPUTE
COMPUTE BANK-RND-1-EXCESS =
(ACCRUE-P1 * ACCRUE-P2) - BANK-RND-1-VALUE
COMPUTE BANK-RND-1-STEP = 0.01
IF BANK-RND-1-EXCESS < 0
COMPUTE BANK-RND-1-STEP = -0.01
END-IF
COMPUTE BANK-RND-1-UNITS = BANK-RND-1-VALUE * 100
EVALUATE TRUE
WHEN FUNCTION ABS (BANK-RND-1-EXCESS) > 0.005
ADD BANK-RND-1-STEP TO BANK-RND-1-VALUE
WHEN FUNCTION ABS (BANK-RND-1-EXCESS) = 0.005
IF FUNCTION MOD (BANK-RND-1-UNITS, 2) = 1
ADD BANK-RND-1-STEP TO BANK-RND-1-VALUE
END-IF
END-EVALUATE
MOVE BANK-RND-1-VALUE TO ACCRUE-RESULT
CONTINUE.
That sequence is executed and compared against exact arithmetic over every boundary case, for a product and for a quotient, in all seven rounding modes. The overflow branch is not decoration: storing a scale-6 product in a scale-2 field would discard four digits, and the compiler will not do that silently.
What happens to the COBOL you already have
A classic sequential update: read a transaction file, apply each posting, write a new master and a reject file. Every estate has thirty of these.
FILE-CONTROL.
SELECT TRANS-FILE ASSIGN TO TRANSIN.
SELECT MASTER-IN ASSIGN TO MASTIN.
SELECT MASTER-OUT ASSIGN TO MASTOUT.
SELECT REJECT-FILE ASSIGN TO REJECTS.
* 41 lines: the FDs and WORKING-STORAGE
0000-MAIN.
OPEN INPUT TRANS-FILE
MASTER-IN
OUTPUT MASTER-OUT
REJECT-FILE.
file transFile sequential input record TransRecord status transStatus;
file masterIn sequential input record MasterRecord status masterInStatus;
file masterOut sequential output record MasterRecord status masterOutStatus;
file rejectFile sequential output record RejectRecord status rejectStatus;
on error transFile {
log "TRANSIN FAILED, STATUS ", transStatus;
returnCode = 12;
}
Four SELECT statements with no FILE STATUS
clause between them, and one OPEN whose outcome nothing
tests. A missing input gives file status 35 and the first read hits
AT END — the job ends with return code 0 having processed
nothing. The layouts are unchanged, field for field, so the same
datasets are read by the same DD names. What changes is everything the
original left implicit.
What it is not
- It has never run on z/OS. Every example compiles with GnuCOBOL, under a dialect configured to Enterprise COBOL 6.4 and under GnuCOBOL's own default. GnuCOBOL is not IBM's compiler.
- No money has moved through it. No institution has used it. There is no production deployment and no pilot.
- It covers a narrow subset. Batch and CICS programs against QSAM, VSAM and Db2, with a ledger and audit calling convention it defines itself. It does not replace a core banking package.
- 23 of 25 examples are executed against a reference runtime in the repository, and 3 of those have expected balances somebody worked out by hand. The rest are run twice and compared against each other, which catches a defect that compiles and would not catch one that is wrong the same way twice. The grades are generated, not asserted.
The full list, with what each limit costs → For the person deciding →