banklang.mwhassan.com — the compiler runs in your browser at /playground/.
A deterministic compiler from a small banking language to readable IBM Enterprise COBOL — with banking safety rules enforced at compile time.
BankLang compiles BankTS into COBOL a mainframe engineer can read and
review. Its types are TypeScript's; its statements — transaction, file,
cursor, queue — are its own. Everything it emits is decided by code you can
read: no model is involved, and the same input always produces byte-identical
output.
Translation is the ordinary part. What the compiler does beyond it is refuse to build unsafe programs.
transaction postTransfer(request: TransferRequest) {
debit(request.debitAccount, request.amount);
credit(request.creditAccount, request.fee); // ← a different amount
}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.Each is a compile error rather than a production incident.
Validated with GnuCOBOL, not IBM. Every example compiles in CI under a GnuCOBOL configuration shaped to Enterprise COBOL 6.4 and under GnuCOBOL's own default. No IBM Enterprise COBOL validation is claimed.
Built with AI assistance. The design and the decisions are the author's; much of the implementation was written with an AI coding assistant under review. That describes how the compiler was built. Nothing inside it is a model, at build time or at run time.
Read this first → · If you have to accept the output → · What it does not do →
Open the playground — nothing to install, and nothing you write is sent anywhere.
Click a line of BankTS and the COBOL it produced lights up, from the source map.
Fill in the entry record or dataset on Input, then Run executes it
against the reference runtime in runtime/ and shows what
it posted. Locally: pnpm install && pnpm playground:dev.
From one BankTS module, bankc build emits a COBOL program, a copybook per
record, the JCL to build and run it, a source map, and an audit bundle.
Interest accrual, in full:
function accrue(balance: MoneyBDT, rate: Rate): MoneyBDT {
return round(balance * rate, "HALF_EVEN");
}MoneyBDT is decimal<18, 2> and Rate is decimal<9, 4>, so the product has
scale 6. Storing it as money discards four digits, which the compiler
will not do silently: round with an explicit mode is required.
Enterprise COBOL has one rounding phrase, and ROUNDED is half-up away from
zero. Banker's rounding is arithmetic this compiler writes out:
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-EVALUATEThat sequence is executed against exact arithmetic over every boundary case, for a product and a quotient, in all seven modes. The numeric model →
| Diagnostic | Rule |
|---|---|
BANK-TXN-001 |
A transaction must carry an idempotency key |
BANK-AUD-001 |
A transaction must emit at least one audit event |
BANK-LED-001 |
Debits and credits must balance |
BANK-DEC-003 |
A division must state its rounding mode |
BANK-SQL-007 |
A SQLCODE test must separate an error from a missing row |
BANK-CICS-004 |
A CICS response must be tested against its condition name |
BANK-AUD-002 |
A sensitive field must not reach an audit event or ledger |
bankc explain BANK-LED-001 prints any of them, and no diagnostic can be
emitted without a catalogue entry. The full catalogue →
Node.js 24+ and pnpm 11.7.0. GnuCOBOL is optional locally.
pnpm bankc init my-service # scaffold a project
pnpm bankc check examples/account-posting # diagnostics only
pnpm bankc build examples/account-posting # full artifact bundle
pnpm bankc verify examples/account-posting # determinism + coverage
pnpm bankc test examples/account-posting # the above, plus cobc
pnpm bankc job examples/end-of-day-settlement # several programs, one job
pnpm bankc explain BANK-LED-001 # explain a diagnostic
pnpm bankc analyse legacy/ # what your COBOL contains
pnpm bankc copybook import ACCTMAST.cpy # your record, as BankTSAdd --watch to any command that reads a project to rerun it on save. The rest
refuse the flag and name the ones that take it.
The whole toolchain →
Each with a checked-in evidence bundle: its artifacts and the report over them.
The language
| Example | Demonstrates |
|---|---|
account-transfer |
Records, decimal aliases, a validator |
account-posting |
Transactions, ledger postings, audit events |
account-file-batch |
Sequential files, FILE-CONTROL and FD |
batch-interest-accrual |
Locals, exact decimal arithmetic, if/else |
withdrawal-with-recovery |
Inheritance, raise / on failure, run |
statement-generation |
Indexed files, enums, tables, nullables |
interest-posting-batch |
Rounding, tiered rates, a fee that can refuse |
amortisation-schedule |
Recursion as a RECURSIVE program |
rounding-conformance |
All seven rounding modes, both signs |
payment-feed-import |
lineSequential text from off the mainframe |
settlement-bill-file |
Header, detail and trailer in one output file |
zunit-tested-posting |
A test beside the program it covers |
The subsystems
| Example | Demonstrates |
|---|---|
online-enquiry |
CICS, commarea, Db2, three-outcome SQL |
branch-accrual-cursor |
WITH HOLD, checkpoint in a loop, run |
vsam-browse |
START / READ NEXT on an alternate index |
mq-request-reply |
A queue drained under syncpoint |
report-with-controls |
Report Writer: control breaks and totals |
When it goes wrong, and a night
| Example | Demonstrates |
|---|---|
failed-open |
File status 35, 37 and 39 named apart |
full-disk |
A WRITE out of extents, halfway through |
deadlock-retry |
Db2 -911 and -913, bounded retry |
high-volume-master |
A file bigger than the loop bound |
parm-driven-batch |
The PARM convention, restart and checkpoint |
end-of-day-settlement |
Three programs and a sort in one JCL stream |
And five conversions — conversions/ — existing COBOL
beside the BankTS it becomes.
Every example is run, not only compiled. Three have hand-written expected
balances; the rest are executed twice — by cobc and by an interpreter written
against the same output — and a test fails on any disagreement. That is what
catches a defect that compiles: the bounds guard once clamped an out-of-range
subscript instead of refusing it, and every static check passed.
That lane covers 27 of the 31 COBOL verbs the backend emits. The other four are a generated zUnit test case's entry points and a Report Writer section, neither of which has anywhere local to run. The grades →
Start here
| Document | Contents |
|---|---|
| Getting started | Thirty minutes from clone to reading COBOL |
| For mainframe engineers | The generated COBOL, construct by construct |
| For the person deciding | The risk, and what it would cost to find out |
| Status and honest limits | What this is not |
| Comparison | Against converters, Micro Focus, and by hand |
The output
| Document | Contents |
|---|---|
| Generated code standards | The house style, as a contract |
| Target conformance | The rules it obeys, with manual citations |
| Divergences | Where GnuCOBOL and Enterprise COBOL disagree |
| Numeric model | Precision, scale, intermediates, rounding |
| Error handling | Return codes, file status, SQLCODE, RESP |
| JCL model | The generated job, and what to change |
| COBOL backend | Emission rules |
The language and the compiler
| Document | Contents |
|---|---|
| Grammar | Every production, in EBNF |
| Language stability | What is settled and what is not |
| Diagnostics | The full catalogue |
| Architecture | Pipeline, packages, compile() |
| Verification | Testing and evidence strategy |
| Security and data | sensitive, PII, dumps |
| Toolchain | CLI, formatter, CI, editors |
| Migration analysis | Reading COBOL you have |
| Glossary | Compiler and mainframe terms |
| Roadmap | What is planned |
Language reference
| Document | Contents |
|---|---|
| Contents | Which page holds which rule |
| Every construct | Records, files, SQL, CICS, MQ |
Decisions
| Document | Contents |
|---|---|
| ADRs | Why the compiler is as it is |
A working compiler for a deliberately narrow subset, not a production mainframe toolchain. It has never run against a real ledger.
The full list, with what each limit costs →
See CONTRIBUTING.md: small changes, real tests, no silent golden updates, and never claim validation that did not happen.