xbintsc 0.3.46 → 0.3.49

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.
Files changed (91) hide show
  1. package/AGENTS.md +95 -0
  2. package/README.md +25 -0
  3. package/README.zh-CN.md +23 -0
  4. package/dist/src/cli/hints.d.ts +54 -0
  5. package/dist/src/cli/hints.js +165 -0
  6. package/dist/src/cli/hints.js.map +1 -0
  7. package/dist/src/cli/main.js +73 -9
  8. package/dist/src/cli/main.js.map +1 -1
  9. package/dist/src/codegen/generator/tables.d.ts +26 -0
  10. package/dist/src/codegen/generator/tables.js +64 -12
  11. package/dist/src/codegen/generator/tables.js.map +1 -1
  12. package/dist/src/diagnostics/source-text.d.ts +22 -0
  13. package/dist/src/diagnostics/source-text.js +76 -0
  14. package/dist/src/diagnostics/source-text.js.map +1 -0
  15. package/dist/src/driver/bundler/graph.js +2 -1
  16. package/dist/src/driver/bundler/graph.js.map +1 -1
  17. package/dist/src/driver/compiler.js +3 -2
  18. package/dist/src/driver/compiler.js.map +1 -1
  19. package/dist/src/lexer/scanner/strings.js +16 -3
  20. package/dist/src/lexer/scanner/strings.js.map +1 -1
  21. package/dist/tests/cli/hints.test.d.ts +9 -0
  22. package/dist/tests/cli/hints.test.js +143 -0
  23. package/dist/tests/cli/hints.test.js.map +1 -0
  24. package/dist/tests/cli/main.test.js +6 -4
  25. package/dist/tests/cli/main.test.js.map +1 -1
  26. package/dist/tests/codegen/llvm.test.js +17 -2
  27. package/dist/tests/codegen/llvm.test.js.map +1 -1
  28. package/dist/tests/helpers.js +3 -2
  29. package/dist/tests/helpers.js.map +1 -1
  30. package/dist/tests/lexer/strings.test.js +14 -2
  31. package/dist/tests/lexer/strings.test.js.map +1 -1
  32. package/doc/DESIGN.md +117 -0
  33. package/doc/ai/README.md +63 -0
  34. package/doc/ai/build-recipe.md +137 -0
  35. package/doc/ai/cli.md +142 -0
  36. package/doc/ai/contributing.md +196 -0
  37. package/doc/ai/extensions.md +148 -0
  38. package/doc/ai/language-support.md +152 -0
  39. package/doc/ai/troubleshooting.md +163 -0
  40. package/doc/ai/zh-CN/README.md +56 -0
  41. package/doc/ai/zh-CN/build-recipe.md +132 -0
  42. package/doc/ai/zh-CN/cli.md +127 -0
  43. package/doc/ai/zh-CN/contributing.md +173 -0
  44. package/doc/ai/zh-CN/extensions.md +139 -0
  45. package/doc/ai/zh-CN/language-support.md +147 -0
  46. package/doc/ai/zh-CN/troubleshooting.md +150 -0
  47. package/doc/gui-scripts.md +350 -0
  48. package/doc/gui.md +646 -0
  49. package/doc/icon.md +265 -0
  50. package/doc/implemented.md +373 -0
  51. package/doc/node-implemented.md +588 -0
  52. package/doc/node-unimplemented.md +167 -0
  53. package/doc/post/announce.md +43 -0
  54. package/doc/requirements.md +145 -0
  55. package/doc/unimplemented.md +286 -0
  56. package/doc/xbintsc.config.schema.json +67 -0
  57. package/doc/zh-CN/DESIGN.md +104 -0
  58. package/doc/zh-CN/gui-scripts.md +329 -0
  59. package/doc/zh-CN/gui.md +588 -0
  60. package/doc/zh-CN/icon.md +241 -0
  61. package/doc/zh-CN/implemented.md +365 -0
  62. package/doc/zh-CN/node-implemented.md +533 -0
  63. package/doc/zh-CN/node-unimplemented.md +141 -0
  64. package/doc/zh-CN/plan-require-node-modules.md +284 -0
  65. package/doc/zh-CN/post/announce.md +47 -0
  66. package/doc/zh-CN/requirements.md +134 -0
  67. package/doc/zh-CN/unimplemented.md +247 -0
  68. package/llms.txt +45 -0
  69. package/package.json +4 -1
  70. package/runtime/ext_gui/gui.cpp +3 -1
  71. package/runtime/ext_gui/renderer.cpp +13 -11
  72. package/runtime/ext_gui/renderer_image.cpp +12 -8
  73. package/runtime/ext_gui/renderer_shaders.h +131 -4
  74. package/runtime/ext_gui/renderer_shaders_data.h +1809 -0
  75. package/runtime/ext_gui/renderer_text.cpp +12 -8
  76. package/runtime/ext_gui/shaders.hlsl +98 -0
  77. package/runtime/ext_gui/spirv/fill.frag +19 -0
  78. package/runtime/ext_gui/spirv/fill.vert +42 -0
  79. package/runtime/ext_gui/spirv/image.frag +16 -0
  80. package/runtime/ext_gui/spirv/quad.vert +30 -0
  81. package/runtime/ext_gui/spirv/text.frag +16 -0
  82. package/scripts/build-gui-shaders.mjs +204 -0
  83. package/scripts/build-gui.ts +35 -0
  84. package/scripts/check-file-length.ts +5 -1
  85. package/src/cli/hints.ts +194 -0
  86. package/src/cli/main.ts +82 -9
  87. package/src/codegen/generator/tables.ts +60 -14
  88. package/src/diagnostics/source-text.ts +78 -0
  89. package/src/driver/bundler/graph.ts +2 -1
  90. package/src/driver/compiler.ts +3 -2
  91. package/src/lexer/scanner/strings.ts +16 -3
