@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 +15 -0
- package/dist/index.cjs +15 -8
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +15 -8
- package/package.json +2 -2
- package/dist/classes/Alphabet.js +0 -50
- package/dist/classes/Command.js +0 -38
- package/dist/classes/Lock.js +0 -34
- package/dist/classes/Reference.js +0 -31
- package/dist/classes/State.js +0 -283
- package/dist/classes/Tape.js +0 -95
- package/dist/classes/TapeBlock.js +0 -197
- package/dist/classes/TapeCommand.js +0 -46
- package/dist/classes/TuringMachine.js +0 -102
- package/dist/index.js +0 -11
- package/dist/utilities/equivalence.js +0 -68
- package/dist/utilities/functions.js +0 -13
- package/dist/utilities/graph.js +0 -127
- package/dist/utilities/graphFormats.js +0 -141
- package/dist/utilities/introspection.js +0 -88
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
1283
|
-
//
|
|
1284
|
-
//
|
|
1285
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
1281
|
-
//
|
|
1282
|
-
//
|
|
1283
|
-
|
|
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.
|
|
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": "
|
|
37
|
+
"gitHead": "0a8d7ff87a3a73ea7ee1844f7f4602cbb23d20fe"
|
|
38
38
|
}
|
package/dist/classes/Alphabet.js
DELETED
|
@@ -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;
|
package/dist/classes/Command.js
DELETED
|
@@ -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;
|
package/dist/classes/Lock.js
DELETED
|
@@ -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;
|
package/dist/classes/State.js
DELETED
|
@@ -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);
|