@hyperfixi/testing-framework 7.5.0 → 7.6.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 CHANGED
@@ -7,6 +7,130 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [7.6.0] - 2026-11-10
11
+
12
+ The engine takes extensions: upstream's `tailwind` and `socket` load from a script tag or
13
+ `register(...)`, and the Vite plugin bundles them. Spanish and Portuguese handlers agree with their
14
+ event (`a la entrada`, `à mudança`), and German writes an event as the noun it is (`wenn Klick`).
15
+ A minor: new features, and renders that change in es, pt and de; everything written before still
16
+ reads.
17
+
18
+ ### Added
19
+
20
+ - **`@hyperfixi/vite-plugin`: `extensions: ['socket', 'tailwind']`.** The generated bundle
21
+ imports each `@hyperfixi/engine/ext/<name>` and registers it after the modules, and the dev
22
+ `everything` fallback does too. Extensions are opt-in, so the scan never adds one. A project
23
+ that lists an extension gets an engine bundle even when the scan found nothing else (a page
24
+ whose only hyperscript is a `socket`).
25
+ - **Which module comes with `toggle` is now read from the grammar.** The plugin used to have
26
+ the rule "`toggleElement` comes with `toggle`" written in; it now reads which modules add
27
+ a reading to a command (`Grammar.fallbacks`).
28
+ - **`verify:reference` checks the engine's extensions as its own.** It registers each
29
+ `dist/ext/<name>.esm.js`, documents their keywords like the engine's, and parses their
30
+ examples: `socket`'s hover example is now checked, where it was skipped as "upstream's".
31
+
32
+ - **`@hyperfixi/engine`: extensions that a script tag can load.** After `hyperfixi-hs.js`, a page
33
+ can load `ext/<name>.js`, which registers its module with the page's engine. Or, from ESM,
34
+ `register(...everything, tailwind)` with `@hyperfixi/engine/ext/tailwind`.
35
+ - **The first extension is upstream's `tailwind`** (the `twDisplay`, `twVisibility` and
36
+ `twOpacity` hide/show strategies), and upstream's 12 tests for it are now in the engine's
37
+ gate: 1,434 of 1,498.
38
+ - **The public object gains `register` and `extensions`, and the script-tag bundle's also gains
39
+ `kit`.** `register` adds modules. `extensions` lists the extension files the page has
40
+ loaded. `kit` holds what a module may call: the same names the library entry exports for
41
+ module authors. Only `hyperfixi-hs.js` carries the kit, so a bundle built from ESM (the Vite
42
+ plugin's) keeps only the parts of the engine it uses; it registers extensions from ESM, and an
43
+ extension file loaded beside it says so.
44
+ - **`register` skips a module it has already added.** When it does add one, it reads again
45
+ the elements whose scripts failed to parse, so an `async` or late-loaded extension still
46
+ applies.
47
+ - **Upstream's own `ext/tailwind.js` also runs on the engine unchanged**, through `use`.
48
+ - **`@hyperfixi/engine/ext/socket`: upstream's `socket` feature.** `socket Chat ws://… [with
49
+ timeout n] [on message [as JSON] …] end` defines `Chat`, a WebSocket with `raw`,
50
+ `dispatchEvent` (`send ping to Chat` sends the event as JSON) and an `rpc` proxy
51
+ (`Chat.rpc.f(…)` returns a promise for the reply).
52
+ - **Upstream's 16 socket tests pass:** the suite is 1,450 of 1,498, with 48 known failures.
53
+ - **Two differences from upstream:** the socket opens when the feature is installed, not
54
+ when it is parsed, so a parse alone opens nothing; and with `as JSON` a message that is
55
+ JSON `0`, `false` or `null` is that value, where upstream rejects it.
56
+ - **The kit grows to what a feature needs:** `dotOrColonPath`, `urlOrExpression`,
57
+ `evalStatic`, `commandList`, `makeContext`, `runList` and `assignToNamespace`. Each is
58
+ exported from `@hyperfixi/engine` and carried on `hyperfixi-hs.js`'s `_hyperscript.kit`.
59
+
60
+ ### Changed
61
+
62
+ - **`@lokascript/semantic`: a Spanish or Portuguese handler's head agrees with its event, and
63
+ German writes an event as a noun.** Renders only; everything written before still reads.
64
+ - **es, pt:** `al` and `ao` hold the masculine article, and were written before every event
65
+ (`al entrada agregar .a`, `ao mudança de #search …`). A feminine event takes `a la` and `à`:
66
+ `a la entrada agregar .a`, `à mudança de #search disparar envio em #form`. The first event of
67
+ an `or` decides; an infinitive (`al redimensionar`) and a custom name (`al my:event`) keep the
68
+ masculine. Each profile lists its feminine event words (`eventHandler.feminineEventMarker`).
69
+ es reads `a la` as the head only before one of them, so `poner x a la` is still a put into a
70
+ variable named `la`. pt's `à` joins its letters and prepositions.
71
+ - **de:** an event's word takes its capital wherever a command names one: `wenn Klick`,
72
+ `wenn Änderung von #search auslösen Absenden auf #form`, `warten Änderung`. An event with no
73
+ German word stays code (`wenn keyup`). The event table lists each one-word name both ways, so
74
+ `wenn klick` and `wenn doppelklick` read as before.
75
+
76
+ - **`@hyperfixi/engine`: a module can add a reading to a command without changing what the
77
+ command reads (`Grammar.fallbacks`).** A fallback registered for a keyword is tried, from just
78
+ after the keyword, only when the command's own rule fails to parse, and it counts only if it
79
+ reads at least as far as that rule got. `toggle <element>` (the `toggleElement` module) is now
80
+ one. Every `toggle` that upstream reads now goes through upstream's rule exactly (registering
81
+ `toggleElement` used to send every `toggle <target>` through a different expression rule).
82
+ An unfinished upstream `toggle` now reports upstream's error: `toggle #d as` used to say
83
+ `Expected 'modal'`, and now says `Expected dotOrColonPath`, as upstream does. The
84
+ `Grammar.toggleElement` slot is gone; a module that set it registers a fallback instead.
85
+ `Parser` gains `mark()` and `reset()`.
86
+
87
+ ### Fixed
88
+
89
+ - **`@lokascript/semantic`: a translated `socket` comes back as the socket upstream reads.**
90
+ A socket's `on message [as JSON] …` is its own clause, closed by the socket's one `end` (or
91
+ by the end of the script). Semantic read it as an event handler, eventsource's shape, so:
92
+ - a clause closed by `end` came back with a second `end`, in every language. The engine
93
+ rejects that, so a translated socket failed to parse on the page;
94
+ - a clause with no `end` (upstream's tests write them that way) lost its body, and a
95
+ clause's `as JSON` was read as leftover input. `translate` refused both;
96
+ - a dotted name came back split: `socket MyApp.chat …` became `socket MyApp .chat …`, a
97
+ different socket.
98
+
99
+ `as JSON` is now written with the language's own word for `as`, as a conversion is (es
100
+ `como JSON`, ja `として JSON`). Text semantic wrote before, with two `end`s, still reads as
101
+ the one socket it was.
102
+
103
+ Where a language puts its handler word after the event (ja `messageで`, tr `message da`), a
104
+ socket or eventsource whose body starts with a word that looks like an event (ja
105
+ `messageで messageを 記録`, "on message log message") now reads. The handler word used to
106
+ be taken as the start of the body.
107
+ - **Measured by the command-shape gate, which now reads extension syntax.** Its engine
108
+ registers every extension the engine builds. Its harvest reads upstream's `ext/` tests,
109
+ and the scripts a test writes as `textContent` (how upstream's socket tests write theirs).
110
+ That adds 33 cases (1,168 in all), and each passes in all 24 lanes. Before this fix, 4 of
111
+ the 16 socket cases were refused in every language and 1 was silent in every language.
112
+
113
+ ## [7.5.1] - 2026-10-23
114
+
115
+ The last two silent misreads of hand-written non-English hyperscript that the 7.3.0 live test
116
+ found: one is now refused, the other reads right.
117
+
118
+ ### Fixed
119
+
120
+ - **ko: an event word followed by its object particle is the verb's object, not a handler.** At
121
+ top level, hand-written `로드 를 대기` (wait for load) read as `on load wait 를`: a handler on
122
+ `load` waiting on a variable named `를`, and translate() did not refuse it. Top level is now
123
+ refused, as ja `ロード を 待つ` and tr `yükleme i bekle` already were; inside a handler,
124
+ `클릭 할 때 로드 를 대기` still reads `wait for load`. ko's own render of
125
+ `trigger click on #btn …` (`#btn에 클릭을 트리거 …`) had been refused for the same reason and now
126
+ translates.
127
+ - **es `eso`, `esto` and a detached `lo` read as `it`, as do pt `isto` and fr `cela`/`ceci`.**
128
+ `poner eso en #o` read `put eso into #o`, a variable nobody set, with no error. Most profiles
129
+ already write `it` as a demonstrative (pt `isso`, fr `ça`, ru `это`, he `זה`, ja `それ`). What
130
+ `it` renders to is unchanged. A variable spelled like one of these words now reads as `it` in
131
+ that language, as one named `isso` already did in pt; MCP `translate_code` reports it
132
+ (`NAME_COLLISION`) with a rename.
133
+
10
134
  ## [7.5.0] - 2026-10-22
11
135
 
12
136
  What a live test of 7.3.0 found users hitting. The Vite plugin loads in a CommonJS project and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hyperfixi/testing-framework",
3
- "version": "7.5.0",
3
+ "version": "7.6.0",
4
4
  "description": "Cross-platform behavior testing suite for LokaScript applications",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -57,25 +57,25 @@
57
57
  "jsdom": "^30.1.3"
58
58
  },
