xbintsc 0.5.32 → 0.5.37

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 (222) hide show
  1. package/README.md +10 -7
  2. package/README.zh-CN.md +10 -8
  3. package/dist/scripts/check-version.d.ts +27 -0
  4. package/dist/scripts/check-version.js +71 -0
  5. package/dist/scripts/check-version.js.map +1 -0
  6. package/dist/scripts/package-platform.d.ts +58 -0
  7. package/dist/scripts/package-platform.js +142 -0
  8. package/dist/scripts/package-platform.js.map +1 -0
  9. package/dist/scripts/verify-platform-package.d.ts +34 -0
  10. package/dist/scripts/verify-platform-package.js +161 -0
  11. package/dist/scripts/verify-platform-package.js.map +1 -0
  12. package/dist/src/cli/help.d.ts +1 -1
  13. package/dist/src/cli/main.js +3 -1
  14. package/dist/src/cli/main.js.map +1 -1
  15. package/dist/src/driver/runtime-lib.d.ts +29 -4
  16. package/dist/src/driver/runtime-lib.js +68 -10
  17. package/dist/src/driver/runtime-lib.js.map +1 -1
  18. package/dist/src/driver/version.d.ts +1 -1
  19. package/dist/src/driver/version.js +1 -1
  20. package/dist/src/extensions/gui/index.js +18 -5
  21. package/dist/src/extensions/gui/index.js.map +1 -1
  22. package/dist/src/extensions/media/index.js +4 -3
  23. package/dist/src/extensions/media/index.js.map +1 -1
  24. package/dist/src/parser/context.d.ts +41 -0
  25. package/dist/src/parser/context.js +89 -4
  26. package/dist/src/parser/context.js.map +1 -1
  27. package/dist/src/parser/literals.js +6 -12
  28. package/dist/src/parser/literals.js.map +1 -1
  29. package/dist/tests/driver/platform-package.test.d.ts +1 -0
  30. package/dist/tests/driver/platform-package.test.js +49 -0
  31. package/dist/tests/driver/platform-package.test.js.map +1 -0
  32. package/dist/tests/driver/runtime-lib.test.js +51 -4
  33. package/dist/tests/driver/runtime-lib.test.js.map +1 -1
  34. package/dist/tests/e2e/gui-audio.test.d.ts +14 -0
  35. package/dist/tests/e2e/gui-audio.test.js +287 -0
  36. package/dist/tests/e2e/gui-audio.test.js.map +1 -0
  37. package/dist/tests/e2e/gui-browser.test.js +65 -0
  38. package/dist/tests/e2e/gui-browser.test.js.map +1 -1
  39. package/dist/tests/e2e/gui-canvas-args.test.d.ts +6 -0
  40. package/dist/tests/e2e/gui-canvas-args.test.js +217 -0
  41. package/dist/tests/e2e/gui-canvas-args.test.js.map +1 -0
  42. package/dist/tests/e2e/gui-canvas.test.js.map +1 -1
  43. package/dist/tests/e2e/gui-dom.test.js +215 -0
  44. package/dist/tests/e2e/gui-dom.test.js.map +1 -1
  45. package/dist/tests/e2e/gui-events.test.js +112 -0
  46. package/dist/tests/e2e/gui-events.test.js.map +1 -1
  47. package/dist/tests/e2e/gui-helpers.d.ts +7 -0
  48. package/dist/tests/e2e/gui-helpers.js +1 -0
  49. package/dist/tests/e2e/gui-helpers.js.map +1 -1
  50. package/dist/tests/e2e/gui-media.test.js +78 -0
  51. package/dist/tests/e2e/gui-media.test.js.map +1 -1
  52. package/dist/tests/e2e/gui-style.test.js +55 -0
  53. package/dist/tests/e2e/gui-style.test.js.map +1 -1
  54. package/dist/tests/e2e/gui-webgl.test.js +258 -0
  55. package/dist/tests/e2e/gui-webgl.test.js.map +1 -1
  56. package/dist/tests/e2e/gui-webgl2-3d.test.d.ts +4 -0
  57. package/dist/tests/e2e/gui-webgl2-3d.test.js +264 -0
  58. package/dist/tests/e2e/gui-webgl2-3d.test.js.map +1 -0
  59. package/dist/tests/e2e/gui-webgl2-blit.test.d.ts +10 -0
  60. package/dist/tests/e2e/gui-webgl2-blit.test.js +262 -0
  61. package/dist/tests/e2e/gui-webgl2-blit.test.js.map +1 -0
  62. package/dist/tests/e2e/gui-webgl2-formats.test.d.ts +6 -0
  63. package/dist/tests/e2e/gui-webgl2-formats.test.js +395 -0
  64. package/dist/tests/e2e/gui-webgl2-formats.test.js.map +1 -0
  65. package/dist/tests/e2e/gui-webgl2-mrt.test.d.ts +4 -0
  66. package/dist/tests/e2e/gui-webgl2-mrt.test.js +105 -0
  67. package/dist/tests/e2e/gui-webgl2-mrt.test.js.map +1 -0
  68. package/dist/tests/e2e/gui-webgl2-ubo.test.d.ts +8 -0
  69. package/dist/tests/e2e/gui-webgl2-ubo.test.js +112 -0
  70. package/dist/tests/e2e/gui-webgl2-ubo.test.js.map +1 -0
  71. package/dist/tests/e2e/gui-webgl2.test.js +45 -1
  72. package/dist/tests/e2e/gui-webgl2.test.js.map +1 -1
  73. package/dist/tests/extensions/gui-index.test.js +1 -0
  74. package/dist/tests/extensions/gui-index.test.js.map +1 -1
  75. package/dist/tests/extensions/media.test.js +1 -0
  76. package/dist/tests/extensions/media.test.js.map +1 -1
  77. package/dist/tests/parser/expressions.test.js +30 -0
  78. package/dist/tests/parser/expressions.test.js.map +1 -1
  79. package/dist/tests/platform-tarball.test.d.ts +1 -0
  80. package/dist/tests/platform-tarball.test.js +111 -0
  81. package/dist/tests/platform-tarball.test.js.map +1 -0
  82. package/dist/tests/version.test.js +6 -0
  83. package/dist/tests/version.test.js.map +1 -1
  84. package/doc/ai/contributing.md +11 -0
  85. package/doc/ai/extensions.md +7 -4
  86. package/doc/ai/troubleshooting.md +7 -5
  87. package/doc/ai/zh-CN/contributing.md +9 -0
  88. package/doc/ai/zh-CN/extensions.md +4 -2
  89. package/doc/ai/zh-CN/troubleshooting.md +5 -3
  90. package/doc/gui/api.md +99 -22
  91. package/doc/gui/build.md +11 -4
  92. package/doc/gui/canvas.md +49 -18
  93. package/doc/gui/dom.md +57 -7
  94. package/doc/gui/media.md +4 -0
  95. package/doc/gui/webgl.md +105 -46
  96. package/doc/gui.md +7 -3
  97. package/doc/npm-package.md +55 -27
  98. package/doc/requirements.md +6 -2
  99. package/doc/zh-CN/gui/api.md +84 -14
  100. package/doc/zh-CN/gui/build.md +7 -2
  101. package/doc/zh-CN/gui/canvas.md +44 -19
  102. package/doc/zh-CN/gui/dom.md +48 -4
  103. package/doc/zh-CN/gui/media.md +3 -0
  104. package/doc/zh-CN/gui/webgl.md +22 -16
  105. package/doc/zh-CN/gui.md +3 -2
  106. package/doc/zh-CN/npm-package.md +44 -23
  107. package/doc/zh-CN/requirements.md +4 -1
  108. package/package.json +17 -1
  109. package/runtime/ext_gui/audio.cpp +365 -153
  110. package/runtime/ext_gui/audio_buffer.cpp +166 -0
  111. package/runtime/ext_gui/audio_buffer.h +37 -0
  112. package/runtime/ext_gui/audio_decode.cpp +219 -0
  113. package/runtime/ext_gui/audio_decode.h +39 -0
  114. package/runtime/ext_gui/audio_device.cpp +87 -0
  115. package/runtime/ext_gui/audio_effects.cpp +163 -0
  116. package/runtime/ext_gui/audio_effects.h +25 -0
  117. package/runtime/ext_gui/audio_internal.h +30 -0
  118. package/runtime/ext_gui/audio_mixer.cpp +494 -0
  119. package/runtime/ext_gui/audio_mixer.h +238 -0
  120. package/runtime/ext_gui/audio_param.cpp +104 -0
  121. package/runtime/ext_gui/audio_param_object.cpp +155 -0
  122. package/runtime/ext_gui/browser.cpp +45 -50
  123. package/runtime/ext_gui/browser_abort.cpp +249 -0
  124. package/runtime/ext_gui/browser_dialogs.cpp +76 -0
  125. package/runtime/ext_gui/browser_media.cpp +176 -0
  126. package/runtime/ext_gui/browser_navigation.cpp +130 -0
  127. package/runtime/ext_gui/canvas.cpp +88 -30
  128. package/runtime/ext_gui/canvas.h +74 -9
  129. package/runtime/ext_gui/canvas_image_data.cpp +143 -0
  130. package/runtime/ext_gui/canvas_internal.h +8 -5
  131. package/runtime/ext_gui/canvas_pattern.cpp +90 -0
  132. package/runtime/ext_gui/canvas_raster.cpp +31 -9
  133. package/runtime/ext_gui/canvas_style.cpp +4 -2
  134. package/runtime/ext_gui/css_selector.cpp +21 -3
  135. package/runtime/ext_gui/dom.h +4 -0
  136. package/runtime/ext_gui/dom_api.cpp +1 -0
  137. package/runtime/ext_gui/dom_api_canvas.cpp +120 -114
  138. package/runtime/ext_gui/dom_api_canvas_extra.cpp +57 -2
  139. package/runtime/ext_gui/dom_api_canvas_image.cpp +239 -0
  140. package/runtime/ext_gui/dom_api_canvas_paths.cpp +179 -0
  141. package/runtime/ext_gui/dom_api_computed_style.cpp +124 -0
  142. package/runtime/ext_gui/dom_api_dataset.cpp +154 -0
  143. package/runtime/ext_gui/dom_api_document.cpp +58 -5
  144. package/runtime/ext_gui/dom_api_events.cpp +93 -17
  145. package/runtime/ext_gui/dom_api_internal.h +71 -2
  146. package/runtime/ext_gui/dom_api_mutation.cpp +159 -0
  147. package/runtime/ext_gui/dom_api_node.cpp +40 -19
  148. package/runtime/ext_gui/dom_api_node_read.cpp +37 -1
  149. package/runtime/ext_gui/dom_api_path2d.cpp +358 -0
  150. package/runtime/ext_gui/dom_api_proto.cpp +35 -0
  151. package/runtime/ext_gui/dom_api_style.cpp +223 -13
  152. package/runtime/ext_gui/dom_api_webgl.cpp +7 -0
  153. package/runtime/ext_gui/dom_api_webgl2.cpp +151 -36
  154. package/runtime/ext_gui/dom_api_webgl2_draw.cpp +3 -3
  155. package/runtime/ext_gui/dom_api_webgl_buffer.cpp +6 -2
  156. package/runtime/ext_gui/dom_api_webgl_compressed.cpp +167 -0
  157. package/runtime/ext_gui/dom_api_webgl_framebuffer.cpp +160 -0
  158. package/runtime/ext_gui/dom_api_webgl_shader.cpp +4 -4
  159. package/runtime/ext_gui/dom_api_webgl_source.cpp +161 -0
  160. package/runtime/ext_gui/dom_api_webgl_state.cpp +169 -23
  161. package/runtime/ext_gui/dom_api_webgl_texture.cpp +276 -236
  162. package/runtime/ext_gui/dom_api_webgl_ubo.cpp +81 -0
  163. package/runtime/ext_gui/gui.cpp +103 -14
  164. package/runtime/ext_gui/gui_engine.h +56 -4
  165. package/runtime/ext_gui/gui_events.cpp +80 -9
  166. package/runtime/ext_gui/image_bitmap.cpp +28 -0
  167. package/runtime/ext_gui/image_bitmap.h +26 -0
  168. package/runtime/ext_gui/image_bitmap_browser.cpp +136 -0
  169. package/runtime/ext_gui/listener_options.h +43 -0
  170. package/runtime/ext_gui/media.cpp +32 -0
  171. package/runtime/ext_gui/media.h +13 -0
  172. package/runtime/ext_gui/media_api.h +23 -0
  173. package/runtime/ext_gui/paint.cpp +28 -0
  174. package/runtime/ext_gui/paint.h +4 -0
  175. package/runtime/ext_gui/webgl.h +152 -18
  176. package/runtime/ext_gui/webgl_blit.cpp +95 -0
  177. package/runtime/ext_gui/webgl_buffer.cpp +178 -263
  178. package/runtime/ext_gui/webgl_buffer_objects.cpp +270 -0
  179. package/runtime/ext_gui/webgl_clear.cpp +142 -0
  180. package/runtime/ext_gui/webgl_compressed.cpp +180 -0
  181. package/runtime/ext_gui/webgl_context.cpp +241 -156
  182. package/runtime/ext_gui/webgl_draw.cpp +68 -3
  183. package/runtime/ext_gui/webgl_draw_buffers.cpp +75 -0
  184. package/runtime/ext_gui/webgl_glsl.h +40 -0
  185. package/runtime/ext_gui/webgl_glsl_compile.cpp +6 -0
  186. package/runtime/ext_gui/webgl_glsl_rewrite.cpp +18 -0
  187. package/runtime/ext_gui/webgl_glsl_scan.cpp +20 -11
  188. package/runtime/ext_gui/webgl_glsl_scan_block.cpp +126 -0
  189. package/runtime/ext_gui/webgl_glsl_tokens.h +28 -0
  190. package/runtime/ext_gui/webgl_pipeline.cpp +71 -19
  191. package/runtime/ext_gui/webgl_program_blocks.cpp +58 -0
  192. package/runtime/ext_gui/webgl_program_internal.h +6 -0
  193. package/runtime/ext_gui/webgl_shader.cpp +13 -1
  194. package/runtime/ext_gui/webgl_state.cpp +228 -0
  195. package/runtime/ext_gui/webgl_texture.cpp +144 -17
  196. package/runtime/ext_gui/webgl_texture_3d.cpp +215 -0
  197. package/runtime/ext_gui/webgl_uniform_block.h +31 -0
  198. package/runtime/ext_gui/webgl_webgl2.cpp +58 -5
  199. package/runtime/ext_gui/window.cpp +14 -65
  200. package/runtime/ext_gui/window_events.cpp +125 -0
  201. package/runtime/ext_media/decode_audio.cpp +308 -0
  202. package/runtime/ext_media/media_bridge.c +2 -0
  203. package/runtime/rt.h +43 -0
  204. package/runtime/rt_internal.h +4 -0
  205. package/runtime/xt_array_buffer.c +30 -0
  206. package/runtime/xt_builtins.c +0 -21
  207. package/runtime/xt_containers/access.inc +94 -2
  208. package/runtime/xt_containers/objects.inc +29 -31
  209. package/runtime/xt_typed_array/elements.inc +13 -0
  210. package/scripts/build-media.ts +14 -6
  211. package/scripts/build-runtime.ts +21 -16
  212. package/scripts/check-version.ts +39 -9
  213. package/scripts/package-platform.ts +183 -0
  214. package/scripts/set-platform-version.ts +62 -0
  215. package/scripts/verify-platform-package.ts +182 -0
  216. package/src/cli/main.ts +5 -1
  217. package/src/driver/runtime-lib.ts +73 -9
  218. package/src/driver/version.ts +1 -1
  219. package/src/extensions/gui/index.ts +18 -5
  220. package/src/extensions/media/index.ts +4 -3
  221. package/src/parser/context.ts +99 -4
  222. package/src/parser/literals.ts +6 -12
