@turing-machine-js/machine 3.0.0 → 3.0.1

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,21 @@ 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
+ ## [3.0.1] - 2026-04-30
8
+
9
+ ### Fixed
10
+
11
+ - **`graphFormats.ts` — polynomial-time regex (CodeQL `js/polynomial-redos`).** `alphabetsRegex` was `/^%%\s*alphabets:\s*(.+)$/`, where the `\s*` and `(.+)` both match whitespace, letting the engine try N+1 split points on inputs like `"%%alphabets:"` followed by many trailing spaces. Anchored the first captured char as `\S` to remove the ambiguity.
12
+
13
+ ### Added
14
+
15
+ - **`MachineState` re-export** from `index.ts`. The type was always `export type MachineState = ...` in `classes/TuringMachine.ts`, but the package barrel didn't surface it. Consumers (notably `@post-machine-js/machine`'s `PostMachine` overrides of `run` / `runStepByStep`) can now `import { type MachineState } from '@turing-machine-js/machine'` directly, dropping any local `Generator<infer T>` workaround.
16
+
17
+ ### Changed (internal)
18
+
19
+ - Removed the unreachable `if (hasCycles) return` guard at the top of `summarizeGraph`'s `visit()` cycle-detection. The recursive call pattern (outer for-loop checks before calling, inner loop checks after each recursive call) ensures `visit()` is never invoked when `hasCycles` is already true. Static analysis confirmed the guard was dead code.
20
+ - Tightened test coverage on `State.ts` and the v3 utilities — overall coverage rose from 95.64% / 88.39% / 95.5% (statements/branches/lines) to 98.39% / 94.01% / 98.34%. New tests cover invalid-input paths in the `State` constructor (string-keyed definitions, non-`State`/`Reference` `nextState`, empty-array commands), the `getSymbol` fallback to `ifOtherSymbol`, the `toGraph` skip of unbound `Reference` transitions, the `fromGraph` cyclic-override-halt error, and the previously-unexercised branches in `splitUnescaped`, `parsePatternString`, `parseMovementLabel`, and `fromMermaid`'s ensureNode update / error paths.
21
+
7
22
  ## [3.0.0] - 2026-04-30
8
23
 
9
24
  ### Added
package/dist/index.cjs CHANGED
@@ -1048,7 +1048,9 @@ const initialNodeRegex = /^s(\d+)\(\("([^"]*)"\)\)$/;
1048
1048
  const regularNodeRegex = /^s(\d+)\["([^"]*)"\]$/;
1049
1049
  const transitionRegex = /^s(\d+)\s+--\s+"(.*)"\s+-->\s+s(\d+)$/;
1050
1050
  const onHaltRegex = /^s(\d+)\s+-\.\s+onHalt\s+\.->\s+s(\d+)$/;