59
59
  "devDependencies": {
60
- "@hyperfixi/engine": "^7.5.0",
61
- "@hyperfixi/patterns-reference": "^7.5.0",
62
- "@lokascript/compilation-service": "^7.5.0",
63
- "@lokascript/hyperscript-adapter": "^7.5.0",
64
- "@lokascript/i18n": "^7.5.0",
65
- "@lokascript/semantic": "^7.5.0",
60
+ "@hyperfixi/engine": "^7.6.0",
61
+ "@hyperfixi/patterns-reference": "^7.6.0",
62
+ "@lokascript/compilation-service": "^7.6.0",
63
+ "@lokascript/hyperscript-adapter": "^7.6.0",
64
+ "@lokascript/i18n": "^7.6.0",
65
+ "@lokascript/semantic": "^7.6.0",
66
66
  "@types/diff": "^5.0.0",
67
- "@types/node": "^26.6.5",
67
+ "@types/node": "^26.6.6",
68
68
  "@vitest/coverage-v8": "^5.0.5",
69
69
  "esbuild": "^0.28.2",
70
- "happy-dom": "^21.0.3",
71
- "hyperscript.org": "0.9.93",
70
+ "happy-dom": "^21.0.4",
71
+ "hyperscript.org": "0.9.94",
72
72
  "playwright": "^1.40.0",
73
- "puppeteer": "^25.14.0",
73
+ "puppeteer": "^25.16.0",
74
74
  "tsup": "^9.0.0",
75
75
  "tsx": "^4.23.15",
76
76
  "typescript": "^5.0.0",
77
77
  "url-parse": "^1.5.10",
78
- "vite": "^8.3.4",
78
+ "vite": "^8.3.5",
79
79
  "vitest": "^5.0.5"
80
80
  },
