@turing-machine-js/machine 3.0.2 → 5.0.0
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 +44 -0
- package/README.md +52 -3
- package/dist/classes/State.d.ts +20 -0
- package/dist/classes/TuringMachine.d.ts +31 -3
- package/dist/index.cjs +168 -31
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +168 -32
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,50 @@ 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
|
+
## [5.0.0] - 2026-05-09
|
|
8
|
+
|
|
9
|
+
### Changed (BREAKING)
|
|
10
|
+
|
|
11
|
+
- **Hook renamed: `onDebugBreak` → `onPause`** on `run()` ([#110](https://github.com/mellonis/turing-machine-js/issues/110)). Hard rename, no deprecation alias. The hook signature, payload shape, and dispatch semantics are unchanged — only the option key on `run()` differs. Resolution of the [#109 RFC](https://github.com/mellonis/turing-machine-js/issues/109): the hook now describes the consumer's relationship (this is where you can pause) rather than the engine's event (a debug break fired). The `m.debugBreak` payload field is unchanged.
|
|
12
|
+
- **`haltState.debug.after` now throws on assignment** ([#108](https://github.com/mellonis/turing-machine-js/issues/108) part 2). Halt is terminal — there is no iteration-after-halt for an after-fire to anchor on. Symmetric `{ before: true, after: true }` writes also throw; use `{ before: true }`. Symbol-list filters on `haltState.debug.before` remain silent no-ops as in v4.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **Halting iter's `state.debug.after` now fires** ([#108](https://github.com/mellonis/turing-machine-js/issues/108) part 1). Previously `pendingAfterFromPrev` set after the halting iter's yield was discarded when the `while (!state.isHalt)` loop exited — losing that after-fire. `run()` now drains a final after-fire after the loop when the prior yield armed one. Observable: a debugger UI sees a "we just halted because of state X's after-filter" event that was silent before.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **`run({ debug: boolean })` flag** ([#106](https://github.com/mellonis/turing-machine-js/issues/106)). Master switch for `onPause` dispatch. When `false`, suppresses all pause-fires (before, after, and the post-loop after-drain) regardless of `state.debug` assignments. `onStep` is unaffected. Defaults to `true`. The `m.debugBreak` field is still populated on yields by the underlying generator (it's a property of the iteration); only `run()`'s hook dispatch is gated. Useful for toggling "debug mode" without editing `state.debug = ...` across the state graph.
|
|
21
|
+
|
|
22
|
+
### Migration
|
|
23
|
+
|
|
24
|
+
```diff
|
|
25
|
+
await machine.run({
|
|
26
|
+
initialState,
|
|
27
|
+
- onDebugBreak: (m) => { ... },
|
|
28
|
+
+ onPause: (m) => { ... },
|
|
29
|
+
});
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```diff
|
|
33
|
+
- haltState.debug = { before: true, after: true }; // throws in v5
|
|
34
|
+
+ haltState.debug = { before: true };
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## [4.0.0] - 2026-05-07
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
|
|
41
|
+
- **`State.debug` field** — runtime-mutable per-state breakpoints with `{ before, after }` symbol filtering ([#98](https://github.com/mellonis/turing-machine-js/issues/98)). Backed by a shared `Ref` cell so `withOverrodeHaltState` wrappers see assignments propagated from the original state. Validator rejects symbols that aren't transition keys of the owning state (catches cross-tape-block / cross-state mistakes); for `haltState`, list filters are silent no-ops because halt has no resolved head symbol.
|
|
42
|
+
- **`DebugConfig` class** — exported from the package. Per-property setters validate and freeze the stored array, so push / index-write throw `TypeError`. Plain-object input to `state.debug = { ... }` is wrapped in a `DebugConfig` instance automatically.
|
|
43
|
+
- **`onDebugBreak` hook** on `run()` — awaited at every break (one or two times per yield: `after` from the previous step, then `before` for the current step). For `after` calls, `run()` substitutes the previous yield's snapshot so `m.state` corresponds to the state whose `after` fired. `onStep` always sees the original (un-substituted) yield.
|
|
44
|
+
- **`haltState.debug.before = true`** — pauses on every halt entry (program exit AND subroutine return via halt-stack pop). Symbol-list filters on `haltState` are silent no-ops; only the `true` wildcard activates. `haltState` is a module-level singleton — clear in `afterEach` / `finally` for test isolation.
|
|
45
|
+
- **`MachineState.debugBreak`** field — optional metadata on every yield from `runStepByStep`; consumers can opt in to reading it and mirror `run()`'s pause behavior if desired.
|
|
46
|
+
|
|
47
|
+
### Changed (BREAKING)
|
|
48
|
+
|
|
49
|
+
- **`run()` is now `async`** and returns `Promise<void>`. Existing callers must update to `await machine.run({ ... })` (or `void machine.run({ ... })` if intentionally discarding the result). Without an `onDebugBreak` hook, debug breaks fire-and-resume invisibly — the trajectory observed by `onStep` is identical to running with all `debug` fields cleared.
|
|
50
|
+
|
|
7
51
|
## [3.0.2] - 2026-05-04
|
|
8
52
|
|
|
9
53
|
### Fixed
|
package/README.md
CHANGED
|
@@ -34,7 +34,7 @@ const tape = new Tape({ alphabet, symbols: ['a', 'b', 'c', 'b', 'a'] });
|
|
|
34
34
|
const tapeBlock = TapeBlock.fromTapes([tape]);
|
|
35
35
|
const machine = new TuringMachine({ tapeBlock });
|
|
36
36
|
|
|
37
|
-
machine.run({
|
|
37
|
+
await machine.run({
|
|
38
38
|
initialState: new State({
|
|
39
39
|
[tapeBlock.symbol(['b'])]: {
|
|
40
40
|
command: [{ symbol: '*', movement: movements.right }],
|
|
@@ -218,8 +218,8 @@ The runtime. Owns one `TapeBlock` and drives a state graph until it reaches `hal
|
|
|
218
218
|
```javascript
|
|
219
219
|
const machine = new TuringMachine({ tapeBlock });
|
|
220
220
|
|
|
221
|
-
// Run to halt
|
|
222
|
-
machine.run({ initialState, stepsLimit: 1e5 });
|
|
221
|
+
// Run to halt — `run()` is async (v4+), it returns a Promise<void>:
|
|
222
|
+
await machine.run({ initialState, stepsLimit: 1e5 });
|
|
223
223
|
|
|
224
224
|
// Or step-by-step (useful for visualization / debugging):
|
|
225
225
|
for (const step of machine.runStepByStep({ initialState })) {
|
|
@@ -229,6 +229,55 @@ for (const step of machine.runStepByStep({ initialState })) {
|
|
|
229
229
|
|
|
230
230
|
`stepsLimit` (default `1e5`) guards against runaway loops — exceeding it throws.
|
|
231
231
|
|
|
232
|
+
## Debugging breakpoints (v4+)
|
|
233
|
+
|
|
234
|
+
Any `State` can carry a runtime-mutable `debug` config that pauses execution at chosen points.
|
|
235
|
+
|
|
236
|
+
```ts
|
|
237
|
+
import { State, haltState, ifOtherSymbol, type DebugConfig } from '@turing-machine-js/machine';
|
|
238
|
+
|
|
239
|
+
const myState = new State({...});
|
|
240
|
+
|
|
241
|
+
// Pause before applying any of myState's commands:
|
|
242
|
+
myState.debug = { before: true };
|
|
243
|
+
|
|
244
|
+
// Pause only when the head shows symA:
|
|
245
|
+
myState.debug = { before: [symA] };
|
|
246
|
+
|
|
247
|
+
// Pause both before and after for the same symbol — two pauses per visit:
|
|
248
|
+
myState.debug = { before: [symA], after: [symA] };
|
|
249
|
+
|
|
250
|
+
// Pause when the engine is about to enter halt (program exit OR subroutine pop):
|
|
251
|
+
haltState.debug = { before: true };
|
|
252
|
+
|
|
253
|
+
// Disable later:
|
|
254
|
+
myState.debug = null;
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
The `debug` field is mutable — toggle breakpoints at runtime without rebuilding the graph. The internal cell is shared with `state.withOverrodeHaltState(...)` wrappers, so an assignment on the original is visible from every wrapper.
|
|
258
|
+
|
|
259
|
+
`run()` is async and accepts an `onPause` hook:
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
await machine.run({
|
|
263
|
+
initialState,
|
|
264
|
+
onStep: (m) => { /* logger sees every step */ },
|
|
265
|
+
onPause: async (m) => {
|
|
266
|
+
// Awaited at every break — hold execution until you resolve.
|
|
267
|
+
if (m.debugBreak?.before) console.log('before:', m.state.name);
|
|
268
|
+
if (m.debugBreak?.after) console.log('after:', m.state.name);
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
For `after` calls, `m` is the previous yield's snapshot — `m.state` is the state whose `after` filter fired. For `before` calls, `m` is the current iteration. `onStep` always sees the original (un-substituted) yield.
|
|
274
|
+
|
|
275
|
+
If `onPause` is not provided, breaks fire-and-resume invisibly — the trajectory is identical to running without `debug` set.
|
|
276
|
+
|
|
277
|
+
**Filter semantics:** `true` is a wildcard (match any symbol). `[ifOtherSymbol]` is NOT a wildcard — it matches only the catch-all resolution case (same meaning as in transition keys).
|
|
278
|
+
|
|
279
|
+
**Caveat:** `haltState` is a module-level singleton. Setting `haltState.debug` affects every machine in the process; clear in `afterEach` / `finally` for test isolation.
|
|
280
|
+
|
|
232
281
|
## Special objects
|
|
233
282
|
|
|
234
283
|
### haltState
|
package/dist/classes/State.d.ts
CHANGED
|
@@ -4,6 +4,18 @@ import TapeBlock from './TapeBlock';
|
|
|
4
4
|
import TapeCommand from './TapeCommand';
|
|
5
5
|
import { type Graph } from '../utilities/graph';
|
|
6
6
|
export declare const ifOtherSymbol: unique symbol;
|
|
7
|
+
declare const validateDebugFilter: unique symbol;
|
|
8
|
+
export declare class DebugConfig {
|
|
9
|
+
#private;
|
|
10
|
+
constructor(ownerState: State, initial?: {
|
|
11
|
+
before?: symbol[] | readonly symbol[] | true;
|
|
12
|
+
after?: symbol[] | readonly symbol[] | true;
|
|
13
|
+
});
|
|
14
|
+
get before(): readonly symbol[] | true | undefined;
|
|
15
|
+
set before(v: symbol[] | readonly symbol[] | true | undefined);
|
|
16
|
+
get after(): readonly symbol[] | true | undefined;
|
|
17
|
+
set after(v: symbol[] | readonly symbol[] | true | undefined);
|
|
18
|
+
}
|
|
7
19
|
export default class State {
|
|
8
20
|
#private;
|
|
9
21
|
constructor(stateDefinition?: Record<string | symbol, {
|
|
@@ -15,6 +27,13 @@ export default class State {
|
|
|
15
27
|
get isHalt(): boolean;
|
|
16
28
|
get overrodeHaltState(): State | null;
|
|
17
29
|
get ref(): this;
|
|
30
|
+
get debug(): DebugConfig | null;
|
|
31
|
+
set debug(value: DebugConfig | {
|
|
32
|
+
before?: symbol[] | readonly symbol[] | true;
|
|
33
|
+
after?: symbol[] | readonly symbol[] | true;
|
|
34
|
+
} | null);
|
|
35
|
+
/** @internal — invoked by DebugConfig setters via module-private symbol. */
|
|
36
|
+
[validateDebugFilter](fieldName: 'before' | 'after', filter: readonly symbol[] | true | undefined): void;
|
|
18
37
|
getSymbol(tapeBlock: TapeBlock): symbol;
|
|
19
38
|
getCommand(symbol: symbol): Command;
|
|
20
39
|
getNextState(symbol: symbol): State | Reference;
|
|
@@ -47,3 +66,4 @@ export default class State {
|
|
|
47
66
|
};
|
|
48
67
|
}
|
|
49
68
|
export declare const haltState: State;
|
|
69
|
+
export {};
|
|
@@ -11,6 +11,19 @@ export type MachineState = {
|
|
|
11
11
|
nextSymbols: string[];
|
|
12
12
|
movements: symbol[];
|
|
13
13
|
nextState: State;
|
|
14
|
+
/**
|
|
15
|
+
* Set only when this iteration boundary is a debug break.
|
|
16
|
+
* Field is OMITTED entirely when no break fires (no `debugBreak: undefined`).
|
|
17
|
+
* At least one of `before` / `after` is `true` when the field is present.
|
|
18
|
+
*
|
|
19
|
+
* For consumers of the `runStepByStep` generator the `state` field reflects
|
|
20
|
+
* the current iteration regardless of timing; `run()` substitutes the prior
|
|
21
|
+
* yield's snapshot for `after` calls so consumers see the source state.
|
|
22
|
+
*/
|
|
23
|
+
debugBreak?: {
|
|
24
|
+
before?: true;
|
|
25
|
+
after?: true;
|
|
26
|
+
};
|
|
14
27
|
};
|
|
15
28
|
export default class TuringMachine {
|
|
16
29
|
#private;
|
|
@@ -18,9 +31,24 @@ export default class TuringMachine {
|
|
|
18
31
|
tapeBlock?: TapeBlock;
|
|
19
32
|
});
|
|
20
33
|
get tapeBlock(): TapeBlock;
|
|
21
|
-
run({ initialState, stepsLimit, onStep }: RunParameter & {
|
|
34
|
+
run({ initialState, stepsLimit, onStep, onPause, }: RunParameter & {
|
|
35
|
+
/**
|
|
36
|
+
* Sync, ~free hook fired on every iteration. Use for logging/tracing —
|
|
37
|
+
* the hot loop runs this without a microtask boundary, so it must not
|
|
38
|
+
* be async.
|
|
39
|
+
*/
|
|
22
40
|
onStep?: (machineState: MachineState) => void;
|
|
23
|
-
|
|
24
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Async hook fired only when `state.debug[when]` matches at the current
|
|
43
|
+
* iteration. The promise is awaited inline, so the consumer can suspend
|
|
44
|
+
* execution by deferring its resolution. Use for pause-capable inspection
|
|
45
|
+
* (debugger UIs, conditional breakpoints in tests).
|
|
46
|
+
*
|
|
47
|
+
* Renamed from `onDebugBreak` in v5.0.0. The `m.debugBreak` payload field
|
|
48
|
+
* keeps its name (it describes the engine's reason for pausing).
|
|
49
|
+
*/
|
|
50
|
+
onPause?: (machineState: MachineState) => void | Promise<void>;
|
|
51
|
+
}): Promise<void>;
|
|
52
|
+
runStepByStep({ initialState, stepsLimit }: RunParameter): Generator<MachineState, MachineState | null>;
|
|
25
53
|
}
|
|
26
54
|
export {};
|
package/dist/index.cjs
CHANGED
|
@@ -186,13 +186,18 @@ var __classPrivateFieldGet$4 = (undefined && undefined.__classPrivateFieldGet) |
|
|
|
186
186
|
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");
|
|
187
187
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
188
188
|
};
|
|
189
|
-
var _Tape_alphabet,
|
|
189
|
+
var _Tape_instances, _Tape_alphabet, _Tape_right, _Tape_left, _Tape_position, _Tape_viewportWidth, _Tape_viewportBuffer, _Tape_viewportDirty, _Tape_cellAt;
|
|
190
|
+
const BLANK_INDEX = 0;
|
|
190
191
|
class Tape {
|
|
191
192
|
constructor({ alphabet, symbols = [], position = 0, viewportWidth = 1, }) {
|
|
193
|
+
_Tape_instances.add(this);
|
|
192
194
|
_Tape_alphabet.set(this, void 0);
|
|
193
|
-
|
|
195
|
+
_Tape_right.set(this, []);
|
|
196
|
+
_Tape_left.set(this, []);
|
|
194
197
|
_Tape_position.set(this, void 0);
|
|
195
198
|
_Tape_viewportWidth.set(this, void 0);
|
|
199
|
+
_Tape_viewportBuffer.set(this, []);
|
|
200
|
+
_Tape_viewportDirty.set(this, true);
|
|
196
201
|
const isSymbolsValid = symbols.every((symbol) => alphabet.has(symbol));
|
|
197
202
|
if (!isSymbolsValid) {
|
|
198
203
|
throw new Error('symbolList contains invalid symbol');
|
|
@@ -200,11 +205,10 @@ class Tape {
|
|
|
200
205
|
__classPrivateFieldSet$4(this, _Tape_alphabet, new Alphabet(alphabet), "f");
|
|
201
206
|
__classPrivateFieldSet$4(this, _Tape_position, position, "f");
|
|
202
207
|
__classPrivateFieldSet$4(this, _Tape_viewportWidth, 1, "f");
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
208
|
+
const initialSymbols = symbols.length === 0 ? [__classPrivateFieldGet$4(this, _Tape_alphabet, "f").blankSymbol] : symbols;
|
|
209
|
+
for (const symbol of initialSymbols) {
|
|
210
|
+
__classPrivateFieldGet$4(this, _Tape_right, "f").push(__classPrivateFieldGet$4(this, _Tape_alphabet, "f").index(symbol));
|
|
206
211
|
}
|
|
207
|
-
__classPrivateFieldSet$4(this, _Tape_symbols, symbolsCopy.map((symbol) => __classPrivateFieldGet$4(this, _Tape_alphabet, "f").index(symbol)), "f");
|
|
208
212
|
this.viewportWidth = viewportWidth;
|
|
209
213
|
}
|
|
210
214
|
get alphabet() {
|
|
@@ -214,27 +218,46 @@ class Tape {
|
|
|
214
218
|
return (__classPrivateFieldGet$4(this, _Tape_viewportWidth, "f") - 1) / 2;
|
|
215
219
|
}
|
|
216
220
|
get position() {
|
|
217
|
-
|
|
221
|
+
// Public contract: index of the head in the `symbols` array. With the
|
|
222
|
+
// two-array deque, `#position` is the head's logical position; adding
|
|
223
|
+
// `#left.length` shifts it back into "index from the leftmost backed cell".
|
|
224
|
+
return __classPrivateFieldGet$4(this, _Tape_position, "f") + __classPrivateFieldGet$4(this, _Tape_left, "f").length;
|
|
218
225
|
}
|
|
219
226
|
get symbol() {
|
|
220
|
-
return __classPrivateFieldGet$4(this, _Tape_alphabet, "f").get(__classPrivateFieldGet$4(this,
|
|
227
|
+
return __classPrivateFieldGet$4(this, _Tape_alphabet, "f").get(__classPrivateFieldGet$4(this, _Tape_instances, "m", _Tape_cellAt).call(this, __classPrivateFieldGet$4(this, _Tape_position, "f")));
|
|
221
228
|
}
|
|
222
229
|
set symbol(symbol) {
|
|
223
230
|
if (!__classPrivateFieldGet$4(this, _Tape_alphabet, "f").has(symbol)) {
|
|
224
231
|
throw new Error('Invalid symbol');
|
|
225
232
|
}
|
|
226
|
-
|
|
233
|
+
const index = __classPrivateFieldGet$4(this, _Tape_alphabet, "f").index(symbol);
|
|
234
|
+
if (__classPrivateFieldGet$4(this, _Tape_position, "f") >= 0) {
|
|
235
|
+
__classPrivateFieldGet$4(this, _Tape_right, "f")[__classPrivateFieldGet$4(this, _Tape_position, "f")] = index;
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
__classPrivateFieldGet$4(this, _Tape_left, "f")[-__classPrivateFieldGet$4(this, _Tape_position, "f") - 1] = index;
|
|
239
|
+
}
|
|
240
|
+
__classPrivateFieldSet$4(this, _Tape_viewportDirty, true, "f");
|
|
227
241
|
}
|
|
228
242
|
get symbols() {
|
|
229
|
-
|
|
230
|
-
|
|
243
|
+
const result = new Array(__classPrivateFieldGet$4(this, _Tape_left, "f").length + __classPrivateFieldGet$4(this, _Tape_right, "f").length);
|
|
244
|
+
for (let i = 0; i < __classPrivateFieldGet$4(this, _Tape_left, "f").length; i += 1) {
|
|
245
|
+
result[i] = __classPrivateFieldGet$4(this, _Tape_alphabet, "f").get(__classPrivateFieldGet$4(this, _Tape_left, "f")[__classPrivateFieldGet$4(this, _Tape_left, "f").length - 1 - i]);
|
|
246
|
+
}
|
|
247
|
+
for (let i = 0; i < __classPrivateFieldGet$4(this, _Tape_right, "f").length; i += 1) {
|
|
248
|
+
result[__classPrivateFieldGet$4(this, _Tape_left, "f").length + i] = __classPrivateFieldGet$4(this, _Tape_alphabet, "f").get(__classPrivateFieldGet$4(this, _Tape_right, "f")[i]);
|
|
249
|
+
}
|
|
250
|
+
return result;
|
|
231
251
|
}
|
|
232
252
|
get viewport() {
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
253
|
+
if (__classPrivateFieldGet$4(this, _Tape_viewportDirty, "f")) {
|
|
254
|
+
const start = __classPrivateFieldGet$4(this, _Tape_position, "f") - this.extraCellsCount;
|
|
255
|
+
for (let i = 0; i < __classPrivateFieldGet$4(this, _Tape_viewportWidth, "f"); i += 1) {
|
|
256
|
+
__classPrivateFieldGet$4(this, _Tape_viewportBuffer, "f")[i] = __classPrivateFieldGet$4(this, _Tape_alphabet, "f").get(__classPrivateFieldGet$4(this, _Tape_instances, "m", _Tape_cellAt).call(this, start + i));
|
|
257
|
+
}
|
|
258
|
+
__classPrivateFieldSet$4(this, _Tape_viewportDirty, false, "f");
|
|
259
|
+
}
|
|
260
|
+
return [...__classPrivateFieldGet$4(this, _Tape_viewportBuffer, "f")];
|
|
238
261
|
}
|
|
239
262
|
get viewportWidth() {
|
|
240
263
|
return __classPrivateFieldGet$4(this, _Tape_viewportWidth, "f");
|
|
@@ -248,27 +271,38 @@ class Tape {
|
|
|
248
271
|
finalWidth += 1;
|
|
249
272
|
}
|
|
250
273
|
__classPrivateFieldSet$4(this, _Tape_viewportWidth, finalWidth, "f");
|
|
274
|
+
__classPrivateFieldGet$4(this, _Tape_viewportBuffer, "f").length = finalWidth;
|
|
275
|
+
__classPrivateFieldSet$4(this, _Tape_viewportDirty, true, "f");
|
|
251
276
|
this.normalise();
|
|
252
277
|
}
|
|
253
278
|
left() {
|
|
254
279
|
__classPrivateFieldSet$4(this, _Tape_position, __classPrivateFieldGet$4(this, _Tape_position, "f") - 1, "f");
|
|
255
280
|
this.normalise();
|
|
281
|
+
__classPrivateFieldSet$4(this, _Tape_viewportDirty, true, "f");
|
|
256
282
|
}
|
|
257
283
|
normalise() {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
284
|
+
const minLogical = __classPrivateFieldGet$4(this, _Tape_position, "f") - this.extraCellsCount;
|
|
285
|
+
const maxLogical = __classPrivateFieldGet$4(this, _Tape_position, "f") + this.extraCellsCount;
|
|
286
|
+
while (-__classPrivateFieldGet$4(this, _Tape_left, "f").length > minLogical) {
|
|
287
|
+
__classPrivateFieldGet$4(this, _Tape_left, "f").push(BLANK_INDEX);
|
|
261
288
|
}
|
|
262
|
-
while (__classPrivateFieldGet$4(this,
|
|
263
|
-
__classPrivateFieldGet$4(this,
|
|
289
|
+
while (__classPrivateFieldGet$4(this, _Tape_right, "f").length - 1 < maxLogical) {
|
|
290
|
+
__classPrivateFieldGet$4(this, _Tape_right, "f").push(BLANK_INDEX);
|
|
264
291
|
}
|
|
265
292
|
}
|
|
266
293
|
right() {
|
|
267
294
|
__classPrivateFieldSet$4(this, _Tape_position, __classPrivateFieldGet$4(this, _Tape_position, "f") + 1, "f");
|
|
268
295
|
this.normalise();
|
|
296
|
+
__classPrivateFieldSet$4(this, _Tape_viewportDirty, true, "f");
|
|
269
297
|
}
|
|
270
298
|
}
|
|
271
|
-
_Tape_alphabet = new WeakMap(),
|
|
299
|
+
_Tape_alphabet = new WeakMap(), _Tape_right = new WeakMap(), _Tape_left = new WeakMap(), _Tape_position = new WeakMap(), _Tape_viewportWidth = new WeakMap(), _Tape_viewportBuffer = new WeakMap(), _Tape_viewportDirty = new WeakMap(), _Tape_instances = new WeakSet(), _Tape_cellAt = function _Tape_cellAt(logical) {
|
|
300
|
+
if (logical >= 0) {
|
|
301
|
+
return logical < __classPrivateFieldGet$4(this, _Tape_right, "f").length ? __classPrivateFieldGet$4(this, _Tape_right, "f")[logical] : BLANK_INDEX;
|
|
302
|
+
}
|
|
303
|
+
const ix = -logical - 1;
|
|
304
|
+
return ix < __classPrivateFieldGet$4(this, _Tape_left, "f").length ? __classPrivateFieldGet$4(this, _Tape_left, "f")[ix] : BLANK_INDEX;
|
|
305
|
+
};
|
|
272
306
|
|
|
273
307
|
var __classPrivateFieldGet$3 = (undefined && undefined.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
274
308
|
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
@@ -624,25 +658,64 @@ function decodeWriteSymbol(symbol) {
|
|
|
624
658
|
}
|
|
625
659
|
// Format converters (toMermaid / fromMermaid) live in ./graphFormats.
|
|
626
660
|
|
|
627
|
-
var __classPrivateFieldGet$1 = (undefined && undefined.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
628
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
629
|
-
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");
|
|
630
|
-
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
631
|
-
};
|
|
632
661
|
var __classPrivateFieldSet$1 = (undefined && undefined.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
633
662
|
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
634
663
|
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
635
664
|
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");
|
|
636
665
|
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
637
666
|
};
|
|
638
|
-
var
|
|
667
|
+
var __classPrivateFieldGet$1 = (undefined && undefined.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
668
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
669
|
+
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");
|
|
670
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
671
|
+
};
|
|
672
|
+
var _DebugConfig_ownerState, _DebugConfig_before, _DebugConfig_after, _State_id, _State_name, _State_overrodeHaltState, _State_symbolToDataMap, _State_debugRef;
|
|
639
673
|
const ifOtherSymbol = Symbol('other symbol');
|
|
674
|
+
// Module-private symbol used by DebugConfig setters to call State's validator
|
|
675
|
+
// without exposing the validator on the public surface.
|
|
676
|
+
const validateDebugFilter = Symbol('validateDebugFilter');
|
|
677
|
+
class DebugConfig {
|
|
678
|
+
constructor(ownerState, initial) {
|
|
679
|
+
_DebugConfig_ownerState.set(this, void 0);
|
|
680
|
+
_DebugConfig_before.set(this, void 0);
|
|
681
|
+
_DebugConfig_after.set(this, void 0);
|
|
682
|
+
__classPrivateFieldSet$1(this, _DebugConfig_ownerState, ownerState, "f");
|
|
683
|
+
if (initial) {
|
|
684
|
+
if (initial.before !== undefined) {
|
|
685
|
+
this.before = initial.before;
|
|
686
|
+
}
|
|
687
|
+
if (initial.after !== undefined) {
|
|
688
|
+
this.after = initial.after;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
get before() {
|
|
693
|
+
return __classPrivateFieldGet$1(this, _DebugConfig_before, "f");
|
|
694
|
+
}
|
|
695
|
+
set before(v) {
|
|
696
|
+
__classPrivateFieldGet$1(this, _DebugConfig_ownerState, "f")[validateDebugFilter]('before', v);
|
|
697
|
+
__classPrivateFieldSet$1(this, _DebugConfig_before, Array.isArray(v) ? Object.freeze([...v]) : v, "f");
|
|
698
|
+
}
|
|
699
|
+
get after() {
|
|
700
|
+
return __classPrivateFieldGet$1(this, _DebugConfig_after, "f");
|
|
701
|
+
}
|
|
702
|
+
set after(v) {
|
|
703
|
+
__classPrivateFieldGet$1(this, _DebugConfig_ownerState, "f")[validateDebugFilter]('after', v);
|
|
704
|
+
__classPrivateFieldSet$1(this, _DebugConfig_after, Array.isArray(v) ? Object.freeze([...v]) : v, "f");
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
_DebugConfig_ownerState = new WeakMap(), _DebugConfig_before = new WeakMap(), _DebugConfig_after = new WeakMap();
|
|
640
708
|
class State {
|
|
641
709
|
constructor(stateDefinition = null, name) {
|
|
642
710
|
_State_id.set(this, id(this));
|
|
643
711
|
_State_name.set(this, void 0);
|
|
644
712
|
_State_overrodeHaltState.set(this, null);
|
|
645
713
|
_State_symbolToDataMap.set(this, new Map());
|
|
714
|
+
// Shared mutable cell — withOverrodeHaltState wrappers reference the same
|
|
715
|
+
// object so that `state.debug = ...` (and nullings) propagate across them.
|
|
716
|
+
// Note: toGraph / fromGraph deliberately do not serialize debug — debug is
|
|
717
|
+
// a runtime concern, not part of the structural graph.
|
|
718
|
+
_State_debugRef.set(this, { current: null });
|
|
646
719
|
if (stateDefinition) {
|
|
647
720
|
const keys = Object.getOwnPropertyNames(stateDefinition);
|
|
648
721
|
if (keys.length) {
|
|
@@ -701,6 +774,36 @@ class State {
|
|
|
701
774
|
get ref() {
|
|
702
775
|
return this;
|
|
703
776
|
}
|
|
777
|
+
get debug() {
|
|
778
|
+
return __classPrivateFieldGet$1(this, _State_debugRef, "f").current;
|
|
779
|
+
}
|
|
780
|
+
set debug(value) {
|
|
781
|
+
if (value === null) {
|
|
782
|
+
__classPrivateFieldGet$1(this, _State_debugRef, "f").current = null;
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
if (value instanceof DebugConfig) {
|
|
786
|
+
__classPrivateFieldGet$1(this, _State_debugRef, "f").current = value;
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
__classPrivateFieldGet$1(this, _State_debugRef, "f").current = new DebugConfig(this, value);
|
|
790
|
+
}
|
|
791
|
+
/** @internal — invoked by DebugConfig setters via module-private symbol. */
|
|
792
|
+
[(_State_id = new WeakMap(), _State_name = new WeakMap(), _State_overrodeHaltState = new WeakMap(), _State_symbolToDataMap = new WeakMap(), _State_debugRef = new WeakMap(), validateDebugFilter)](fieldName, filter) {
|
|
793
|
+
if (filter === undefined || filter === true)
|
|
794
|
+
return;
|
|
795
|
+
// haltState has no own transitions; symbol-list filters on it are silent
|
|
796
|
+
// no-ops at the engine level (spec §8.6), so accept any list shape here.
|
|
797
|
+
if (this.isHalt)
|
|
798
|
+
return;
|
|
799
|
+
for (const sym of filter) {
|
|
800
|
+
if (sym !== ifOtherSymbol && !__classPrivateFieldGet$1(this, _State_symbolToDataMap, "f").has(sym)) {
|
|
801
|
+
throw new Error(`State.debug.${fieldName}: symbol is not a transition key of this state `
|
|
802
|
+
+ `(state name: ${__classPrivateFieldGet$1(this, _State_name, "f")}). Common cause: symbol comes from a `
|
|
803
|
+
+ 'different tape block, or doesn\'t match any of this state\'s transitions.');
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
704
807
|
getSymbol(tapeBlock) {
|
|
705
808
|
const symbol = [...__classPrivateFieldGet$1(this, _State_symbolToDataMap, "f").keys()].find((currentSymbol) => tapeBlock.isMatched({
|
|
706
809
|
symbol: currentSymbol,
|
|
@@ -726,6 +829,7 @@ class State {
|
|
|
726
829
|
const state = new State(null, `${this.name}>${overrodeHaltState.name}`);
|
|
727
830
|
__classPrivateFieldSet$1(state, _State_symbolToDataMap, __classPrivateFieldGet$1(this, _State_symbolToDataMap, "f"), "f");
|
|
728
831
|
__classPrivateFieldSet$1(state, _State_overrodeHaltState, overrodeHaltState, "f");
|
|
832
|
+
__classPrivateFieldSet$1(state, _State_debugRef, __classPrivateFieldGet$1(this, _State_debugRef, "f"), "f");
|
|
729
833
|
return state;
|
|
730
834
|
}
|
|
731
835
|
// Single-state introspection — no traversal, no tapeBlock required.
|
|
@@ -896,7 +1000,6 @@ class State {
|
|
|
896
1000
|
};
|
|
897
1001
|
}
|
|
898
1002
|
}
|
|
899
|
-
_State_id = new WeakMap(), _State_name = new WeakMap(), _State_overrodeHaltState = new WeakMap(), _State_symbolToDataMap = new WeakMap();
|
|
900
1003
|
const haltState = new State(null);
|
|
901
1004
|
|
|
902
1005
|
var __classPrivateFieldSet = (undefined && undefined.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
@@ -911,6 +1014,15 @@ var __classPrivateFieldGet = (undefined && undefined.__classPrivateFieldGet) ||
|
|
|
911
1014
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
912
1015
|
};
|
|
913
1016
|
var _TuringMachine_tapeBlock, _TuringMachine_stack;
|
|
1017
|
+
// True iff `filter` matches `symbol` per the DebugConfig semantics.
|
|
1018
|
+
// undefined / [] -> never; true -> always; symbol[] -> exact membership.
|
|
1019
|
+
function matchFilter(filter, symbol) {
|
|
1020
|
+
if (filter === undefined)
|
|
1021
|
+
return false;
|
|
1022
|
+
if (filter === true)
|
|
1023
|
+
return true;
|
|
1024
|
+
return filter.includes(symbol);
|
|
1025
|
+
}
|
|
914
1026
|
class TuringMachine {
|
|
915
1027
|
constructor({ tapeBlock, } = {}) {
|
|
916
1028
|
_TuringMachine_tapeBlock.set(this, void 0);
|
|
@@ -923,12 +1035,22 @@ class TuringMachine {
|
|
|
923
1035
|
get tapeBlock() {
|
|
924
1036
|
return __classPrivateFieldGet(this, _TuringMachine_tapeBlock, "f");
|
|
925
1037
|
}
|
|
926
|
-
run({ initialState, stepsLimit = 1e5, onStep }) {
|
|
1038
|
+
async run({ initialState, stepsLimit = 1e5, onStep, onDebugBreak, }) {
|
|
927
1039
|
const generator = this.runStepByStep({ initialState, stepsLimit });
|
|
1040
|
+
let prevYield = null;
|
|
928
1041
|
for (const machineState of generator) {
|
|
1042
|
+
// 'after' (from prev step) — fire FIRST, with prev yield substituted as the source view.
|
|
1043
|
+
if (machineState.debugBreak?.after && onDebugBreak && prevYield) {
|
|
1044
|
+
await onDebugBreak({ ...prevYield, debugBreak: { after: true } });
|
|
1045
|
+
}
|
|
1046
|
+
// 'before' (current step) — pass current machineState with only the before flag.
|
|
1047
|
+
if (machineState.debugBreak?.before && onDebugBreak) {
|
|
1048
|
+
await onDebugBreak({ ...machineState, debugBreak: { before: true } });
|
|
1049
|
+
}
|
|
929
1050
|
if (onStep instanceof Function) {
|
|
930
1051
|
onStep(machineState);
|
|
931
1052
|
}
|
|
1053
|
+
prevYield = machineState;
|
|
932
1054
|
}
|
|
933
1055
|
}
|
|
934
1056
|
*runStepByStep({ initialState, stepsLimit = 1e5 }) {
|
|
@@ -942,6 +1064,7 @@ class TuringMachine {
|
|
|
942
1064
|
stack.push(state.overrodeHaltState);
|
|
943
1065
|
}
|
|
944
1066
|
let i = 0;
|
|
1067
|
+
let pendingAfterFromPrev = false;
|
|
945
1068
|
while (!state.isHalt) {
|
|
946
1069
|
if (i === stepsLimit) {
|
|
947
1070
|
throw new Error('Long execution');
|
|
@@ -951,10 +1074,12 @@ class TuringMachine {
|
|
|
951
1074
|
const command = state.getCommand(symbol);
|
|
952
1075
|
let nextState = state.getNextState(symbol).ref;
|
|
953
1076
|
try {
|
|
1077
|
+
const beforeMatch = matchFilter(state.debug?.before, symbol)
|
|
1078
|
+
|| (nextState.isHalt && nextState.debug?.before === true);
|
|
954
1079
|
const nextStateForYield = nextState.isHalt && stack.length
|
|
955
1080
|
? stack.slice(-1)[0]
|
|
956
1081
|
: nextState;
|
|
957
|
-
|
|
1082
|
+
const yielded = {
|
|
958
1083
|
step: i,
|
|
959
1084
|
state,
|
|
960
1085
|
currentSymbols: __classPrivateFieldGet(this, _TuringMachine_tapeBlock, "f").currentSymbols,
|
|
@@ -974,6 +1099,17 @@ class TuringMachine {
|
|
|
974
1099
|
movements: command.tapesCommands.map((tapeCommand) => tapeCommand.movement),
|
|
975
1100
|
nextState: nextStateForYield,
|
|
976
1101
|
};
|
|
1102
|
+
if (pendingAfterFromPrev || beforeMatch) {
|
|
1103
|
+
const dbg = {};
|
|
1104
|
+
if (pendingAfterFromPrev)
|
|
1105
|
+
dbg.after = true;
|
|
1106
|
+
if (beforeMatch)
|
|
1107
|
+
dbg.before = true;
|
|
1108
|
+
yielded.debugBreak = dbg;
|
|
1109
|
+
}
|
|
1110
|
+
yield yielded;
|
|
1111
|
+
// Re-evaluate 'after' for THIS visit, to fire on the NEXT yield.
|
|
1112
|
+
pendingAfterFromPrev = matchFilter(state.debug?.after, symbol);
|
|
977
1113
|
__classPrivateFieldGet(this, _TuringMachine_tapeBlock, "f").applyCommand(command, executionSymbol);
|
|
978
1114
|
if (nextState.isHalt && stack.length) {
|
|
979
1115
|
nextState = stack.pop();
|
|
@@ -1304,6 +1440,7 @@ function runOnce(runnable, input, stepsLimit) {
|
|
|
1304
1440
|
|
|
1305
1441
|
exports.Alphabet = Alphabet;
|
|
1306
1442
|
exports.Command = Command;
|
|
1443
|
+
exports.DebugConfig = DebugConfig;
|
|
1307
1444
|
exports.Reference = Reference;
|
|
1308
1445
|
exports.State = State;
|
|
1309
1446
|
exports.Tape = Tape;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { default as Alphabet } from './classes/Alphabet';
|
|
2
2
|
export { default as Command } from './classes/Command';
|
|
3
3
|
export { default as Reference } from './classes/Reference';
|
|
4
|
-
export { default as State, haltState, ifOtherSymbol } from './classes/State';
|
|
4
|
+
export { default as State, DebugConfig, 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';
|
package/dist/index.mjs
CHANGED
|
@@ -184,13 +184,18 @@ var __classPrivateFieldGet$4 = (undefined && undefined.__classPrivateFieldGet) |
|
|
|
184
184
|
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");
|
|
185
185
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
186
186
|
};
|
|
187
|
-
var _Tape_alphabet,
|
|
187
|
+
var _Tape_instances, _Tape_alphabet, _Tape_right, _Tape_left, _Tape_position, _Tape_viewportWidth, _Tape_viewportBuffer, _Tape_viewportDirty, _Tape_cellAt;
|
|
188
|
+
const BLANK_INDEX = 0;
|
|
188
189
|
class Tape {
|
|
189
190
|
constructor({ alphabet, symbols = [], position = 0, viewportWidth = 1, }) {
|
|
191
|
+
_Tape_instances.add(this);
|
|
190
192
|
_Tape_alphabet.set(this, void 0);
|
|
191
|
-
|
|
193
|
+
_Tape_right.set(this, []);
|
|
194
|
+
_Tape_left.set(this, []);
|
|
192
195
|
_Tape_position.set(this, void 0);
|
|
193
196
|
_Tape_viewportWidth.set(this, void 0);
|
|
197
|
+
_Tape_viewportBuffer.set(this, []);
|
|
198
|
+
_Tape_viewportDirty.set(this, true);
|
|
194
199
|
const isSymbolsValid = symbols.every((symbol) => alphabet.has(symbol));
|
|
195
200
|
if (!isSymbolsValid) {
|
|
196
201
|
throw new Error('symbolList contains invalid symbol');
|
|
@@ -198,11 +203,10 @@ class Tape {
|
|
|
198
203
|
__classPrivateFieldSet$4(this, _Tape_alphabet, new Alphabet(alphabet), "f");
|
|
199
204
|
__classPrivateFieldSet$4(this, _Tape_position, position, "f");
|
|
200
205
|
__classPrivateFieldSet$4(this, _Tape_viewportWidth, 1, "f");
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
206
|
+
const initialSymbols = symbols.length === 0 ? [__classPrivateFieldGet$4(this, _Tape_alphabet, "f").blankSymbol] : symbols;
|
|
207
|
+
for (const symbol of initialSymbols) {
|
|
208
|
+
__classPrivateFieldGet$4(this, _Tape_right, "f").push(__classPrivateFieldGet$4(this, _Tape_alphabet, "f").index(symbol));
|
|
204
209
|
}
|
|
205
|
-
__classPrivateFieldSet$4(this, _Tape_symbols, symbolsCopy.map((symbol) => __classPrivateFieldGet$4(this, _Tape_alphabet, "f").index(symbol)), "f");
|
|
206
210
|
this.viewportWidth = viewportWidth;
|
|
207
211
|
}
|
|
208
212
|
get alphabet() {
|
|
@@ -212,27 +216,46 @@ class Tape {
|
|
|
212
216
|
return (__classPrivateFieldGet$4(this, _Tape_viewportWidth, "f") - 1) / 2;
|
|
213
217
|
}
|
|
214
218
|
get position() {
|
|
215
|
-
|
|
219
|
+
// Public contract: index of the head in the `symbols` array. With the
|
|
220
|
+
// two-array deque, `#position` is the head's logical position; adding
|
|
221
|
+
// `#left.length` shifts it back into "index from the leftmost backed cell".
|
|
222
|
+
return __classPrivateFieldGet$4(this, _Tape_position, "f") + __classPrivateFieldGet$4(this, _Tape_left, "f").length;
|
|
216
223
|
}
|
|
217
224
|
get symbol() {
|
|
218
|
-
return __classPrivateFieldGet$4(this, _Tape_alphabet, "f").get(__classPrivateFieldGet$4(this,
|
|
225
|
+
return __classPrivateFieldGet$4(this, _Tape_alphabet, "f").get(__classPrivateFieldGet$4(this, _Tape_instances, "m", _Tape_cellAt).call(this, __classPrivateFieldGet$4(this, _Tape_position, "f")));
|
|
219
226
|
}
|
|
220
227
|
set symbol(symbol) {
|
|
221
228
|
if (!__classPrivateFieldGet$4(this, _Tape_alphabet, "f").has(symbol)) {
|
|
222
229
|
throw new Error('Invalid symbol');
|
|
223
230
|
}
|
|
224
|
-
|
|
231
|
+
const index = __classPrivateFieldGet$4(this, _Tape_alphabet, "f").index(symbol);
|
|
232
|
+
if (__classPrivateFieldGet$4(this, _Tape_position, "f") >= 0) {
|
|
233
|
+
__classPrivateFieldGet$4(this, _Tape_right, "f")[__classPrivateFieldGet$4(this, _Tape_position, "f")] = index;
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
__classPrivateFieldGet$4(this, _Tape_left, "f")[-__classPrivateFieldGet$4(this, _Tape_position, "f") - 1] = index;
|
|
237
|
+
}
|
|
238
|
+
__classPrivateFieldSet$4(this, _Tape_viewportDirty, true, "f");
|
|
225
239
|
}
|
|
226
240
|
get symbols() {
|
|
227
|
-
|
|
228
|
-
|
|
241
|
+
const result = new Array(__classPrivateFieldGet$4(this, _Tape_left, "f").length + __classPrivateFieldGet$4(this, _Tape_right, "f").length);
|
|
242
|
+
for (let i = 0; i < __classPrivateFieldGet$4(this, _Tape_left, "f").length; i += 1) {
|
|
243
|
+
result[i] = __classPrivateFieldGet$4(this, _Tape_alphabet, "f").get(__classPrivateFieldGet$4(this, _Tape_left, "f")[__classPrivateFieldGet$4(this, _Tape_left, "f").length - 1 - i]);
|
|
244
|
+
}
|
|
245
|
+
for (let i = 0; i < __classPrivateFieldGet$4(this, _Tape_right, "f").length; i += 1) {
|
|
246
|
+
result[__classPrivateFieldGet$4(this, _Tape_left, "f").length + i] = __classPrivateFieldGet$4(this, _Tape_alphabet, "f").get(__classPrivateFieldGet$4(this, _Tape_right, "f")[i]);
|
|
247
|
+
}
|
|
248
|
+
return result;
|
|
229
249
|
}
|
|
230
250
|
get viewport() {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
251
|
+
if (__classPrivateFieldGet$4(this, _Tape_viewportDirty, "f")) {
|
|
252
|
+
const start = __classPrivateFieldGet$4(this, _Tape_position, "f") - this.extraCellsCount;
|
|
253
|
+
for (let i = 0; i < __classPrivateFieldGet$4(this, _Tape_viewportWidth, "f"); i += 1) {
|
|
254
|
+
__classPrivateFieldGet$4(this, _Tape_viewportBuffer, "f")[i] = __classPrivateFieldGet$4(this, _Tape_alphabet, "f").get(__classPrivateFieldGet$4(this, _Tape_instances, "m", _Tape_cellAt).call(this, start + i));
|
|
255
|
+
}
|
|
256
|
+
__classPrivateFieldSet$4(this, _Tape_viewportDirty, false, "f");
|
|
257
|
+
}
|
|
258
|
+
return [...__classPrivateFieldGet$4(this, _Tape_viewportBuffer, "f")];
|
|
236
259
|
}
|
|
237
260
|
get viewportWidth() {
|
|
238
261
|
return __classPrivateFieldGet$4(this, _Tape_viewportWidth, "f");
|
|
@@ -246,27 +269,38 @@ class Tape {
|
|
|
246
269
|
finalWidth += 1;
|
|
247
270
|
}
|
|
248
271
|
__classPrivateFieldSet$4(this, _Tape_viewportWidth, finalWidth, "f");
|
|
272
|
+
__classPrivateFieldGet$4(this, _Tape_viewportBuffer, "f").length = finalWidth;
|
|
273
|
+
__classPrivateFieldSet$4(this, _Tape_viewportDirty, true, "f");
|
|
249
274
|
this.normalise();
|
|
250
275
|
}
|
|
251
276
|
left() {
|
|
252
277
|
__classPrivateFieldSet$4(this, _Tape_position, __classPrivateFieldGet$4(this, _Tape_position, "f") - 1, "f");
|
|
253
278
|
this.normalise();
|
|
279
|
+
__classPrivateFieldSet$4(this, _Tape_viewportDirty, true, "f");
|
|
254
280
|
}
|
|
255
281
|
normalise() {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
282
|
+
const minLogical = __classPrivateFieldGet$4(this, _Tape_position, "f") - this.extraCellsCount;
|
|
283
|
+
const maxLogical = __classPrivateFieldGet$4(this, _Tape_position, "f") + this.extraCellsCount;
|
|
284
|
+
while (-__classPrivateFieldGet$4(this, _Tape_left, "f").length > minLogical) {
|
|
285
|
+
__classPrivateFieldGet$4(this, _Tape_left, "f").push(BLANK_INDEX);
|
|
259
286
|
}
|
|
260
|
-
while (__classPrivateFieldGet$4(this,
|
|
261
|
-
__classPrivateFieldGet$4(this,
|
|
287
|
+
while (__classPrivateFieldGet$4(this, _Tape_right, "f").length - 1 < maxLogical) {
|
|
288
|
+
__classPrivateFieldGet$4(this, _Tape_right, "f").push(BLANK_INDEX);
|
|
262
289
|
}
|
|
263
290
|
}
|
|
264
291
|
right() {
|
|
265
292
|
__classPrivateFieldSet$4(this, _Tape_position, __classPrivateFieldGet$4(this, _Tape_position, "f") + 1, "f");
|
|
266
293
|
this.normalise();
|
|
294
|
+
__classPrivateFieldSet$4(this, _Tape_viewportDirty, true, "f");
|
|
267
295
|
}
|
|
268
296
|
}
|
|
269
|
-
_Tape_alphabet = new WeakMap(),
|
|
297
|
+
_Tape_alphabet = new WeakMap(), _Tape_right = new WeakMap(), _Tape_left = new WeakMap(), _Tape_position = new WeakMap(), _Tape_viewportWidth = new WeakMap(), _Tape_viewportBuffer = new WeakMap(), _Tape_viewportDirty = new WeakMap(), _Tape_instances = new WeakSet(), _Tape_cellAt = function _Tape_cellAt(logical) {
|
|
298
|
+
if (logical >= 0) {
|
|
299
|
+
return logical < __classPrivateFieldGet$4(this, _Tape_right, "f").length ? __classPrivateFieldGet$4(this, _Tape_right, "f")[logical] : BLANK_INDEX;
|
|
300
|
+
}
|
|
301
|
+
const ix = -logical - 1;
|
|
302
|
+
return ix < __classPrivateFieldGet$4(this, _Tape_left, "f").length ? __classPrivateFieldGet$4(this, _Tape_left, "f")[ix] : BLANK_INDEX;
|
|
303
|
+
};
|
|
270
304
|
|
|
271
305
|
var __classPrivateFieldGet$3 = (undefined && undefined.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
272
306
|
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
@@ -622,25 +656,64 @@ function decodeWriteSymbol(symbol) {
|
|
|
622
656
|
}
|
|
623
657
|
// Format converters (toMermaid / fromMermaid) live in ./graphFormats.
|
|
624
658
|
|
|
625
|
-
var __classPrivateFieldGet$1 = (undefined && undefined.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
626
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
627
|
-
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");
|
|
628
|
-
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
629
|
-
};
|
|
630
659
|
var __classPrivateFieldSet$1 = (undefined && undefined.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
631
660
|
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
632
661
|
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
633
662
|
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");
|
|
634
663
|
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
635
664
|
};
|
|
636
|
-
var
|
|
665
|
+
var __classPrivateFieldGet$1 = (undefined && undefined.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
666
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
667
|
+
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");
|
|
668
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
669
|
+
};
|
|
670
|
+
var _DebugConfig_ownerState, _DebugConfig_before, _DebugConfig_after, _State_id, _State_name, _State_overrodeHaltState, _State_symbolToDataMap, _State_debugRef;
|
|
637
671
|
const ifOtherSymbol = Symbol('other symbol');
|
|
672
|
+
// Module-private symbol used by DebugConfig setters to call State's validator
|
|
673
|
+
// without exposing the validator on the public surface.
|
|
674
|
+
const validateDebugFilter = Symbol('validateDebugFilter');
|
|
675
|
+
class DebugConfig {
|
|
676
|
+
constructor(ownerState, initial) {
|
|
677
|
+
_DebugConfig_ownerState.set(this, void 0);
|
|
678
|
+
_DebugConfig_before.set(this, void 0);
|
|
679
|
+
_DebugConfig_after.set(this, void 0);
|
|
680
|
+
__classPrivateFieldSet$1(this, _DebugConfig_ownerState, ownerState, "f");
|
|
681
|
+
if (initial) {
|
|
682
|
+
if (initial.before !== undefined) {
|
|
683
|
+
this.before = initial.before;
|
|
684
|
+
}
|
|
685
|
+
if (initial.after !== undefined) {
|
|
686
|
+
this.after = initial.after;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
get before() {
|
|
691
|
+
return __classPrivateFieldGet$1(this, _DebugConfig_before, "f");
|
|
692
|
+
}
|
|
693
|
+
set before(v) {
|
|
694
|
+
__classPrivateFieldGet$1(this, _DebugConfig_ownerState, "f")[validateDebugFilter]('before', v);
|
|
695
|
+
__classPrivateFieldSet$1(this, _DebugConfig_before, Array.isArray(v) ? Object.freeze([...v]) : v, "f");
|
|
696
|
+
}
|
|
697
|
+
get after() {
|
|
698
|
+
return __classPrivateFieldGet$1(this, _DebugConfig_after, "f");
|
|
699
|
+
}
|
|
700
|
+
set after(v) {
|
|
701
|
+
__classPrivateFieldGet$1(this, _DebugConfig_ownerState, "f")[validateDebugFilter]('after', v);
|
|
702
|
+
__classPrivateFieldSet$1(this, _DebugConfig_after, Array.isArray(v) ? Object.freeze([...v]) : v, "f");
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
_DebugConfig_ownerState = new WeakMap(), _DebugConfig_before = new WeakMap(), _DebugConfig_after = new WeakMap();
|
|
638
706
|
class State {
|
|
639
707
|
constructor(stateDefinition = null, name) {
|
|
640
708
|
_State_id.set(this, id(this));
|
|
641
709
|
_State_name.set(this, void 0);
|
|
642
710
|
_State_overrodeHaltState.set(this, null);
|
|
643
711
|
_State_symbolToDataMap.set(this, new Map());
|
|
712
|
+
// Shared mutable cell — withOverrodeHaltState wrappers reference the same
|
|
713
|
+
// object so that `state.debug = ...` (and nullings) propagate across them.
|
|
714
|
+
// Note: toGraph / fromGraph deliberately do not serialize debug — debug is
|
|
715
|
+
// a runtime concern, not part of the structural graph.
|
|
716
|
+
_State_debugRef.set(this, { current: null });
|
|
644
717
|
if (stateDefinition) {
|
|
645
718
|
const keys = Object.getOwnPropertyNames(stateDefinition);
|
|
646
719
|
if (keys.length) {
|
|
@@ -699,6 +772,36 @@ class State {
|
|
|
699
772
|
get ref() {
|
|
700
773
|
return this;
|
|
701
774
|
}
|
|
775
|
+
get debug() {
|
|
776
|
+
return __classPrivateFieldGet$1(this, _State_debugRef, "f").current;
|
|
777
|
+
}
|
|
778
|
+
set debug(value) {
|
|
779
|
+
if (value === null) {
|
|
780
|
+
__classPrivateFieldGet$1(this, _State_debugRef, "f").current = null;
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
if (value instanceof DebugConfig) {
|
|
784
|
+
__classPrivateFieldGet$1(this, _State_debugRef, "f").current = value;
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
__classPrivateFieldGet$1(this, _State_debugRef, "f").current = new DebugConfig(this, value);
|
|
788
|
+
}
|
|
789
|
+
/** @internal — invoked by DebugConfig setters via module-private symbol. */
|
|
790
|
+
[(_State_id = new WeakMap(), _State_name = new WeakMap(), _State_overrodeHaltState = new WeakMap(), _State_symbolToDataMap = new WeakMap(), _State_debugRef = new WeakMap(), validateDebugFilter)](fieldName, filter) {
|
|
791
|
+
if (filter === undefined || filter === true)
|
|
792
|
+
return;
|
|
793
|
+
// haltState has no own transitions; symbol-list filters on it are silent
|
|
794
|
+
// no-ops at the engine level (spec §8.6), so accept any list shape here.
|
|
795
|
+
if (this.isHalt)
|
|
796
|
+
return;
|
|
797
|
+
for (const sym of filter) {
|
|
798
|
+
if (sym !== ifOtherSymbol && !__classPrivateFieldGet$1(this, _State_symbolToDataMap, "f").has(sym)) {
|
|
799
|
+
throw new Error(`State.debug.${fieldName}: symbol is not a transition key of this state `
|
|
800
|
+
+ `(state name: ${__classPrivateFieldGet$1(this, _State_name, "f")}). Common cause: symbol comes from a `
|
|
801
|
+
+ 'different tape block, or doesn\'t match any of this state\'s transitions.');
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
702
805
|
getSymbol(tapeBlock) {
|
|
703
806
|
const symbol = [...__classPrivateFieldGet$1(this, _State_symbolToDataMap, "f").keys()].find((currentSymbol) => tapeBlock.isMatched({
|
|
704
807
|
symbol: currentSymbol,
|
|
@@ -724,6 +827,7 @@ class State {
|
|
|
724
827
|
const state = new State(null, `${this.name}>${overrodeHaltState.name}`);
|
|
725
828
|
__classPrivateFieldSet$1(state, _State_symbolToDataMap, __classPrivateFieldGet$1(this, _State_symbolToDataMap, "f"), "f");
|
|
726
829
|
__classPrivateFieldSet$1(state, _State_overrodeHaltState, overrodeHaltState, "f");
|
|
830
|
+
__classPrivateFieldSet$1(state, _State_debugRef, __classPrivateFieldGet$1(this, _State_debugRef, "f"), "f");
|
|
727
831
|
return state;
|
|
728
832
|
}
|
|
729
833
|
// Single-state introspection — no traversal, no tapeBlock required.
|
|
@@ -894,7 +998,6 @@ class State {
|
|
|
894
998
|
};
|
|
895
999
|
}
|
|
896
1000
|
}
|
|
897
|
-
_State_id = new WeakMap(), _State_name = new WeakMap(), _State_overrodeHaltState = new WeakMap(), _State_symbolToDataMap = new WeakMap();
|
|
898
1001
|
const haltState = new State(null);
|
|
899
1002
|
|
|
900
1003
|
var __classPrivateFieldSet = (undefined && undefined.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
@@ -909,6 +1012,15 @@ var __classPrivateFieldGet = (undefined && undefined.__classPrivateFieldGet) ||
|
|
|
909
1012
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
910
1013
|
};
|
|
911
1014
|
var _TuringMachine_tapeBlock, _TuringMachine_stack;
|
|
1015
|
+
// True iff `filter` matches `symbol` per the DebugConfig semantics.
|
|
1016
|
+
// undefined / [] -> never; true -> always; symbol[] -> exact membership.
|
|
1017
|
+
function matchFilter(filter, symbol) {
|
|
1018
|
+
if (filter === undefined)
|
|
1019
|
+
return false;
|
|
1020
|
+
if (filter === true)
|
|
1021
|
+
return true;
|
|
1022
|
+
return filter.includes(symbol);
|
|
1023
|
+
}
|
|
912
1024
|
class TuringMachine {
|
|
913
1025
|
constructor({ tapeBlock, } = {}) {
|
|
914
1026
|
_TuringMachine_tapeBlock.set(this, void 0);
|
|
@@ -921,12 +1033,22 @@ class TuringMachine {
|
|
|
921
1033
|
get tapeBlock() {
|
|
922
1034
|
return __classPrivateFieldGet(this, _TuringMachine_tapeBlock, "f");
|
|
923
1035
|
}
|
|
924
|
-
run({ initialState, stepsLimit = 1e5, onStep }) {
|
|
1036
|
+
async run({ initialState, stepsLimit = 1e5, onStep, onDebugBreak, }) {
|
|
925
1037
|
const generator = this.runStepByStep({ initialState, stepsLimit });
|
|
1038
|
+
let prevYield = null;
|
|
926
1039
|
for (const machineState of generator) {
|
|
1040
|
+
// 'after' (from prev step) — fire FIRST, with prev yield substituted as the source view.
|
|
1041
|
+
if (machineState.debugBreak?.after && onDebugBreak && prevYield) {
|
|
1042
|
+
await onDebugBreak({ ...prevYield, debugBreak: { after: true } });
|
|
1043
|
+
}
|
|
1044
|
+
// 'before' (current step) — pass current machineState with only the before flag.
|
|
1045
|
+
if (machineState.debugBreak?.before && onDebugBreak) {
|
|
1046
|
+
await onDebugBreak({ ...machineState, debugBreak: { before: true } });
|
|
1047
|
+
}
|
|
927
1048
|
if (onStep instanceof Function) {
|
|
928
1049
|
onStep(machineState);
|
|
929
1050
|
}
|
|
1051
|
+
prevYield = machineState;
|
|
930
1052
|
}
|
|
931
1053
|
}
|
|
932
1054
|
*runStepByStep({ initialState, stepsLimit = 1e5 }) {
|
|
@@ -940,6 +1062,7 @@ class TuringMachine {
|
|
|
940
1062
|
stack.push(state.overrodeHaltState);
|
|
941
1063
|
}
|
|
942
1064
|
let i = 0;
|
|
1065
|
+
let pendingAfterFromPrev = false;
|
|
943
1066
|
while (!state.isHalt) {
|
|
944
1067
|
if (i === stepsLimit) {
|
|
945
1068
|
throw new Error('Long execution');
|
|
@@ -949,10 +1072,12 @@ class TuringMachine {
|
|
|
949
1072
|
const command = state.getCommand(symbol);
|
|
950
1073
|
let nextState = state.getNextState(symbol).ref;
|
|
951
1074
|
try {
|
|
1075
|
+
const beforeMatch = matchFilter(state.debug?.before, symbol)
|
|
1076
|
+
|| (nextState.isHalt && nextState.debug?.before === true);
|
|
952
1077
|
const nextStateForYield = nextState.isHalt && stack.length
|
|
953
1078
|
? stack.slice(-1)[0]
|
|
954
1079
|
: nextState;
|
|
955
|
-
|
|
1080
|
+
const yielded = {
|
|
956
1081
|
step: i,
|
|
957
1082
|
state,
|
|
958
1083
|
currentSymbols: __classPrivateFieldGet(this, _TuringMachine_tapeBlock, "f").currentSymbols,
|
|
@@ -972,6 +1097,17 @@ class TuringMachine {
|
|
|
972
1097
|
movements: command.tapesCommands.map((tapeCommand) => tapeCommand.movement),
|
|
973
1098
|
nextState: nextStateForYield,
|
|
974
1099
|
};
|
|
1100
|
+
if (pendingAfterFromPrev || beforeMatch) {
|
|
1101
|
+
const dbg = {};
|
|
1102
|
+
if (pendingAfterFromPrev)
|
|
1103
|
+
dbg.after = true;
|
|
1104
|
+
if (beforeMatch)
|
|
1105
|
+
dbg.before = true;
|
|
1106
|
+
yielded.debugBreak = dbg;
|
|
1107
|
+
}
|
|
1108
|
+
yield yielded;
|
|
1109
|
+
// Re-evaluate 'after' for THIS visit, to fire on the NEXT yield.
|
|
1110
|
+
pendingAfterFromPrev = matchFilter(state.debug?.after, symbol);
|
|
975
1111
|
__classPrivateFieldGet(this, _TuringMachine_tapeBlock, "f").applyCommand(command, executionSymbol);
|
|
976
1112
|
if (nextState.isHalt && stack.length) {
|
|
977
1113
|
nextState = stack.pop();
|
|
@@ -1300,4 +1436,4 @@ function runOnce(runnable, input, stepsLimit) {
|
|
|
1300
1436
|
};
|
|
1301
1437
|
}
|
|
1302
1438
|
|
|
1303
|
-
export { Alphabet, Command, Reference, State, Tape, TapeBlock, TapeCommand, TuringMachine, equivalentOn, fromMermaid, haltState, ifOtherSymbol, movements, summarize, summarizeGraph, symbolCommands, toMermaid };
|
|
1439
|
+
export { Alphabet, Command, DebugConfig, Reference, State, Tape, TapeBlock, TapeCommand, TuringMachine, equivalentOn, fromMermaid, haltState, ifOtherSymbol, movements, summarize, summarizeGraph, symbolCommands, toMermaid };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@turing-machine-js/machine",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
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": "ccf9a4743965c2a4ea6340ce1de8f6596e094251"
|
|
38
38
|
}
|