1051
- const alphabetsRegex = /^%%\s*alphabets:\s*(.+)$/;
1051
+ // First capture char anchored as \S to avoid polynomial backtracking between
1052
+ // the preceding \s* and a permissive (.+); see CodeQL js/polynomial-redos.
1053
+ const alphabetsRegex = /^%%\s*alphabets:\s*(\S.*)$/;
1052
1054
  function fromMermaid(text) {
1053
1055
  const lines = text.split('\n').map((l) => l.trim()).filter(Boolean);
1054
1056
  let alphabets = [];
@@ -1180,9 +1182,10 @@ function summarizeGraph(graph) {
1180
1182
  }
1181
1183
  let hasCycles = false;
1182
1184
  const visit = (id) => {
1183
- if (hasCycles) {
1184
- return;
1185
- }
1185
+ // No `if (hasCycles) return` guard at function entry: the recursive call
1186
+ // pattern (outer for-loop checks before calling, inner loop checks after
1187
+ // each recursive call) ensures visit() is never invoked when hasCycles
1188
+ // is already true. Static analysis confirmed the guard was unreachable.
1186
1189
  if (color.get(id) === GREY) {
1187
1190
  hasCycles = true;
1188
1191
  return;
@@ -1279,12 +1282,16 @@ function runOnce(runnable, input, stepsLimit) {
1279
1282
  const machine = new TuringMachine({ tapeBlock });
1280
1283
  const snapshots = [];
1281
1284
  let stepCount = 0;
1282
- // Inside the for-of body, the tape reflects the state BEFORE the current
1283
- // step's command (i.e. AFTER the previous step's command or initial for
1284
- // step 1). After the loop, the tape has had every command applied.
1285
- for (const _ of machine.runStepByStep({ initialState: runnable.state, stepsLimit })) {
1285
+ // Iterate the generator manually (the yielded MachineState isn't needed
1286
+ // we only care about the side effects on the tape). At each yield the tape
1287
+ // reflects the state BEFORE the current step's command; after the loop
1288
+ // exits the tape has had every command applied.
1289
+ const generator = machine.runStepByStep({ initialState: runnable.state, stepsLimit });
1290
+ let result = generator.next();
1291
+ while (!result.done) {
1286
1292
  snapshots.push(tape.symbols.join(''));
1287
1293
  stepCount += 1;
1294
+ result = generator.next();
1288
1295
  }
1289
1296
  snapshots.push(tape.symbols.join(''));
1290
1297
  return {
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ export { default as State, haltState, ifOtherSymbol } from './classes/State';
5
5
  export { default as Tape } from './classes/Tape';
6
6
  export { default as TapeBlock } from './classes/TapeBlock';
7
7
  export { default as TapeCommand, movements, symbolCommands } from './classes/TapeCommand';
8
- export { default as TuringMachine } from './classes/TuringMachine';
8
+ export { default as TuringMachine, type MachineState } from './classes/TuringMachine';
9
9
  export { type Graph, type GraphNode, type GraphTransition, type GraphCommand } from './utilities/graph';
10
10
  export { toMermaid, fromMermaid } from './utilities/graphFormats';
11
11
  export { summarize, summarizeGraph, type GraphSummary } from './utilities/introspection';
package/dist/index.mjs CHANGED
@@ -1046,7 +1046,9 @@ const initialNodeRegex = /^s(\d+)\(\("([^"]*)"\)\)$/;
1046
1046
  const regularNodeRegex = /^s(\d+)\["([^"]*)"\]$/;
1047
1047
  const transitionRegex = /^s(\d+)\s+--\s+"(.*)"\s+-->\s+s(\d+)$/;
1048
1048
  const onHaltRegex = /^s(\d+)\s+-\.\s+onHalt\s+\.->\s+s(\d+)$/;
1049
- const alphabetsRegex = /^%%\s*alphabets:\s*(.+)$/;
1049
+ // First capture char anchored as \S to avoid polynomial backtracking between
1050
+ // the preceding \s* and a permissive (.+); see CodeQL js/polynomial-redos.
1051
+ const alphabetsRegex = /^%%\s*alphabets:\s*(\S.*)$/;
1050
1052
  function fromMermaid(text) {
1051
1053
  const lines = text.split('\n').map((l) => l.trim()).filter(Boolean);
1052
1054
  let alphabets = [];
@@ -1178,9 +1180,10 @@ function summarizeGraph(graph) {
1178
1180
  }
1179
1181
  let hasCycles = false;
1180
1182
  const visit = (id) => {
1181
- if (hasCycles) {
1182
- return;
1183
- }
1183
+ // No `if (hasCycles) return` guard at function entry: the recursive call
1184
+ // pattern (outer for-loop checks before calling, inner loop checks after
1185
+ // each recursive call) ensures visit() is never invoked when hasCycles
1186
+ // is already true. Static analysis confirmed the guard was unreachable.
1184
1187
  if (color.get(id) === GREY) {
1185
1188
  hasCycles = true;
1186
1189
  return;
@@ -1277,12 +1280,16 @@ function runOnce(runnable, input, stepsLimit) {
1277
1280
  const machine = new TuringMachine({ tapeBlock });
1278
1281
  const snapshots = [];
1279
1282
  let stepCount = 0;
1280
- // Inside the for-of body, the tape reflects the state BEFORE the current
1281
- // step's command (i.e. AFTER the previous step's command or initial for
1282
- // step 1). After the loop, the tape has had every command applied.
1283
- for (const _ of machine.runStepByStep({ initialState: runnable.state, stepsLimit })) {
1283
+ // Iterate the generator manually (the yielded MachineState isn't needed
1284
+ // we only care about the side effects on the tape). At each yield the tape
1285
+ // reflects the state BEFORE the current step's command; after the loop
1286
+ // exits the tape has had every command applied.
1287
+ const generator = machine.runStepByStep({ initialState: runnable.state, stepsLimit });
1288
+ let result = generator.next();
1289
+ while (!result.done) {
1284
1290
  snapshots.push(tape.symbols.join(''));
1285
1291
  stepCount += 1;
1292
+ result = generator.next();
1286
1293
  }
1287
1294
  snapshots.push(tape.symbols.join(''));
1288
1295
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@turing-machine-js/machine",
3
- "version": "3.0.0",
3
+ "version": "3.0.1",
4
4
  "description": "A convenient Turing machine",
5
5
  "engines": {
6
6
  "npm": ">=7.0.0"
@@ -34,5 +34,5 @@
34
34
  "default": "./dist/index.mjs"
35
35
  }
36
36
  },
37
- "gitHead": "98decf323129d528febafac1bec581a9ee3800ff"
37
+ "gitHead": "0a8d7ff87a3a73ea7ee1844f7f4602cbb23d20fe"
38
38
  }
@@ -1,50 +0,0 @@
1
- var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
- if (kind === "m") throw new TypeError("Private method is not writable");
3
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5
- return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
- };
7
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
- };
12
- var _Alphabet_symbols;
13
- import { uniquePredicate } from '../utilities/functions';
14
- class Alphabet {
15
- constructor(symbols) {
16
- _Alphabet_symbols.set(this, void 0);
17
- if (symbols instanceof Alphabet) {
18
- symbols = symbols.symbols;
19
- }
20
- const uniqueSymbols = symbols.filter(uniquePredicate);
21
- if (uniqueSymbols.length < 2) {
22
- throw new Error('Invalid symbols length');
23
- }
24
- const isSymbolsValid = uniqueSymbols.every((symbol) => symbol.length === 1);
25
- if (!isSymbolsValid) {
26
- throw new Error('symbols contains invalid symbol');
27
- }
28
- __classPrivateFieldSet(this, _Alphabet_symbols, Array.from(uniqueSymbols), "f");
29
- }
30
- get symbols() {
31
- return Array.from(__classPrivateFieldGet(this, _Alphabet_symbols, "f"));
32
- }
33
- get blankSymbol() {
34
- return __classPrivateFieldGet(this, _Alphabet_symbols, "f")[0];
35
- }
36
- has(symbol) {
37
- return __classPrivateFieldGet(this, _Alphabet_symbols, "f").indexOf(symbol) >= 0;
38
- }
39
- get(index) {
40
- if (index < 0 || index >= __classPrivateFieldGet(this, _Alphabet_symbols, "f").length) {
41
- throw new Error('Invalid index');
42
- }
43
- return __classPrivateFieldGet(this, _Alphabet_symbols, "f")[index];
44
- }
45
- index(symbol) {
46
- return __classPrivateFieldGet(this, _Alphabet_symbols, "f").indexOf(symbol);
47
- }
48
- }
49
- _Alphabet_symbols = new WeakMap();
50
- export default Alphabet;
@@ -1,38 +0,0 @@
1
- var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
2
- if (kind === "m") throw new TypeError("Private method is not writable");
3
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
4
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
5
- return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
6
- };
7
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
8
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
9
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
10
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
11
- };
12
- var _Command_tapesCommands;
13
- import TapeCommand from './TapeCommand';
14
- class Command {
15
- constructor(tapesCommands) {
16
- _Command_tapesCommands.set(this, void 0);
17
- if (tapesCommands.length === 0) {
18
- throw new Error('invalid parameter');
19
- }
20
- try {
21
- __classPrivateFieldSet(this, _Command_tapesCommands, tapesCommands.map((tapeCommand) => {
22
- if (tapeCommand instanceof TapeCommand) {
23
- return tapeCommand;
24
- }
25
- return new TapeCommand(tapeCommand);
26
- }), "f");
27
- }
28
- catch (error) {
29
- void error;
30
- throw new Error('invalid tapeCommand');
31
- }
32
- }
33
- get tapesCommands() {
34
- return [...__classPrivateFieldGet(this, _Command_tapesCommands, "f")];
35
- }
36
- }
37
- _Command_tapesCommands = new WeakMap();
38
- export default Command;
@@ -1,34 +0,0 @@
1
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
2
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
3
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
4
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5
- };
6
- var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
7
- if (kind === "m") throw new TypeError("Private method is not writable");
8
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
9
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
10
- return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
11
- };
12
- var _Lock_lockSymbol;
13
- class Lock {
14
- constructor() {
15
- _Lock_lockSymbol.set(this, null);
16
- }
17
- lock(symbol) {
18
- if (__classPrivateFieldGet(this, _Lock_lockSymbol, "f") === null) {
19
- __classPrivateFieldSet(this, _Lock_lockSymbol, symbol, "f");
20
- }
21
- }
22
- unlock(symbol) {
23
- if (__classPrivateFieldGet(this, _Lock_lockSymbol, "f") === symbol) {
24
- __classPrivateFieldSet(this, _Lock_lockSymbol, null, "f");
25
- }
26
- }
27
- check(symbol) {
28
- if (__classPrivateFieldGet(this, _Lock_lockSymbol, "f") && __classPrivateFieldGet(this, _Lock_lockSymbol, "f") !== symbol) {
29
- throw new Error('Lock check failed');
30
- }
31
- }
32
- }
33
- _Lock_lockSymbol = new WeakMap();
34
- export default Lock;
@@ -1,31 +0,0 @@
1
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
2
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
3
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
4
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5
- };
6
- var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
7
- if (kind === "m") throw new TypeError("Private method is not writable");
8
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
9
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
10
- return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
11
- };
12
- var _Reference_referenceBinding;
13
- class Reference {
14
- constructor() {
15
- _Reference_referenceBinding.set(this, null);
16
- }
17
- get ref() {
18
- if (!__classPrivateFieldGet(this, _Reference_referenceBinding, "f")) {
19
- throw new Error('unbounded reference');
20
- }
21
- return __classPrivateFieldGet(this, _Reference_referenceBinding, "f");
22
- }
23
- bind(state) {
24
- if (__classPrivateFieldGet(this, _Reference_referenceBinding, "f") == null) {
25
- __classPrivateFieldSet(this, _Reference_referenceBinding, state, "f");
26
- }
27
- return __classPrivateFieldGet(this, _Reference_referenceBinding, "f");
28
- }
29
- }
30
- _Reference_referenceBinding = new WeakMap();
31
- export default Reference;
@@ -1,283 +0,0 @@
1
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
2
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
3
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
4
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5
- };
6
- var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
7
- if (kind === "m") throw new TypeError("Private method is not writable");
8
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
9
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
10
- return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
11
- };
12
- var _State_id, _State_name, _State_overrodeHaltState, _State_symbolToDataMap;
13
- import Alphabet from './Alphabet';
14
- import Command from './Command';
15
- import Reference from './Reference';
16
- import TapeBlock from './TapeBlock';
17
- import TapeCommand from './TapeCommand';
18
- import { id } from '../utilities/functions';
19
- import { decodeMovement, decodePatternDescription, decodeWriteSymbol, parseMovementLabel, parsePatternString, parseWriteSymbolLabel, } from '../utilities/graph';
20
- export const ifOtherSymbol = Symbol('other symbol');
21
- class State {
22
- constructor(stateDefinition = null, name) {
23
- _State_id.set(this, id(this));
24
- _State_name.set(this, void 0);
25
- _State_overrodeHaltState.set(this, null);
26
- _State_symbolToDataMap.set(this, new Map());
27
- if (stateDefinition) {
28
- const keys = Object.getOwnPropertyNames(stateDefinition);
29
- if (keys.length) {
30
- throw new Error(`invalid state definition while constructing state #${__classPrivateFieldGet(this, _State_id, "f")}`);
31
- }
32
- const symbols = Object.getOwnPropertySymbols(stateDefinition);
33
- if (symbols.length === 0) {
34
- throw new Error(`invalid state definition while constructing state #${__classPrivateFieldGet(this, _State_id, "f")}`);
35
- }
36
- symbols.forEach((symbol) => {
37
- const { nextState } = stateDefinition[symbol];
38
- const nextStateLocal = nextState ?? this;
39
- if (!(nextStateLocal instanceof State) && !(nextStateLocal instanceof Reference)) {
40
- throw new Error('invalid nextState');
41
- }
42
- let { command } = stateDefinition[symbol];
43
- if (command == null) {
44
- command = new Command([
45
- new TapeCommand({}),
46
- ]);
47
- }
48
- if (!(command instanceof Command) && !Array.isArray(command)) {
49
- command = [command];
50
- }
51
- let commandLocal = command;
52
- if (Array.isArray(command)) {
53
- try {
54
- commandLocal = new Command(command);
55
- }
56
- catch (error) {
57
- void error;
58
- }
59
- }
60
- if (!(commandLocal instanceof Command)) {
61
- throw new Error('invalid command');
62
- }
63
- __classPrivateFieldGet(this, _State_symbolToDataMap, "f").set(symbol, {
64
- command: commandLocal,
65
- nextState: nextStateLocal,
66
- });
67
- });
68
- }
69
- __classPrivateFieldSet(this, _State_name, name ?? `id:${__classPrivateFieldGet(this, _State_id, "f")}`, "f");
70
- }
71
- get id() {
72
- return __classPrivateFieldGet(this, _State_id, "f");
73
- }
74
- get name() {
75
- return __classPrivateFieldGet(this, _State_name, "f");
76
- }
77
- get isHalt() {
78
- return __classPrivateFieldGet(this, _State_id, "f") === 0;
79
- }
80
- get overrodeHaltState() {
81
- return __classPrivateFieldGet(this, _State_overrodeHaltState, "f");
82
- }
83
- get ref() {
84
- return this;
85
- }
86
- getSymbol(tapeBlock) {
87
- const symbol = [...__classPrivateFieldGet(this, _State_symbolToDataMap, "f").keys()].find((currentSymbol) => tapeBlock.isMatched({
88
- symbol: currentSymbol,
89
- }));
90
- if (symbol) {
91
- return symbol;
92
- }
93
- return ifOtherSymbol;
94
- }
95
- getCommand(symbol) {
96
- if (__classPrivateFieldGet(this, _State_symbolToDataMap, "f").has(symbol)) {
97
- return __classPrivateFieldGet(this, _State_symbolToDataMap, "f").get(symbol).command;
98
- }
99
- throw new Error(`No command for symbol at state named ${__classPrivateFieldGet(this, _State_name, "f")}`);
100
- }
101
- getNextState(symbol) {
102
- if (__classPrivateFieldGet(this, _State_symbolToDataMap, "f").has(symbol)) {
103
- return __classPrivateFieldGet(this, _State_symbolToDataMap, "f").get(symbol).nextState;
104
- }
105
- throw new Error(`No nextState for symbol at state named ${__classPrivateFieldGet(this, _State_id, "f")}`);
106
- }
107
- withOverrodeHaltState(overrodeHaltState) {
108
- const state = new State(null, `${this.name}>${overrodeHaltState.name}`);
109
- __classPrivateFieldSet(state, _State_symbolToDataMap, __classPrivateFieldGet(this, _State_symbolToDataMap, "f"), "f");
110
- __classPrivateFieldSet(state, _State_overrodeHaltState, overrodeHaltState, "f");
111
- return state;
112
- }
113
- // Single-state introspection — no traversal, no tapeBlock required.
114
- // Returns id, name, halt-status, override-halt target, and the list of
115
- // transitions out of this state with decoded write/movement labels.
116
- // Symbol patterns are returned as the raw description string from the
117
- // interned JS Symbol (decode via decodePatternDescription if needed).
118
- static inspect(state) {
119
- const transitions = [];
120
- for (const [sym, { command, nextState }] of __classPrivateFieldGet(state, _State_symbolToDataMap, "f")) {
121
- let target = null;
122
- try {
123
- target = nextState instanceof State ? nextState : nextState.ref;
124
- }
125
- catch {
126
- target = null; // unbound Reference
127
- }
128
- transitions.push({
129
- rawPatternDescription: sym.description,
130
- command: command.tapesCommands.map((tc) => ({
131
- symbol: decodeWriteSymbol(tc.symbol),
132
- movement: decodeMovement(tc.movement.description),
133
- })),
134
- nextState: target ? { id: target.id, name: target.name } : null,
135
- });
136
- }
137
- return {
138
- id: __classPrivateFieldGet(state, _State_id, "f"),
139
- name: __classPrivateFieldGet(state, _State_name, "f"),
140
- isHalt: state.isHalt,
141
- overrodeHaltState: __classPrivateFieldGet(state, _State_overrodeHaltState, "f")
142
- ? { id: __classPrivateFieldGet(state, _State_overrodeHaltState, "f").id, name: __classPrivateFieldGet(state, _State_overrodeHaltState, "f").name }
143
- : null,
144
- transitions,
145
- };
146
- }
147
- static toGraph(initialState, tapeBlock) {
148
- const nodes = {};
149
- const queue = [initialState];
150
- const alphabets = tapeBlock.alphabets.map((alphabet) => alphabet.symbols);
151
- while (queue.length > 0) {
152
- const current = queue.shift();
153
- if (__classPrivateFieldGet(current, _State_id, "f") in nodes) {
154
- continue;
155
- }
156
- const node = {
157
- id: __classPrivateFieldGet(current, _State_id, "f"),
158
- name: __classPrivateFieldGet(current, _State_name, "f"),
159
- isHalt: current.isHalt,
160
- transitions: [],
161
- overrodeHaltStateId: __classPrivateFieldGet(current, _State_overrodeHaltState, "f")?.id ?? null,
162
- };
163
- nodes[__classPrivateFieldGet(current, _State_id, "f")] = node;
164
- if (__classPrivateFieldGet(current, _State_overrodeHaltState, "f")) {
165
- queue.push(__classPrivateFieldGet(current, _State_overrodeHaltState, "f"));
166
- }
167
- for (const [sym, { command, nextState }] of __classPrivateFieldGet(current, _State_symbolToDataMap, "f")) {
168
- let target;
169
- try {
170
- target = nextState instanceof State ? nextState : nextState.ref;
171
- }
172
- catch {
173
- continue;
174
- }
175
- node.transitions.push({
176
- pattern: decodePatternDescription(sym.description, alphabets),
177
- command: command.tapesCommands.map((tc) => ({
178
- symbol: decodeWriteSymbol(tc.symbol),
179
- movement: decodeMovement(tc.movement.description),
180
- })),
181
- nextStateId: target.id,
182
- });
183
- queue.push(target);
184
- }
185
- }
186
- return { initialId: __classPrivateFieldGet(initialState, _State_id, "f"), alphabets, nodes };
187
- }
188
- // Inverse of toGraph: rebuilds a State graph (and a fresh TapeBlock with the
189
- // graph's alphabets) from a serialized Graph. Round-trips with toGraph in
190
- // the sense that running the rebuilt machine on the same input gives the
191
- // same output, but the rebuilt State instances have *new* internal IDs.
192
- static fromGraph(graph) {
193
- const alphabetObjs = graph.alphabets.map((syms) => new Alphabet(syms));
194
- const tapeBlock = TapeBlock.fromAlphabets(alphabetObjs);
195
- const ids = Object.keys(graph.nodes).map(Number);
196
- // Pass 1: pre-create a Reference for each non-halt node so transitions can
197
- // forward-declare their targets.
198
- const refs = {};
199
- for (const nodeId of ids) {
200
- if (!graph.nodes[nodeId].isHalt) {
201
- refs[nodeId] = new Reference();
202
- }
203
- }
204
- // Convert a parsed pattern back to the symbol key the State expects.
205
- const patternToKey = (parsed) => {
206
- if (parsed === null) {
207
- return ifOtherSymbol;
208
- }
209
- const flat = [];
210
- for (const row of parsed) {
211
- for (const cell of row) {
212
- flat.push(cell === null ? ifOtherSymbol : cell);
213
- }
214
- }
215
- return tapeBlock.symbol(flat);
216
- };
217
- // Pass 2: build a "bare" State for each non-halt node (no override yet).
218
- // nextState entries point at refs so cycles work; haltState is used directly.
219
- const bareStates = {};
220
- for (const nodeId of ids) {
221
- const node = graph.nodes[nodeId];
222
- if (node.isHalt) {
223
- continue;
224
- }
225
- const stateDefinition = {};
226
- for (const t of node.transitions) {
227
- const key = patternToKey(parsePatternString(t.pattern, graph.alphabets));
228
- const target = graph.nodes[t.nextStateId];
229
- const nextState = target.isHalt ? haltState : refs[t.nextStateId];
230
- stateDefinition[key] = {
231
- command: t.command.map((c) => ({
232
- symbol: parseWriteSymbolLabel(c.symbol),
233
- movement: parseMovementLabel(c.movement),
234
- })),
235
- nextState,
236
- };
237
- }
238
- bareStates[nodeId] = new State(stateDefinition, node.name);
239
- }
240
- // Pass 3: apply overrideHaltStates transitively.
241
- const finalStates = {};
242
- const inProgress = new Set();
243
- const getFinal = (nodeId) => {
244
- if (finalStates[nodeId]) {
245
- return finalStates[nodeId];
246
- }
247
- const node = graph.nodes[nodeId];
248
- if (node.isHalt) {
249
- finalStates[nodeId] = haltState;
250
- return haltState;
251
- }
252
- if (inProgress.has(nodeId)) {
253
- throw new Error(`override-halt cycle at state #${nodeId}`);
254
- }
255
- inProgress.add(nodeId);
256
- let state = bareStates[nodeId];
257
- if (node.overrodeHaltStateId !== null) {
258
- state = bareStates[nodeId].withOverrodeHaltState(getFinal(node.overrodeHaltStateId));
259
- }
260
- inProgress.delete(nodeId);
261
- finalStates[nodeId] = state;
262
- return state;
263
- };
264
- for (const nodeId of ids) {
265
- getFinal(nodeId);
266
- }
267
- // Pass 4: bind each ref to the FINAL (possibly wrapped) state so transitions
268
- // resolve to the version that has its override-halt set.
269
- for (const nodeId of ids) {
270
- if (!graph.nodes[nodeId].isHalt) {
271
- refs[nodeId].bind(finalStates[nodeId]);
272
- }
273
- }
274
- return {
275
- start: finalStates[graph.initialId],
276
- tapeBlock,
277
- states: finalStates,
278
- };
279
- }
280
- }
281
- _State_id = new WeakMap(), _State_name = new WeakMap(), _State_overrodeHaltState = new WeakMap(), _State_symbolToDataMap = new WeakMap();
282
- export default State;
283
- export const haltState = new State(null);