Design Document: TypeScript Bindings for TLA+
Status: Draft Author: Terraphim AI Date: 2026-03-14 Phase: 2 (Disciplined Design) Research: tlaplus-typescript-bindings.md
Design Overview
A TypeScript library (@terraphim/tlaplus) providing native TLA+ tooling: parsing via tree-sitter-tlaplus, typed AST, expression evaluation, formatting, and TLC model checking bridge. Published as an npm package with CLI tool.
Architecture
@terraphim/tlaplus
├── src/
│ ├── parser/ -- tree-sitter wrapper, CST-to-AST transform
│ │ ├── index.ts -- TlaParser class
│ │ └── cst-to-ast.ts -- CST node visitors -> typed AST
│ ├── ast/ -- TypeScript type definitions for TLA+ AST
│ │ ├── types.ts -- Node types (Module, OpDef, Expression, etc.)
│ │ ├── visitors.ts -- Visitor pattern for AST traversal
│ │ └── builders.ts -- AST node factory functions
│ ├── eval/ -- Expression evaluator
│ │ ├── evaluator.ts -- Evaluate constant TLA+ expressions
│ │ ├── operators.ts -- Built-in operator implementations
│ │ └── values.ts -- TLA+ value types (sets, functions, records)
│ ├── format/ -- Pretty-printer
│ │ ├── formatter.ts -- AST-to-string formatting
│ │ └── rules.ts -- Formatting rules (indentation, line width)
│ ├── bridge/ -- TLC model checking bridge
│ │ ├── tlc.ts -- Spawn Java TLC process, parse output
│ │ └── types.ts -- TLC result types (states, errors, coverage)
│ ├── cli/ -- Command-line interface
│ │ └── index.ts -- parse|format|validate|check subcommands
│ └── index.ts -- Public API exports
├── test/
│ ├── fixtures/ -- TLA+ spec fixtures for testing
│ ├── parser.test.ts
│ ├── ast.test.ts
│ ├── eval.test.ts
│ ├── format.test.ts
│ ├── bridge.test.ts
│ └── cli.test.ts
├── package.json
├── tsconfig.json
├── vitest.config.ts
└── README.mdIssue Decomposition for Symphony
Issue 1: Scaffold TypeScript project
Title: Scaffold TypeScript project with build tooling and CI
Labels: priority/P1-high, type/infrastructure
Blocks: #3, #4, #5, #6, #7, #8
Description:
Create the initial TypeScript project structure for @terraphim/tlaplus.
Requirements:
package.jsonwith name@terraphim/tlaplus, typemodule, exports maptsconfig.jsontargeting ES2022, NodeNext module resolution, strict modevitest.config.tswith coverage reporting.eslintrc.jsonwith TypeScript eslint.gitignorefor node_modules, dist, coveragesrc/index.tsexporting a placeholder version stringtest/smoke.test.tsverifying the placeholder export- Install dev dependencies:
typescript,vitest,eslint,@typescript-eslint/*,tsup(bundler) - Install runtime dependencies:
tree-sitter,@tlaplus/tree-sitter-tlaplus tsup.config.tsfor building CJS + ESM bundles- README.md with project description and basic usage
Acceptance criteria:
npm installsucceedsnpm run buildproducesdist/with CJS and ESMnpm testruns and passesnpm run lintpasses
Issue 2: Create TLA+ AST type definitions
Title: Define TypeScript types for TLA+ abstract syntax tree
Labels: priority/P1-high, type/feature
Blocks: #3, #4, #5
Description:
Define comprehensive TypeScript types for the TLA+ AST in src/ast/types.ts.
The type hierarchy should cover:
// Top-level
interface TlaModule {
kind: 'module';
name: string;
extends: string[];
declarations: Declaration[];
instances: InstanceDecl[];
}
// Declarations
type Declaration = OpDef | VarDecl | ConstDecl | Assumption | Theorem | InstanceDecl;
interface OpDef {
kind: 'op_def';
name: string;
params: FormalParam[];
body: Expression;
isRecursive: boolean;
isLocal: boolean;
}
interface VarDecl { kind: 'var_decl'; names: string[]; }
interface ConstDecl { kind: 'const_decl'; names: string[]; }
// Expressions (discriminated union)
type Expression =
| LiteralExpr // numbers, strings, booleans
| NameExpr // identifiers
| OpCallExpr // operator application
| SetExpr // {1, 2, 3}
| SetFilterExpr // {x \in S : P(x)}
| SetMapExpr // {f(x) : x \in S}
| FunctionExpr // [x \in S |-> e]
| FunctionCallExpr // f[x]
| RecordExpr // [a |-> 1, b |-> 2]
| TupleExpr // <<1, 2, 3>>
| IfThenElseExpr
| LetInExpr
| QuantifierExpr // \A, \E
| ChooseExpr // CHOOSE
| CaseExpr
| ExceptExpr // [f EXCEPT ![x] = y]
| PrefixOpExpr // UNCHANGED, ENABLED, ~, []
| InfixOpExpr // /\, \/, =>, +, -, \in, etc.
| PostfixOpExpr // ' (prime)
| ActionExpr // <<A>>_v, [A]_v
| TemporalExpr // []F, <>F, F ~> G
| SubstitutionExpr;
// PlusCal (optional, in separate file)
interface PlusCalAlgorithm {
kind: 'pluscal';
name: string;
variables: PcalVarDecl[];
procedures: PcalProcedure[];
body: PcalBody;
}Also create:
src/ast/visitors.ts: Visitor interface and base walkersrc/ast/builders.ts: Factory functions for creating AST nodes
Acceptance criteria:
- All node types are discriminated unions with
kindfield - Each node type has JSDoc documentation
- Visitor interface covers all node types
- Builder functions create valid nodes
- Unit tests verify type narrowing works correctly
Issue 3: Implement tree-sitter parser wrapper with CST-to-AST transform
Title: Implement TLA+ parser using tree-sitter-tlaplus with CST-to-AST transform
Labels: priority/P1-high, type/feature
Blocked by: #1, #2
Description:
Create src/parser/index.ts and src/parser/cst-to-ast.ts that wrap tree-sitter-tlaplus and transform the concrete syntax tree into our typed AST.
Reference: Study js/eval.js in Spectacle for how it traverses the tree-sitter CST.
// src/parser/index.ts
export class TlaParser {
constructor();
parse(source: string): TlaModule;
parseExpression(source: string): Expression;
getErrors(source: string): ParseError[];
}Implementation steps:
- Initialise tree-sitter with the TLA+ grammar
- Parse source string to get tree-sitter
Tree - Walk the CST recursively, mapping node types to AST types
- Handle PlusCal blocks (embedded in comments)
- Collect parse errors with line/column information
Key tree-sitter node types to handle (from grammar.js):
source_file-> Moduleoperator_definition-> OpDefvariable_declaration-> VarDeclconstant_declaration-> ConstDeclbounded_quantification-> QuantifierExprfinite_set_literal-> SetExprset_filter-> SetFilterExprfunction_literal-> FunctionExprrecord_literal-> RecordExprtuple_literal-> TupleExprif_then_else-> IfThenElseExprlet_in-> LetInExpr
Test with TLA+ specs from tlaplus/Examples.
Acceptance criteria:
- Parses simple specs (DieHard, TwoPhaseCommit) into correct AST
- Reports parse errors with location information
- Handles multi-module specs (EXTENDS)
- Handles PlusCal blocks
- 90%+ test coverage for parser module
Issue 4: Implement basic expression evaluator
Title: Implement TLA+ expression evaluator for constant expressions
Labels: priority/P2-medium, type/feature
Blocked by: #2, #3
Description:
Create src/eval/evaluator.ts that evaluates constant TLA+ expressions (no temporal operators, no state variables).
Reference: Study js/eval.js in Spectacle for evaluation patterns.
Value types (src/eval/values.ts):
type TlaValue =
| { kind: 'bool'; value: boolean }
| { kind: 'int'; value: number }
| { kind: 'string'; value: string }
| { kind: 'set'; elements: TlaValue[] }
| { kind: 'tuple'; elements: TlaValue[] }
| { kind: 'record'; fields: Map<string, TlaValue> }
| { kind: 'function'; domain: TlaValue; mapping: Map<string, TlaValue> }
| { kind: 'model_value'; name: string };Operators to implement (src/eval/operators.ts):
- Logic:
/\,\/,~,=>,<=>,TRUE,FALSE - Arithmetic:
+,-,*,\div,%,..(range) - Comparison:
=,/=,<,>,<=,>= - Sets:
\in,\notin,\union,\intersect,\,SUBSET,UNION,Cardinality - Functions: function application
f[x], DOMAIN - Sequences:
Append,Head,Tail,Len,SubSeq - Records: field access
r.field - Quantifiers:
\A,\E(over finite domains) - CHOOSE:
CHOOSE x \in S : P(x) - IF-THEN-ELSE
- LET-IN
- CASE
export class TlaEvaluator {
evaluate(expr: Expression, context?: Map<string, TlaValue>): TlaValue;
}Acceptance criteria:
- Evaluates all listed operators correctly
- Provides clear error messages for unsupported operations
- Context/environment support for variable bindings
- Tests against known TLA+ expression results
- Performance: evaluate 1000 simple expressions in < 100ms
Issue 5: Implement TLA+ formatter (pretty-printer)
Title: Implement TLA+ source code formatter
Labels: priority/P2-medium, type/feature
Blocked by: #3
Description:
Create src/format/formatter.ts that takes a parsed TLA+ AST and produces formatted source code.
Reference: tlaplus-formatter for formatting conventions.
export interface FormatOptions {
lineWidth: number; // default: 80
indentWidth: number; // default: 2
alignConjuncts: boolean; // default: true
}
export class TlaFormatter {
constructor(options?: Partial<FormatOptions>);
format(module: TlaModule): string;
formatExpression(expr: Expression): string;
}Formatting rules:
- Align conjuncts (
/\) and disjuncts (\/) vertically - Indent nested expressions consistently
- Preserve comments (attach to nearest AST node)
- Handle long lines by breaking at operators
- Follow tlaplus-formatter conventions where sensible
- Idempotent:
format(format(x)) === format(x)
Acceptance criteria:
- Formats standard TLA+ specs (DieHard, etc.) readably
- Idempotent formatting
- Preserves semantic meaning (parse(format(parse(x))) === parse(x))
- Configurable line width and indent
- Comment preservation
- Tests with before/after fixtures
Issue 6: Implement TLC model checking bridge
Title: Implement TLC CLI bridge for model checking from TypeScript
Labels: priority/P2-medium, type/feature
Blocked by: #1
Description:
Create src/bridge/tlc.ts that spawns a Java TLC process and parses its output.
export interface TlcOptions {
javaPath?: string; // default: 'java'
tla2toolsPath?: string; // default: search common locations
workers?: number; // default: auto
maxDepth?: number;
checkDeadlock?: boolean; // default: true
}
export interface TlcResult {
success: boolean;
states: { distinct: number; found: number; left: number };
errors: TlcError[];
coverage: TlcCoverage[];
duration: number; // milliseconds
rawOutput: string;
}
export interface TlcError {
type: 'invariant_violation' | 'deadlock' | 'assertion_failure' | 'parse_error';
message: string;
trace?: TlcState[]; // counter-example trace
}
export class TlcBridge {
constructor(options?: TlcOptions);
check(specPath: string, configPath?: string): Promise<TlcResult>;
simulate(specPath: string, depth: number): Promise<TlcResult>;
isAvailable(): Promise<boolean>;
}Implementation:
- Locate
tla2tools.jar(checkTLA2TOOLS_PATHenv, common install locations) - Spawn
java -cp tla2tools.jar tlc2.TLCas child process - Parse stdout for state counts, errors, and coverage
- Parse counter-example traces into structured data
- Handle timeouts and process cleanup
Acceptance criteria:
isAvailable()correctly detects Java + tla2tools.jar- Parses TLC output into structured TlcResult
- Handles invariant violations with counter-example traces
- Handles deadlock detection
- Timeout and cancellation support
- Tests with known TLA+ specs that have violations
Issue 7: Create CLI tool
Title: Create tlaplus-ts CLI with parse, format, validate, check subcommands
Labels: priority/P2-medium, type/feature
Blocked by: #3, #4, #5, #6
Description:
Create src/cli/index.ts as the CLI entry point.
tlaplus-ts parse <file.tla> -- Parse and print AST as JSON
tlaplus-ts format <file.tla> -- Format in place (or --stdout)
tlaplus-ts validate <file.tla> -- Parse + evaluate constant expressions
tlaplus-ts check <file.tla> -- Run TLC model checking (requires Java)
tlaplus-ts version -- Print versionImplementation:
- Use a lightweight CLI argument parser (no heavy framework)
- Exit codes: 0 success, 1 errors found, 2 usage error
--jsonflag for machine-readable output--quietflag for suppressing non-error output- Coloured terminal output (with
--no-colorflag)
Add bin field to package.json:
Acceptance criteria:
- All four subcommands work correctly
--helpshows usage for each subcommand- Exit codes are correct
- JSON output mode works
- Works with stdin pipe:
cat spec.tla | tlaplus-ts parse
Issue 8: Documentation, examples, and npm publish preparation
Title: Add documentation, examples, and prepare for npm publishing
Labels: priority/P3-low, type/documentation
Blocked by: #7
Description:
Finalise the package for npm publishing.
README.md sections:
- Installation (
npm install @terraphim/tlaplus) - Quick start (parse a spec, evaluate an expression, format, run TLC)
- API reference (TlaParser, TlaEvaluator, TlaFormatter, TlcBridge)
- CLI usage
- Browser usage (WASM notes)
- Contributing
Examples:
examples/parse-spec.ts-- Parse and inspect a TLA+ specexamples/evaluate.ts-- Evaluate TLA+ expressionsexamples/format.ts-- Format a specexamples/model-check.ts-- Run TLC via bridgeexamples/specs/DieHard.tla-- Classic TLA+ example
Package preparation:
- Verify
package.jsonexports map is correct - Verify TypeScript declaration files are generated
- Add
filesfield to package.json (include only dist/, README, LICENSE) - Add LICENSE file (Apache-2.0)
- Verify
npm packproduces correct tarball
Acceptance criteria:
- README is comprehensive and accurate
- All examples run successfully
npm packproduces correct package- TypeScript declaration files are included
- All tests pass
- Package size is reasonable (< 2MB excluding WASM)
Dependency Graph
Issue 1 (scaffold) ──┬──> Issue 3 (parser) ──┬──> Issue 4 (evaluator) ──┐
│ │ │
Issue 2 (AST types) ─┤ ├──> Issue 5 (formatter) ──┤
│ │ │
└──> Issue 6 (TLC bridge)┘──> Issue 7 (CLI) ────────┤
│
└──> Issue 8 (docs)Symphony dispatch waves:
- Wave A: Issue 1, Issue 2 (parallel, no dependencies)
- Wave B: Issue 3, Issue 6 (parallel, depend on A)
- Wave C: Issue 4, Issue 5 (parallel, depend on B)
- Wave D: Issue 7 (depends on C)
- Wave E: Issue 8 (depends on D)