@@ -7,26 +7,90 @@
7
7
  * runtime/lib/<os>-<arch>/core.a (the core `xt_*.c` runtime)
8
8
  * runtime/lib/<os>-<arch>/ext_<name>.a (per-extension sources, e.g. ext_node.a)
9
9
  *
10
+ * A source checkout or a release archive carries them in the package itself.
11
+ * An npm install instead receives them from the optional
12
+ * `@xbintsc/<os>-<arch>` package (installed only for the host platform), which
13
+ * uses the same `runtime/lib/<os>-<arch>/` layout. The driver reads that folder
14
+ * in place, so no install script has to copy anything into `node_modules`.
15
+ *
10
16
  * Shipping these lets `build` link without compiling C, and shortens the first
11
17
  * build. When an archive is missing the driver transparently falls back to
12
18
  * compiling the `.c` sources, so the archives are an optimization, not a hard
13
19
  * dependency.
14
20
  */
15
21
 
16
- import { existsSync } from "node:fs";
17
- import { join } from "node:path";
22
+ import { existsSync, readdirSync } from "node:fs";
23
+ import { dirname, join } from "node:path";
18
24
  import { findRuntimeDir, platformSlug } from "./paths.js";
19
25
 
20
- /** Directory holding the prebuilt archives for the current host. */
21
- export function runtimeLibDir(): string {
26
+ /** Archive suffixes, in the order a name is probed. */
27
+ const ARCHIVE_SUFFIXES = [".a", ".lib"] as const;
28
+
29
+ /**
30
+ * Folder holding the archives for the current host inside the xbintsc package
31
+ * itself (a source checkout or an unpacked release archive).
32
+ */
33
+ function packagedRuntimeLibDir(): string {
22
34
  return join(findRuntimeDir(), "lib", platformSlug());
23
35
  }
24
36
 
25
- /** Absolute path to a prebuilt archive (`core`, `ext_node`, …), if present. */
26
- export function findRuntimeLibrary(name: string): string | undefined {
27
- const base = join(runtimeLibDir(), name);
28
- for (const candidate of [`${base}.a`, `${base}.lib`]) {
29
- if (existsSync(candidate)) return candidate;
37
+ /**
38
+ * Archive folders of an installed `@xbintsc/<os>-<arch>` package, given the
39
+ * root of the installed xbintsc package.
40
+ *
41
+ * Package managers place the optional dependency either inside xbintsc's own
42
+ * `node_modules` or in the nearest hoisted `node_modules`, so every ancestor is
43
+ * probed. Yarn PnP has no `node_modules`; there the driver simply falls back to
44
+ * compiling the runtime sources.
45
+ */
46
+ export function platformRuntimeLibDirs(packageRoot: string, slug: string = platformSlug()): string[] {
47
+ const dirs = new Set<string>();
48
+ let dir = packageRoot;
49
+ for (;;) {
50
+ dirs.add(join(dir, "node_modules", "@xbintsc", slug, "runtime", "lib", slug));
51
+ const parent = dirname(dir);
52
+ if (parent === dir) break;
53
+ dir = parent;
54
+ }
55
+ return [...dirs];
56
+ }
57
+
58
+ /** Every folder searched for a prebuilt archive, most specific first. */
59
+ export function runtimeLibDirs(): readonly string[] {
60
+ return [packagedRuntimeLibDir(), ...platformRuntimeLibDirs(dirname(findRuntimeDir()))];
61
+ }
62
+
63
+ /** True when a folder holds at least one runtime archive. */
64
+ function containsArchive(dir: string): boolean {
65
+ try {
66
+ return readdirSync(dir).some((entry) => ARCHIVE_SUFFIXES.some((suffix) => entry.endsWith(suffix)));
67
+ } catch {
68
+ return false;
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Directory holding the prebuilt archives for the current host: the package's
74
+ * own `runtime/lib/<slug>`, or the optional platform package when the former
75
+ * has no archives (a normal npm install).
76
+ */
77
+ export function runtimeLibDir(dirs: readonly string[] = runtimeLibDirs()): string {
78
+ return dirs.find((dir) => containsArchive(dir)) ?? dirs[0]!;
79
+ }
80
+
81
+ /**
82
+ * Absolute path to a prebuilt archive (`core`, `ext_node`, `gui`, `media`, …),
83
+ * if present. `dirs` is injectable for tests.
84
+ */
85
+ export function findRuntimeLibrary(
86
+ name: string,
87
+ dirs: readonly string[] = runtimeLibDirs(),
88
+ ): string | undefined {
89
+ for (const dir of dirs) {
90
+ for (const suffix of ARCHIVE_SUFFIXES) {
91
+ const candidate = join(dir, `${name}${suffix}`);
92
+ if (existsSync(candidate)) return candidate;
93
+ }
30
94
  }
31
95
  return undefined;
32
96
  }
@@ -9,4 +9,4 @@
9
9
  * `main`/`master` only (see `.githooks/pre-commit`), so feature branches never
10
10
  * change this line and merging two of them cannot conflict here.
11
11
  */
12
- export const COMPILER_VERSION = "0.5.32";
12
+ export const COMPILER_VERSION = "0.5.37";
@@ -23,8 +23,8 @@
23
23
 
24
24
  import { join } from "node:path";
25
25
  import type { Extension, ExtensionModule } from "../registry.js";
26
- import { findRuntimeDir, platformSlug } from "../../driver/paths.js";
27
- import { findRuntimeLibrary } from "../../driver/runtime-lib.js";
26
+ import { platformSlug } from "../../driver/paths.js";
27
+ import { findRuntimeLibrary, runtimeLibDir } from "../../driver/runtime-lib.js";
28
28
  import { loadHtmlAsset } from "./html.js";
29
29
  import { loadScriptAsset } from "./three.js";
30
30
 
@@ -61,6 +61,7 @@ const hostTypes = [
61
61
  "ImageBitmap",
62
62
  "ImageData",
63
63
  "HTMLCanvasElement",
64
+ "Path2D",
64
65
  "HTMLImageElement",
65
66
  "HTMLVideoElement",
66
67
  "HTMLAudioElement",
@@ -70,6 +71,16 @@ const hostTypes = [
70
71
  "Event",
71
72
  "CustomEvent",
72
73
  "ProgressEvent",
74
+ "UIEvent",
75
+ "MouseEvent",
76
+ "PointerEvent",
77
+ "WheelEvent",
78
+ "KeyboardEvent",
79
+ "FocusEvent",
80
+ "InputEvent",
81
+ "SubmitEvent",
82
+ "AbortController",
83
+ "AbortSignal",
73
84
  "EventTarget",
74
85
  "DOMParser",
75
86
  "Request",
@@ -102,7 +113,7 @@ const browserGlobals: Record<string, { symbol: string; argument?: string }> = {
102
113
  navigator: { symbol: "xt_browser_navigator" },
103
114
  localStorage: { symbol: "xt_browser_local_storage" },
104
115
  __THREE_DEVTOOLS__: { symbol: "xt_browser_missing" },
105
- createImageBitmap: { symbol: "xt_browser_missing" },
116
+ createImageBitmap: { symbol: "xt_browser_create_image_bitmap" },
106
117
  };
107
118
  for (const type of hostTypes) {
108
119
  browserGlobals[type] = { symbol: "xt_browser_type", argument: type };
@@ -111,7 +122,7 @@ for (const type of hostTypes) {
111
122
  /** Absolute path to the per-platform prebuilt GUI archive (`gui.a`, or
112
123
  * `gui.lib` with the MSVC ABI on Windows). */
113
124
  function guiArchive(): string {
114
- return join(findRuntimeDir(), "lib", platformSlug(), "gui.a");
125
+ return join(runtimeLibDir(), "gui.a");
115
126
  }
116
127
 
117
128
  export const guiExtension: Extension = {
@@ -122,7 +133,8 @@ export const guiExtension: Extension = {
122
133
  if (archive === undefined) {
123
134
  throw new Error(
124
135
  `GUI native library not found at ${guiArchive()} (or gui.lib).\n` +
125
- `Build it for ${platformSlug()} first (see doc/gui.md).`,
136
+ `The optional @xbintsc/${platformSlug()} package ships it: reinstall xbintsc ` +
137
+ `without --omit=optional, or build it from a source checkout (see doc/gui.md).`,
126
138
  );
127
139
  }
128
140
  return [archive];
@@ -197,6 +209,7 @@ export const guiExtension: Extension = {
197
209
  escape: { symbol: "xt_browser_escape" },
198
210
  alert: { symbol: "xt_browser_alert" },
199
211
  prompt: { symbol: "xt_browser_prompt" },
212
+ confirm: { symbol: "xt_browser_confirm_global" },
200
213
  }),
201
214
  /** Browser globals (`window`, `document`, the DOM type names). */
202
215
  globalValues: () => browserGlobals,
@@ -28,11 +28,11 @@
28
28
  import { join } from "node:path";
29
29
  import type { Extension } from "../registry.js";
30
30
  import { findRuntimeDir, platformSlug } from "../../driver/paths.js";
31
- import { findRuntimeLibrary } from "../../driver/runtime-lib.js";
31
+ import { findRuntimeLibrary, runtimeLibDir } from "../../driver/runtime-lib.js";
32
32
 
33
33
  /** Absolute path to the per-platform prebuilt media archive. */
34
34
  function mediaArchive(): string {
35
- return join(findRuntimeDir(), "lib", platformSlug(), process.platform === "win32" ? "media.lib" : "media.a");
35
+ return join(runtimeLibDir(), process.platform === "win32" ? "media.lib" : "media.a");
36
36
  }
37
37
 
38
38
  export const mediaExtension: Extension = {
@@ -50,7 +50,8 @@ export const mediaExtension: Extension = {
50
50
  if (archive === undefined) {
51
51
  throw new Error(
52
52
  `media native library not found at ${mediaArchive()} (media.a or media.lib).\n` +
53
- `Build it for ${platformSlug()} first with \`npm run media\` (see doc/gui/build.md).`,
53
+ `The optional @xbintsc/${platformSlug()} package ships it: reinstall xbintsc ` +
54
+ `without --omit=optional, or build it with \`npm run media\` (see doc/gui/build.md).`,
54
55
  );
55
56
  }
56
57
  return [archive];
@@ -15,11 +15,25 @@ import { SyntaxKind } from "../ast/kinds.js";
15
15
  import type { Identifier, StringLiteral } from "../ast/nodes.js";
16
16
  import { SpeculationError } from "./speculation.js";
17
17
 
18
+ /**
19
+ * The template-literal context of the scanner at one point in the token
20
+ * stream. `frames` holds the brace depth of every open `${ … }` substitution
21
+ * (innermost last); `pendingOffset` is the `}` that must be re-read as template
22
+ * text before the next token is scanned.
23
+ */
24
+ interface TemplateState {
25
+ readonly frames: readonly number[];
26
+ readonly pendingOffset: number | undefined;
27
+ }
28
+
29
+ const OUTSIDE_TEMPLATE: TemplateState = { frames: [], pendingOffset: undefined };
30
+
18
31
  export interface ParserState {
19
32
  index: number;
20
33
  tokens: Token[];
21
34
  pos: number;
22
35
  consumed: number;
36
+ template: TemplateState;
23
37
  }
24
38
 
25
39
  export class ParserContext {
@@ -32,12 +46,22 @@ export class ParserContext {
32
46
  speculating = 0;
33
47
  /** Monotonic count of tokens consumed; used to detect lack of progress. */
34
48
  consumed = 0;
49
+ /** Brace depth of each open `${ … }` substitution, innermost last. */
50
+ private templateFrames: number[] = [];
51
+ /** Offset of the `}` that must be re-read as template text on the next scan. */
52
+ private pendingTemplateOffset: number | undefined;
53
+ /**
54
+ * Template context after each buffered token. A parser method that rewinds
55
+ * the scanner (the regex fallback) restores the context recorded on the
56
+ * current token so a template that was only seen by lookahead stays coherent.
57
+ */
58
+ private readonly templateStates = new WeakMap<Token, TemplateState>();
35
59
 
36
60
  constructor(source: SourceFile, diagnostics: DiagnosticBag) {
37
61
  this.source = source;
38
62
  this.diagnostics = diagnostics;
39
63
  this.scanner = new Scanner(source, diagnostics);
40
- this.tokens = [this.scanner.nextToken()];
64
+ this.tokens = [this.scanToken()];
41
65
  }
42
66
 
43
67
  // -- token access ---------------------------------------------------------
@@ -61,7 +85,7 @@ export class ParserContext {
61
85
 
62
86
  lookAhead(k: number): Token {
63
87
  while (this.tokens.length <= this.index + k) {
64
- this.tokens.push(this.scanner.nextToken());
88
+ this.tokens.push(this.scanToken());
65
89
  }
66
90
  return this.tokens[this.index + k]!;
67
91
  }
@@ -72,12 +96,76 @@ export class ParserContext {
72
96
  if (this.index + 1 < this.tokens.length) {
73
97
  this.index++;
74
98
  } else {
75
- this.tokens = [this.scanner.nextToken()];
99
+ this.tokens = [this.scanToken()];
76
100
  this.index = 0;
77
101
  }
78
102
  return current;
79
103
  }
80
104
 
105
+ /**
106
+ * Scan the next token, keeping a template literal together. A `${ … }`
107
+ * substitution is ordinary token stream (so the parser can parse the
108
+ * expression), but the `}` that closes it must be followed by a token that
109
+ * re-reads the source as template text. Scanning the tail of a template as
110
+ * normal tokens would let its raw text (quotes, parens, `}`) leak into
111
+ * lookahead - and `findMatchingParen` would then count characters that are
112
+ * part of the literal.
113
+ */
114
+ private scanToken(): Token {
115
+ let token: Token;
116
+ if (this.pendingTemplateOffset !== undefined) {
117
+ const offset = this.pendingTemplateOffset;
118
+ this.pendingTemplateOffset = undefined;
119
+ token = this.scanner.continueTemplate(offset);
120
+ if (token.kind === TokenKind.NoSubstitutionTemplateLiteral) {
121
+ this.templateFrames.pop();
122
+ } else if (token.kind === TokenKind.TemplateHead) {
123
+ /* Another `${` in the same template restarts its brace depth. */
124
+ const top = this.templateFrames.length - 1;
125
+ if (top >= 0) this.templateFrames[top] = 0;
126
+ }
127
+ } else {
128
+ token = this.scanner.nextToken();
129
+ if (token.kind === TokenKind.TemplateHead) {
130
+ this.templateFrames.push(0);
131
+ } else if (this.templateFrames.length > 0) {
132
+ const top = this.templateFrames.length - 1;
133
+ if (token.kind === TokenKind.OpenBrace) {
134
+ this.templateFrames[top] = this.templateFrames[top]! + 1;
135
+ } else if (token.kind === TokenKind.CloseBrace) {
136
+ if (this.templateFrames[top]! === 0) this.pendingTemplateOffset = token.start;
137
+ else this.templateFrames[top] = this.templateFrames[top]! - 1;
138
+ }
139
+ }
140
+ }
141
+ if (this.templateFrames.length > 0 || this.pendingTemplateOffset !== undefined) {
142
+ this.templateStates.set(token, this.snapshotTemplateState());
143
+ }
144
+ return token;
145
+ }
146
+
147
+ /**
148
+ * Discard any lookahead and continue scanning at `offset`. Used when a
149
+ * literal's raw text has to be re-read (the regex fallback). The template
150
+ * context is restored to the state recorded for the current token, because
151
+ * the skipped source cannot have opened or closed a template.
152
+ */
153
+ rescanFrom(offset: number): void {
154
+ this.restoreTemplateState(this.templateStates.get(this.token) ?? OUTSIDE_TEMPLATE);
155
+ this.scanner.seek(offset);
156
+ this.tokens = [this.scanToken()];
157
+ this.index = 0;
158
+ }
159
+
160
+ private snapshotTemplateState(): TemplateState {
161
+ return { frames: this.templateFrames.slice(), pendingOffset: this.pendingTemplateOffset };
162
+ }
163
+
164
+ private restoreTemplateState(state: TemplateState): void {
165
+ this.templateFrames = [...state.frames];
166
+ this.pendingTemplateOffset = state.pendingOffset;
167
+ }
168
+
81
169
  parseExpected(kind: TokenKind, message?: string): Token {
82
170
  if (this.token.kind === kind) return this.nextToken();
83
171
  this.error(
@@ -173,7 +261,13 @@ export class ParserContext {
173
261
  // -- state save / restore (for speculative parsing) -----------------------
174
262
 
175
263
  saveState(): ParserState {
176
- return { index: this.index, tokens: this.tokens.slice(), pos: this.scanner.position, consumed: this.consumed };
264
+ return {
265
+ index: this.index,
266
+ tokens: this.tokens.slice(),
267
+ pos: this.scanner.position,
268
+ consumed: this.consumed,
269
+ template: this.snapshotTemplateState(),
270
+ };
177
271
  }
178
272
 
179
273
  restoreState(state: ParserState): void {
@@ -181,5 +275,6 @@ export class ParserContext {
181
275
  this.tokens = state.tokens;
182
276
  this.scanner.seek(state.pos);
183
277
  this.consumed = state.consumed;
278
+ this.restoreTemplateState(state.template);
184
279
  }
185
280
  }
@@ -206,10 +206,8 @@ export const literalMethods: LiteralMethods = {
206
206
  } as unknown as TemplateLiteral;
207
207
  }
208
208
  if (startToken.kind === TokenKind.Backtick) {
209
- // Re-scan from the backtick so the scanner produces a TemplateHead.
210
- this.scanner.seek(startToken.start);
211
- this.tokens = [this.scanner.nextToken()];
212
- this.index = 0;
209
+ // Re-scan from the backtick so the scanner produces a template token.
210
+ this.rescanFrom(startToken.start);
213
211
  return this.parseTemplateLiteral();
214
212
  }
215
213
  const head = String(startToken.value ?? "");
@@ -222,11 +220,9 @@ export const literalMethods: LiteralMethods = {
222
220
  this.error(DiagnosticCode.UnterminatedTemplate, "Expected '}' to close template substitution");
223
221
  return { kind: SyntaxKind.TemplateLiteral, head, raw: headRaw, spans, start: startToken.start, end: this.token.end };
224
222
  }
225
- // The `}` terminates a substitution; re-read it as template text without
226
- // first scanning the following (template) characters as normal tokens.
227
- const closeStart = this.token.start;
228
- this.tokens = [this.scanner.continueTemplate(closeStart)];
229
- this.index = 0;
223
+ // The `}` terminates a substitution; the token after it is the template
224
+ // continuation (the token stream re-reads that source as literal text).
225
+ this.nextToken();
230
226
  const literalToken = this.token;
231
227
  if (literalToken.kind === TokenKind.NoSubstitutionTemplateLiteral) {
232
228
  this.nextToken();
@@ -287,9 +283,7 @@ export const literalMethods: LiteralMethods = {
287
283
  const flagsStart = i;
288
284
  while (i < text.length && /[a-z]/i.test(text[i]!)) i++;
289
285
  const flags = text.slice(flagsStart, i);
290
- this.scanner.seek(i);
291
- this.tokens = [this.scanner.nextToken()];
292
- this.index = 0;
286
+ this.rescanFrom(i);
293
287
  return { kind: SyntaxKind.RegularExpressionLiteral, text: text.slice(token.start, i), pattern, flags, start: token.start, end: i };
294
288
  },
295
289
  };