@turing-machine-js/machine 3.0.1 → 4.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 +24 -0
- package/README.md +52 -3
- package/dist/classes/State.d.ts +20 -0
- package/dist/classes/TuringMachine.d.ts +16 -2
- package/dist/index.cjs +170 -32
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +170 -33
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,30 @@ 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
|
+
## [4.0.0] - 2026-05-07
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`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.
|
|
12
|
+
- **`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.
|
|
13
|
+
- **`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.
|
|
14
|
+
- **`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.
|
|
15
|
+
- **`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.
|
|
16
|
+
|
|
17
|
+
### Changed (BREAKING)
|
|
18
|
+
|
|
19
|
+
- **`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.
|
|
20
|
+
|
|
21
|
+
## [3.0.2] - 2026-05-04
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- **`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.
|
|
26
|
+
|
|
27
|
+
### Changed (observable)
|
|
28
|
+
|
|
29
|
+
- 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.
|
|
30
|
+
|
|
7
31
|
## [3.0.1] - 2026-04-30
|
|
8
32
|
|
|
9
33
|
### 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 `onDebugBreak` hook:
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
await machine.run({
|
|
263
|
+
initialState,
|
|
264
|
+
onStep: (m) => { /* logger sees every step */ },
|
|
265
|
+
onDebugBreak: 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 `onDebugBreak` 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,10 @@ 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, onDebugBreak, }: RunParameter & {
|
|
22
35
|
onStep?: (machineState: MachineState) => void;
|
|
23
|
-
|
|
36
|
+
onDebugBreak?: (machineState: MachineState) => void | Promise<void>;
|
|
37
|
+
}): Promise<void>;
|
|
24
38
|
runStepByStep({ initialState, stepsLimit }: RunParameter): Generator<MachineState>;
|
|
25
39
|
}
|
|
26
40
|
export {};
|
package/dist/index.cjs
CHANGED
|
@@ -186,25 +186,30 @@ 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');
|
|
199
204
|
}
|
|
200
205
|
__classPrivateFieldSet$4(this, _Tape_alphabet, new Alphabet(alphabet), "f");
|
|
201
206
|
__classPrivateFieldSet$4(this, _Tape_position, position, "f");
|
|
202
|
-
__classPrivateFieldSet$4(this, _Tape_viewportWidth,
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
207
|
+
__classPrivateFieldSet$4(this, _Tape_viewportWidth, 1, "f");
|
|
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
|
-
|
|
212
|
+
this.viewportWidth = viewportWidth;
|
|
208
213
|
}
|
|
209
214
|
get alphabet() {
|
|
210
215
|
return __classPrivateFieldGet$4(this, _Tape_alphabet, "f");
|
|
@@ -213,27 +218,46 @@ class Tape {
|
|
|
213
218
|
return (__classPrivateFieldGet$4(this, _Tape_viewportWidth, "f") - 1) / 2;
|
|
214
219
|
}
|
|
215
220
|
get position() {
|
|
216
|
-
|
|
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;
|
|
217
225
|
}
|
|
218
226
|
get symbol() {
|
|
219
|
-
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")));
|
|
220
228
|
}
|
|
221
229
|
set symbol(symbol) {
|
|
222
230
|
if (!__classPrivateFieldGet$4(this, _Tape_alphabet, "f").has(symbol)) {
|
|
223
231
|
throw new Error('Invalid symbol');
|
|
224
232
|
}
|
|
225
|
-
|
|
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");
|
|
226
241
|
}
|
|
227
242
|
get symbols() {
|
|
228
|
-
|
|
229
|
-
|
|
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;
|
|
230
251
|
}
|
|
231
252
|
get viewport() {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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")];
|
|
237
261
|
}
|
|
238
262
|
get viewportWidth() {
|
|
239
263
|
return __classPrivateFieldGet$4(this, _Tape_viewportWidth, "f");
|
|
@@ -247,27 +271,38 @@ class Tape {
|
|
|
247
271
|
finalWidth += 1;
|
|
248
272
|
}
|
|
249
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");
|
|
250
276
|
this.normalise();
|
|
251
277
|
}
|
|
252
278
|
left() {
|
|
253
279
|
__classPrivateFieldSet$4(this, _Tape_position, __classPrivateFieldGet$4(this, _Tape_position, "f") - 1, "f");
|
|
254
280
|
this.normalise();
|
|
281
|
+
__classPrivateFieldSet$4(this, _Tape_viewportDirty, true, "f");
|
|
255
282
|
}
|
|
256
283
|
normalise() {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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);
|
|
260
288
|
}
|
|
261
|
-
while (__classPrivateFieldGet$4(this,
|
|
262
|
-
__classPrivateFieldGet$4(this,
|
|
289
|
+
while (__classPrivateFieldGet$4(this, _Tape_right, "f").length - 1 < maxLogical) {
|
|
290
|
+
__classPrivateFieldGet$4(this, _Tape_right, "f").push(BLANK_INDEX);
|
|
263
291
|
}
|
|
264
292
|
}
|
|
265
293
|
right() {
|
|
266
294
|
__classPrivateFieldSet$4(this, _Tape_position, __classPrivateFieldGet$4(this, _Tape_position, "f") + 1, "f");
|
|
267
295
|
this.normalise();
|
|
296
|
+
__classPrivateFieldSet$4(this, _Tape_viewportDirty, true, "f");
|
|
268
297
|
}
|
|
269
298
|
}
|
|
270
|
-
_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
|
+
};
|
|
271
306
|
|
|
272
307
|
var __classPrivateFieldGet$3 = (undefined && undefined.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
273
308
|
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
@@ -623,25 +658,64 @@ function decodeWriteSymbol(symbol) {
|
|
|
623
658
|
}
|
|
624
659
|
// Format converters (toMermaid / fromMermaid) live in ./graphFormats.
|
|
625
660
|
|
|
626
|
-
var __classPrivateFieldGet$1 = (undefined && undefined.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
627
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
628
|
-
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");
|
|
629
|
-
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
630
|
-
};
|
|
631
661
|
var __classPrivateFieldSet$1 = (undefined && undefined.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
632
662
|
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
633
663
|
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
634
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");
|
|
635
665
|
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
636
666
|
};
|
|
637
|
-
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;
|
|
638
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();
|
|
639
708
|
class State {
|
|
640
709
|
constructor(stateDefinition = null, name) {
|
|
641
710
|
_State_id.set(this, id(this));
|
|
642
711
|
_State_name.set(this, void 0);
|
|
643
712
|
_State_overrodeHaltState.set(this, null);
|
|
644
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 });
|
|
645
719
|
if (stateDefinition) {
|
|
646
720
|
const keys = Object.getOwnPropertyNames(stateDefinition);
|
|
647
721
|
if (keys.length) {
|
|
@@ -700,6 +774,36 @@ class State {
|
|
|
700
774
|
get ref() {
|
|
701
775
|
return this;
|
|
702
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
|
+
}
|
|
703
807
|
getSymbol(tapeBlock) {
|
|
704
808
|
const symbol = [...__classPrivateFieldGet$1(this, _State_symbolToDataMap, "f").keys()].find((currentSymbol) => tapeBlock.isMatched({
|
|
705
809
|
symbol: currentSymbol,
|
|
@@ -725,6 +829,7 @@ class State {
|
|
|
725
829
|
const state = new State(null, `${this.name}>${overrodeHaltState.name}`);
|
|
726
830
|
__classPrivateFieldSet$1(state, _State_symbolToDataMap, __classPrivateFieldGet$1(this, _State_symbolToDataMap, "f"), "f");
|
|
727
831
|
__classPrivateFieldSet$1(state, _State_overrodeHaltState, overrodeHaltState, "f");
|
|
832
|
+
__classPrivateFieldSet$1(state, _State_debugRef, __classPrivateFieldGet$1(this, _State_debugRef, "f"), "f");
|
|
728
833
|
return state;
|
|
729
834
|
}
|
|
730
835
|
// Single-state introspection — no traversal, no tapeBlock required.
|
|
@@ -895,7 +1000,6 @@ class State {
|
|
|
895
1000
|
};
|
|
896
1001
|
}
|
|
897
1002
|
}
|
|
898
|
-
_State_id = new WeakMap(), _State_name = new WeakMap(), _State_overrodeHaltState = new WeakMap(), _State_symbolToDataMap = new WeakMap();
|
|
899
1003
|
const haltState = new State(null);
|
|
900
1004
|
|
|
901
1005
|
var __classPrivateFieldSet = (undefined && undefined.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
@@ -910,6 +1014,15 @@ var __classPrivateFieldGet = (undefined && undefined.__classPrivateFieldGet) ||
|
|
|
910
1014
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
911
1015
|
};
|
|
912
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
|
+
}
|
|
913
1026
|
class TuringMachine {
|
|
914
1027
|
constructor({ tapeBlock, } = {}) {
|
|
915
1028
|
_TuringMachine_tapeBlock.set(this, void 0);
|
|
@@ -922,12 +1035,22 @@ class TuringMachine {
|
|
|
922
1035
|
get tapeBlock() {
|
|
923
1036
|
return __classPrivateFieldGet(this, _TuringMachine_tapeBlock, "f");
|
|
924
1037
|
}
|
|
925
|
-
run({ initialState, stepsLimit = 1e5, onStep }) {
|
|
1038
|
+
async run({ initialState, stepsLimit = 1e5, onStep, onDebugBreak, }) {
|
|
926
1039
|
const generator = this.runStepByStep({ initialState, stepsLimit });
|
|
1040
|
+
let prevYield = null;
|
|
927
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
|
+
}
|
|
928
1050
|
if (onStep instanceof Function) {
|
|
929
1051
|
onStep(machineState);
|
|
930
1052
|
}
|
|
1053
|
+
prevYield = machineState;
|
|
931
1054
|
}
|
|
932
1055
|
}
|
|
933
1056
|
*runStepByStep({ initialState, stepsLimit = 1e5 }) {
|
|
@@ -941,6 +1064,7 @@ class TuringMachine {
|
|
|
941
1064
|
stack.push(state.overrodeHaltState);
|
|
942
1065
|
}
|
|
943
1066
|
let i = 0;
|
|
1067
|
+
let pendingAfterFromPrev = false;
|
|
944
1068
|
while (!state.isHalt) {
|
|
945
1069
|
if (i === stepsLimit) {
|
|
946
1070
|
throw new Error('Long execution');
|
|
@@ -950,10 +1074,12 @@ class TuringMachine {
|
|
|
950
1074
|
const command = state.getCommand(symbol);
|
|
951
1075
|
let nextState = state.getNextState(symbol).ref;
|
|
952
1076
|
try {
|
|
1077
|
+
const beforeMatch = matchFilter(state.debug?.before, symbol)
|
|
1078
|
+
|| (nextState.isHalt && nextState.debug?.before === true);
|
|
953
1079
|
const nextStateForYield = nextState.isHalt && stack.length
|
|
954
1080
|
? stack.slice(-1)[0]
|
|
955
1081
|
: nextState;
|
|
956
|
-
|
|
1082
|
+
const yielded = {
|
|
957
1083
|
step: i,
|
|
958
1084
|
state,
|
|
959
1085
|
currentSymbols: __classPrivateFieldGet(this, _TuringMachine_tapeBlock, "f").currentSymbols,
|
|
@@ -973,6 +1099,17 @@ class TuringMachine {
|
|
|
973
1099
|
movements: command.tapesCommands.map((tapeCommand) => tapeCommand.movement),
|
|
974
1100
|
nextState: nextStateForYield,
|
|
975
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);
|
|
976
1113
|
__classPrivateFieldGet(this, _TuringMachine_tapeBlock, "f").applyCommand(command, executionSymbol);
|
|
977
1114
|
if (nextState.isHalt && stack.length) {
|
|
978
1115
|
nextState = stack.pop();
|
|
@@ -1303,6 +1440,7 @@ function runOnce(runnable, input, stepsLimit) {
|
|
|
1303
1440
|
|
|
1304
1441
|
exports.Alphabet = Alphabet;
|
|
1305
1442
|
exports.Command = Command;
|
|
1443
|
+
exports.DebugConfig = DebugConfig;
|
|
1306
1444
|
exports.Reference = Reference;
|
|
1307
1445
|
exports.State = State;
|
|
1308
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,25 +184,30 @@ 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');
|
|
197
202
|
}
|
|
198
203
|
__classPrivateFieldSet$4(this, _Tape_alphabet, new Alphabet(alphabet), "f");
|
|
199
204
|
__classPrivateFieldSet$4(this, _Tape_position, position, "f");
|
|
200
|
-
__classPrivateFieldSet$4(this, _Tape_viewportWidth,
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
205
|
+
__classPrivateFieldSet$4(this, _Tape_viewportWidth, 1, "f");
|
|
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
|
-
|
|
210
|
+
this.viewportWidth = viewportWidth;
|
|
206
211
|
}
|
|
207
212
|
get alphabet() {
|
|
208
213
|
return __classPrivateFieldGet$4(this, _Tape_alphabet, "f");
|
|
@@ -211,27 +216,46 @@ class Tape {
|
|
|
211
216
|
return (__classPrivateFieldGet$4(this, _Tape_viewportWidth, "f") - 1) / 2;
|
|
212
217
|
}
|
|
213
218
|
get position() {
|
|
214
|
-
|
|
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;
|
|
215
223
|
}
|
|
216
224
|
get symbol() {
|
|
217
|
-
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")));
|
|
218
226
|
}
|
|
219
227
|
set symbol(symbol) {
|
|
220
228
|
if (!__classPrivateFieldGet$4(this, _Tape_alphabet, "f").has(symbol)) {
|
|
221
229
|
throw new Error('Invalid symbol');
|
|
222
230
|
}
|
|
223
|
-
|
|
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");
|
|
224
239
|
}
|
|
225
240
|
get symbols() {
|
|
226
|
-
|
|
227
|
-
|
|
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;
|
|
228
249
|
}
|
|
229
250
|
get viewport() {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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")];
|
|
235
259
|
}
|
|
236
260
|
get viewportWidth() {
|
|
237
261
|
return __classPrivateFieldGet$4(this, _Tape_viewportWidth, "f");
|
|
@@ -245,27 +269,38 @@ class Tape {
|
|
|
245
269
|
finalWidth += 1;
|
|
246
270
|
}
|
|
247
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");
|
|
248
274
|
this.normalise();
|
|
249
275
|
}
|
|
250
276
|
left() {
|
|
251
277
|
__classPrivateFieldSet$4(this, _Tape_position, __classPrivateFieldGet$4(this, _Tape_position, "f") - 1, "f");
|
|
252
278
|
this.normalise();
|
|
279
|
+
__classPrivateFieldSet$4(this, _Tape_viewportDirty, true, "f");
|
|
253
280
|
}
|
|
254
281
|
normalise() {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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);
|
|
258
286
|
}
|
|
259
|
-
while (__classPrivateFieldGet$4(this,
|
|
260
|
-
__classPrivateFieldGet$4(this,
|
|
287
|
+
while (__classPrivateFieldGet$4(this, _Tape_right, "f").length - 1 < maxLogical) {
|
|
288
|
+
__classPrivateFieldGet$4(this, _Tape_right, "f").push(BLANK_INDEX);
|
|
261
289
|
}
|
|
262
290
|
}
|
|
263
291
|
right() {
|
|
264
292
|
__classPrivateFieldSet$4(this, _Tape_position, __classPrivateFieldGet$4(this, _Tape_position, "f") + 1, "f");
|
|
265
293
|
this.normalise();
|
|
294
|
+
__classPrivateFieldSet$4(this, _Tape_viewportDirty, true, "f");
|
|
266
295
|
}
|
|
267
296
|
}
|
|
268
|
-
_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
|
+
};
|
|
269
304
|
|
|
270
305
|
var __classPrivateFieldGet$3 = (undefined && undefined.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
271
306
|
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
@@ -621,25 +656,64 @@ function decodeWriteSymbol(symbol) {
|
|
|
621
656
|
}
|
|
622
657
|
// Format converters (toMermaid / fromMermaid) live in ./graphFormats.
|
|
623
658
|
|
|
624
|
-
var __classPrivateFieldGet$1 = (undefined && undefined.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
625
|
-
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
626
|
-
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");
|
|
627
|
-
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
628
|
-
};
|
|
629
659
|
var __classPrivateFieldSet$1 = (undefined && undefined.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
630
660
|
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
631
661
|
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
632
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");
|
|
633
663
|
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
634
664
|
};
|
|
635
|
-
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;
|
|
636
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();
|
|
637
706
|
class State {
|
|
638
707
|
constructor(stateDefinition = null, name) {
|
|
639
708
|
_State_id.set(this, id(this));
|
|
640
709
|
_State_name.set(this, void 0);
|
|
641
710
|
_State_overrodeHaltState.set(this, null);
|
|
642
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 });
|
|
643
717
|
if (stateDefinition) {
|
|
644
718
|
const keys = Object.getOwnPropertyNames(stateDefinition);
|
|
645
719
|
if (keys.length) {
|
|
@@ -698,6 +772,36 @@ class State {
|
|
|
698
772
|
get ref() {
|
|
699
773
|
return this;
|
|
700
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
|
+
}
|
|
701
805
|
getSymbol(tapeBlock) {
|
|
702
806
|
const symbol = [...__classPrivateFieldGet$1(this, _State_symbolToDataMap, "f").keys()].find((currentSymbol) => tapeBlock.isMatched({
|
|
703
807
|
symbol: currentSymbol,
|
|
@@ -723,6 +827,7 @@ class State {
|
|
|
723
827
|
const state = new State(null, `${this.name}>${overrodeHaltState.name}`);
|
|
724
828
|
__classPrivateFieldSet$1(state, _State_symbolToDataMap, __classPrivateFieldGet$1(this, _State_symbolToDataMap, "f"), "f");
|
|
725
829
|
__classPrivateFieldSet$1(state, _State_overrodeHaltState, overrodeHaltState, "f");
|
|
830
|
+
__classPrivateFieldSet$1(state, _State_debugRef, __classPrivateFieldGet$1(this, _State_debugRef, "f"), "f");
|
|
726
831
|
return state;
|
|
727
832
|
}
|
|
728
833
|
// Single-state introspection — no traversal, no tapeBlock required.
|
|
@@ -893,7 +998,6 @@ class State {
|
|
|
893
998
|
};
|
|
894
999
|
}
|
|
895
1000
|
}
|
|
896
|
-
_State_id = new WeakMap(), _State_name = new WeakMap(), _State_overrodeHaltState = new WeakMap(), _State_symbolToDataMap = new WeakMap();
|
|
897
1001
|
const haltState = new State(null);
|
|
898
1002
|
|
|
899
1003
|
var __classPrivateFieldSet = (undefined && undefined.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
@@ -908,6 +1012,15 @@ var __classPrivateFieldGet = (undefined && undefined.__classPrivateFieldGet) ||
|
|
|
908
1012
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
909
1013
|
};
|
|
910
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
|
+
}
|
|
911
1024
|
class TuringMachine {
|
|
912
1025
|
constructor({ tapeBlock, } = {}) {
|
|
913
1026
|
_TuringMachine_tapeBlock.set(this, void 0);
|
|
@@ -920,12 +1033,22 @@ class TuringMachine {
|
|
|
920
1033
|
get tapeBlock() {
|
|
921
1034
|
return __classPrivateFieldGet(this, _TuringMachine_tapeBlock, "f");
|
|
922
1035
|
}
|
|
923
|
-
run({ initialState, stepsLimit = 1e5, onStep }) {
|
|
1036
|
+
async run({ initialState, stepsLimit = 1e5, onStep, onDebugBreak, }) {
|
|
924
1037
|
const generator = this.runStepByStep({ initialState, stepsLimit });
|
|
1038
|
+
let prevYield = null;
|
|
925
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
|
+
}
|
|
926
1048
|
if (onStep instanceof Function) {
|
|
927
1049
|
onStep(machineState);
|
|
928
1050
|
}
|
|
1051
|
+
prevYield = machineState;
|
|
929
1052
|
}
|
|
930
1053
|
}
|
|
931
1054
|
*runStepByStep({ initialState, stepsLimit = 1e5 }) {
|
|
@@ -939,6 +1062,7 @@ class TuringMachine {
|
|
|
939
1062
|
stack.push(state.overrodeHaltState);
|
|
940
1063
|
}
|
|
941
1064
|
let i = 0;
|
|
1065
|
+
let pendingAfterFromPrev = false;
|
|
942
1066
|
while (!state.isHalt) {
|
|
943
1067
|
if (i === stepsLimit) {
|
|
944
1068
|
throw new Error('Long execution');
|
|
@@ -948,10 +1072,12 @@ class TuringMachine {
|
|
|
948
1072
|
const command = state.getCommand(symbol);
|
|
949
1073
|
let nextState = state.getNextState(symbol).ref;
|
|
950
1074
|
try {
|
|
1075
|
+
const beforeMatch = matchFilter(state.debug?.before, symbol)
|
|
1076
|
+
|| (nextState.isHalt && nextState.debug?.before === true);
|
|
951
1077
|
const nextStateForYield = nextState.isHalt && stack.length
|
|
952
1078
|
? stack.slice(-1)[0]
|
|
953
1079
|
: nextState;
|
|
954
|
-
|
|
1080
|
+
const yielded = {
|
|
955
1081
|
step: i,
|
|
956
1082
|
state,
|
|
957
1083
|
currentSymbols: __classPrivateFieldGet(this, _TuringMachine_tapeBlock, "f").currentSymbols,
|
|
@@ -971,6 +1097,17 @@ class TuringMachine {
|
|
|
971
1097
|
movements: command.tapesCommands.map((tapeCommand) => tapeCommand.movement),
|
|
972
1098
|
nextState: nextStateForYield,
|
|
973
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);
|
|
974
1111
|
__classPrivateFieldGet(this, _TuringMachine_tapeBlock, "f").applyCommand(command, executionSymbol);
|
|
975
1112
|
if (nextState.isHalt && stack.length) {
|
|
976
1113
|
nextState = stack.pop();
|
|
@@ -1299,4 +1436,4 @@ function runOnce(runnable, input, stepsLimit) {
|
|
|
1299
1436
|
};
|
|
1300
1437
|
}
|
|
1301
1438
|
|
|
1302
|
-
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": "4.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": "6641f9e8fd9073771ffd1768a7a5931ff93349a6"
|
|
38
38
|
}
|