81
81
  "peerDependencies": {
@@ -4094,6 +4094,108 @@
4094
4094
  "origin": "upstream:expressions/relativePositionalExpression.js",
4095
4095
  "family": "expressions/relativePositionalExpression"
4096
4096
  },
4097
+ {
4098
+ "id": "091229c5d5",
4099
+ "source": "init set :userId to 42",
4100
+ "origin": "upstream:ext/hs-include.js",
4101
+ "family": "ext/hs-include"
4102
+ },
4103
+ {
4104
+ "id": "20864ca9a9",
4105
+ "source": "init set :a to 1 then set :b to 2",
4106
+ "origin": "upstream:ext/hs-include.js",
4107
+ "family": "ext/hs-include"
4108
+ },
4109
+ {
4110
+ "id": "681a650ebc",
4111
+ "source": "init set :x to 10 then set :y to 20",
4112
+ "origin": "upstream:ext/hs-include.js",
4113
+ "family": "ext/hs-include"
4114
+ },
4115
+ {
4116
+ "id": "f0587c6604",
4117
+ "source": "init set :shared to 99",
4118
+ "origin": "upstream:ext/hs-include.js",
4119
+ "family": "ext/hs-include"
4120
+ },
4121
+ {
4122
+ "id": "1984d270b6",
4123
+ "source": "init set :data to 'hello'",
4124
+ "origin": "upstream:ext/hs-include.js",
4125
+ "family": "ext/hs-include"
4126
+ },
4127
+ {
4128
+ "id": "fca5d91170",
4129
+ "source": "init set :obj to {name: 'Alice', age: 30}",
4130
+ "origin": "upstream:ext/hs-include.js",
4131
+ "family": "ext/hs-include"
4132
+ },
4133
+ {
4134
+ "id": "c097c27bb7",
4135
+ "source": "init set :ctx to 'parent-val'",
4136
+ "origin": "upstream:ext/hs-include.js",
4137
+ "family": "ext/hs-include"
4138
+ },
4139
+ {
4140
+ "id": "f0a9124287",
4141
+ "source": "init set :secret to 42",
4142
+ "origin": "upstream:ext/hs-include.js",
4143
+ "family": "ext/hs-include"
4144
+ },
4145
+ {
4146
+ "id": "fc81c34197",
4147
+ "source": "init set :parent to 1",
4148
+ "origin": "upstream:ext/hs-include.js",
4149
+ "family": "ext/hs-include"
4150
+ },
4151
+ {
4152
+ "id": "db6494f862",
4153
+ "source": "init set :child to 2",
4154
+ "origin": "upstream:ext/hs-include.js",
4155
+ "family": "ext/hs-include"
4156
+ },
4157
+ {
4158
+ "id": "0f42ea807f",
4159
+ "source": "on click hide with twDisplay",
4160
+ "origin": "upstream:ext/tailwind.js",
4161
+ "family": "ext/tailwind"
4162
+ },
4163
+ {
4164
+ "id": "d4dd4fbc1d",
4165
+ "source": "on click hide with twVisibility",
4166
+ "origin": "upstream:ext/tailwind.js",
4167
+ "family": "ext/tailwind"
4168
+ },
4169
+ {
4170
+ "id": "0152108937",
4171
+ "source": "on click hide with twOpacity",
4172
+ "origin": "upstream:ext/tailwind.js",
4173
+ "family": "ext/tailwind"
4174
+ },
4175
+ {
4176
+ "id": "ff1010c461",
4177
+ "source": "on click show",
4178
+ "origin": "upstream:ext/tailwind.js",
4179
+ "family": "ext/tailwind"
4180
+ },
4181
+ {
4182
+ "id": "f7364805ea",
4183
+ "source": "on click show with twDisplay",
4184
+ "origin": "upstream:ext/tailwind.js",
4185
+ "family": "ext/tailwind"
4186
+ },
4187
+ {
4188
+ "id": "f11631adde",
4189
+ "source": "on click show with twVisibility",
4190
+ "origin": "upstream:ext/tailwind.js",
4191
+ "family": "ext/tailwind"
4192
+ },
4193
+ {
4194
+ "id": "1148eae2fa",
4195
+ "source": "on click show with twOpacity",
4196
+ "origin": "upstream:ext/tailwind.js",
4197
+ "family": "ext/tailwind"
4198
+ },
4097
4199
  {
4098
4200
  "id": "64506b6e66",
4099
4201
  "source": "behavior TheBehaviorWeAreDefiningForHyperscriptTestingPurposes init log 'foo' end end",
@@ -5378,6 +5480,96 @@
5378
5480
  "origin": "upstream:features/set.js",
5379
5481
  "family": "set"
5380
5482
  },
