@turing-machine-js/visuals 7.0.0-alpha.6 → 7.0.0-alpha.7
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 +19 -0
- package/README.md +237 -11
- package/dist/format.d.ts +115 -0
- package/dist/index.cjs +100 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +98 -1
- package/package.json +3 -3
- package/dist/applyHighlight.js +0 -191
- package/dist/format.js +0 -46
- package/dist/graphIndexes.js +0 -58
- package/dist/graphUtils.js +0 -76
- package/dist/highlightOps.js +0 -36
- package/dist/index.js +0 -6
- package/dist/recordSnippet.js +0 -92
- package/dist/types.js +0 -1
- package/src/applyHighlight.spec.ts +0 -331
- package/src/applyHighlight.ts +0 -217
- package/src/fixtures/graphs/post-walk-mark.json +0 -108
- package/src/fixtures/graphs/turing-callable-subtree.json +0 -108
- package/src/fixtures/graphs/turing-copy-two-tapes.json +0 -87
- package/src/fixtures/graphs/turing-replace-b.json +0 -72
- package/src/format.spec.ts +0 -100
- package/src/format.ts +0 -51
- package/src/graphIndexes.ts +0 -84
- package/src/graphUtils.spec.ts +0 -112
- package/src/graphUtils.ts +0 -74
- package/src/highlightOps.ts +0 -94
- package/src/index.ts +0 -10
- package/src/recordSnippet.spec.ts +0 -275
- package/src/recordSnippet.ts +0 -141
- package/src/types.ts +0 -96
- package/tsconfig.build.json +0 -11
- package/tsconfig.build.tsbuildinfo +0 -1
- package/tsconfig.json +0 -10
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"initialId": 5,
|
|
3
|
-
"alphabets": [
|
|
4
|
-
[
|
|
5
|
-
" ",
|
|
6
|
-
"a",
|
|
7
|
-
"b",
|
|
8
|
-
"*"
|
|
9
|
-
]
|
|
10
|
-
],
|
|
11
|
-
"nodes": {
|
|
12
|
-
"0": {
|
|
13
|
-
"id": 0,
|
|
14
|
-
"name": "id:0",
|
|
15
|
-
"isHalt": true,
|
|
16
|
-
"isHaltMarker": false,
|
|
17
|
-
"isWrapper": false,
|
|
18
|
-
"bareStateId": null,
|
|
19
|
-
"frameId": null,
|
|
20
|
-
"transitions": [],
|
|
21
|
-
"overriddenHaltStateId": null,
|
|
22
|
-
"tags": []
|
|
23
|
-
},
|
|
24
|
-
"3": {
|
|
25
|
-
"id": 3,
|
|
26
|
-
"name": "walkToBlank",
|
|
27
|
-
"isHalt": false,
|
|
28
|
-
"isHaltMarker": false,
|
|
29
|
-
"isWrapper": false,
|
|
30
|
-
"bareStateId": null,
|
|
31
|
-
"frameId": 3,
|
|
32
|
-
"transitions": [
|
|
33
|
-
{
|
|
34
|
-
"pattern": "B",
|
|
35
|
-
"command": [
|
|
36
|
-
{
|
|
37
|
-
"symbol": "K",
|
|
38
|
-
"movement": "S"
|
|
39
|
-
}
|
|
40
|
-
],
|
|
41
|
-
"nextStateId": -3,
|
|
42
|
-
"id": "3.0"
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
"pattern": "*",
|
|
46
|
-
"command": [
|
|
47
|
-
{
|
|
48
|
-
"symbol": "K",
|
|
49
|
-
"movement": "R"
|
|
50
|
-
}
|
|
51
|
-
],
|
|
52
|
-
"nextStateId": 3,
|
|
53
|
-
"id": "3.1"
|
|
54
|
-
}
|
|
55
|
-
],
|
|
56
|
-
"overriddenHaltStateId": null,
|
|
57
|
-
"tags": []
|
|
58
|
-
},
|
|
59
|
-
"4": {
|
|
60
|
-
"id": 4,
|
|
61
|
-
"name": "writeMarker",
|
|
62
|
-
"isHalt": false,
|
|
63
|
-
"isHaltMarker": false,
|
|
64
|
-
"isWrapper": false,
|
|
65
|
-
"bareStateId": null,
|
|
66
|
-
"frameId": null,
|
|
67
|
-
"transitions": [
|
|
68
|
-
{
|
|
69
|
-
"pattern": "*",
|
|
70
|
-
"command": [
|
|
71
|
-
{
|
|
72
|
-
"symbol": "'*'",
|
|
73
|
-
"movement": "S"
|
|
74
|
-
}
|
|
75
|
-
],
|
|
76
|
-
"nextStateId": 0,
|
|
77
|
-
"id": "4.0"
|
|
78
|
-
}
|
|
79
|
-
],
|
|
80
|
-
"overriddenHaltStateId": null,
|
|
81
|
-
"tags": []
|
|
82
|
-
},
|
|
83
|
-
"5": {
|
|
84
|
-
"id": 5,
|
|
85
|
-
"name": "walkToBlank(writeMarker)",
|
|
86
|
-
"isHalt": false,
|
|
87
|
-
"isHaltMarker": false,
|
|
88
|
-
"isWrapper": true,
|
|
89
|
-
"bareStateId": 3,
|
|
90
|
-
"frameId": null,
|
|
91
|
-
"transitions": [],
|
|
92
|
-
"overriddenHaltStateId": 4,
|
|
93
|
-
"tags": []
|
|
94
|
-
},
|
|
95
|
-
"-3": {
|
|
96
|
-
"id": -3,
|
|
97
|
-
"name": "halt",
|
|
98
|
-
"isHalt": true,
|
|
99
|
-
"isHaltMarker": true,
|
|
100
|
-
"isWrapper": false,
|
|
101
|
-
"bareStateId": null,
|
|
102
|
-
"frameId": 3,
|
|
103
|
-
"transitions": [],
|
|
104
|
-
"overriddenHaltStateId": null,
|
|
105
|
-
"tags": []
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"initialId": 2,
|
|
3
|
-
"alphabets": [
|
|
4
|
-
[
|
|
5
|
-
"␣",
|
|
6
|
-
"a",
|
|
7
|
-
"b"
|
|
8
|
-
],
|
|
9
|
-
[
|
|
10
|
-
"␣",
|
|
11
|
-
"a",
|
|
12
|
-
"b"
|
|
13
|
-
]
|
|
14
|
-
],
|
|
15
|
-
"nodes": {
|
|
16
|
-
"0": {
|
|
17
|
-
"id": 0,
|
|
18
|
-
"name": "id:0",
|
|
19
|
-
"isHalt": true,
|
|
20
|
-
"isHaltMarker": false,
|
|
21
|
-
"isWrapper": false,
|
|
22
|
-
"bareStateId": null,
|
|
23
|
-
"frameId": null,
|
|
24
|
-
"transitions": [],
|
|
25
|
-
"overriddenHaltStateId": null,
|
|
26
|
-
"tags": []
|
|
27
|
-
},
|
|
28
|
-
"2": {
|
|
29
|
-
"id": 2,
|
|
30
|
-
"name": "id:2",
|
|
31
|
-
"isHalt": false,
|
|
32
|
-
"isHaltMarker": false,
|
|
33
|
-
"isWrapper": false,
|
|
34
|
-
"bareStateId": null,
|
|
35
|
-
"frameId": null,
|
|
36
|
-
"transitions": [
|
|
37
|
-
{
|
|
38
|
-
"pattern": "'a',*",
|
|
39
|
-
"command": [
|
|
40
|
-
{
|
|
41
|
-
"symbol": "'a'",
|
|
42
|
-
"movement": "R"
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
"symbol": "'a'",
|
|
46
|
-
"movement": "R"
|
|
47
|
-
}
|
|
48
|
-
],
|
|
49
|
-
"nextStateId": 2,
|
|
50
|
-
"id": "2.0"
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
"pattern": "'b',*",
|
|
54
|
-
"command": [
|
|
55
|
-
{
|
|
56
|
-
"symbol": "'b'",
|
|
57
|
-
"movement": "R"
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
"symbol": "'b'",
|
|
61
|
-
"movement": "R"
|
|
62
|
-
}
|
|
63
|
-
],
|
|
64
|
-
"nextStateId": 2,
|
|
65
|
-
"id": "2.1"
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
"pattern": "B,*",
|
|
69
|
-
"command": [
|
|
70
|
-
{
|
|
71
|
-
"symbol": "K",
|
|
72
|
-
"movement": "S"
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
"symbol": "K",
|
|
76
|
-
"movement": "S"
|
|
77
|
-
}
|
|
78
|
-
],
|
|
79
|
-
"nextStateId": 0,
|
|
80
|
-
"id": "2.2"
|
|
81
|
-
}
|
|
82
|
-
],
|
|
83
|
-
"overriddenHaltStateId": null,
|
|
84
|
-
"tags": []
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"initialId": 1,
|
|
3
|
-
"alphabets": [
|
|
4
|
-
[
|
|
5
|
-
"␣",
|
|
6
|
-
"a",
|
|
7
|
-
"b",
|
|
8
|
-
"c",
|
|
9
|
-
"*"
|
|
10
|
-
]
|
|
11
|
-
],
|
|
12
|
-
"nodes": {
|
|
13
|
-
"0": {
|
|
14
|
-
"id": 0,
|
|
15
|
-
"name": "id:0",
|
|
16
|
-
"isHalt": true,
|
|
17
|
-
"isHaltMarker": false,
|
|
18
|
-
"isWrapper": false,
|
|
19
|
-
"bareStateId": null,
|
|
20
|
-
"frameId": null,
|
|
21
|
-
"transitions": [],
|
|
22
|
-
"overriddenHaltStateId": null,
|
|
23
|
-
"tags": []
|
|
24
|
-
},
|
|
25
|
-
"1": {
|
|
26
|
-
"id": 1,
|
|
27
|
-
"name": "id:1",
|
|
28
|
-
"isHalt": false,
|
|
29
|
-
"isHaltMarker": false,
|
|
30
|
-
"isWrapper": false,
|
|
31
|
-
"bareStateId": null,
|
|
32
|
-
"frameId": null,
|
|
33
|
-
"transitions": [
|
|
34
|
-
{
|
|
35
|
-
"pattern": "'b'",
|
|
36
|
-
"command": [
|
|
37
|
-
{
|
|
38
|
-
"symbol": "'*'",
|
|
39
|
-
"movement": "R"
|
|
40
|
-
}
|
|
41
|
-
],
|
|
42
|
-
"nextStateId": 1,
|
|
43
|
-
"id": "1.0"
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
"pattern": "B",
|
|
47
|
-
"command": [
|
|
48
|
-
{
|
|
49
|
-
"symbol": "K",
|
|
50
|
-
"movement": "L"
|
|
51
|
-
}
|
|
52
|
-
],
|
|
53
|
-
"nextStateId": 0,
|
|
54
|
-
"id": "1.1"
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
"pattern": "*",
|
|
58
|
-
"command": [
|
|
59
|
-
{
|
|
60
|
-
"symbol": "K",
|
|
61
|
-
"movement": "R"
|
|
62
|
-
}
|
|
63
|
-
],
|
|
64
|
-
"nextStateId": 1,
|
|
65
|
-
"id": "1.2"
|
|
66
|
-
}
|
|
67
|
-
],
|
|
68
|
-
"overriddenHaltStateId": null,
|
|
69
|
-
"tags": []
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
package/src/format.spec.ts
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
Alphabet,
|
|
4
|
-
Tape,
|
|
5
|
-
TapeBlock,
|
|
6
|
-
TapeCommand,
|
|
7
|
-
TuringMachine,
|
|
8
|
-
State,
|
|
9
|
-
haltState,
|
|
10
|
-
movements,
|
|
11
|
-
symbolCommands,
|
|
12
|
-
} from '@turing-machine-js/machine';
|
|
13
|
-
import { formatCommand, formatStep } from './format';
|
|
14
|
-
|
|
15
|
-
describe('formatCommand', () => {
|
|
16
|
-
it('formats a literal symbol write + right move', () => {
|
|
17
|
-
const tc = new TapeCommand({ symbol: 'X', movement: movements.right });
|
|
18
|
-
expect(formatCommand(tc)).toBe("'X'/R");
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
it('formats keep + stay as K/S', () => {
|
|
22
|
-
const tc = new TapeCommand({ movement: movements.stay });
|
|
23
|
-
// default symbol is symbolCommands.keep
|
|
24
|
-
expect(formatCommand(tc)).toBe('K/S');
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
it('formats erase + left as E/L', () => {
|
|
28
|
-
const tc = new TapeCommand({ symbol: symbolCommands.erase, movement: movements.left });
|
|
29
|
-
expect(formatCommand(tc)).toBe('E/L');
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('formats keep + right as K/R', () => {
|
|
33
|
-
const tc = new TapeCommand({ symbol: symbolCommands.keep, movement: movements.right });
|
|
34
|
-
expect(formatCommand(tc)).toBe('K/R');
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('formats a literal symbol write + left move', () => {
|
|
38
|
-
const tc = new TapeCommand({ symbol: 'a', movement: movements.left });
|
|
39
|
-
expect(formatCommand(tc)).toBe("'a'/L");
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
describe('formatStep', () => {
|
|
44
|
-
it("formats a single-tape iter: 'a' → 'b'/R", () => {
|
|
45
|
-
const alphabet = new Alphabet([' ', 'a', 'b']);
|
|
46
|
-
const tape = new Tape({ alphabet, symbols: ['a'] });
|
|
47
|
-
const tapeBlock = TapeBlock.fromTapes([tape]);
|
|
48
|
-
const machine = new TuringMachine({ tapeBlock });
|
|
49
|
-
const initialState = new State({
|
|
50
|
-
[tapeBlock.symbol(['a'])]: {
|
|
51
|
-
command: [{ symbol: 'b', movement: movements.right }],
|
|
52
|
-
nextState: haltState,
|
|
53
|
-
},
|
|
54
|
-
});
|
|
55
|
-
const gen = machine.runStepByStep({ initialState });
|
|
56
|
-
const m = gen.next().value!;
|
|
57
|
-
|
|
58
|
-
expect(formatStep(m)).toBe("['a'] → ['b']/[R]");
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('encodes keep as K when nextSymbol equals currentSymbol', () => {
|
|
62
|
-
const alphabet = new Alphabet([' ', 'a', 'b']);
|
|
63
|
-
const tape = new Tape({ alphabet, symbols: ['a'] });
|
|
64
|
-
const tapeBlock = TapeBlock.fromTapes([tape]);
|
|
65
|
-
const machine = new TuringMachine({ tapeBlock });
|
|
66
|
-
const initialState = new State({
|
|
67
|
-
[tapeBlock.symbol(['a'])]: {
|
|
68
|
-
command: [{ symbol: symbolCommands.keep, movement: movements.stay }],
|
|
69
|
-
nextState: haltState,
|
|
70
|
-
},
|
|
71
|
-
});
|
|
72
|
-
const gen = machine.runStepByStep({ initialState });
|
|
73
|
-
const m = gen.next().value!;
|
|
74
|
-
|
|
75
|
-
// keep → nextSymbols[0] === currentSymbols[0], so write cell is K
|
|
76
|
-
expect(formatStep(m)).toBe("['a'] → [K]/[S]");
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('formats a 2-tape iter with comma-separated entries', () => {
|
|
80
|
-
const alphabetA = new Alphabet([' ', 'a', 'b']);
|
|
81
|
-
const alphabetB = new Alphabet([' ', 'x', 'y']);
|
|
82
|
-
const tape1 = new Tape({ alphabet: alphabetA, symbols: ['a'] });
|
|
83
|
-
const tape2 = new Tape({ alphabet: alphabetB, symbols: ['x'] });
|
|
84
|
-
const tapeBlock = TapeBlock.fromTapes([tape1, tape2]);
|
|
85
|
-
const machine = new TuringMachine({ tapeBlock });
|
|
86
|
-
const initialState = new State({
|
|
87
|
-
[tapeBlock.symbol(['a', 'x'])]: {
|
|
88
|
-
command: [
|
|
89
|
-
{ symbol: 'b', movement: movements.right },
|
|
90
|
-
{ symbol: symbolCommands.keep, movement: movements.left },
|
|
91
|
-
],
|
|
92
|
-
nextState: haltState,
|
|
93
|
-
},
|
|
94
|
-
});
|
|
95
|
-
const gen = machine.runStepByStep({ initialState });
|
|
96
|
-
const m = gen.next().value!;
|
|
97
|
-
|
|
98
|
-
expect(formatStep(m)).toBe("['a','x'] → ['b',K]/[R,L]");
|
|
99
|
-
});
|
|
100
|
-
});
|
package/src/format.ts
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
import { movements, symbolCommands, type MachineState, type TapeCommand } from '@turing-machine-js/machine';
|
|
2
|
-
|
|
3
|
-
export const MOVEMENT_LETTER = new Map<symbol, 'L' | 'R' | 'S'>([
|
|
4
|
-
[movements.left, 'L'],
|
|
5
|
-
[movements.right, 'R'],
|
|
6
|
-
[movements.stay, 'S'],
|
|
7
|
-
]);
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Render a single tape command in `WRITE/MOVE` form.
|
|
11
|
-
* - Write: `'X'` (literal symbol) | `K` (keep) | `E` (erase = write blank).
|
|
12
|
-
* - Move: `L` / `R` / `S` from `movements.*`.
|
|
13
|
-
*
|
|
14
|
-
* Matches the engine's edge-label vocabulary so formatted commands line up
|
|
15
|
-
* with the write/move cells in `toMermaid`-emitted edge labels.
|
|
16
|
-
*/
|
|
17
|
-
export function formatCommand(tapeCommand: TapeCommand): string {
|
|
18
|
-
let write: string;
|
|
19
|
-
|
|
20
|
-
if (tapeCommand.symbol === symbolCommands.keep) {
|
|
21
|
-
write = 'K';
|
|
22
|
-
} else if (tapeCommand.symbol === symbolCommands.erase) {
|
|
23
|
-
write = 'E';
|
|
24
|
-
} else {
|
|
25
|
-
write = `'${tapeCommand.symbol as string}'`;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const move = MOVEMENT_LETTER.get(tapeCommand.movement) ?? '?';
|
|
29
|
-
|
|
30
|
-
return `${write}/${move}`;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Render one step's edge-label notation: `[reads] → [writes]/[moves]`.
|
|
35
|
-
* Each role is wrapped in a single `[…]`; multi-tape entries are
|
|
36
|
-
* comma-separated inside the brackets.
|
|
37
|
-
*
|
|
38
|
-
* Matches the engine's `toMermaid` emit so logged steps line up with
|
|
39
|
-
* graph edge labels. Note: `nextSymbols` in `MachineState` is already
|
|
40
|
-
* resolved (keep → current symbol, erase → blank) — `K` is inferred
|
|
41
|
-
* by comparing `nextSymbols[i] === currentSymbols[i]`.
|
|
42
|
-
*/
|
|
43
|
-
export function formatStep(m: MachineState): string {
|
|
44
|
-
const reads = m.currentSymbols.map((s) => `'${s}'`).join(',');
|
|
45
|
-
const writes = m.nextSymbols
|
|
46
|
-
.map((s, i) => (s === m.currentSymbols[i] ? 'K' : `'${s}'`))
|
|
47
|
-
.join(',');
|
|
48
|
-
const moves = m.movements.map((mv) => MOVEMENT_LETTER.get(mv) ?? '?').join(',');
|
|
49
|
-
|
|
50
|
-
return `[${reads}] → [${writes}]/[${moves}]`;
|
|
51
|
-
}
|
package/src/graphIndexes.ts
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import type { Graph } from '@turing-machine-js/machine';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Derived lookups over an engine `Graph` that the highlight + indicator
|
|
5
|
-
* passes need. Recomputed once per Build; consumed read-only thereafter.
|
|
6
|
-
*
|
|
7
|
-
* Pure transformation of `graph` — same input always produces deep-equal
|
|
8
|
-
* output. See `docs/graph-highlight-and-breakpoints.md` for how each
|
|
9
|
-
* field is consumed.
|
|
10
|
-
*/
|
|
11
|
-
export type GraphIndexes = {
|
|
12
|
-
/** `GraphNode.id` → containing callable-subtree frameId. Only nodes
|
|
13
|
-
* with `frameId !== null` (i.e. in-frame states) are present. */
|
|
14
|
-
nodeFrameMap: Map<number, number>;
|
|
15
|
-
|
|
16
|
-
/** frameId → list of wrappers calling into that frame, each with the
|
|
17
|
-
* wrapper's id and its override-target id. Used by both source and
|
|
18
|
-
* destination return-chain passes. */
|
|
19
|
-
frameWrappersMap: Map<number, Array<{ wrapperId: number; overrideId: number | null }>>;
|
|
20
|
-
|
|
21
|
-
/** Cluster label text (as emitted by `toMermaid`) → frameId. The
|
|
22
|
-
* rendered SVG's `g.cluster` carries the label inside a
|
|
23
|
-
* `<foreignObject>`; consumers match by `label.textContent.trim()` to
|
|
24
|
-
* build their own `clusterCache: Map<frameId, SVGElement>`. */
|
|
25
|
-
frameLabelToId: Map<string, number>;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Walk the engine graph once and build all derived lookups. Cheap;
|
|
30
|
-
* intended to run on every Build (graph identity changes per build).
|
|
31
|
-
*/
|
|
32
|
-
export function indexGraph(graph: Graph | null): GraphIndexes {
|
|
33
|
-
const nodeFrameMap = new Map<number, number>();
|
|
34
|
-
const frameWrappersMap = new Map<
|
|
35
|
-
number,
|
|
36
|
-
Array<{ wrapperId: number; overrideId: number | null }>
|
|
37
|
-
>();
|
|
38
|
-
const frameLabelToId = new Map<string, number>();
|
|
39
|
-
|
|
40
|
-
if (!graph) return { nodeFrameMap, frameWrappersMap, frameLabelToId };
|
|
41
|
-
|
|
42
|
-
for (const node of Object.values(graph.nodes)) {
|
|
43
|
-
if (node.frameId !== null) nodeFrameMap.set(node.id, node.frameId);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// For each wrapper, append to its bare's frame entry. Multiple wrappers
|
|
47
|
-
// can share the same bare with different overrides; we record them all
|
|
48
|
-
// so the return-chain passes can highlight every candidate.
|
|
49
|
-
for (const node of Object.values(graph.nodes)) {
|
|
50
|
-
if (!node.isWrapper || node.bareStateId === null) continue;
|
|
51
|
-
const bare = graph.nodes[node.bareStateId];
|
|
52
|
-
if (!bare || bare.frameId === null) continue;
|
|
53
|
-
const entry = { wrapperId: node.id, overrideId: node.overriddenHaltStateId };
|
|
54
|
-
const arr = frameWrappersMap.get(bare.frameId);
|
|
55
|
-
if (arr) arr.push(entry);
|
|
56
|
-
else frameWrappersMap.set(bare.frameId, [entry]);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Cluster label reconstruction: mirrors the engine's `toMermaid` emit
|
|
60
|
-
// (`callable subtree of NAME` for single-bare frames, `callable scope:
|
|
61
|
-
// A ∪ B ∪ …` for union frames; bare names sorted by id). Consumers
|
|
62
|
-
// need this to map mermaid's rendered cluster (whose own SVG id is the
|
|
63
|
-
// useless literal `[object Object]`) back to a frameId.
|
|
64
|
-
const bareIds = new Set<number>();
|
|
65
|
-
for (const n of Object.values(graph.nodes)) {
|
|
66
|
-
if (n.isWrapper && n.bareStateId !== null) bareIds.add(n.bareStateId);
|
|
67
|
-
}
|
|
68
|
-
const frameToBareNames = new Map<number, string[]>();
|
|
69
|
-
for (const n of Object.values(graph.nodes).sort((a, b) => a.id - b.id)) {
|
|
70
|
-
if (n.isWrapper || n.isHaltMarker || n.frameId === null) continue;
|
|
71
|
-
if (!bareIds.has(n.id)) continue;
|
|
72
|
-
const arr = frameToBareNames.get(n.frameId) ?? [];
|
|
73
|
-
arr.push(n.name);
|
|
74
|
-
frameToBareNames.set(n.frameId, arr);
|
|
75
|
-
}
|
|
76
|
-
for (const [frameId, names] of frameToBareNames) {
|
|
77
|
-
const label = names.length > 1
|
|
78
|
-
? `callable scope: ${names.join(' ∪ ')}`
|
|
79
|
-
: `callable subtree of ${names[0] ?? frameId}`;
|
|
80
|
-
frameLabelToId.set(label, frameId);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return { nodeFrameMap, frameWrappersMap, frameLabelToId };
|
|
84
|
-
}
|
package/src/graphUtils.spec.ts
DELETED
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { readFileSync } from 'node:fs';
|
|
3
|
-
import { resolve, dirname } from 'node:path';
|
|
4
|
-
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import { bareIdOf, equivalentIds, highlightExpand } from './graphUtils';
|
|
6
|
-
import type { Graph } from '@turing-machine-js/machine';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Tests for the pure breakpoint / highlight class helpers in
|
|
10
|
-
* `graphUtils.ts`. These rules feed both the breakpoint indicator
|
|
11
|
-
* (`applyIndicator`) and the context-menu "Shared with" info line
|
|
12
|
-
* (`MachineGraph.svelte`); the upstream engine v7 collapses wrapper
|
|
13
|
-
* states (`State.withOverriddenHaltState`) onto a shared `#debugRef`
|
|
14
|
-
* with their bare, so the demo collapses them into one breakpoint per
|
|
15
|
-
* equivalence class.
|
|
16
|
-
*
|
|
17
|
-
* Fixture: `turing-callable-subtree` has
|
|
18
|
-
* 3 → bare `walkToBlank` (frameId 3)
|
|
19
|
-
* 4 → `writeMarker` (override)
|
|
20
|
-
* 5 → wrapper `walkToBlank(writeMarker)` (bareStateId 3)
|
|
21
|
-
* 0 → halt singleton
|
|
22
|
-
* -3 → halt marker for frame 3
|
|
23
|
-
*/
|
|
24
|
-
|
|
25
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
26
|
-
const __dirname = dirname(__filename);
|
|
27
|
-
|
|
28
|
-
function loadGraph(name: string): Graph {
|
|
29
|
-
const path = resolve(__dirname, './fixtures/graphs', `${name}.json`);
|
|
30
|
-
return JSON.parse(readFileSync(path, 'utf8')) as Graph;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
describe('bareIdOf', () => {
|
|
34
|
-
const g = loadGraph('turing-callable-subtree');
|
|
35
|
-
|
|
36
|
-
it('maps a wrapper id to its bareStateId', () => {
|
|
37
|
-
expect(bareIdOf(5, g)).toBe(3);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('returns the bare id unchanged for non-wrapper positive ids', () => {
|
|
41
|
-
expect(bareIdOf(3, g)).toBe(3);
|
|
42
|
-
expect(bareIdOf(4, g)).toBe(4);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('collapses halt markers (negative ids) to the halt singleton (0)', () => {
|
|
46
|
-
expect(bareIdOf(-3, g)).toBe(0);
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('returns 0 for the halt singleton itself', () => {
|
|
50
|
-
expect(bareIdOf(0, g)).toBe(0);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('returns the id unchanged when graph is null', () => {
|
|
54
|
-
expect(bareIdOf(5, null)).toBe(5);
|
|
55
|
-
expect(bareIdOf(-3, null)).toBe(-3);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('returns the id unchanged when no node entry exists', () => {
|
|
59
|
-
expect(bareIdOf(999, g)).toBe(999);
|
|
60
|
-
});
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
describe('highlightExpand (asymmetric)', () => {
|
|
64
|
-
const g = loadGraph('turing-callable-subtree');
|
|
65
|
-
|
|
66
|
-
it('expands wrapper → [wrapper, bare]', () => {
|
|
67
|
-
expect(highlightExpand(5, g).sort()).toEqual([3, 5]);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('does NOT expand bare → [bare] only (no wrapper sync from bare side)', () => {
|
|
71
|
-
expect(highlightExpand(3, g)).toEqual([3]);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it('returns [id] for a non-wrapper non-bare id', () => {
|
|
75
|
-
expect(highlightExpand(4, g)).toEqual([4]);
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it('returns [id] when graph is null', () => {
|
|
79
|
-
expect(highlightExpand(5, null)).toEqual([5]);
|
|
80
|
-
});
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
describe('equivalentIds (symmetric class lookup)', () => {
|
|
84
|
-
const g = loadGraph('turing-callable-subtree');
|
|
85
|
-
|
|
86
|
-
it('returns [bare, ...wrappers] from a wrapper input', () => {
|
|
87
|
-
expect(equivalentIds(5, g).sort()).toEqual([3, 5]);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('returns [bare, ...wrappers] from the bare input — symmetric', () => {
|
|
91
|
-
expect(equivalentIds(3, g).sort()).toEqual([3, 5]);
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('returns [singleton] for a stand-alone state', () => {
|
|
95
|
-
expect(equivalentIds(4, g)).toEqual([4]);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('halt singleton (0) → [0, ...all halt markers]', () => {
|
|
99
|
-
const ids = equivalentIds(0, g).sort((a, b) => a - b);
|
|
100
|
-
expect(ids).toEqual([-3, 0]);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('halt marker (-3) → same class as halt singleton (symmetric)', () => {
|
|
104
|
-
const ids = equivalentIds(-3, g).sort((a, b) => a - b);
|
|
105
|
-
expect(ids).toEqual([-3, 0]);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('returns [id] when graph is null', () => {
|
|
109
|
-
expect(equivalentIds(5, null)).toEqual([5]);
|
|
110
|
-
expect(equivalentIds(0, null)).toEqual([0]);
|
|
111
|
-
});
|
|
112
|
-
});
|
package/src/graphUtils.ts
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import type { Graph } from '@turing-machine-js/machine';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Normalize an engine `GraphNode.id` to its canonical representative for
|
|
5
|
-
* breakpoint-class lookups (machines-demo#37). Wrappers produced by
|
|
6
|
-
* `State.withOverriddenHaltState` share `#debugRef` with their bare state
|
|
7
|
-
* engine-side (turing-machine-js v7 `State.ts`: `state.#debugRef =
|
|
8
|
-
* bare.#debugRef`), so they form a single breakpoint from the user's POV.
|
|
9
|
-
* This collapses any wrapper id to its bare's id; non-wrapper ids return
|
|
10
|
-
* self. Used so the demo can store ONE canonical id per equivalence class
|
|
11
|
-
* in its breakpoint set, and expand to all class members for indicator
|
|
12
|
-
* rendering — keeping the worker-side toggle count to one per class
|
|
13
|
-
* (multiple toggles on the shared ref would double-flip).
|
|
14
|
-
*/
|
|
15
|
-
export function bareIdOf(id: number, graph: Graph | null): number {
|
|
16
|
-
if (!graph) return id;
|
|
17
|
-
// Halt markers (negative ids, one per frame) are visualization sentinels;
|
|
18
|
-
// at runtime they all collapse to the haltState singleton (id 0). For
|
|
19
|
-
// breakpoint purposes they're a single class — setting BP on any
|
|
20
|
-
// halt-related node sets it on the global haltState.
|
|
21
|
-
if (id < 0) return 0;
|
|
22
|
-
const node = graph.nodes[id];
|
|
23
|
-
if (node && node.isWrapper && node.bareStateId !== null) {
|
|
24
|
-
return node.bareStateId;
|
|
25
|
-
}
|
|
26
|
-
return id;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Asymmetric expansion for the highlight effect (machines-demo#37).
|
|
31
|
-
* Wrapper → `[wrapper, bare]` (the wrapper-entry pause is visually joined
|
|
32
|
-
* to its bare, since the user thinks of them as one call site).
|
|
33
|
-
* Bare → `[bare]` only (when the engine is genuinely on the bare — e.g. a
|
|
34
|
-
* loop iter — the wrapper is not the "active" state and shouldn't get the
|
|
35
|
-
* strong highlight).
|
|
36
|
-
* Non-wrapper / non-bare ids return `[id]`.
|
|
37
|
-
*/
|
|
38
|
-
export function highlightExpand(id: number, graph: Graph | null): number[] {
|
|
39
|
-
if (!graph) return [id];
|
|
40
|
-
const node = graph.nodes[id];
|
|
41
|
-
if (node?.isWrapper && node.bareStateId !== null) {
|
|
42
|
-
return [id, node.bareStateId];
|
|
43
|
-
}
|
|
44
|
-
return [id];
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* All GraphNode ids in the same breakpoint equivalence class as `id`.
|
|
49
|
-
* Symmetric — gives consumers the full list of nodes that share an engine
|
|
50
|
-
* breakpoint, regardless of which class member is the input. Used by the
|
|
51
|
-
* context-menu's "Shared with" info line so the user can see at a glance
|
|
52
|
-
* which other nodes flip together.
|
|
53
|
-
*
|
|
54
|
-
* Halt class (canonical id 0): the halt singleton + every halt marker in
|
|
55
|
-
* the graph. Wrapper/bare class: the bare + every wrapper pointing at it.
|
|
56
|
-
* Singleton classes (regular states, idle sentinel proxies) return just
|
|
57
|
-
* the input id.
|
|
58
|
-
*/
|
|
59
|
-
export function equivalentIds(id: number, graph: Graph | null): number[] {
|
|
60
|
-
if (!graph) return [id];
|
|
61
|
-
const canonical = bareIdOf(id, graph);
|
|
62
|
-
if (canonical === 0) {
|
|
63
|
-
const ids: number[] = [0];
|
|
64
|
-
for (const node of Object.values(graph.nodes)) {
|
|
65
|
-
if (node.isHaltMarker) ids.push(node.id);
|
|
66
|
-
}
|
|
67
|
-
return ids;
|
|
68
|
-
}
|
|
69
|
-
const result = new Set<number>([canonical]);
|
|
70
|
-
for (const node of Object.values(graph.nodes)) {
|
|
71
|
-
if (node.isWrapper && node.bareStateId === canonical) result.add(node.id);
|
|
72
|
-
}
|
|
73
|
-
return [...result];
|
|
74
|
-
}
|