@turing-machine-js/machine 6.0.1 → 6.1.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/README.md +59 -13
- package/dist/classes/State.d.ts +1 -1
- package/dist/index.cjs +14 -0
- package/dist/index.mjs +14 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -3,7 +3,25 @@
|
|
|
3
3
|
[](https://github.com/mellonis/turing-machine-js/actions/workflows/main.yml)
|
|
4
4
|

|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
A composable Turing-machine engine for JavaScript: multi-tape, subroutine composition via `withOverrodeHaltState`, Mermaid round-trip, and runtime breakpoints.
|
|
7
|
+
|
|
8
|
+
<details>
|
|
9
|
+
<summary>Table of contents</summary>
|
|
10
|
+
|
|
11
|
+
- [Install](#install)
|
|
12
|
+
- [Quick start](#quick-start)
|
|
13
|
+
- [Building from a state table](#building-from-a-state-table)
|
|
14
|
+
- [Classes](#classes) — [`Alphabet`](#alphabet) · [`Tape`](#tape) · [`TapeBlock`](#tapeblock) · [`TapeCommand`](#tapecommand) · [`Command`](#command) · [`State`](#state) · [`Reference`](#reference) · [`TuringMachine`](#turingmachine)
|
|
15
|
+
- [Subroutine composition with `withOverrodeHaltState`](#subroutine-composition-with-withoverrodehaltstate)
|
|
16
|
+
- [Debugging breakpoints](#debugging-breakpoints)
|
|
17
|
+
- [Special objects](#special-objects) — [`haltState`](#haltstate) · [`ifOtherSymbol`](#ifothersymbol) · [`movements`](#movements) · [`symbolCommands`](#symbolcommands)
|
|
18
|
+
- [Introspection and testing](#introspection-and-testing)
|
|
19
|
+
- [Versioning notes](#versioning-notes)
|
|
20
|
+
- [Libraries](#libraries)
|
|
21
|
+
- [Links](#links)
|
|
22
|
+
|
|
23
|
+
</details>
|
|
24
|
+
|
|
7
25
|
|
|
8
26
|
## Install
|
|
9
27
|
|
|
@@ -82,7 +100,7 @@ Engine notation: `read → write/move`; `·` = keep, `⌫` = erase, `*` = `ifOth
|
|
|
82
100
|
|
|
83
101
|
</details>
|
|
84
102
|
|
|
85
|
-
A `State` is keyed by JS `Symbol`s returned from `tapeBlock.symbol(pattern)` — the pattern lists the expected symbol under each tape's head. `ifOtherSymbol` is the fallback key when nothing else matches; transitioning into `haltState` stops the run.
|
|
103
|
+
A `State` is keyed by JS `Symbol`s returned from `tapeBlock.symbol(pattern)` — the pattern lists the expected symbol under each tape's head. Sentinels and constants used throughout: [`ifOtherSymbol`](#ifothersymbol) is the fallback key when nothing else matches; transitioning into [`haltState`](#haltstate) stops the run; [`movements`](#movements)`.{left,right,stay}` direct head moves; [`symbolCommands`](#symbolcommands)`.{keep,erase}` are write shortcuts. Full definitions in [§Special objects](#special-objects).
|
|
86
104
|
|
|
87
105
|
For multi-tape machines, pass one element per tape: `tapeBlock.symbol(['0', 'a'])` matches only when tape 1 is at `'0'` and tape 2 is at `'a'`.
|
|
88
106
|
|
|
@@ -309,7 +327,7 @@ The runtime. Owns one `TapeBlock` and drives a state graph until it reaches `hal
|
|
|
309
327
|
```javascript
|
|
310
328
|
const machine = new TuringMachine({ tapeBlock });
|
|
311
329
|
|
|
312
|
-
// Run to halt — `run()`
|
|
330
|
+
// Run to halt — `run()` returns a Promise<void>:
|
|
313
331
|
await machine.run({ initialState, stepsLimit: 1e5 });
|
|
314
332
|
|
|
315
333
|
// Or step-by-step (useful for visualization / debugging):
|
|
@@ -332,6 +350,22 @@ Each yielded `step` (`MachineState`) has these fields:
|
|
|
332
350
|
|
|
333
351
|
`stepsLimit` (default `1e5`) guards against runaway loops — exceeding it throws.
|
|
334
352
|
|
|
353
|
+
#### Choosing between `run()` and `runStepByStep()`
|
|
354
|
+
|
|
355
|
+
Both APIs are first-class — `run()` is built on top of `runStepByStep()` (see [TuringMachine.ts](src/classes/TuringMachine.ts)), and both stay supported. They model different consumer needs:
|
|
356
|
+
|
|
357
|
+
| | `run()` | `runStepByStep()` |
|
|
358
|
+
|---|---|---|
|
|
359
|
+
| Shape | async, returns `Promise<void>` | synchronous generator |
|
|
360
|
+
| Iteration timing | owned by the engine | owned by the consumer (`.next()` per step) |
|
|
361
|
+
| Lifecycle hooks | dispatches `onStep`, `onPause` (gated by the `debug` master switch) | none — yields raw `MachineState` |
|
|
362
|
+
| How `state.debug` reaches the consumer | the `onPause` callback (when `debug: true`) | the optional `debugBreak` field on each yield (always populated; consumer decides what to do) |
|
|
363
|
+
| Best for | run-to-halt with optional breakpoint UI; anything wanting the v6 per-iter `before → step → after` callbacks | synchronous test harnesses, visualizers that need tight control over step timing, custom batching |
|
|
364
|
+
|
|
365
|
+
**Rule of thumb.** If your consumer reads `state.debug` and expects the engine to act on it (pause, fire callbacks), use `run()`. If you want pull-based iteration with full control over timing, use `runStepByStep()` — the `debugBreak` field is still on every yield, so you can inspect breakpoint metadata yourself.
|
|
366
|
+
|
|
367
|
+
**Don't split one logical flow across both APIs.** A consumer that wants stepwise UI *and* hook-driven breakpoints should use `run({ onStep, onPause, debug })` exclusively, implementing "wait between steps" by awaiting a resolvable Promise inside `onStep`. Routing some operations through `runStepByStep()` and others through `run()` means `state.debug` only flows through one of the two paths — a subtle footgun where breakpoints silently disappear on whichever code path uses the generator directly.
|
|
368
|
+
|
|
335
369
|
## Subroutine composition with `withOverrodeHaltState`
|
|
336
370
|
|
|
337
371
|
`state.withOverrodeHaltState(other)` returns a copy of `state` whose would-be halt transitions fall through to `other` at run time. The original is left untouched. This is the engine's only composition primitive — bigger machines are built by stacking smaller halt-on-completion subroutines.
|
|
@@ -430,34 +464,35 @@ flowchart TD
|
|
|
430
464
|
|
|
431
465
|
Wrappers nest: `inner.withOverrodeHaltState(middle).withOverrodeHaltState(outer)` chains halt-redirects through `middle → outer → halt`. `library-binary-numbers/src/index.ts`'s `minusOne` (the `~(~x + 1)` composition) uses a 4-deep nest of wrappers.
|
|
432
466
|
|
|
433
|
-
## Debugging breakpoints
|
|
467
|
+
## Debugging breakpoints
|
|
434
468
|
|
|
435
469
|
Any `State` can carry a runtime-mutable `debug` config that pauses execution at chosen points.
|
|
436
470
|
|
|
437
471
|
```ts
|
|
438
|
-
import { State, haltState, ifOtherSymbol
|
|
472
|
+
import { State, haltState, ifOtherSymbol } from '@turing-machine-js/machine';
|
|
439
473
|
|
|
440
474
|
const myState = new State({...});
|
|
441
475
|
|
|
442
|
-
//
|
|
443
|
-
|
|
476
|
+
// state.debug is always a DebugConfig instance — chained writes work
|
|
477
|
+
// without prior whole-object assignment:
|
|
478
|
+
myState.debug.before = true;
|
|
479
|
+
myState.debug.after = [symA];
|
|
444
480
|
|
|
445
|
-
//
|
|
481
|
+
// Whole-object assignment also works for one-shot setup:
|
|
482
|
+
myState.debug = { before: true };
|
|
446
483
|
myState.debug = { before: [symA] };
|
|
447
|
-
|
|
448
|
-
// Pause both before and after for the same symbol — two pauses per visit:
|
|
449
484
|
myState.debug = { before: [symA], after: [symA] };
|
|
450
485
|
|
|
451
486
|
// Pause when the engine is about to enter halt (program exit OR subroutine pop):
|
|
452
487
|
haltState.debug = { before: true };
|
|
453
488
|
|
|
454
|
-
//
|
|
489
|
+
// Reset filters later — next read returns a fresh empty DebugConfig:
|
|
455
490
|
myState.debug = null;
|
|
456
491
|
```
|
|
457
492
|
|
|
458
493
|
> ⚠️ **`haltState.debug.after` throws.** Halt is terminal — there is no iteration-after-halt for an after-fire to anchor on. Assigning a truthy `.after` to `haltState.debug` (including `{ before: true, after: true }`) throws at write time. Symbol-list filters on `haltState.debug.before` are silent no-ops, since halt has no head symbol; only the wildcard `true` activates.
|
|
459
494
|
|
|
460
|
-
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.
|
|
495
|
+
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. `state.debug` is always a `DebugConfig` instance (lazy-initialized on first read); plain-object input (`state.debug = { before: true }`) is wrapped in a fresh `DebugConfig` automatically. The instance itself is `Object.seal`-ed — typos like `state.debug.bofore = true` throw `TypeError` instead of silently creating a useless property. Per-property setters validate and freeze the stored array, so `state.debug.before.push(...)` also throws `TypeError`.
|
|
461
496
|
|
|
462
497
|
`run()` is async and accepts an `onPause` hook:
|
|
463
498
|
|
|
@@ -547,6 +582,17 @@ Together: use `summarize` to ask "is this machine the right shape?" (size, compo
|
|
|
547
582
|
|
|
548
583
|
For visualization and round-tripping, see `State.toGraph` / `State.fromGraph` and `toMermaid` / `fromMermaid`.
|
|
549
584
|
|
|
585
|
+
## Versioning notes
|
|
586
|
+
|
|
587
|
+
API surface changes since v3, in past tense so the timing of each piece is explicit:
|
|
588
|
+
|
|
589
|
+
- **v4** — `run()` became async (`Promise<void>`). Per-state runtime breakpoints landed (`state.debug.before` / `state.debug.after`); `run()` accepted an `onDebugBreak` hook. `MachineState` exposed on each yield.
|
|
590
|
+
- **v5** — `onDebugBreak` renamed to `onPause`. New `run({ debug: boolean })` master switch suppresses all `onPause` dispatches without unsetting `state.debug` assignments. Assigning a truthy `.after` to `haltState.debug` now throws at write time (halt is terminal — no iteration-after-halt to anchor on).
|
|
591
|
+
- **v6** — Per-iter lifecycle reordered to `before → step → after`, all firing on the same yield. Previously `after` fired on iter K+1's tick with a `prevYield` substitution dance; that substitution is gone. The `MachineState.debugBreak` field shape is unchanged across all three versions.
|
|
592
|
+
- **v6.1** — `state.debug` ergonomics: the field is now always a non-null `DebugConfig` instance (lazy-initialized on first read), so chained field writes like `state.debug.before = true` work on a fresh state without a prior whole-object assignment. The `DebugConfig` instance is `Object.seal`-ed, so typos like `state.debug.bofore = true` throw `TypeError` at write time instead of silently creating a useless property. `state.debug = null` continues to work but semantically means "reset filters" — the next read returns a fresh empty `DebugConfig` (#150).
|
|
593
|
+
|
|
594
|
+
For the full release history, see the [GitHub releases page](https://github.com/mellonis/turing-machine-js/releases).
|
|
595
|
+
|
|
550
596
|
## Libraries
|
|
551
597
|
|
|
552
598
|
- [@turing-machine-js/library-binary-numbers](https://github.com/mellonis/turing-machine-js/tree/master/packages/library-binary-numbers) — binary arithmetic with `^…$` markers, multi-number-per-tape support
|
|
@@ -554,4 +600,4 @@ For visualization and round-tripping, see `State.toGraph` / `State.fromGraph` an
|
|
|
554
600
|
|
|
555
601
|
## Links
|
|
556
602
|
|
|
557
|
-
- [Turing Machine](https://en.wikipedia.org/wiki/Turing_machine) on
|
|
603
|
+
- [Turing Machine](https://en.wikipedia.org/wiki/Turing_machine) on Wikipedia
|
package/dist/classes/State.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ export default class State {
|
|
|
27
27
|
get isHalt(): boolean;
|
|
28
28
|
get overrodeHaltState(): State | null;
|
|
29
29
|
get ref(): this;
|
|
30
|
-
get debug(): DebugConfig
|
|
30
|
+
get debug(): DebugConfig;
|
|
31
31
|
set debug(value: DebugConfig | {
|
|
32
32
|
before?: symbol[] | readonly symbol[] | true;
|
|
33
33
|
after?: symbol[] | readonly symbol[] | true;
|
package/dist/index.cjs
CHANGED
|
@@ -688,6 +688,12 @@ class DebugConfig {
|
|
|
688
688
|
this.after = initial.after;
|
|
689
689
|
}
|
|
690
690
|
}
|
|
691
|
+
// Seal the instance so typos like `cfg.bofore = true` throw at write
|
|
692
|
+
// time (in strict mode, which TS-emitted modules use) instead of
|
|
693
|
+
// silently creating a useless own property. The class's `before`/`after`
|
|
694
|
+
// setters still work — they resolve through the prototype chain and
|
|
695
|
+
// write to private fields, neither of which Object.seal restricts.
|
|
696
|
+
Object.seal(this);
|
|
691
697
|
}
|
|
692
698
|
get before() {
|
|
693
699
|
return __classPrivateFieldGet$1(this, _DebugConfig_before, "f");
|
|
@@ -775,6 +781,14 @@ class State {
|
|
|
775
781
|
return this;
|
|
776
782
|
}
|
|
777
783
|
get debug() {
|
|
784
|
+
// Lazy-init: `state.debug` is never null at read time, so chained writes
|
|
785
|
+
// like `state.debug.before = true` work on a fresh state without a prior
|
|
786
|
+
// whole-object assignment. The setter still accepts `null` to reset the
|
|
787
|
+
// filters; the next read recreates a fresh empty `DebugConfig` on demand.
|
|
788
|
+
// See #150.
|
|
789
|
+
if (__classPrivateFieldGet$1(this, _State_debugRef, "f").current === null) {
|
|
790
|
+
__classPrivateFieldGet$1(this, _State_debugRef, "f").current = new DebugConfig(this);
|
|
791
|
+
}
|
|
778
792
|
return __classPrivateFieldGet$1(this, _State_debugRef, "f").current;
|
|
779
793
|
}
|
|
780
794
|
set debug(value) {
|
package/dist/index.mjs
CHANGED
|
@@ -686,6 +686,12 @@ class DebugConfig {
|
|
|
686
686
|
this.after = initial.after;
|
|
687
687
|
}
|
|
688
688
|
}
|
|
689
|
+
// Seal the instance so typos like `cfg.bofore = true` throw at write
|
|
690
|
+
// time (in strict mode, which TS-emitted modules use) instead of
|
|
691
|
+
// silently creating a useless own property. The class's `before`/`after`
|
|
692
|
+
// setters still work — they resolve through the prototype chain and
|
|
693
|
+
// write to private fields, neither of which Object.seal restricts.
|
|
694
|
+
Object.seal(this);
|
|
689
695
|
}
|
|
690
696
|
get before() {
|
|
691
697
|
return __classPrivateFieldGet$1(this, _DebugConfig_before, "f");
|
|
@@ -773,6 +779,14 @@ class State {
|
|
|
773
779
|
return this;
|
|
774
780
|
}
|
|
775
781
|
get debug() {
|
|
782
|
+
// Lazy-init: `state.debug` is never null at read time, so chained writes
|
|
783
|
+
// like `state.debug.before = true` work on a fresh state without a prior
|
|
784
|
+
// whole-object assignment. The setter still accepts `null` to reset the
|
|
785
|
+
// filters; the next read recreates a fresh empty `DebugConfig` on demand.
|
|
786
|
+
// See #150.
|
|
787
|
+
if (__classPrivateFieldGet$1(this, _State_debugRef, "f").current === null) {
|
|
788
|
+
__classPrivateFieldGet$1(this, _State_debugRef, "f").current = new DebugConfig(this);
|
|
789
|
+
}
|
|
776
790
|
return __classPrivateFieldGet$1(this, _State_debugRef, "f").current;
|
|
777
791
|
}
|
|
778
792
|
set debug(value) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@turing-machine-js/machine",
|
|
3
|
-
"version": "6.0
|
|
3
|
+
"version": "6.1.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": "5a3e3ced5bacd541fefdf087f5b4301267be01d2"
|
|
38
38
|
}
|