Parser fixes:
- Fixed multiline state/init block parsing: trailing commas no longer
treated as continuation markers, merging all state vars into one
- Added splitFields() function for comma-aware emit field splitting
that respects parenthesized groups and quoted strings
- Fixed interface block parsing: enum-style interfaces (name-only
values like 'Idle, Parsing, Parsed, ...') now correctly parsed as
methods instead of being skipped (methods.length was 0)
TypeScript generator fixes:
- String[] array type syntax now correctly maps to string[] in output
- collectIdents filters out names containing brackets
- Duplicate event interfaces deduplicated (implicit emit events
filtered when explicit events already declare them)
- join(X, Y) translates to X + Y (string concatenation) instead of
X.join(Y) which is wrong for string args
- now() generates new Date().toISOString() instead of Date.now()
to match String/string type annotations
- decisionLog type uses proper union: (A | B | C)[] instead of A[]
- String literals protected from this.-prefixing during identifier
replacement (fixes 'leader' inside quotes becoming 'this.leader')
- Removed debug console.log from enum generation
Both TigerBeetleLedger.speck and speckl-compile.speckdl now generate
TypeScript that passes tsc --noEmit with zero errors.
Closes#9
- Add braceDepth tracking to Map/Set type parsing for inline records
- Fix multiline state block joining to preserve type expressions
- Fix multiline object literal parsing in assignments
- Add smart set-difference for Maps vs Sets
- Fix bare notIn for Sets
- Add non-null assertion (!) to Map.get() calls
- Add implicit event interface generation from emit statements
- Protect object literals from this. prefix injection
- Declare speckl_len_Int helper for list/sequence lengths
- Fix infix = inside => bodies (final pass regex)
- Handle prime notation: currentTerm' → currentTerm_post
- Handle length() calls: length(log) → (speckl_len_Int log)
- Handle min(): (ite (<= a b) a b) instead of unsupported 'min'
- Handle subtraction: i-1 → (- i 1)
- Declare _post state vars when invariants compare pre/post
- Added 3 BMC verify blocks to KRaft spec (TermMonotonicity, CommitSafety, LeaderCompleteness)
- Z3 returns clean sat with zero errors for all specs
- All 37 tests pass
- Sanitize sort names: Option(NodeId) → Option_NodeId
- Emit action transition predicates only for verify/BMC mode
- Handle -> implication operator in translateExpr
- Fleshed out KRaft invariant bodies (4/6 now have real expressions)
- ToggleSwitch: clean sat with model
- TigerBeetleLedger: sat with model
- KafkaKRaft: sat with model
- All 37 tests pass
- x in someSet → someSet.has(x)
- x in someMap.keys → someMap.has(x)
- x notIn someMap.keys → !someMap.has(x)
- m[k] → m.get(k) for Map state variables
- s.size() → s.size (property, not method)
- Track Map-typed state variables via mapVarNames for accurate .get() translation
- Thread mapVarNames through emitAction and rewriteExpr
All 41 tests passing. TigerBeetleLedger now generates correct TypeScript
with .get() for Map access and .has() for Set membership.
Part of M0.1: Fix TS generator bugs (speckl#18)
- Add 'precondition' and 'postcondition' ActionStatement types
- Parse precondition: expr and postcondition: expr in action bodies
- Support 'or' continuation lines for multi-clause pre/postconditions
- TigerBeetleLedger now parses 21 preconditions + 24 postconditions
(previously all dropped, resulting in empty action bodies)
- All 41 existing tests pass
Addresses marcus/engineering#9 (parser gap)
Does NOT wire into generators yet — TypeScript/WASM output
will be empty for pre/postconditions. Generator wiring
is follow-up work.
Undcompiled map/record/unrecognized assignments emitted (i32.const 1)
placeholders that left values on the WASM stack. Combined with
the default return i32.const 1 at end of function, this caused
type mismatch errors (expected [], got [i32, i32] or [i32]).
Fix: make uncompiled assignments emit comment-only output with
no stack effect. Scalar assignments already work correctly since
emitExpr feeds into global.set which consumes the value.
Closes#9 WASM E2E portion — parser fix for precondition:/postcondition:
keyword handling still needed.
- check-dist.sh: Prevents DIST DESYNC by verifying all dist/*.js files
are newer than their src/*.ts sources. Fails build if stale.
- Makefile: Standard build targets (build/check-dist/test/recompile/clean)
- src/index.ts: Integrated dist freshness check before compilation
- src/parser.ts: Added List<T> type parsing alongside existing Set<T>/Map<K,V>
(e.g., 'log: List<LogEntry>' now parses as { type: 'list', elementType })
- examples/: Restored v0.3 state machine specs (TigerBeetleLedger,
AccountLedger, ToggleSwitch, TwoPhaseCommit)
- DIST DESYNC is a known recurring anti-pattern — this guard prevents it
Tests: 39/41 pass (2 pre-existing E2E WASM failures due to missing wabt)
- Add __tests__/e2e-wasm.test.ts: full pipeline test
- Parse Counter speck → generate WAT → assemble with wabt → instantiate → run
- Verify init, action calls, guard pass/fail, state mutations
- TigerBeetle WAT validation test (wabt parse + validate + toBinary)
- Fix wasm.ts bugs found by E2E:
- Guard condition inverted: added i32.eqz to negate comparison
- Missing local declarations: locals now inserted before body
- Uncompiled guards left stack values: now emit comments only (no i32.const 1)
- Install wabt npm package for WAT→WASM assembly in tests
- 41/41 tests passing (39 original + 2 E2E)
Milestone: Speckl produces runnable WebAssembly from specs.
Adds a first-class TypeScript code generator to speckl-compile that emits:
- Const enum for workflow states (IssueWorkflowState)
- Strongly-typed class with guards, state transitions, decision log
- Proper this. prefixing (state vars only, not local params)
- Enum references instead of string literals for state comparisons
- Clean tsc --noEmit compilation
Also includes:
- specs/forgejo-issue-workflow.speckdl — full workflow with 9 actions,
8 constraints, 5 temporal verifications
- Generator deduplication of duplicate AST nodes
- Support for init block initialization of state variables
The generator was validated against the Forgejo issue workflow spec,
producing IssueWorkflow.ts that compiles with zero errors.
Refs: heartbeat conversation re: speckl-compile extensibility
- Fixed speck name regex to allow hyphens (\w → [\w-])
- Added provenance { } block handling in parseMember()
- Updated hyphenated name test from 'My' to 'My-Service-Handler'
- All 48 tests passing (27 parser + 21 validator)
- Connected PROV-O, CycloneDX, and SPDX generators to CLI entry point
- Fixed parseAuthor regex to handle angle-bracket email format
- Fixed parseDerives to handle hyphenated names and quoted rationale
- Fixed parseSatisfies to handle hyphenated requirement IDs
- Fixed parseSource to handle ref keyword with quoted value
- Added graceful fallbacks (warn instead of throw) for parse errors
- All 5 example .speck files compile and validate successfully
- CLI entry point with yargs parsing for speckl-compile <file> --output-dir --bom-format
- TypeScript parser with AST types for all SpeckDL primitives
- Generator for PROV-O JSON-LD provenance graphs
- Generator for CycloneDX v1.6 BOMs
- Generator for SPDX 3.0.1 BOMs
- E2E test verifying RetryHandler.speck parsing and all output formats
All tests pass.