@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 +50 -0
- package/README.md +34 -1
- package/dist/classes/State.d.ts +81 -0
- package/dist/index.cjs +845 -514
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +845 -514
- package/dist/utilities/stateGraph.d.ts +91 -0
- package/package.json +2 -2
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.
|
|
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
|
|
package/dist/classes/State.d.ts
CHANGED
|
@@ -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 {};
|