BankLang

Write banking logic in BankTS. Review the COBOL it generates.

BankTS is a small, statically typed language designed for banking workloads, and BankLang is its compiler. With consistent compiler settings and versions, a module generates IBM Enterprise COBOL, record copybooks, JCL, a source map, and an audit report. The compiler does not call an AI model during a build or at runtime.

BankTS makes selected rules explicit: decimal precision and scale, rounding mode, idempotency keys, audit events, file status, and selected SQL and CICS outcomes. BankLang rejects scenarios it can prove unsafe. It is a narrow compiler, not a replacement for COBOL or for a bank's core systems.

Current validation uses GnuCOBOL, not IBM's compiler. The examples are compiled and, where supported, executed locally with GnuCOBOL 3.2.0 under an IBM-shaped profile. No IBM Enterprise COBOL validation has been performed, and no generated output has run on z/OS. What that leaves open →

Compile-time checks for selected banking risks

This transfer contains three independent issues. BankLang reports them before it emits COBOL.

BankTS
transaction postTransfer(request: TransferRequest) {
  debit(request.debitAccount, request.amount);
  credit(request.creditAccount, request.fee);
}
What the compiler says
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 →

In plain terms, a retry can apply the transfer twice, no audit event records the movement, and the amount debited does not match the amount credited. These are the kinds of omissions that otherwise depend on a review or a test case noticing them.

The compiler currently implements 16 rules in this category, alongside 114 implemented diagnostics overall. The catalogue explains each rule, and the test suite exercises the rules. Mutation results are tracked separately in the verification documentation.

Why keep COBOL as the output

Existing systems often hold the rules for balances, interest, and settlement in long-lived COBOL programs. Replacing them means rediscovering those rules and proving that the new system behaves the same way. That makes a full rewrite a large operational and verification project.

BankLang takes a narrower approach for new programs. Developers write BankTS, while the reviewed and deployed artifact remains COBOL. The source language adds types and compile-time checks for a selected banking surface; it does not migrate an existing estate automatically.

Why the rewrites keep stalling →

Generated arithmetic is explicit

Money fields use decimal types with declared precision and scale. When a result needs rounding, BankTS requires a named mode and the emitter writes the corresponding arithmetic into COBOL.

BankTS
// 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");
}
Generated COBOL
       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.

Enterprise COBOL's ROUNDED phrase uses half-up rounding. Banker's rounding, where a tie goes to the even digit, needs explicit arithmetic in the generated program. The test suite compares the generated result with exact rational arithmetic across boundary cases, products, quotients, and all seven supported modes.

Scale is part of the type. A balance times a rate can have more fractional places than a money field, so round makes the conversion explicit instead of discarding digits silently.

The generated ON SIZE ERROR branch handles a result that does not fit in its receiving field. It reports the overflow and leaves the step with a failure code instead of allowing a truncated value to continue.

How the arithmetic is defined →

Work with existing layouts

BankLang is not a COBOL-to-BankTS converter. It can import selected copybook and DCLGEN layouts, and the repository includes worked conversions to show the boundary. This example is a sequential master update that reads transactions, applies them, and writes balances and rejects.

The original
       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.
The BankTS it becomes
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;
}

The original opens four files but declares no FILE STATUS fields. A missing input can therefore look like a clean run after the first read reaches AT END.

The BankTS version declares the same layouts and gives each file a status field with an error handler. In this example, the generated step distinguishes a file failure from a run that had no records to apply, so a following JCL step can make a different decision.

Five conversions, side by side →

Scope and limits

The full list, with what each limit costs →

Choose a starting point