5483
+ {
5484
+ "id": "65ad8f100f",
5485
+ "source": "socket MySocket ws://localhost:1234/ws end",
5486
+ "origin": "upstream:features/socket.js",
5487
+ "family": "socket"
5488
+ },
5489
+ {
5490
+ "id": "e825557954",
5491
+ "source": "socket RelSocket /my-ws end",
5492
+ "origin": "upstream:features/socket.js",
5493
+ "family": "socket"
5494
+ },
5495
+ {
5496
+ "id": "986e3eaa40",
5497
+ "source": "socket TimedSocket ws://localhost/ws with timeout 1500 end",
5498
+ "origin": "upstream:features/socket.js",
5499
+ "family": "socket"
5500
+ },
5501
+ {
5502
+ "id": "1a9bebbdab",
5503
+ "source": "socket TextSocket ws://localhost/ws\n on message\n set window.socketFired to true",
5504
+ "origin": "upstream:features/socket.js",
5505
+ "family": "socket"
5506
+ },
5507
+ {
5508
+ "id": "9034d0021e",
5509
+ "source": "socket JsonSocket ws://localhost/ws\n on message as JSON\n set window.socketFiredJson to true",
5510
+ "origin": "upstream:features/socket.js",
5511
+ "family": "socket"
5512
+ },
5513
+ {
5514
+ "id": "06052308f5",
5515
+ "source": "socket RpcSocket ws://localhost/ws end",
5516
+ "origin": "upstream:features/socket.js",
5517
+ "family": "socket"
5518
+ },
5519
+ {
5520
+ "id": "36a011f738",
5521
+ "source": "socket ErrSocket ws://localhost/ws end",
5522
+ "origin": "upstream:features/socket.js",
5523
+ "family": "socket"
5524
+ },
5525
+ {
5526
+ "id": "d891336fd9",
5527
+ "source": "socket NoTOSocket ws://localhost/ws with timeout 20 end",
5528
+ "origin": "upstream:features/socket.js",
5529
+ "family": "socket"
5530
+ },
5531
+ {
5532
+ "id": "6df4022d85",
5533
+ "source": "socket CustomTOSocket ws://localhost/ws with timeout 60000 end",
5534
+ "origin": "upstream:features/socket.js",
5535
+ "family": "socket"
5536
+ },
5537
+ {
5538
+ "id": "bc88ad8664",
5539
+ "source": "socket DefTOSocket ws://localhost/ws with timeout 50 end",
5540
+ "origin": "upstream:features/socket.js",
5541
+ "family": "socket"
5542
+ },
5543
+ {
5544
+ "id": "658e4c1024",
5545
+ "source": "socket BlacklistSocket ws://localhost/ws end",
5546
+ "origin": "upstream:features/socket.js",
5547
+ "family": "socket"
5548
+ },
5549
+ {
5550
+ "id": "e390bb9a76",
5551
+ "source": "socket DispatchSocket ws://localhost/ws end",
5552
+ "origin": "upstream:features/socket.js",
5553
+ "family": "socket"
5554
+ },
5555
+ {
5556
+ "id": "b013d4626b",
5557
+ "source": "socket ReconnectSocket ws://localhost/ws end",
5558
+ "origin": "upstream:features/socket.js",
5559
+ "family": "socket"
5560
+ },
5561
+ {
5562
+ "id": "6bed0e799a",
5563
+ "source": "socket StrictJsonSocket ws://localhost/ws\n on message as JSON\n set window.strictFired to true",
5564
+ "origin": "upstream:features/socket.js",
5565
+ "family": "socket"
5566
+ },
5567
+ {
5568
+ "id": "50c36977a8",
5569
+ "source": "socket MyApp.chat ws://localhost/ws end",
5570
+ "origin": "upstream:features/socket.js",
5571
+ "family": "socket"
5572
+ },
5381
5573
  {
5382
5574
  "id": "e094621ad0",
5383
5575
  "source": "when $global changes put it into me",
@@ -6800,6 +6992,12 @@
6800
6992
  "origin": "hover:live",
6801
6993
  "family": "live"
6802
6994
  },
