BankLang

Toolchain

Everything around the compiler: the CLI, the formatter, project configuration, CI integration, and editor support.

Commands

Every command bankc --help lists, and nothing else. tests/cli-toolchain.test.ts holds this table to the CLI's own inventory, so a command added without a row here fails the build.

Command Purpose
bankc init <dir> Scaffold a project that compiles on first run
bankc check <project> Diagnostics only, no artifacts
bankc build <project> COBOL, copybooks, JCL, source map, audit bundle
bankc job <directory> Several programs and their sorts, as one JCL stream
bankc emit <what> <project> One artifact kind: cobol, copybooks or jcl
bankc audit-report <project> The audit bundle on its own
bankc verify <project> Determinism and source-map coverage
bankc test <project> check, build, verify, plus local cobc validation
bankc zunit <project> zUnit test case: configuration, driver, and job
bankc layout <project> Copybook byte layout report
bankc config <project> Show the resolved configuration
bankc fmt <project> Format source; --check to verify only
bankc analyse <path>... COBOL inventory, paragraph and copybook graphs
bankc copybook <sub> <file> Import, inspect, summarise, or diff a copybook
bankc dclgen import <file> A Db2 DCLGEN member, as BankTS
bankc explain [id] Explain a diagnostic, or list the catalogue
bankc doctor Environment report
bankc version The compiler version, and nothing else

Global options

Option Applies to Meaning
--format text|json|sarif check Diagnostic output format
--output <file> check Write the machine-readable report to a file
--out <dir> build-like Output root for generated artifacts
--watch project Rerun when a .bank.ts file changes
--debug any Print the stack when the compiler itself fails

Positional arguments may appear before or after flags.

bankc analyse recursively reads .cbl, .cob, and .cpy members from its supplied paths. --out writes the inventory, one paragraph graph per program, and deterministic copybook dependency reports as Mermaid Markdown and JSON. Dependency resolution reports missing and duplicate member names rather than guessing, and does not expand copybook content into the inventory counts.

Everything the compiler means to report (a diagnostic, a missing project, an unreadable copybook) comes back as a message and an exit code. A failure it did not anticipate is still a thrown error, and bankc prints its message on one line prefixed with bankc: and exits 1. --debug adds the stack, which is the right output for a bug in the compiler and the wrong output for a mistake in a program.

Under --watch the same failure is printed and the session continues. A watch exists to shorten the loop on exactly the errors that end a build, so ending the watch on one is the wrong response: the exit code follows the last build, and saving a fix clears it. bankc job <directory> --watch watches the directory itself, since a job's sources are one level down in each step's project.

--watch applies to the commands that read a project (check, build, job, emit, audit-report, verify, test, zunit, layout, config) and is refused with exit code 2 on the rest, which name the commands that take it. It used to be accepted everywhere, and "everywhere" included commands with no project to find: bankc explain BANK-LED-001 --watch read the diagnostic identifier as a project path and died on ENOENT … watch '/…/BANK-LED-001', and bankc doctor --watch opened a recursive watch over the working directory to rerun a command no .bank.ts can change.

bankc doctor

The first command worth running on a machine that has just cloned this, and the one to paste into a bug report. It reports the compiler's own version, the working directory and whether it holds a project, Node and the platform, the backend target, and whether GnuCOBOL is installed: by running cobc --version through the same resolution pnpm test:gnucobol uses, including GNUCOBOL_COBC_PATH, so it names the compiler that lane would actually run rather than whatever is first on the path.

BankLang doctor
bankc: 0.10.0
cwd: /home/somebody/banklang
project: src/main.bank.ts
node: v24.18.0
platform: linux
arch: x64
compiler target: ibm-enterprise-cobol-zos
local validation target: gnucobol-local
gnucobol: cobc (GnuCOBOL) 3.2.0
ibm enterprise cobol: not detected, and no native IBM validation is claimed

An absent cobc is a normal state, not a failure: the line says so and says what it costs, which is that pnpm test:gnucobol and the cobc tests skip.

The last line is a constant. Nothing on a workstation can detect IBM Enterprise COBOL, so printing it only on failure would leave a reader unsure whether a missing line meant success or no check at all. It is always there, and never conditional on something a machine could accidentally satisfy.

Formatting

bankc fmt prints from the AST, so the output shape is decided by one code path rather than by regex rewriting.

Two properties are tested:

The formatter refuses to rewrite a file with syntax errors, because a formatter that reshapes source it could not fully parse can destroy work.

