@turing-machine-js/machine 7.0.0-alpha.3 → 7.0.0-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,56 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [7.0.0-alpha.4] - 2026-05-23
8
+
9
+ Fourth v7 pre-release. Adds an id-keyed lookup helper for the State graph ([#195](https://github.com/mellonis/turing-machine-js/issues/195)), fixes two upstream issues surfaced while wiring the new helper into downstream tooling — a `toMermaid` label-grammar bug ([#194](https://github.com/mellonis/turing-machine-js/issues/194)) and a halt-stack lifetime bug in `runStepByStep` ([#196](https://github.com/mellonis/turing-machine-js/issues/196)) — and extracts graph serialization into its own module without API change ([#180](https://github.com/mellonis/turing-machine-js/issues/180)). Published under the `next` dist-tag: `npm install @turing-machine-js/machine@next`.
10
+
11
+ **Pre-release — the API surface may still shift before stable v7.0.0.** Pin to a specific alpha for reproducibility: `@turing-machine-js/machine@7.0.0-alpha.4`.
12
+
13
+ ### Added
14
+
15
+ - **`State.collectStates(initialState, tapeBlock)`** ([#195](https://github.com/mellonis/turing-machine-js/issues/195)). Static helper that returns a `Map<number, {state: State; transitionSymbols: symbol[]}>` keyed by engine `GraphNode.id`. Lets downstream tooling (graph renderers, debugger panels) mutate `state.debug` on a specific State by numeric id, and set per-pattern breakpoints by `GraphTransition.id`. The K-th `transitionSymbols` slot is positionally aligned with the GraphTransition whose id is `${stateId}-${K}`, so a consumer holding `(stateId, patternIx)` from the rendered graph reaches the firing Symbol with no walk.
16
+
17
+ ```ts
18
+ const stateMap = State.collectStates(initial, tapeBlock);
19
+
20
+ // State-level breakpoint by id (any pattern fires).
21
+ stateMap.get(clickedStateId)!.state.debug.before = true;
22
+
23
+ // Per-pattern breakpoint by GraphTransition.id ("${stateId}-${patternIx}").
24
+ const [n, k] = clickedEdgeId.split('-').map(Number);
25
+ const entry = stateMap.get(n)!;
26
+ entry.state.debug.before = [entry.transitionSymbols[k]!];
27
+ ```
28
+
29
+ Coverage: regular / bare states get the full `[...#symbolToDataMap.keys()]` including `ifOtherSymbol` at its natural slot; wrappers and the halt singleton get empty `transitionSymbols`; synthetic halt markers (`isHaltMarker: true`, id `= -frameId`) are excluded — they all collapse to `haltState` at runtime, and the named consumer surfaces halt-pause via a separate UI control, not via clicks on halt glyphs. The halt singleton entry at id `0` is the process-wide `haltState` — toggling its `.debug` affects every machine in the runtime, same caveat as direct `haltState.debug` writes.
30
+
31
+ - **`StateMap` and `StateMapEntry` types** ([#195](https://github.com/mellonis/turing-machine-js/issues/195)). Exported from the package's public surface so TypeScript consumers can annotate `collectStates` results without re-deriving the shape.
32
+
33
+ ### Changed
34
+
35
+ - **Graph serialization extracted to `utilities/stateGraph.ts`** ([#180](https://github.com/mellonis/turing-machine-js/issues/180)). `State.toGraph` and `State.fromGraph` move out of the State class into a sibling module, alongside the new `collectStates`. Public surface preserved — `State.toGraph` / `State.fromGraph` remain as thin static delegates. State.ts shrank ~440 lines and now focuses on the runtime machinery (transitions, debug, halt-stack composition) rather than mixing in serialization concerns. A new `@internal` `STATE_INTERNAL` Symbol-keyed accessor on `State` gives sibling modules in `packages/machine/src` getter/setter access to private fields (`id`, `name`, `bareState`, `overriddenHaltState`, `symbolToDataMap`, `tags`); not re-exported from the public `index.ts`, so external consumers can't observe it.
36
+
37
+ ### Fixed
38
+
39
+ - **`toMermaid` edge labels containing literal `"` now parse correctly** ([#194](https://github.com/mellonis/turing-machine-js/issues/194)). Before: an alphabet that includes printable ASCII as literal symbols (e.g. a Brainfuck-flavored UTM whose data alphabet covers U+0020–U+007E) would emit an edge label like `s1 -- "['a'] → ['"']/[R]" --> s0`; Mermaid's parser terminated the string early on the inner `"`. After: user-supplied content (alphabet symbols, state names, tag names, frame bare names) is HTML-entity-escaped at the leaf — `&`, `"`, `<`, `>` to named entities; statement terminators (`\n`, `\r`), C0 controls minus `\t`, DEL, bidi controls, and lone UTF-16 surrogates to numeric entities. Printable Unicode (Cyrillic, CJK, accented Latin, etc.) passes through unchanged so non-ASCII alphabets stay readable in the emitted `.mmd`. `fromMermaid` mirrors with a single-pass entity decoder applied at the leaf, after structural parsing.
40
+
41
+ - **`runStepByStep` halt-stack is now run-scoped, not machine-scoped** ([#196](https://github.com/mellonis/turing-machine-js/issues/196)). Before: the `#stack` field on `TuringMachine` was an instance field; a build-time peek that didn't drain the generator (e.g. graph-construction utilities that ask for one yield to inspect the initial state) left leftover entries in the stack that were popped during the NEXT halt-bound transition, producing a "ghost iteration" and silently leaking memory across consecutive `runStepByStep` calls on the same machine. After: the halt stack is a local `const stack: State[] = []` declared inside `runStepByStep`, so each generator call starts with a clean stack and entries can't survive into the next call.
42
+
43
+ ### Compatibility
44
+
45
+ - Peer dep `@turing-machine-js/machine` widened `^7.0.0-alpha.3` → `^7.0.0-alpha.4` on `@turing-machine-js/builder`, `@turing-machine-js/library-binary-numbers`, `@turing-machine-js/library-binary-numbers-bare`.
46
+
47
+ ### Migration from alpha.3
48
+
49
+ Purely additive — no breaking changes. Existing code that doesn't call `State.collectStates` continues to work identically. `State.toGraph` / `State.fromGraph` behave identically (the delegate-to-stateGraph wiring is internal); no consumer changes needed.
50
+
51
+ The `STATE_INTERNAL` accessor is `@internal` and not part of the supported surface; ignore it unless you're authoring a sibling module inside `packages/machine/src`.
52
+
53
+ ### Out of v7-alpha.4 (still pending for stable v7.0.0)
54
+
55
+ - **[#102](https://github.com/mellonis/turing-machine-js/issues/102)** — debugger step-in / step-out / step-over primitives.
56
+
7
57
  ## [7.0.0-alpha.3] - 2026-05-21
8
58
 
9
59
  Third v7 pre-release. Adds first-class out-of-band tags on `State` ([#186](https://github.com/mellonis/turing-machine-js/issues/186)) — a metadata channel for visualization grouping and debugger labels that survives `toGraph` / `fromGraph` / `toMermaid` / `fromMermaid` round-trips. Driven by downstream [post-machine-js#86](https://github.com/mellonis/post-machine-js/issues/86), which will build a path-based registry and inline pseudo-command on top once this ships. Published under the `next` dist-tag: `npm install @turing-machine-js/machine@next`.
package/README.md CHANGED
@@ -242,6 +242,7 @@ Notable members and statics:
242
242
  - **`state.withOverriddenHaltState(other)`** — returns a copy whose would-be halt transitions fall through to `other`. The subroutine-call composition mechanism (see `library-binary-numbers/src/index.ts` for examples).
243
243
  - **`State.toGraph(state, tapeBlock)`** — walks the reachable graph from `state` and returns a serializable `Graph` (states, transitions, alphabets).
244
244
  - **`State.fromGraph(graph)`** — inverse of `toGraph`: rebuilds `State` instances + a fresh `TapeBlock` from a `Graph`. Round-trips together with `toMermaid` / `fromMermaid`.
245
+ - **`State.collectStates(state, tapeBlock)`** — walks the same graph and returns a `Map<number, {state, transitionSymbols}>` keyed by `GraphNode.id`. Use when downstream tooling holds a numeric id (e.g. a clicked node in a rendered graph) and needs the live `State` instance or the per-pattern `Symbol` for breakpoint setup. See [Setting breakpoints by graph id](#setting-breakpoints-by-graph-id).
245
246
 
246
247
  For visualization, pair `State.toGraph` with `toMermaid` to render the graph in any Mermaid-aware viewer (GitHub, VS Code, mermaid.live):
247
248
 
@@ -499,6 +500,36 @@ If `onPause` is not provided, breaks fire-and-resume invisibly — the trajector
499
500
 
500
501
  **Caveat:** `haltState` is a module-level singleton. Setting `haltState.debug` affects every machine in the process; clear in `afterEach` / `finally` for test isolation.
501
502
 
503
+ ### Setting breakpoints by graph id
504
+
505
+ Downstream UIs (graph renderers, debugger panels) often have only a numeric `GraphNode.id` — the user clicked a state node, or a transition edge in a rendered SVG. `State.collectStates(initial, tapeBlock)` returns a `Map` keyed by that numeric id, with the live `State` instance and the per-pattern `Symbol` array as its value:
506
+
507
+ ```ts
508
+ import { State, ifOtherSymbol } from '@turing-machine-js/machine';
509
+
510
+ const stateMap = State.collectStates(initial, tapeBlock);
511
+
512
+ // Toggle a state-level breakpoint by id (any pattern triggers).
513
+ const entry = stateMap.get(clickedStateId);
514
+ if (entry) {
515
+ entry.state.debug.before = true;
516
+ }
517
+
518
+ // Per-pattern breakpoint by GraphTransition.id — the contract is
519
+ // positional: `transitionSymbols[K]` is the Symbol that the
520
+ // `${stateId}-${K}` GraphTransition fires on.
521
+ const [n, k] = clickedEdgeId.split('-').map(Number);
522
+ const e = stateMap.get(n);
523
+ const sym = e?.transitionSymbols[k];
524
+ if (e && sym) {
525
+ e.state.debug.before = [sym];
526
+ }
527
+ ```
528
+
529
+ **Coverage rules:** regular / bare states get the full `[...#symbolToDataMap.keys()]` including `ifOtherSymbol` at its natural slot; wrappers and the halt singleton get empty `transitionSymbols`; synthetic halt markers (Graph nodes with `id = -frameId`, one per callable-subtree frame) are excluded from the map. See `State.collectStates` JSDoc for the full contract.
530
+
531
+ > ⚠️ `stateMap.get(0)!.state === haltState` — the entry at id `0` is the process-wide halt singleton. Toggling its `debug` affects every machine in the runtime, same caveat as direct `haltState.debug` writes.
532
+
502
533
  ### Throttle pattern
503
534
 
504
535
  For per-iter throttle / animation / "wait between steps" UIs, use the **`onIter`** hook — an awaited callback that fires once at the end of every iter, after both `onPause` dispatches on the same yield. It's the engine-native shape for per-iter coordination:
@@ -683,7 +714,9 @@ API surface changes since v3, in past tense so the timing of each piece is expli
683
714
  - **v6.2** *(superseded by v6.3.0)* — widened `onStep`'s signature to `(m) => void | Promise<void>` and added an inline `await onStep(...)` in the run loop, enabling throttle-in-`onStep` patterns. This overturned the docstring-stated contract that `onStep` is sync (microtask-free); the right place for per-iter throttling is `onPause` with self-rearm (see [Throttle pattern](#throttle-pattern)). Restored in v6.3.0.
684
715
  - **v6.3** — `onStep` reverted to its v6.0–v6.1 sync contract — `(m) => void`, called synchronously inside the run loop. The Throttle pattern section documents the engine-native shape for per-iter throttle / "wait between iters" UIs. No other API changes.
685
716
  - **v6.4** — New **`onIter`** hook on `run()`: awaited, fires once at the end of every iter (after both `onPause` dispatches on the same yield), unaffected by the `debug` master switch. Use for per-iter throttle / animation / coordination needing a suspend point; complements the existing sync `onStep` (tracing) and conditional `onPause` (user breakpoints). Three-hook contract is now `onStep` (sync, mid-iter) / `onPause` (awaited, on `state.debug` match) / `onIter` (awaited, end-of-iter). Additive — peer-deps unchanged. The v6.3.0 README's `onPause`-rearm throttle workaround is superseded.
686
- - **v7** *(latest alpha: alpha.3, 2026-05-21)* — Composition-representation overhaul + first-class state tags. **Pre-release on the `next` dist-tag:** `npm install @turing-machine-js/machine@next` (or pin `@7.0.0-alpha.3`). Stable v7.0.0 still pending [#102](https://github.com/mellonis/turing-machine-js/issues/102) (debugger step-in/over/out primitives). Highlights across alphas:
717
+ - **v7** *(latest alpha: alpha.4, 2026-05-23)* — Composition-representation overhaul + first-class state tags + id-keyed `State.collectStates` lookup. **Pre-release on the `next` dist-tag:** `npm install @turing-machine-js/machine@next` (or pin `@7.0.0-alpha.4`). Stable v7.0.0 still pending [#102](https://github.com/mellonis/turing-machine-js/issues/102) (debugger step-in/over/out primitives). Highlights across alphas:
718
+
719
+ **alpha.4** — **`State.collectStates(initial, tapeBlock)`** ([#195](https://github.com/mellonis/turing-machine-js/issues/195)) returns a `Map<number, {state, transitionSymbols}>` keyed by `GraphNode.id` so downstream tooling can mutate `state.debug` by numeric id and set per-pattern breakpoints by `GraphTransition.id`. Graph serialization extracted to `utilities/stateGraph.ts` with a Symbol-keyed `@internal` accessor on `State` ([#180](https://github.com/mellonis/turing-machine-js/issues/180); no public-API change — the `State.toGraph` / `.fromGraph` statics remain as thin delegates). Two upstream fixes: `toMermaid` HTML-entity-escapes user content in labels so alphabets containing `"`, `<`, etc. parse correctly ([#194](https://github.com/mellonis/turing-machine-js/issues/194)); `runStepByStep`'s halt stack is now run-scoped, fixing a memory leak / ghost-iteration when the same `TuringMachine` instance is reused across calls ([#196](https://github.com/mellonis/turing-machine-js/issues/196)). See [§Setting breakpoints by graph id](#setting-breakpoints-by-graph-id).
687
720
 
688
721
  **alpha.3** — first-class **State tags** ([#186](https://github.com/mellonis/turing-machine-js/issues/186)). `state.tag(...) / .untag(...) / .tags` API; `GraphNode.tags: string[]` round-trips through `toGraph`/`fromGraph`; `toMermaid` emits tags two ways simultaneously — inline via `<br>` in node labels (`sN["name<br>tag1, tag2"]`) and as `classDef`/`class` for color grouping. Tags live on the State instance (not on the shared `#symbolToDataMap`), so engine [#175](https://github.com/mellonis/turing-machine-js/issues/175) memoization doesn't leak tags across wrappers sharing a bare. See [§State tags](#state-tags).
689
722
 
@@ -3,8 +3,31 @@ import Reference from './Reference';
3
3
  import TapeBlock from './TapeBlock';
4
4
  import TapeCommand from './TapeCommand';
5
5
  import { type Graph } from '../utilities/graph';
6
+ import { type StateMap } from '../utilities/stateGraph';
6
7
  export declare const ifOtherSymbol: unique symbol;
7
8
  declare const validateDebugFilter: unique symbol;
9
+ /**
10
+ * @internal
11
+ *
12
+ * Package-private accessor key for sibling modules in
13
+ * `packages/machine/src` (e.g. `utilities/stateGraph.ts`, and the planned
14
+ * `utilities/stateCollect.ts` for #195). Re-exported from this module so
15
+ * sibling files can import it; intentionally NOT re-exported from the
16
+ * package's public `index.ts`, so downstream consumers don't see it on
17
+ * the supported surface.
18
+ *
19
+ * Calling `state[STATE_INTERNAL]()` returns a getter/setter view onto the
20
+ * State's private fields. Reads are live (they close over `this`), so the
21
+ * view stays in sync with subsequent mutations on the State. There's one
22
+ * mutating setter on the view — `name` — used exclusively by
23
+ * `fromGraph` to assign graph-sourced composite names (e.g. `A(target)`)
24
+ * that the public name validator would reject; see the JSDoc on the
25
+ * accessor itself.
26
+ *
27
+ * Designed in #180 with #195 in mind so its surface doesn't need to grow
28
+ * when `collectStates` lands.
29
+ */
30
+ export declare const STATE_INTERNAL: unique symbol;
8
31
  export declare class DebugConfig {
9
32
  #private;
10
33
  constructor(ownerState: State, initial?: {
@@ -56,6 +79,41 @@ export default class State {
56
79
  getCommand(symbol: symbol): Command;
57
80
  getNextState(symbol: symbol): State | Reference;
58
81
  withOverriddenHaltState(overriddenHaltState: State): State;
82
+ /**
83
+ * @internal
84
+ *
85
+ * Package-private getter/setter view onto this State's private fields,
86
+ * for sibling modules in `packages/machine/src` (currently `stateGraph.ts`
87
+ * for `toGraph` / `fromGraph`, and the planned `stateCollect.ts` for
88
+ * #195's `collectStates`).
89
+ *
90
+ * Read access is live — the getters close over `this`, so the view
91
+ * stays in sync with subsequent mutations on this State. There's a
92
+ * single mutating setter on the view, `name`, which exists to let
93
+ * `fromGraph` assign graph-sourced composite names (e.g. `A(target)`)
94
+ * to freshly-constructed bare States. The constructor's name validator
95
+ * rejects parens (reserved as wrapper-composition delimiters in
96
+ * `withOverriddenHaltState`); the setter intentionally bypasses that
97
+ * check because the same delimiters appear in legitimate wrapper-bare
98
+ * names round-tripped through the graph.
99
+ *
100
+ * Returns a fresh view object on every call — cheap enough for the
101
+ * BFS-once-per-build callers, and avoids holding a reference object on
102
+ * every State instance. Keep this surface tight: callers should only
103
+ * read what they need. Adding fields here is a deliberate decision —
104
+ * each adds to the implicit contract sibling modules can rely on.
105
+ */
106
+ [STATE_INTERNAL](): {
107
+ readonly id: number;
108
+ name: string;
109
+ readonly bareState: State | null;
110
+ readonly overriddenHaltState: State | null;
111
+ readonly symbolToDataMap: Map<symbol, {
112
+ command: Command;
113
+ nextState: State | Reference;
114
+ }>;
115
+ readonly tags: ReadonlySet<string>;
116
+ };
59
117
  static inspect(state: State): {
60
118
  id: number;
61
119
  name: string;
@@ -76,12 +134,35 @@ export default class State {
76
134
  } | null;
77
135
  }>;
78
136
  };
137
+ /**
138
+ * Walks the reachable State graph from `initialState` and returns a
139
+ * serializable `Graph`. Thin delegate to `utilities/stateGraph.ts`'s
140
+ * `toGraph` (extracted in #180); see that module for the BFS shape and
141
+ * v7 callable-subtree emit semantics.
142
+ */
79
143
  static toGraph(initialState: State, tapeBlock: TapeBlock): Graph;
144
+ /**
145
+ * Inverse of `toGraph`: rebuilds a State graph and a fresh TapeBlock
146
+ * from a serialized `Graph`. Thin delegate to `utilities/stateGraph.ts`'s
147
+ * `fromGraph` (extracted in #180); see that module for the
148
+ * reconstruction pass shape (Reference pre-create, bare build, wrapper
149
+ * resolution via `withOverriddenHaltState`, ref binding).
150
+ */
80
151
  static fromGraph(graph: Graph): {
81
152
  start: State;
82
153
  tapeBlock: TapeBlock;
83
154
  states: Record<number, State>;
84
155
  };
156
+ /**
157
+ * Returns a `Map<number, {state, transitionSymbols}>` keyed by engine
158
+ * `GraphNode.id`, exposing the live `State` instance + per-pattern
159
+ * Symbol references for each node so downstream tooling can mutate
160
+ * `state.debug` by numeric id and set per-pattern breakpoints by
161
+ * `GraphTransition.id` (#195). Thin delegate to
162
+ * `utilities/stateGraph.ts`'s `collectStates`; see that module for
163
+ * the alignment contract, coverage rules, and halt-singleton warning.
164
+ */
165
+ static collectStates(initialState: State, tapeBlock: TapeBlock): StateMap;
85
166
  }
86
167
  export declare const haltState: State;
87
168
  export {};