6995
+ {
6996
+ "id": "dc07d12b70",
6997
+ "source": "socket Chat ws://localhost:8080/chat\n on message as json\n log message\nend",
6998
+ "origin": "hover:socket",
6999
+ "family": "socket"
7000
+ },
6803
7001
  {
6804
7002
  "id": "90d44b4675",
6805
7003
  "source": "bind $name and #input's value",
@@ -37,15 +37,18 @@ describe('command-shape cases', () => {
37
37
  });
38
38
 
39
39
  // A re-harvest that changes the count is a change to the gate's input:
40
- // update this pin in the same PR, and say what moved.
41
- it('are the 1135 the harvest found', () => {
42
- expect(cases.length).toBe(1135);
40
+ // update this pin in the same PR, and say what moved. (1135 → 1168, 2026-10-25: the
41
+ // engine reads with its extensions, and the harvest reads `ext/` and `textContent`
42
+ // scripts: upstream's 15 socket scripts and socket's hover example, 7 tailwind, 10
43
+ // hs-include `init` scripts.)
44
+ it('are the 1168 the harvest found', () => {
45
+ expect(cases.length).toBe(1168);
43
46
  const byOrigin = cases.reduce<Record<string, number>>((m, c) => {
44
47
  const kind = c.origin.split(':')[0] ?? '';
45
48
  m[kind] = (m[kind] ?? 0) + 1;
46
49
  return m;
47
50
  }, {});
48
- expect(byOrigin).toEqual({ upstream: 947, reference: 122, 'reference-pattern': 8, hover: 58 });
51
+ expect(byOrigin).toEqual({ upstream: 979, reference: 122, 'reference-pattern': 8, hover: 59 });
49
52
  });
50
53
 
51
54
  it('are not edited by hand: each id is the hash of its source', () => {
@@ -42,9 +42,9 @@
42
42
  * Regenerate with tools/regen-command-shapes-baseline.ts.
43
43
  */
44
44
 
45
- import { readFileSync } from 'node:fs';
45
+ import { readdirSync, readFileSync } from 'node:fs';
46
46
  import path from 'node:path';
47
- import { fileURLToPath } from 'node:url';
47
+ import { fileURLToPath, pathToFileURL } from 'node:url';
48
48
  import { FOREIGN_LANGUAGES } from './value-matrix';
49
49
 
50
50
  const here = path.dirname(fileURLToPath(import.meta.url));
@@ -52,6 +52,7 @@ const here = path.dirname(fileURLToPath(import.meta.url));
52
52
  export const CASES_PATH = path.join(here, 'command-shapes.cases.json');
53
53
  export const BASELINE_PATH = path.resolve(here, '../../baselines/command-shapes.json');
54
54
  const RUNNER_PATH = path.resolve(here, '../../../engine/upstream-suite/run.mjs');
55
+ const EXT_DIR = path.resolve(here, '../../../engine/dist/ext');
55
56
 
56
57
  // ---------------------------------------------------------------------------
57
58
  // Cases
@@ -157,10 +158,29 @@ export type Reader = 'program' | 'statements';
157
158
  export const readerOf = (shape: Pick<CommandShapeCase, 'origin'>): Reader =>
158
159
  shape.origin.startsWith('upstream:') ? 'program' : 'statements';
159
160
 
160
- /** The engine's two readers. Statements exclude a lone expression: it is not a script. */
161
+ /**
162
+ * The engine's two readers. Statements exclude a lone expression: it is not a script.
163
+ *
164
+ * They read with every module AND every extension the engine builds (`dist/ext/<name>.esm.js`,
165
+ * each exporting a module named `<name>`): a page that loads `ext/socket.js` is translated
166
+ * like any other, so its syntax is a case like any other. Read with `everything` alone, the
167
+ * harvest dropped every socket script as one the engine does not parse.
168
+ */
161
169
  export async function loadEngineReaders(): Promise<Record<Reader, EngineParser>> {
162
170
  const engine = await import('@hyperfixi/engine');
163
- engine.register(...engine.everything);
171
+ const extensions = await Promise.all(
172
+ readdirSync(EXT_DIR)
173
+ .filter(f => f.endsWith('.esm.js'))
174
+ .sort()
175
+ .map(async f => {
176
+ const name = f.slice(0, -'.esm.js'.length);
177
+ const mod = (await import(pathToFileURL(path.join(EXT_DIR, f)).href))[name];
178
+ if (typeof mod !== 'function') throw new Error(`${f} exports no module named ${name}`);
179
+ return mod as (typeof engine.everything)[number];
180
+ })
181
+ );
182
+ if (extensions.length === 0) throw new Error(`no extensions in ${EXT_DIR}: build the engine`);
183
+ engine.register(...engine.everything, ...extensions);
164
184
  return {
165
185
  program: { parse: engine.parseProgram },
166
186
  statements: {