Author blank lines inside a body are preserved (normalised to one), since grouping is meaningful. Blank lines between top-level declarations are normalised to exactly one.

pnpm fmt          # format every example
pnpm fmt:check    # verify, used by CI

Project configuration

banklang.json sits beside src/:

{
  "$schema": "https://banklang.mwhassan.com/schema/banklang.json",
  "entry": "src/main.bank.ts",
  "outDir": "dist",
  "backendProfile": "ibm-enterprise-cobol-zos",
  "formatCheck": false
}
Option Default Meaning
entry src/main.bank.ts Entry source file
outDir dist Output root
backendProfile ibm-enterprise-cobol-zos Target profile
formatCheck false Treat formatting drift as a failure
copybookMode inline Whether records are written in or COPYd
decimalPoint point DECIMAL-POINT IS COMMA when comma
currencySign $ CURRENCY SIGN IS, for an edited picture
runtimeOptions TERMTHDACT(UADUMP), TRAP(ON) Cards written to the job's CEEOPTS DD

Language Environment run-time options

A step that states none runs on whatever the installation's defaults are, which is not something a job's behaviour should depend on silently. The two defaults are about whether a bad night can be diagnosed at all: TERMTHDACT(UADUMP) asks for a readable dump when a program abends, and TRAP(ON) is what puts LE in the path to produce one.

Everything else is a site's. A long-running batch wants HEAP and STACK sized for the region and the data, and those are numbers this compiler cannot see and does not invent:

{
  "runtimeOptions": [
    "TERMTHDACT(UADUMP)",
    "TRAP(ON)",
    "HEAP(4M,1M,ANYWHERE,KEEP)",
    "STACK(1M,1M,ANYWHERE)"
  ]
}

Each entry is written on its own card, exactly as given. Nothing validates the option names: the set belongs to Language Environment and grows with it, and a compiler that refused an option it had not heard of would be one nobody could use a new release of.

Unknown keys and wrong types are reported as warnings and fall back to defaults, rather than throwing. A typo in a config file should produce a clear message, not a stack trace. Inspect the resolved values with bankc config.

CI integration

Diagnostics as pull request annotations

bankc check --format sarif emits SARIF 2.1.0, which GitHub code scanning ingests. Banking safety diagnostics then appear inline on the pull request instead of buried in a log.

- name: Produce SARIF report
  run: pnpm bankc check examples/account-posting --format sarif --output bankc.sarif

- name: Upload SARIF
  uses: github/codeql-action/upload-sarif@v3
  with:
    sarif_file: bankc.sarif

Uploading requires a public repository or GitHub Advanced Security.

Each SARIF rule carries the catalogue's explanation and remediation, so a reviewer seeing BANK-LED-001 for the first time gets the reasoning inline.

JSON

--format json is the simpler machine-readable option, enriched with the catalogue title and explanation for each diagnostic.

Both formats emit a valid report even when there are no diagnostics, so a CI step can upload unconditionally. Exit status is 1 when any diagnostic was reported and 0 otherwise, regardless of format.

Editor support

Language server

packages/language-server implements LSP over stdio with no dependencies:

Capability Behaviour
publishDiagnostics On open and change, from the real compiler
hover Diagnostic explanation, or the COBOL a line produces
documentFormatting Runs the formatter
documentSymbol Outline, with record fields nested
pnpm lsp   # start the server on stdio

Transport is separate from request handling, so the whole protocol surface is unit-tested without spawning a process.

Hover is worth calling out: on a clean line it reports which COBOL lines that source produced, reading the same source map the playground uses.

VS Code extension

packages/vscode-extension provides the language client, a TextMate grammar, and editor configuration.

pnpm --filter banklang-vscode build

The extension is not published to the marketplace. Load it with Run Extension from VS Code, or point banklang.server.path at the server entry point.

Programmatic use

import { compile } from "@banklang/compiler";
import { formatBankTs } from "@banklang/formatter";
import { explainDiagnostic } from "@banklang/diagnostics";

const result = compile(source);
if (!result.ok) {
  for (const diagnostic of result.diagnostics) {
    const doc = explainDiagnostic(diagnostic.id);
    console.error(`${diagnostic.id}: ${doc?.remediation}`);
  }
}

compile and formatBankTs perform no file system or network access, so they run in Node and in a browser. That is what lets the playground run the real compiler client-side, and a test fails if any compiler package imports a Node built-in.


Read this page as Markdown on GitHub →