@@ -0,0 +1,350 @@
1
+ # GUI extension — DOM handles, events and AOT `<script>`
2
+
3
+ Status: **design** — this document specifies how the `gui` extension grows from
4
+ read-only HTML/CSS rendering (M7) into a small, *interactive* DOM with
5
+ **compile-time (AOT) scripts**, without adding a JavaScript engine and without
6
+ letting GUI concerns leak into the core compiler.
7
+
8
+ It is the plan behind milestones **M8** (DOM object model + mutation + element
9
+ events) and **M9** (AOT `<script>` pipeline). The locked decisions in
10
+ `doc/gui.md` still hold; this document only refines what "no page JS" means.
11
+
12
+ ## Motivation
13
+
14
+ Today the engine renders an HTML/CSS tree and exposes only *window-level*
15
+ events (`win.on("click", …)`). The handler receives `e.target` as a CSS
16
+ descriptor string (`div#main.card`), the DOM is read-only (diagnostics only),
17
+ and the only way to change the UI is `win.loadHTML(...)`, which rebuilds
18
+ everything.
19
+
20
+ That is enough for static demos, but the obvious next question — "can I write
21
+ `<script>` and react to clicks on a specific element?" — has no answer yet.
22
+ xbintsc is a **pure AOT compiler** and has **no runtime interpreter/JIT**, so
23
+ `eval`-style script execution is impossible. The only self-consistent way to
24
+ run scripts is to **compile them ahead of time**.
25
+
26
+ ## Goals
27
+
28
+ 1. **Element handles** — `document.querySelector(...)` returns an object with
29
+ stable identity (`a === b` for the same element) and readable/writable DOM
30
+ properties (`id`, `className`, `textContent`, `innerHTML`, `style`,
31
+ `classList`, attributes, traversal, geometry).
32
+ 2. **Mutation** — `appendChild` / `removeChild` / `insertBefore` /
33
+ `replaceChild` / `textContent=` / `innerHTML=` / `classList.*` /
34
+ `style.setProperty` update the tree; the engine restyles + relayouts lazily
35
+ and repaints.
36
+ 3. **Element events** — `el.addEventListener(type, fn, options?)` with capture
37
+ and bubble phases, `removeEventListener`, `dispatchEvent`, `el.click()`,
38
+ event objects with `target` / `currentTarget` / `preventDefault` /
39
+ `stopPropagation`.
40
+ 4. **AOT `<script>`** — a `<script>` body written in TypeScript is extracted at
41
+ **compile time**, compiled by the *existing* xbintsc front-end as a normal
42
+ module, and invoked by the engine after the document is parsed.
43
+ 5. **Zero core-compiler coupling** — the lexer/parser/binder/codegen learn
44
+ nothing about HTML or the GUI. Extensions contribute only modules/objects
45
+ (and, new in M9, an **asset-loader hook**).
46
+ 6. **Backward compatibility** — existing programs and tests keep working:
47
+ `win.on(...)` handlers keep receiving a *string* `e.target`.
48
+
49
+ ## Non-goals
50
+
51
+ - **A JS engine** (QuickJS/V8/…). Still explicitly out of scope. There is no
52
+ runtime `eval`, no interpreter, no JIT.
53
+ - **Dynamic scripts at runtime.** Scripts are compile-time assets. HTML fetched
54
+ over the network or produced at runtime does **not** execute its scripts.
55
+ - **A security sandbox / browser semantics.** There is no origin model; a
56
+ script is ordinary native TS and can `import` anything the compiler allows.
57
+ This is documented as "not a browser".
58
+ - **Full DOM/BOM.** We implement the practical subset a UI needs.
59
+
60
+ ## Locked decisions (refined)
61
+
62
+ | # | Decision |
63
+ | --- | --- |
64
+ | 1 | **No page JS engine.** Behaviour is native TS. `<script>` bodies are *AOT-compiled* to native code by xbintsc and invoked through `xt_call_with_this` — there is still no interpreter. |
65
+ | 8 | **Scripts are compile-time assets.** The HTML asset loader transforms inline `<script>` bodies into compiled modules and replaces them with `data-xt-id` markers; the runtime only *invokes* already-compiled functions. |
66
+ | 9 | **The DOM is owned by the engine; handles are engine objects.** Handles use the same shared-prototype object model as window handles; hidden fields carry a window reference, a document generation and a node index. |
67
+ | 10 | **`window`/`document` are injected as parameters**, not globals, so no global-object machinery is added to the compiler. |
68
+
69
+ ## Architecture
70
+
71
+ ```
72
+ <script>…</script> compile time (bundler)
73
+ │ gui .html asset loader
74
+ ▼
75
+ ┌──────────────────────┐ ┌───────────────────────────┐
76
+ │ transform the HTML │ │ emit __xt_script_<hash> │ a normal
77
+ │ body → function │ │ (window, document) │ xbintsc
78
+ │ <script data-xt-id> │ │ + __registerScript(hash) │ module
79
+ └──────────────────────┘ └───────────────────────────┘
80
+ │ │
81
+ ▼ ▼
82
+ runtime HTML string native code linked into the binary
83
+ │ win.loadHTML(html)
84
+ ▼
85
+ engine parses HTML, finds `data-xt-id` markers, looks each hash up in the
86
+ script registry and calls `fn(windowHandle, documentHandle)`
87
+ ```
88
+
89
+ ### 1. `Extension.assetLoaders` (the only new core hook)
90
+
91
+ The core compiler currently has no way for an extension to influence *how a
92
+ file is loaded*. M9 adds one generic hook to `src/extensions/registry.ts`:
93
+
94
+ ```ts
95
+ interface AssetLoadResult {
96
+ /** Replaces the file contents as seen by the bundler. */
97
+ moduleSource: string;
98
+ /** Additional files this asset depends on (recompiled when they change). */
99
+ dependencies?: string[];
100
+ }
101
+
102
+ interface Extension {
103
+ // …existing fields…
104
+ /** Optional: rewrite an imported asset into a TS module. Keyed by extension. */
105
+ assetLoaders?(): Record<string, (path: string, source: string) => AssetLoadResult>;
106
+ }
107
+ ```
108
+
109
+ The bundler (`src/driver/bundler/graph.ts`) consults the registry immediately
110
+ after `readFileSync`, *before* parsing. The core stays platform-agnostic: it
111
+ only knows "an extension may transform the bytes of `*.foo` into TS".
112
+ The `gui` extension registers an `.html` loader. A plain `import html from
113
+ "./index.html"` therefore yields a **string constant** by default, and (when
114
+ the file contains `<script>`) a **side-effecting module** that registers the
115
+ compiled script bodies and default-exports the rewritten HTML.
116
+
117
+ ### 2. The `.html` transform
118
+
119
+ Given:
120
+
121
+ ```html
122
+ <button id="b">0</button>
123
+ <script lang="ts">
124
+ const b = document.getElementById("b");
125
+ let n = 0;
126
+ b.addEventListener("click", () => { b.textContent = String(++n); });
127
+ </script>
128
+ ```
129
+
130
+ the loader emits a module shaped like:
131
+
132
+ ```ts
133
+ // generated
134
+ const __html = "<button id=\"b\">0</button>\n<script data-xt-id=\"a1b2…\"></script>";
135
+ function __xt_script_a1b2(window: any, document: any): void {
136
+ const b = document.getElementById("b");
137
+ let n = 0;
138
+ b.addEventListener("click", () => { b.textContent = String(++n); });
139
+ }
140
+ __registerScript("a1b2…", __xt_script_a1b2);
141
+ export default __html;
142
+ ```
143
+
144
+ - The id is a content hash of the body (sha256, first 32 hex chars); the body is
145
+ replaced by an empty marker element so the engine can find it in document
146
+ order.
147
+ - `window` and `document` are **function parameters**, so script globals resolve
148
+ as ordinary locals — **no compiler changes**, no global object.
149
+ - `__registerScript(id, fn)` is a new runtime builtin that stores the closure in
150
+ a global `unordered_map<string, xt_value>`.
151
+ - Deferred scripts (`<script defer>`, or all inline scripts, as decided in M9c)
152
+ run after the full document is parsed and the first layout is computed, in
153
+ document order. `DOMContentLoaded` then `load` are fired on `window`.
154
+
155
+ ### 2b. External `<script src>`
156
+
157
+ `<script src="./app.ts">` is read relative to the HTML file, parsed, and split:
158
+ its top-level `import`/`export … from` statements are **hoisted** to the
159
+ generated module (with relative specifiers rewritten so they resolve from the
160
+ HTML file's directory), and the remaining statements are wrapped in the script
161
+ function. This is how an external script still runs at `loadHTML` time while
162
+ being able to `import` other modules.
163
+
164
+ Two limitations are enforced with clear errors rather than silent breakage:
165
+
166
+ - a missing `src` file throws (turned into a bundler diagnostic), and
167
+ - two `src` scripts that import the same local binding name collide; the user
168
+ must alias one of them. (Non-conflicting or identical imports are fine.)
169
+
170
+ `src` URLs (`https://…`, `data:…`, `//host`) are left untouched — they are not
171
+ compile-time assets and never run.
172
+
173
+ ```html
174
+ <button id="b">0</button>
175
+ <script src="./counter.ts"></script>
176
+ ```
177
+
178
+ ```ts
179
+ // counter.ts
180
+ import { double } from "./helper";
181
+ const b = document.getElementById("b");
182
+ let n = 0;
183
+ b.addEventListener("click", () => { n = double(n) + 1; b.textContent = String(n); });
184
+ ```
185
+
186
+ ### 3. Runtime execution
187
+
188
+ `win.loadHTML(html)`:
189
+
190
+ 1. parses the document as today,
191
+ 2. scans for `data-xt-id` markers,
192
+ 3. for each, calls `registry[id](winHandle, docHandle)`,
193
+ 4. triggers the first layout, then fires `DOMContentLoaded` and `load`.
194
+
195
+ Because the calls happen after parsing, `document.getElementById(...)` inside a
196
+ script always finds its element. `innerHTML = …` never executes embedded
197
+ scripts (markers are inert).
198
+
199
+ ## Element handle object model (M8)
200
+
201
+ Handles are plain runtime objects (`xt_object_new_with_proto`) sharing a
202
+ prototype per kind, exactly like window handles:
203
+
204
+ | Hidden field | Meaning |
205
+ | --- | --- |
206
+ | `__xt_gui_win` | owning window object (resolved by `xt_gui_window_from_this`) |
207
+ | `__xt_gui_gen` | document generation the handle was created for |
208
+ | `__xt_gui_node` | index into the window's node table (element/text handles) |
209
+ | `__xt_gui_doc` | `true` for the singleton `document` handle |
210
+
211
+ **Identity is stable**: the window keeps `node_order: vector<Node*>` plus
212
+ `node_index`/`node_handles: map<Node*, …>`, so querying the same element twice
213
+ returns the *same* object value (`a === b`).
214
+
215
+ **Detached nodes** stay alive in the document pool, so a handle held across a
216
+ `remove()` still resolves (tombstoned semantics); a generation mismatch makes a
217
+ stale handle a no-op instead of a dangling pointer.
218
+
219
+ ### Accessors, not fields
220
+
221
+ Read/write DOM properties are defined with `xt_object_define_getter` /
222
+ `xt_object_define_setter`, which already support prototype inheritance with
223
+ `this` = receiver. So `el.textContent = "x"` and `el.id = "y"` work while the
224
+ values live in the C++ tree. `classList` and `style` are small objects that hold
225
+ a back-reference to the element handle (no closures/env needed; the arena never
226
+ frees, so cycles are harmless).
227
+
228
+ ### Implemented surface (M8)
229
+
230
+ - `document`: `querySelector`, `querySelectorAll`, `getElementById`,
231
+ `createElement`, `createTextNode`, `body`, `documentElement`,
232
+ `addEventListener` / `removeEventListener` / `dispatchEvent`.
233
+ - node (element/text/document): `tagName` / `nodeName` / `nodeType`, `id`,
234
+ `className`, `textContent` / `innerText`, `innerHTML`, `parentNode` /
235
+ `parentElement`, `children` / `childNodes` / `childElementCount`,
236
+ `firstElementChild` / `lastElementChild`, `nextElementSibling` /
237
+ `previousElementSibling`, `isConnected`, `classList`, `style`.
238
+ - attributes: `getAttribute` / `setAttribute` / `hasAttribute` /
239
+ `removeAttribute`.
240
+ - queries: `querySelector` / `querySelectorAll` / `matches` (subtree-scoped).
241
+ - mutation: `appendChild` / `insertBefore` / `removeChild` / `replaceChild` /
242
+ `remove` / `cloneNode(deep?)`.
243
+ - geometry/focus: `getBoundingClientRect`, `focus`, `blur`, `click`.
244
+ - events: `addEventListener(type, fn, optionsOrCapture?)`,
245
+ `removeEventListener`, `dispatchEvent`.
246
+
247
+ ### Lazy restyle / relayout
248
+
249
+ Every mutation calls `XtDocument::invalidate()` and sets `win->struct_dirty`.
250
+ Before any synchronous read (`computedStyle`, `getBoundingClientRect`,
251
+ `queryCount`, `layoutTree`, `paintList`, `hitTest`, …) and before each frame,
252
+ `xt_gui_flush_dom()` restyles + relayouts **once** if anything is pending. This
253
+ keeps mutations cheap and batched while making reads immediately consistent.
254
+
255
+ ## Events and bubbling
256
+
257
+ Element listeners are stored on the window (`node_listeners`), keyed by node
258
+ (not on `Node` itself, so the tree stays layout-focused).
259
+
260
+ Dispatch:
261
+
262
+ 1. build the propagation path `target → … → root`,
263
+ 2. create an Event object (`type`, `target` = node handle, `currentTarget`,
264
+ `bubbles`, `defaultPrevented`, internal `__stop` / `__stopImmediate`),
265
+ 3. **capture** phase: root → target (listeners registered with `capture: true`),
266
+ 4. **bubble** phase: target → root,
267
+ 5. legacy window-level `win.on(type, fn)` handlers run **last**.
268
+
269
+ `once` listeners are removed before being called; `stopPropagation` ends the
270
+ current phase chain and `stopImmediatePropagation` also skips later listeners on
271
+ the same node. `el.click()` synthesises `click` at that node (DOM only);
272
+ real pointer/wheel/key input dispatches **both** the DOM event and the legacy
273
+ window payload.
274
+
275
+ ### Backward compatibility
276
+
277
+ The legacy window payload keeps its **string** `e.target`
278
+ (`tests/e2e/gui-*.test.ts` asserts `e.target === "div#inner"`). Only the *element*
279
+ Event's `target` is a handle, whose descriptor (`toString`) matches the same
280
+ `div#id.class` string. `make_dom_event` deliberately does **not** copy the
281
+ legacy `target` field over the handle.
282
+
283
+ ## Milestones
284
+
285
+ - **M8 — DOM object model + mutation + element events** ✅
286
+ - `runtime/ext_gui/dom_api.{h,cpp}`, `document.{h,cpp}`, `dom.{h,cpp}`,
287
+ `gui_engine.h`, `gui.cpp`, `window.cpp`, e2e coverage.
288
+ - No compiler changes; independently testable.
289
+ - **M9 — AOT `<script>`** ✅
290
+ - **M9a** ✅ — `Extension.assetLoaders` in `src/extensions/registry.ts` +
291
+ bundler integration in `src/driver/bundler/{graph,merge}.ts`; unit tests.
292
+ - **M9b** ✅ — gui `.html` loader (`src/extensions/gui/html.ts`), the
293
+ `__registerScript` builtin, the script registry (`runtime/ext_gui/script.cpp`),
294
+ `loadHTML` execution and `DOMContentLoaded`/`load`; e2e coverage.
295
+ - **M9c** ✅ — `<script src>` files are read, their top-level imports are
296
+ hoisted (specifiers rewritten to resolve from the HTML file) and their body
297
+ is wrapped and registered; missing files and import-binding collisions
298
+ surface as diagnostics. All scripts run in document order (effectively
299
+ deferred). e2e coverage.
300
+ - **M9d** *(optional)* — detect inline HTML in template literals passed to
301
+ `win.loadHTML(...)` and transform them too (fragile; deferred).
302
+ - **M10 — polish** ✅ — `requestAnimationFrame`/`cancelAnimationFrame` on the
303
+ window, `offsetWidth`/`offsetHeight`/`contains` on elements, docs.
304
+
305
+ ### Landing order
306
+
307
+ 1. M8 (this branch) — pure engine work, no compiler impact.
308
+ 2. M9a — the generic hook + bundler wiring (unit-testable with a fake loader).
309
+ 3. M9b — the minimal end-to-end script path (inline `<script>` only).
310
+ 4. M9c — external `<script src>`.
311
+ 5. M10 — animation frames and helpers.
312
+
313
+ ## Open risks
314
+
315
+ - **Handle lifetime.** Solved with a generation counter + detached pool; stale
316
+ handles no-op. `Node*` addresses are never exposed to TS.
317
+ - **No sandbox.** A `<script>` can `import fs`. Documented as "not a browser";
318
+ a real origin/sandbox model is out of scope.
319
+ - **Dynamic HTML scripts.** Scripts in runtime-created HTML (`innerHTML`,
320
+ network fetches) do not run. Documented.
321
+ - **Template-literal HTML** passed to `win.loadHTML(…)` is not transformed in
322
+ M9b (only imported assets are). M9d addresses this if needed.
323
+ - **Import-binding collisions** between external `<script src>` files. Detected
324
+ and reported (alias the import); a per-script rename with reference rewriting
325
+ is possible later.
326
+ - **Assets.** `<script src>` is resolved relative to the HTML asset; URL `src`
327
+ values are ignored. There is no fetch/network loading.
328
+
329
+ ## Testing
330
+
331
+ - **M8**: a new e2e case in `tests/e2e/gui-*.test.ts` covering handle identity,
332
+ traversal, attributes, `classList`, inline `style`, `createElement` +
333
+ `appendChild` + `removeChild`, `getBoundingClientRect`, element bubbling,
334
+ `stopPropagation`, `el.click()` and **window-level `e.target` compatibility**.
335
+ - **M9a**: a unit test with a fake extension loader asserting the bundler
336
+ rewrites `*.foo` and records dependencies.
337
+ - **M9b**: an e2e case with an inline `<script>` incrementing a counter on
338
+ click, asserted through `console.log` from the handler.
339
+ - **M10**: e2e cases for `requestAnimationFrame` (runs each frame, re-queues,
340
+ receives a timestamp, honours `cancelAnimationFrame`) and for
341
+ `offsetWidth`/`offsetHeight`/`contains`.
342
+
343
+ ## Docs to update
344
+
345
+ - `doc/gui.md` — Non-goals ("Executing page `<script>`" → "Executing *runtime*
346
+ page scripts; compile-time scripts are AOT-compiled"), locked decision #1,
347
+ TS-facing API, milestones, progress log.
348
+ - `doc/implemented.md` / `doc/unimplemented.md` — move the new DOM/script
349
+ capabilities across.
350
+ - `README.md` — mention the DOM API + AOT scripts.