@turing-machine-js/machine 3.0.0 → 3.0.2

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,31 @@ 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.2] - 2026-05-04
8
+
9
+ ### Fixed
10
+
11
+ - **`Tape` constructor now normalises and validates `viewportWidth`** ([#95](https://github.com/mellonis/turing-machine-js/issues/95)). The constructor stored `viewportWidth` raw — the setter padded `#symbols` via `normalise()` and validated `>= 1` / bumped even values to odd, but the constructor did neither. As a result, `new Tape({ alphabet, symbols: ['a','b','a','b'], viewportWidth: 23 }).viewport.length` was `4` instead of `23`, and `viewportWidth: 0` or `viewportWidth: 4` silently produced an invalid tape. The constructor now defers to the setter, so a single source of truth handles validation + normalisation.
12
+
13
+ ### Changed (observable)
14
+
15
+ - A constructor call with an out-of-range `position` (e.g. `position: 5` with `symbols: ['x']`) now normalises at construction — the symbol array is padded with blanks so `position` lands inside the viewport. Previously the tape was left in a malformed state until the first head movement triggered `normalise()`. Strictly a fix, but observable if user code inspected `.symbols.length` immediately after construction.
16
+
17
+ ## [3.0.1] - 2026-04-30
18
+
19
+ ### Fixed
20
+
21
+ - **`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.
22
+
23
+ ### Added
24
+
25
+ - **`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.
26
+
27
+ ### Changed (internal)
28
+
29
+ - 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.
30
+ - 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.
31
+
7
32
  ## [3.0.0] - 2026-04-30
8
33
 
9
34
  ### Added
package/dist/index.cjs CHANGED
@@ -199,12 +199,13 @@ class Tape {
199
199
  }
200
200
  __classPrivateFieldSet$4(this, _Tape_alphabet, new Alphabet(alphabet), "f");
201
201
  __classPrivateFieldSet$4(this, _Tape_position, position, "f");
202
- __classPrivateFieldSet$4(this, _Tape_viewportWidth, viewportWidth, "f");
202
+ __classPrivateFieldSet$4(this, _Tape_viewportWidth, 1, "f");
203
203
  const symbolsCopy = Array.from(symbols);
204
204
  if (symbolsCopy.length === 0) {
205
205
  symbolsCopy.push(__classPrivateFieldGet$4(this, _Tape_alphabet, "f").blankSymbol);
206
206
  }
207
207
  __classPrivateFieldSet$4(this, _Tape_symbols, symbolsCopy.map((symbol) => __classPrivateFieldGet$4(this, _Tape_alphabet, "f").index(symbol)), "f");
208
+ this.viewportWidth = viewportWidth;
208
209
  }
209
210
  get alphabet() {
210
211
  return __classPrivateFieldGet$4(this, _Tape_alphabet, "f");
@@ -1048,7 +1049,9 @@ const initialNodeRegex = /^s(\d+)\(\("([^"]*)"\)\)$/;
1048
1049
  const regularNodeRegex = /^s(\d+)\["([^"]*)"\]$/;
1049
1050
  const transitionRegex = /^s(\d+)\s+--\s+"(.*)"\s+-->\s+s(\d+)$/;
1050
1051
  const onHaltRegex = /^s(\d+)\s+-\.\s+onHalt\s+\.->\s+s(\d+)$/;
1051
- const alphabetsRegex = /^%%\s*alphabets:\s*(.+)$/;
1052
+ // First capture char anchored as \S to avoid polynomial backtracking between
1053
+ // the preceding \s* and a permissive (.+); see CodeQL js/polynomial-redos.
1054
+ const alphabetsRegex = /^%%\s*alphabets:\s*(\S.*)$/;
1052
1055
  function fromMermaid(text) {
1053
1056
  const lines = text.split('\n').map((l) => l.trim()).filter(Boolean);
1054
1057
  let alphabets = [];
@@ -1180,9 +1183,10 @@ function summarizeGraph(graph) {
1180
1183
  }
1181
1184
  let hasCycles = false;
1182
1185
  const visit = (id) => {
1183
- if (hasCycles) {
1184
- return;
1185
- }
1186
+ // No `if (hasCycles) return` guard at function entry: the recursive call
1187
+ // pattern (outer for-loop checks before calling, inner loop checks after
1188
+ // each recursive call) ensures visit() is never invoked when hasCycles
1189
+ // is already true. Static analysis confirmed the guard was unreachable.
1186
1190
  if (color.get(id) === GREY) {
1187
1191
  hasCycles = true;
1188
1192
  return;
@@ -1279,12 +1283,16 @@ function runOnce(runnable, input, stepsLimit) {
1279
1283
  const machine = new TuringMachine({ tapeBlock });
1280
1284
  const snapshots = [];
1281
1285
  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 })) {
1286
+ // Iterate the generator manually (the yielded MachineState isn't needed
1287
+ // we only care about the side effects on the tape). At each yield the tape
1288
+ // reflects the state BEFORE the current step's command; after the loop
1289
+ // exits the tape has had every command applied.
1290
+ const generator = machine.runStepByStep({ initialState: runnable.state, stepsLimit });
1291
+ let result = generator.next();
1292
+ while (!result.done) {
1286
1293
  snapshots.push(tape.symbols.join(''));
1287
1294
  stepCount += 1;
1295
+ result = generator.next();
1288
1296
  }
1289
1297
  snapshots.push(tape.symbols.join(''));
1290
1298
  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
@@ -197,12 +197,13 @@ class Tape {
197
197
  }
198
198
  __classPrivateFieldSet$4(this, _Tape_alphabet, new Alphabet(alphabet), "f");
199
199
  __classPrivateFieldSet$4(this, _Tape_position, position, "f");
200
- __classPrivateFieldSet$4(this, _Tape_viewportWidth, viewportWidth, "f");
200
+ __classPrivateFieldSet$4(this, _Tape_viewportWidth, 1, "f");
201
201
  const symbolsCopy = Array.from(symbols);
202
202
  if (symbolsCopy.length === 0) {
203
203
  symbolsCopy.push(__classPrivateFieldGet$4(this, _Tape_alphabet, "f").blankSymbol);
204
204
  }
205
205
  __classPrivateFieldSet$4(this, _Tape_symbols, symbolsCopy.map((symbol) => __classPrivateFieldGet$4(this, _Tape_alphabet, "f").index(symbol)), "f");
206
+ this.viewportWidth = viewportWidth;
206
207
  }
207
208
  get alphabet() {
208
209
  return __classPrivateFieldGet$4(this, _Tape_alphabet, "f");
@@ -1046,7 +1047,9 @@ const initialNodeRegex = /^s(\d+)\(\("([^"]*)"\)\)$/;
1046
1047
  const regularNodeRegex = /^s(\d+)\["([^"]*)"\]$/;
1047
1048
  const transitionRegex = /^s(\d+)\s+--\s+"(.*)"\s+-->\s+s(\d+)$/;
1048
1049
  const onHaltRegex = /^s(\d+)\s+-\.\s+onHalt\s+\.->\s+s(\d+)$/;
1049
- const alphabetsRegex = /^%%\s*alphabets:\s*(.+)$/;
1050
+ // First capture char anchored as \S to avoid polynomial backtracking between
1051
+ // the preceding \s* and a permissive (.+); see CodeQL js/polynomial-redos.
1052
+ const alphabetsRegex = /^%%\s*alphabets:\s*(\S.*)$/;
1050
1053
  function fromMermaid(text) {
1051
1054
  const lines = text.split('\n').map((l) => l.trim()).filter(Boolean);
1052
1055
  let alphabets = [];
@@ -1178,9 +1181,10 @@ function summarizeGraph(graph) {
1178
1181
  }
1179
1182
  let hasCycles = false;
1180
1183
  const visit = (id) => {
1181
- if (hasCycles) {
1182
- return;
1183
- }
1184
+ // No `if (hasCycles) return` guard at function entry: the recursive call
1185
+ // pattern (outer for-loop checks before calling, inner loop checks after
1186
+ // each recursive call) ensures visit() is never invoked when hasCycles
1187
+ // is already true. Static analysis confirmed the guard was unreachable.
1184
1188
  if (color.get(id) === GREY) {
1185
1189
  hasCycles = true;
1186
1190
  return;
@@ -1277,12 +1281,16 @@ function runOnce(runnable, input, stepsLimit) {
1277
1281
  const machine = new TuringMachine({ tapeBlock });
1278
1282
  const snapshots = [];
1279
1283
  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 })) {
1284
+ // Iterate the generator manually (the yielded MachineState isn't needed
1285
+ // we only care about the side effects on the tape). At each yield the tape
1286
+ // reflects the state BEFORE the current step's command; after the loop
1287
+ // exits the tape has had every command applied.
1288
+ const generator = machine.runStepByStep({ initialState: runnable.state, stepsLimit });
1289
+ let result = generator.next();
1290
+ while (!result.done) {
1284
1291
  snapshots.push(tape.symbols.join(''));
1285
1292
  stepCount += 1;
1293
+ result = generator.next();
1286
1294
  }
1287
1295
  snapshots.push(tape.symbols.join(''));
1288
1296
  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.2",
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": "0a78b4d0d40bbdbf79e3c53694adf930f0567b0a"
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);