xbintsc 0.5.22 → 0.5.31

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 (391) hide show
  1. package/AGENTS.md +12 -3
  2. package/README.md +46 -11
  3. package/README.zh-CN.md +32 -6
  4. package/dist/scripts/support-matrix.d.ts +18 -0
  5. package/dist/scripts/support-matrix.js +141 -0
  6. package/dist/scripts/support-matrix.js.map +1 -0
  7. package/dist/src/ast/common.d.ts +2 -2
  8. package/dist/src/ast/declarations.d.ts +7 -1
  9. package/dist/src/ast/expressions.d.ts +5 -0
  10. package/dist/src/ast/kinds.d.ts +2 -0
  11. package/dist/src/ast/kinds.js +2 -0
  12. package/dist/src/ast/kinds.js.map +1 -1
  13. package/dist/src/binder/binder.d.ts +2 -1
  14. package/dist/src/binder/binder.js +2 -0
  15. package/dist/src/binder/binder.js.map +1 -1
  16. package/dist/src/binder/declarations.d.ts +9 -0
  17. package/dist/src/binder/declarations.js +64 -0
  18. package/dist/src/binder/declarations.js.map +1 -1
  19. package/dist/src/binder/references.js +85 -3
  20. package/dist/src/binder/references.js.map +1 -1
  21. package/dist/src/binder/types.d.ts +32 -0
  22. package/dist/src/binder/types.js.map +1 -1
  23. package/dist/src/canonical.d.ts +29 -0
  24. package/dist/src/canonical.js +35 -0
  25. package/dist/src/canonical.js.map +1 -0
  26. package/dist/src/checker/assignability.d.ts +13 -0
  27. package/dist/src/checker/assignability.js +146 -0
  28. package/dist/src/checker/assignability.js.map +1 -0
  29. package/dist/src/checker/calls.d.ts +13 -0
  30. package/dist/src/checker/calls.js +127 -0
  31. package/dist/src/checker/calls.js.map +1 -0
  32. package/dist/src/checker/checker.d.ts +26 -0
  33. package/dist/src/checker/checker.js +31 -0
  34. package/dist/src/checker/checker.js.map +1 -0
  35. package/dist/src/checker/declarations.d.ts +9 -0
  36. package/dist/src/checker/declarations.js +15 -0
  37. package/dist/src/checker/declarations.js.map +1 -0
  38. package/dist/src/checker/flow.d.ts +21 -0
  39. package/dist/src/checker/flow.js +241 -0
  40. package/dist/src/checker/flow.js.map +1 -0
  41. package/dist/src/checker/globals.d.ts +9 -0
  42. package/dist/src/checker/globals.js +29 -0
  43. package/dist/src/checker/globals.js.map +1 -0
  44. package/dist/src/checker/source.d.ts +38 -0
  45. package/dist/src/checker/source.js +48 -0
  46. package/dist/src/checker/source.js.map +1 -0
  47. package/dist/src/checker/type-infer.d.ts +18 -0
  48. package/dist/src/checker/type-infer.js +173 -0
  49. package/dist/src/checker/type-infer.js.map +1 -0
  50. package/dist/src/checker/type-model.d.ts +73 -0
  51. package/dist/src/checker/type-model.js +151 -0
  52. package/dist/src/checker/type-model.js.map +1 -0
  53. package/dist/src/checker/type-system.d.ts +58 -0
  54. package/dist/src/checker/type-system.js +569 -0
  55. package/dist/src/checker/type-system.js.map +1 -0
  56. package/dist/src/checker/typecheck-types.d.ts +22 -0
  57. package/dist/src/checker/typecheck-types.js +109 -0
  58. package/dist/src/checker/typecheck-types.js.map +1 -0
  59. package/dist/src/checker/typecheck.d.ts +62 -0
  60. package/dist/src/checker/typecheck.js +574 -0
  61. package/dist/src/checker/typecheck.js.map +1 -0
  62. package/dist/src/checker/undefined.d.ts +14 -0
  63. package/dist/src/checker/undefined.js +32 -0
  64. package/dist/src/checker/undefined.js.map +1 -0
  65. package/dist/src/checker/walk.d.ts +10 -0
  66. package/dist/src/checker/walk.js +15 -0
  67. package/dist/src/checker/walk.js.map +1 -0
  68. package/dist/src/cli/help.d.ts +7 -0
  69. package/dist/src/cli/help.js +64 -0
  70. package/dist/src/cli/help.js.map +1 -0
  71. package/dist/src/cli/hints.js +13 -1
  72. package/dist/src/cli/hints.js.map +1 -1
  73. package/dist/src/cli/main.js +12 -48
  74. package/dist/src/cli/main.js.map +1 -1
  75. package/dist/src/codegen/generator/calls/access.js +8 -8
  76. package/dist/src/codegen/generator/calls/access.js.map +1 -1
  77. package/dist/src/codegen/generator/calls/invocation.js +9 -4
  78. package/dist/src/codegen/generator/calls/invocation.js.map +1 -1
  79. package/dist/src/codegen/generator/calls/super.d.ts +9 -1
  80. package/dist/src/codegen/generator/calls/super.js +56 -5
  81. package/dist/src/codegen/generator/calls/super.js.map +1 -1
  82. package/dist/src/codegen/generator/context.d.ts +13 -2
  83. package/dist/src/codegen/generator/context.js +25 -19
  84. package/dist/src/codegen/generator/context.js.map +1 -1
  85. package/dist/src/codegen/generator/debug.js +2 -1
  86. package/dist/src/codegen/generator/debug.js.map +1 -1
  87. package/dist/src/codegen/generator/expressions/primary.d.ts +4 -1
  88. package/dist/src/codegen/generator/expressions/primary.js +68 -8
  89. package/dist/src/codegen/generator/expressions/primary.js.map +1 -1
  90. package/dist/src/codegen/generator/generator.js +11 -1
  91. package/dist/src/codegen/generator/generator.js.map +1 -1
  92. package/dist/src/codegen/generator/module.d.ts +2 -0
  93. package/dist/src/codegen/generator/module.js +62 -26
  94. package/dist/src/codegen/generator/module.js.map +1 -1
  95. package/dist/src/codegen/generator/state.d.ts +11 -1
  96. package/dist/src/codegen/generator/state.js.map +1 -1
  97. package/dist/src/codegen/generator/statements/variables.js +20 -3
  98. package/dist/src/codegen/generator/statements/variables.js.map +1 -1
  99. package/dist/src/codegen/generator/tables.d.ts +2 -0
  100. package/dist/src/codegen/generator/tables.js +4 -0
  101. package/dist/src/codegen/generator/tables.js.map +1 -1
  102. package/dist/src/driver/build-key.d.ts +2 -0
  103. package/dist/src/driver/build-key.js +1 -0
  104. package/dist/src/driver/build-key.js.map +1 -1
  105. package/dist/src/driver/bundler/graph.js +5 -1
  106. package/dist/src/driver/bundler/graph.js.map +1 -1
  107. package/dist/src/driver/bundler/merge.js +16 -1
  108. package/dist/src/driver/bundler/merge.js.map +1 -1
  109. package/dist/src/driver/bundler/types.d.ts +12 -0
  110. package/dist/src/driver/compiler.d.ts +1 -1
  111. package/dist/src/driver/compiler.js +4 -0
  112. package/dist/src/driver/compiler.js.map +1 -1
  113. package/dist/src/driver/fingerprint.js +2 -1
  114. package/dist/src/driver/fingerprint.js.map +1 -1
  115. package/dist/src/driver/frontend.d.ts +8 -6
  116. package/dist/src/driver/frontend.js +4 -0
  117. package/dist/src/driver/frontend.js.map +1 -1
  118. package/dist/src/driver/options.d.ts +6 -0
  119. package/dist/src/driver/version.d.ts +1 -1
  120. package/dist/src/driver/version.js +1 -1
  121. package/dist/src/extensions/catalog.js +2 -1
  122. package/dist/src/extensions/catalog.js.map +1 -1
  123. package/dist/src/extensions/gui/html.js +20 -6
  124. package/dist/src/extensions/gui/html.js.map +1 -1
  125. package/dist/src/extensions/gui/index.js +63 -0
  126. package/dist/src/extensions/gui/index.js.map +1 -1
  127. package/dist/src/extensions/media/index.d.ts +27 -0
  128. package/dist/src/extensions/media/index.js +60 -0
  129. package/dist/src/extensions/media/index.js.map +1 -0
  130. package/dist/src/extensions/registry.d.ts +50 -0
  131. package/dist/src/extensions/registry.js +26 -0
  132. package/dist/src/extensions/registry.js.map +1 -1
  133. package/dist/src/index.d.ts +2 -0
  134. package/dist/src/index.js +2 -0
  135. package/dist/src/index.js.map +1 -1
  136. package/dist/src/parser/declarations/classes.js +7 -0
  137. package/dist/src/parser/declarations/classes.js.map +1 -1
  138. package/dist/src/parser/expressions.js +14 -1
  139. package/dist/src/parser/expressions.js.map +1 -1
  140. package/dist/src/parser/literals.js +6 -2
  141. package/dist/src/parser/literals.js.map +1 -1
  142. package/dist/tests/binder/binder.test.js +29 -0
  143. package/dist/tests/binder/binder.test.js.map +1 -1
  144. package/dist/tests/canonical.test.d.ts +1 -0
  145. package/dist/tests/canonical.test.js +19 -0
  146. package/dist/tests/canonical.test.js.map +1 -0
  147. package/dist/tests/checker/checker.test.d.ts +1 -0
  148. package/dist/tests/checker/checker.test.js +245 -0
  149. package/dist/tests/checker/checker.test.js.map +1 -0
  150. package/dist/tests/cli/check.test.js +20 -0
  151. package/dist/tests/cli/check.test.js.map +1 -1
  152. package/dist/tests/cli/hints.test.js +15 -0
  153. package/dist/tests/cli/hints.test.js.map +1 -1
  154. package/dist/tests/cli/process.test.js +12 -0
  155. package/dist/tests/cli/process.test.js.map +1 -1
  156. package/dist/tests/conformance/matrix.test.d.ts +6 -0
  157. package/dist/tests/conformance/matrix.test.js +23 -0
  158. package/dist/tests/conformance/matrix.test.js.map +1 -0
  159. package/dist/tests/conformance/test262/manifest.d.ts +115 -0
  160. package/dist/tests/conformance/test262/manifest.js +257 -0
  161. package/dist/tests/conformance/test262/manifest.js.map +1 -0
  162. package/dist/tests/conformance/test262.test.d.ts +11 -0
  163. package/dist/tests/conformance/test262.test.js +74 -0
  164. package/dist/tests/conformance/test262.test.js.map +1 -0
  165. package/dist/tests/docs/structure.test.js +9 -11
  166. package/dist/tests/docs/structure.test.js.map +1 -1
  167. package/dist/tests/e2e/child-process.test.js +3 -2
  168. package/dist/tests/e2e/child-process.test.js.map +1 -1
  169. package/dist/tests/e2e/compile-builtins.test.js +21 -0
  170. package/dist/tests/e2e/compile-builtins.test.js.map +1 -1
  171. package/dist/tests/e2e/compile-objects-classes.test.js +54 -0
  172. package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
  173. package/dist/tests/e2e/differential-syntax.test.d.ts +5 -0
  174. package/dist/tests/e2e/differential-syntax.test.js +95 -0
  175. package/dist/tests/e2e/differential-syntax.test.js.map +1 -0
  176. package/dist/tests/e2e/differential-values.test.js +15 -0
  177. package/dist/tests/e2e/differential-values.test.js.map +1 -1
  178. package/dist/tests/e2e/gui-browser.test.d.ts +13 -0
  179. package/dist/tests/e2e/gui-browser.test.js +163 -0
  180. package/dist/tests/e2e/gui-browser.test.js.map +1 -0
  181. package/dist/tests/e2e/gui-example-browser.test.js +2 -2
  182. package/dist/tests/e2e/gui-example-browser.test.js.map +1 -1
  183. package/dist/tests/e2e/gui-example-canvas.test.js +8 -5
  184. package/dist/tests/e2e/gui-example-canvas.test.js.map +1 -1
  185. package/dist/tests/e2e/gui-example-svg.test.js +4 -3
  186. package/dist/tests/e2e/gui-example-svg.test.js.map +1 -1
  187. package/dist/tests/e2e/gui-example.test.js +4 -3
  188. package/dist/tests/e2e/gui-example.test.js.map +1 -1
  189. package/dist/tests/e2e/gui-helpers.d.ts +3 -0
  190. package/dist/tests/e2e/gui-helpers.js +6 -3
  191. package/dist/tests/e2e/gui-helpers.js.map +1 -1
  192. package/dist/tests/e2e/gui-media-disabled.test.d.ts +9 -0
  193. package/dist/tests/e2e/gui-media-disabled.test.js +42 -0
  194. package/dist/tests/e2e/gui-media-disabled.test.js.map +1 -0
  195. package/dist/tests/e2e/gui-media.test.d.ts +6 -4
  196. package/dist/tests/e2e/gui-media.test.js +20 -16
  197. package/dist/tests/e2e/gui-media.test.js.map +1 -1
  198. package/dist/tests/e2e/harness.d.ts +9 -0
  199. package/dist/tests/e2e/harness.js +14 -2
  200. package/dist/tests/e2e/harness.js.map +1 -1
  201. package/dist/tests/e2e/native-extensions.test.js +2 -1
  202. package/dist/tests/e2e/native-extensions.test.js.map +1 -1
  203. package/dist/tests/e2e/property-descriptors.test.js +20 -0
  204. package/dist/tests/e2e/property-descriptors.test.js.map +1 -1
  205. package/dist/tests/e2e/unicode-output.test.d.ts +16 -0
  206. package/dist/tests/e2e/unicode-output.test.js +67 -0
  207. package/dist/tests/e2e/unicode-output.test.js.map +1 -0
  208. package/dist/tests/e2e/weak-collections.test.js +16 -2
  209. package/dist/tests/e2e/weak-collections.test.js.map +1 -1
  210. package/dist/tests/extensions/media.test.d.ts +1 -0
  211. package/dist/tests/extensions/media.test.js +58 -0
  212. package/dist/tests/extensions/media.test.js.map +1 -0
  213. package/dist/tests/extensions/registry.test.js +8 -0
  214. package/dist/tests/extensions/registry.test.js.map +1 -1
  215. package/dist/tests/helpers.d.ts +1 -0
  216. package/dist/tests/helpers.js +1 -0
  217. package/dist/tests/helpers.js.map +1 -1
  218. package/dist/tests/lexer/identifiers.test.js +12 -0
  219. package/dist/tests/lexer/identifiers.test.js.map +1 -1
  220. package/dist/tests/lexer/scanner.test.js +8 -0
  221. package/dist/tests/lexer/scanner.test.js.map +1 -1
  222. package/dist/tests/lexer/strings.test.js +30 -0
  223. package/dist/tests/lexer/strings.test.js.map +1 -1
  224. package/dist/tests/parser/declarations.test.js +20 -0
  225. package/dist/tests/parser/declarations.test.js.map +1 -1
  226. package/dist/tests/parser/expressions.test.js +36 -0
  227. package/dist/tests/parser/expressions.test.js.map +1 -1
  228. package/dist/tests/platform.d.ts +32 -0
  229. package/dist/tests/platform.js +40 -0
  230. package/dist/tests/platform.js.map +1 -0
  231. package/dist/tests/platform.test.d.ts +12 -0
  232. package/dist/tests/platform.test.js +51 -0
  233. package/dist/tests/platform.test.js.map +1 -0
  234. package/dist/tests/version.test.js +4 -4
  235. package/dist/tests/version.test.js.map +1 -1
  236. package/doc/ai/README.md +10 -4
  237. package/doc/ai/build-recipe.md +1 -0
  238. package/doc/ai/cli.md +18 -6
  239. package/doc/ai/contributing.md +59 -2
  240. package/doc/ai/extensions.md +46 -23
  241. package/doc/ai/language-support.md +25 -16
  242. package/doc/ai/troubleshooting.md +9 -0
  243. package/doc/ai/zh-CN/README.md +9 -4
  244. package/doc/ai/zh-CN/build-recipe.md +1 -0
  245. package/doc/ai/zh-CN/cli.md +14 -4
  246. package/doc/ai/zh-CN/contributing.md +49 -3
  247. package/doc/ai/zh-CN/extensions.md +29 -8
  248. package/doc/ai/zh-CN/language-support.md +23 -14
  249. package/doc/ai/zh-CN/troubleshooting.md +8 -0
  250. package/doc/gui/api.md +45 -0
  251. package/doc/gui/build.md +37 -21
  252. package/doc/gui/known-issues.md +17 -6
  253. package/doc/gui/media.md +26 -11
  254. package/doc/gui/runtime.md +18 -0
  255. package/doc/gui-scripts.md +7 -0
  256. package/doc/gui.md +43 -33
  257. package/doc/implemented/binder.md +2 -0
  258. package/doc/implemented/checker.md +117 -0
  259. package/doc/implemented/cli.md +13 -1
  260. package/doc/implemented/codegen.md +3 -2
  261. package/doc/implemented/driver.md +2 -2
  262. package/doc/implemented/operators.md +2 -1
  263. package/doc/implemented/parser.md +3 -2
  264. package/doc/implemented/pipeline.md +8 -1
  265. package/doc/implemented/stdlib.md +2 -1
  266. package/doc/implemented/tests.md +3 -1
  267. package/doc/implemented.md +2 -0
  268. package/doc/npm-package.md +417 -0
  269. package/doc/requirements.md +4 -1
  270. package/doc/support-matrix.md +83 -0
  271. package/doc/unimplemented/classes.md +3 -0
  272. package/doc/unimplemented/deviations.md +3 -2
  273. package/doc/unimplemented/toolchain.md +1 -1
  274. package/doc/unimplemented/types.md +24 -8
  275. package/doc/unimplemented.md +1 -1
  276. package/doc/zh-CN/gui/api.md +41 -0
  277. package/doc/zh-CN/gui/build.md +28 -16
  278. package/doc/zh-CN/gui/known-issues.md +13 -5
  279. package/doc/zh-CN/gui/media.md +21 -10
  280. package/doc/zh-CN/gui/runtime.md +16 -0
  281. package/doc/zh-CN/gui-scripts.md +5 -0
  282. package/doc/zh-CN/gui.md +37 -32
  283. package/doc/zh-CN/implemented/binder.md +2 -0
  284. package/doc/zh-CN/implemented/checker.md +99 -0
  285. package/doc/zh-CN/implemented/cli.md +12 -1
  286. package/doc/zh-CN/implemented/codegen.md +3 -2
  287. package/doc/zh-CN/implemented/driver.md +2 -2
  288. package/doc/zh-CN/implemented/operators.md +1 -1
  289. package/doc/zh-CN/implemented/parser.md +3 -2
  290. package/doc/zh-CN/implemented/pipeline.md +3 -1
  291. package/doc/zh-CN/implemented/stdlib.md +2 -1
  292. package/doc/zh-CN/implemented/tests.md +3 -1
  293. package/doc/zh-CN/implemented.md +2 -0
  294. package/doc/zh-CN/npm-package.md +391 -0
  295. package/doc/zh-CN/post/announce.md +47 -47
  296. package/doc/zh-CN/requirements.md +4 -1
  297. package/doc/zh-CN/support-matrix.md +82 -0
  298. package/doc/zh-CN/unimplemented/classes.md +3 -0
  299. package/doc/zh-CN/unimplemented/deviations.md +3 -2
  300. package/doc/zh-CN/unimplemented/toolchain.md +1 -1
  301. package/doc/zh-CN/unimplemented/types.md +16 -6
  302. package/doc/zh-CN/unimplemented.md +1 -1
  303. package/llms.txt +4 -1
  304. package/package.json +3 -1
  305. package/runtime/ext_gui/audio.cpp +316 -0
  306. package/runtime/ext_gui/browser.cpp +547 -0
  307. package/runtime/ext_gui/browser_escape.cpp +85 -0
  308. package/runtime/ext_gui/browser_loop.cpp +83 -0
  309. package/runtime/ext_gui/canvas.cpp +9 -3
  310. package/runtime/ext_gui/canvas_raster.cpp +19 -1
  311. package/runtime/ext_gui/dom_api.cpp +36 -0
  312. package/runtime/ext_gui/dom_api_document.cpp +96 -5
  313. package/runtime/ext_gui/dom_api_events.cpp +20 -0
  314. package/runtime/ext_gui/dom_api_internal.h +19 -0
  315. package/runtime/ext_gui/dom_api_proto.cpp +6 -0
  316. package/runtime/ext_gui/gui.cpp +90 -23
  317. package/runtime/ext_gui/gui_engine.h +44 -0
  318. package/runtime/ext_gui/gui_media.cpp +69 -2
  319. package/runtime/ext_gui/media.cpp +34 -488
  320. package/runtime/ext_gui/media.h +54 -19
  321. package/runtime/ext_gui/media_api.h +65 -0
  322. package/runtime/ext_gui/window.cpp +9 -0
  323. package/runtime/ext_media/media.cpp +507 -0
  324. package/runtime/ext_media/media_bridge.c +35 -0
  325. package/runtime/ext_node/path/path.c +7 -7
  326. package/runtime/ext_node/url/url.c +4 -2
  327. package/runtime/rt.h +15 -0
  328. package/runtime/xt_constructors.c +2 -1
  329. package/runtime/xt_containers/objects.inc +22 -2
  330. package/runtime/xt_io/exception.inc +4 -2
  331. package/runtime/xt_stdlib/math.inc +62 -0
  332. package/runtime/xt_stdlib2/date.inc +7 -1
  333. package/runtime/xt_stdlib2/objects.inc +14 -0
  334. package/scripts/build-gui.ts +6 -72
  335. package/scripts/build-media.ts +245 -0
  336. package/scripts/support-matrix.ts +167 -0
  337. package/src/ast/common.ts +2 -0
  338. package/src/ast/declarations.ts +12 -1
  339. package/src/ast/expressions.ts +6 -0
  340. package/src/ast/kinds.ts +2 -0
  341. package/src/binder/binder.ts +3 -0
  342. package/src/binder/declarations.ts +70 -0
  343. package/src/binder/references.ts +84 -3
  344. package/src/binder/types.ts +30 -0
  345. package/src/canonical.ts +37 -0
  346. package/src/checker/assignability.ts +134 -0
  347. package/src/checker/calls.ts +135 -0
  348. package/src/checker/checker.ts +47 -0
  349. package/src/checker/declarations.ts +26 -0
  350. package/src/checker/flow.ts +236 -0
  351. package/src/checker/globals.ts +40 -0
  352. package/src/checker/source.ts +53 -0
  353. package/src/checker/type-infer.ts +185 -0
  354. package/src/checker/type-model.ts +212 -0
  355. package/src/checker/type-system.ts +578 -0
  356. package/src/checker/typecheck-types.ts +112 -0
  357. package/src/checker/typecheck.ts +586 -0
  358. package/src/checker/undefined.ts +43 -0
  359. package/src/checker/walk.ts +16 -0
  360. package/src/cli/help.ts +65 -0
  361. package/src/cli/hints.ts +13 -1
  362. package/src/cli/main.ts +12 -49
  363. package/src/codegen/generator/calls/access.ts +8 -8
  364. package/src/codegen/generator/calls/invocation.ts +8 -5
  365. package/src/codegen/generator/calls/super.ts +67 -5
  366. package/src/codegen/generator/context.ts +31 -19
  367. package/src/codegen/generator/debug.ts +2 -1
  368. package/src/codegen/generator/expressions/primary.ts +84 -13
  369. package/src/codegen/generator/generator.ts +11 -1
  370. package/src/codegen/generator/module.ts +65 -24
  371. package/src/codegen/generator/state.ts +11 -1
  372. package/src/codegen/generator/statements/variables.ts +20 -3
  373. package/src/codegen/generator/tables.ts +5 -0
  374. package/src/driver/build-key.ts +3 -0
  375. package/src/driver/bundler/graph.ts +6 -2
  376. package/src/driver/bundler/merge.ts +14 -1
  377. package/src/driver/bundler/types.ts +13 -0
  378. package/src/driver/compiler.ts +5 -1
  379. package/src/driver/fingerprint.ts +2 -1
  380. package/src/driver/frontend.ts +13 -2
  381. package/src/driver/options.ts +6 -0
  382. package/src/driver/version.ts +1 -1
  383. package/src/extensions/catalog.ts +2 -1
  384. package/src/extensions/gui/html.ts +23 -10
  385. package/src/extensions/gui/index.ts +65 -0
  386. package/src/extensions/media/index.ts +67 -0
  387. package/src/extensions/registry.ts +72 -0
  388. package/src/index.ts +2 -0
  389. package/src/parser/declarations/classes.ts +7 -0
  390. package/src/parser/expressions.ts +13 -1
  391. package/src/parser/literals.ts +6 -2
@@ -301,12 +301,32 @@ xt_value xt_object_get(xt_value value, xt_value key) {
301
301
  if (prop->getter != XT_UNDEFINED) return XT_UNDEFINED;
302
302
  return prop->value;
303
303
  }
304
+ /* `class Dog extends Animal` links Dog's property bag to the Animal
305
+ *constructor*, not to an object prototype, so a lookup of `Dog.kind`
306
+ must be able to continue through a function value. */
307
+ if (XT_IS_FUNCTION(cur->prototype)) {
308
+ xt_object *next = xt_as_object(cur->prototype);
309
+ if (!next || next == cur) break;
310
+ cur = next;
311
+ continue;
312
+ }
304
313
  if (!XT_IS_OBJECT(cur->prototype)) break;
305
314
  cur = (xt_object *)XT_GET_PTR(cur->prototype);
306
315
  }
307
316
  return XT_UNDEFINED;
308
317
  }
309
318
 
319
+ /* The next link of a prototype chain, or NULL at the end. `class B extends A`
320
+ * links B's property bag to the *constructor value* A, so the walk has to
321
+ * continue through a function just like through a plain object. */
322
+ static xt_object *xt_object_prototype_of(xt_value prototype, xt_object *current) {
323
+ xt_object *next = xt_as_object(prototype);
324
+ if (!next || next == current) return NULL;
325
+ if (next->header.kind != XT_OBJECT_KIND_OBJECT && next->header.kind != XT_OBJECT_KIND_ERROR)
326
+ return NULL;
327
+ return next;
328
+ }
329
+
310
330
  xt_value xt_object_set(xt_value value, xt_value key, xt_value newValue) {
311
331
  if (XT_IS_FUNCTION(value) && xt_key_is_prototype(key)) {
312
332
  return xt_function_set_prototype(value, newValue);
@@ -332,7 +352,7 @@ xt_value xt_object_set(xt_value value, xt_value key, xt_value newValue) {
332
352
  }
333
353
  /* No own property: an inherited accessor still intercepts the assignment
334
354
  (JavaScript assigns through prototype setters instead of shadowing). */
335
- xt_object *ancestor = XT_IS_OBJECT(obj->prototype) ? (xt_object *)XT_GET_PTR(obj->prototype) : NULL;
355
+ xt_object *ancestor = xt_object_prototype_of(obj->prototype, obj);
336
356
  while (ancestor) {
337
357
  xt_property *inherited = xt_object_find(ancestor, k);
338
358
  if (inherited) {
@@ -346,7 +366,7 @@ xt_value xt_object_set(xt_value value, xt_value key, xt_value newValue) {
346
366
  if (inherited->getter != XT_UNDEFINED) return newValue; /* getter-only accessor */
347
367
  break; /* plain inherited data property: create an own shadowing property */
348
368
  }
349
- ancestor = XT_IS_OBJECT(ancestor->prototype) ? (xt_object *)XT_GET_PTR(ancestor->prototype) : NULL;
369
+ ancestor = xt_object_prototype_of(ancestor->prototype, ancestor);
350
370
  }
351
371
  xt_property *inserted = xt_object_insert_property(obj, k);
352
372
  inserted->value = newValue;
@@ -217,9 +217,11 @@ static xt_value xt_file_url(const char *path) {
217
217
  const char *prefix = "file://";
218
218
  for (; *prefix; prefix++) url[j++] = *prefix;
219
219
  #if defined(_WIN32)
220
- url[j++] = '/';
220
+ url[j++] = XT_ARTIFACT_PATH_SEPARATOR;
221
221
  #endif
222
- for (const char *p = path; *p && j < sizeof(url) - 1; p++) url[j++] = (*p == '\\') ? '/' : *p;
222
+ for (const char *p = path; *p && j < sizeof(url) - 1; p++) {
223
+ url[j++] = (*p == '\\') ? XT_ARTIFACT_PATH_SEPARATOR : *p;
224
+ }
223
225
  url[j] = 0;
224
226
  return xt_string_from_cstr(url);
225
227
  }
@@ -70,3 +70,65 @@ xt_value xt_math_call(xt_value name, int32_t argc, xt_value *argv) {
70
70
  return xt_number(NAN);
71
71
  }
72
72
 
73
+
74
+ /* -- first-class Math ------------------------------------------------------ */
75
+
76
+ /* `Math` is lowered to `xt_math_call` at call sites, but it is also an object
77
+ * in JavaScript: `const cos = Math.cos`, `[1, 2].map(Math.max)` and
78
+ * `const { sqrt, PI } = Math` all read members as values. The singleton below
79
+ * holds one native closure per function, plus the constants. */
80
+
81
+ /* `M_PI` and friends are not portable: MSVC's `<math.h>` only defines them
82
+ * when `_USE_MATH_DEFINES` is set before the include, and strict-ANSI libcs
83
+ * hide them as well. Each literal below is the correctly rounded double of the
84
+ * mathematical constant, so it is bit-identical to the `M_*` macro on hosts
85
+ * that do provide one. */
86
+ static const double XT_MATH_PI = 3.14159265358979323846;
87
+ static const double XT_MATH_E = 2.71828182845904523536;
88
+ static const double XT_MATH_LN2 = 0.69314718055994530942;
89
+ static const double XT_MATH_LN10 = 2.30258509299404568402;
90
+ static const double XT_MATH_LOG2E = 1.44269504088896340736;
91
+ static const double XT_MATH_LOG10E = 0.43429448190325182765;
92
+ static const double XT_MATH_SQRT2 = 1.41421356237309504880;
93
+ static const double XT_MATH_SQRT1_2 = 0.70710678118654752440;
94
+
95
+ static const char *const XT_MATH_FUNCTIONS[] = {
96
+ "abs", "floor", "ceil", "round", "trunc", "sqrt", "cbrt", "pow",
97
+ "exp", "log", "log2", "log10", "log1p", "expm1", "sinh", "cosh",
98
+ "tanh", "asinh", "acosh", "atanh", "fround", "imul", "clz32", "sin",
99
+ "cos", "tan", "asin", "acos", "atan", "atan2", "hypot", "sign",
100
+ "random", "min", "max",
101
+ };
102
+
103
+ static xt_value g_math_object;
104
+
105
+ static xt_value xt_math_bridge(xt_value thisValue, xt_value env, int32_t argc, xt_value *argv) {
106
+ (void)thisValue;
107
+ return xt_math_call(xt_closure_env(env, 0), argc, argv);
108
+ }
109
+
110
+ xt_value xt_math_object(int32_t argc, xt_value *argv) {
111
+ (void)argc;
112
+ (void)argv;
113
+ if (g_math_object != 0) return g_math_object;
114
+ xt_value object = xt_object_new();
115
+ for (size_t i = 0; i < sizeof(XT_MATH_FUNCTIONS) / sizeof(XT_MATH_FUNCTIONS[0]); i++) {
116
+ /* The closure environment owns its own copy: the string is stored by the
117
+ * object, but a rooted value is cheap and keeps the lifetime obvious. */
118
+ xt_value name = xt_string_from_cstr(XT_MATH_FUNCTIONS[i]);
119
+ xt_value closure = xt_closure_new((void *)xt_math_bridge, 1, &name);
120
+ xt_function_set_metadata(closure, name, 1);
121
+ xt_object_set(object, name, closure);
122
+ }
123
+ xt_object_set(object, xt_string_from_cstr("PI"), xt_number(XT_MATH_PI));
124
+ xt_object_set(object, xt_string_from_cstr("E"), xt_number(XT_MATH_E));
125
+ xt_object_set(object, xt_string_from_cstr("LN2"), xt_number(XT_MATH_LN2));
126
+ xt_object_set(object, xt_string_from_cstr("LN10"), xt_number(XT_MATH_LN10));
127
+ xt_object_set(object, xt_string_from_cstr("LOG2E"), xt_number(XT_MATH_LOG2E));
128
+ xt_object_set(object, xt_string_from_cstr("LOG10E"), xt_number(XT_MATH_LOG10E));
129
+ xt_object_set(object, xt_string_from_cstr("SQRT2"), xt_number(XT_MATH_SQRT2));
130
+ xt_object_set(object, xt_string_from_cstr("SQRT1_2"), xt_number(XT_MATH_SQRT1_2));
131
+ g_math_object = object;
132
+ xt_gc_add_root(&g_math_object);
133
+ return object;
134
+ }
@@ -138,7 +138,13 @@ xt_value xt_date_static(xt_value name, int32_t argc, xt_value *argv) {
138
138
  tm.tm_year = xt_to_int32(xt_arg_at(argc, argv, 0)) - 1900;
139
139
  tm.tm_mon = xt_to_int32(xt_arg_at(argc, argv, 1));
140
140
  tm.tm_mday = argc > 2 ? xt_to_int32(xt_arg_at(argc, argv, 2)) : 1;
141
- return xt_number((double)xt_timegm(&tm) * 1000.0);
141
+ /* Date.UTC(year, month[, day[, hour[, minute[, second[, ms]]]]]). The time
142
+ * fields used to be dropped, so every UTC date came back at midnight. */
143
+ tm.tm_hour = argc > 3 ? xt_to_int32(xt_arg_at(argc, argv, 3)) : 0;
144
+ tm.tm_min = argc > 4 ? xt_to_int32(xt_arg_at(argc, argv, 4)) : 0;
145
+ tm.tm_sec = argc > 5 ? xt_to_int32(xt_arg_at(argc, argv, 5)) : 0;
146
+ double millis = argc > 6 ? xt_to_int32(xt_arg_at(argc, argv, 6)) : 0;
147
+ return xt_number((double)xt_timegm(&tm) * 1000.0 + millis);
142
148
  }
143
149
  return XT_UNDEFINED;
144
150
  }
@@ -135,6 +135,17 @@ static xt_value xt_object_define_property(xt_value target, xt_value key, xt_valu
135
135
  return target;
136
136
  }
137
137
 
138
+ static xt_value xt_object_define_properties(xt_value target, xt_value descriptors) {
139
+ xt_value names = xt_object_own_property_names(descriptors);
140
+ xt_array *keys = xt_as_array(names);
141
+ if (!keys) return target;
142
+ for (uint32_t i = 0; i < keys->length; i++) {
143
+ xt_value key = keys->items[i];
144
+ xt_object_define_property(target, key, xt_object_get(descriptors, key));
145
+ }
146
+ return target;
147
+ }
148
+
138
149
  static xt_value xt_object_get_own_property_descriptors(xt_value target) {
139
150
  xt_value result = xt_object_new();
140
151
  xt_value names = xt_object_own_property_names(target);
@@ -160,6 +171,9 @@ xt_value xt_object_static(xt_value name, int32_t argc, xt_value *argv) {
160
171
  return xt_object_define_property(xt_arg_at(argc, argv, 0), xt_arg_at(argc, argv, 1),
161
172
  xt_arg_at(argc, argv, 2));
162
173
  }
174
+ if (strcmp(fn, "defineProperties") == 0) {
175
+ return xt_object_define_properties(xt_arg_at(argc, argv, 0), xt_arg_at(argc, argv, 1));
176
+ }
163
177
  if (strcmp(fn, "keys") == 0) return xt_object_keys(xt_arg_at(argc, argv, 0));
164
178
  if (strcmp(fn, "getOwnPropertyNames") == 0) return xt_object_own_property_names(xt_arg_at(argc, argv, 0));
165
179
  if (strcmp(fn, "getOwnPropertySymbols") == 0) return xt_object_own_property_symbols(xt_arg_at(argc, argv, 0));
@@ -13,12 +13,14 @@
13
13
  * 1. fetch the pinned SDL3 source into `vendor/SDL` (gitignored),
14
14
  * 2. build `libSDL3.a` with CMake,
15
15
  * 2b. fetch + build static FreeType and HarfBuzz for text,
16
- * 2c. fetch + build a minimal static FFmpeg for `<video>`/`<audio>`
17
- * (skipped on Windows, where media support is compiled out),
18
16
  * 3. compile the engine translation units,
19
17
  * 4. merge everything into `gui.a` (libtool on macOS, `ar -M` elsewhere;
20
18
  * `gui.lib` on Windows, built from MSVC-compatible COFF objects).
21
19
  *
20
+ * Media decoding is *not* part of this archive: it lives in the separate
21
+ * `media` extension (`npm run media`, `runtime/ext_media/`), so a program that
22
+ * only renders HTML/CSS never links FFmpeg.
23
+ *
22
24
  * Environment:
23
25
  * xbintsc_CXX C++ compiler override (default: clang++ next to the resolved clang)
24
26
  * xbintsc_CMAKE CMake override (default: a vendored CMake, then `cmake`)
@@ -35,7 +37,6 @@
35
37
  */
36
38
 
37
39
  import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync } from "node:fs";
38
- import { availableParallelism } from "node:os";
39
40
  import { spawnSync, type SpawnSyncReturns } from "node:child_process";
40
41
  import { dirname, join, resolve } from "node:path";
41
42
  import { fileURLToPath } from "node:url";
@@ -62,9 +63,6 @@ const freetypeSrc = join(vendorDir, "freetype");
62
63
  const freetypeBuild = join(vendorDir, "freetype-build");
63
64
  const harfbuzzSrc = join(vendorDir, "harfbuzz");
64
65
  const harfbuzzBuild = join(vendorDir, "harfbuzz-build");
65
- const ffmpegSrc = join(vendorDir, "ffmpeg");
66
- const ffmpegBuild = join(vendorDir, "ffmpeg-build");
67
- const ffmpegInstall = join(ffmpegBuild, "dist");
68
66
  const objDir = join(root, "build", "gui-obj");
69
67
  const outDir = join(runtimeDir, "lib", platformSlug());
70
68
 
@@ -110,24 +108,8 @@ function cmakeToolchainArgs(): string[] {
110
108
  const SDL_TAG = process.env.SDL3_TAG || "release-3.2.10";
111
109
  const FREETYPE_VERSION = process.env.FREETYPE_VERSION || "2.13.3";
112
110
  const HARFBUZZ_VERSION = process.env.HARFBUZZ_VERSION || "10.1.0";
113
- const FFMPEG_VERSION = process.env.FFMPEG_VERSION || "7.1";
114
111
  const FREETYPE_URL = `https://download.savannah.gnu.org/releases/freetype/freetype-${FREETYPE_VERSION}.tar.xz`;
115
112
  const HARFBUZZ_URL = `https://github.com/harfbuzz/harfbuzz/releases/download/${HARFBUZZ_VERSION}/harfbuzz-${HARFBUZZ_VERSION}.tar.xz`;
116
- const FFMPEG_URL = `https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.xz`;
117
-
118
- /* A deliberately minimal FFmpeg: containers and decoders for the common web
119
- * media formats, file access only, no network, no encoder, no filter graph.
120
- * Disabling everything first keeps the static libraries small and the build
121
- * short. `--disable-x86asm` drops the nasm/yasm build dependency (the C
122
- * fallbacks are plenty for playback). This configuration is LGPL-only, so the
123
- * resulting `gui.a` stays redistributable under the engine's terms. */
124
- const FFMPEG_COMPONENTS: readonly string[] = [
125
- "--enable-protocol=file",
126
- "--enable-demuxer=mov,matroska,avi,flv,mpegts,mp3,ogg,wav,flac,aac,m4v,asf,mpeg,mpegvideo,h264,hevc,vc1",
127
- "--enable-decoder=h264,hevc,vp8,vp9,av1,aac,aac_latm,mp3,mp3float,opus,vorbis,flac,alac,pcm_s16le,pcm_s24le,pcm_s32le,pcm_u8,pcm_f32le,pcm_alaw,pcm_mulaw,ac3,eac3,wmav2,mjpeg",
128
- "--enable-parser=h264,hevc,vp8,vp9,av1,aac,aac_latm,mpegaudio,opus,vorbis,mpeg4video,mjpeg,flac",
129
- "--enable-bsf=h264_mp4toannexb,hevc_mp4toannexb,aac_adtstoasc,vp9_superframe,vp9_superframe_split,extract_extradata",
130
- ];
131
113
 
132
114
  function fail(message: string): never {
133
115
  console.error(`xbintsc: ${message}`);
@@ -345,16 +327,6 @@ function harfbuzzArchive(): string | undefined {
345
327
  return builtLibrary(harfbuzzBuild, ["libharfbuzz.a", "harfbuzz.lib"], /^harfbuzz[-\d.]*\.lib$/);
346
328
  }
347
329
 
348
- /* FFmpeg's release tarball has no CMake build, so it uses its own configure +
349
- * make. It also installs its headers under a prefix (rather than in the source
350
- * tree) because the *source* root contains a file literally named `version`
351
- * that would shadow the C++ standard header for any `.cpp` that includes it. */
352
- const FFMPEG_LIBRARY_NAMES = ["libavformat.a", "libavcodec.a", "libswscale.a", "libswresample.a", "libavutil.a"];
353
- function ffmpegStaticLibraries(): string[] | undefined {
354
- const libraries = FFMPEG_LIBRARY_NAMES.map((name) => join(ffmpegInstall, "lib", name));
355
- return libraries.every((library) => existsSync(library)) ? libraries : undefined;
356
- }
357
-
358
330
  /* -- 1. SDL3 source ------------------------------------------------------- */
359
331
 
360
332
  if (!existsSync(join(sdlSrc, "CMakeLists.txt"))) {
@@ -407,8 +379,7 @@ if (!existingSdlArchive) {
407
379
  /** Download a release tarball into `vendor/` and extract it under `name`.
408
380
  * Release tarballs are used (not git clones) because they ship generated
409
381
  * sources, so no ragel/autotools step is needed. `marker` is a file that only
410
- * exists once the tarball has been extracted (CMake projects ship
411
- * `CMakeLists.txt`; FFmpeg ships `configure`). */
382
+ * exists once the tarball has been extracted (e.g. `CMakeLists.txt`). */
412
383
  function ensureTarballSource(name: string, url: string, extractedName: string, marker = "CMakeLists.txt"): string {
413
384
  const destination = join(vendorDir, name);
414
385
  if (existsSync(join(destination, marker))) return destination;
@@ -463,41 +434,6 @@ if (!existsSync(harfbuzzArchive() ?? "")) {
463
434
  console.log(`xbintsc: reusing existing ${harfbuzzArchive()}`);
464
435
  }
465
436
 
466
- /* -- 2c. FFmpeg (media decoding) ------------------------------------------ */
467
-
468
- /* Windows builds have no `configure`/`make`, so media support is compiled out
469
- * there; the element still works and reports an `error` event. */
470
- let ffmpegLibraries: string[] | undefined;
471
- if (isWindows) {
472
- console.log("xbintsc: skipping FFmpeg on Windows; <video>/<audio> will report an error");
473
- } else {
474
- ensureTarballSource("ffmpeg", FFMPEG_URL, `ffmpeg-${FFMPEG_VERSION}`, "configure");
475
- ffmpegLibraries = ffmpegStaticLibraries();
476
- if (!ffmpegLibraries) {
477
- console.log(`xbintsc: building minimal static FFmpeg ${FFMPEG_VERSION} with ${clang}`);
478
- mkdirSync(ffmpegBuild, { recursive: true });
479
- run(join(ffmpegSrc, "configure"), [
480
- `--prefix=${ffmpegInstall}`,
481
- "--enable-static", "--disable-shared",
482
- "--disable-programs", "--disable-doc",
483
- "--disable-htmlpages", "--disable-manpages", "--disable-podpages", "--disable-txtpages",
484
- "--disable-avdevice", "--disable-avfilter", "--disable-postproc",
485
- "--disable-everything",
486
- "--enable-avformat", "--enable-avcodec", "--enable-avutil", "--enable-swscale", "--enable-swresample",
487
- "--disable-network", "--disable-autodetect", "--disable-x86asm", "--disable-debug", "--enable-pic",
488
- `--cc=${clang}`,
489
- `--cxx=${clangxx}`,
490
- ...FFMPEG_COMPONENTS,
491
- ], { cwd: ffmpegBuild });
492
- run("make", ["-j", String(availableParallelism())], { cwd: ffmpegBuild });
493
- run("make", ["install"], { cwd: ffmpegBuild });
494
- ffmpegLibraries = ffmpegStaticLibraries();
495
- } else {
496
- console.log(`xbintsc: reusing existing ${ffmpegInstall}/lib`);
497
- }
498
- if (!ffmpegLibraries) fail("FFmpeg build did not produce the expected static libraries");
499
- }
500
-
501
437
  /* -- 3. engine objects ---------------------------------------------------- */
502
438
 
503
439
  mkdirSync(objDir, { recursive: true });
@@ -508,14 +444,12 @@ const includeFlags = [
508
444
  "-I", join(freetypeBuild, "include"),
509
445
  "-I", join(harfbuzzSrc, "src"),
510
446
  ];
511
- if (ffmpegLibraries) includeFlags.push("-I", join(ffmpegInstall, "include"));
512
447
  const sources = readdirSync(engineDir)
513
448
  .filter((entry) => entry.endsWith(".cpp"))
514
449
  .sort();
515
450
 
516
451
  const objects: string[] = [];
517
452
  const cxxFlags = ["-std=c++17", "-O2", "-Wall", "-Wextra", ...includeFlags];
518
- if (ffmpegLibraries) cxxFlags.push("-DXT_GUI_WITH_FFMPEG=1");
519
453
  /* The engine is C++ and the MSVC C++ runtime must match the static C CRT that
520
454
  * clang links by default, so let every engine object record `libcpmt` as a
521
455
  * default library. The extension itself only contributes the DLL import library
@@ -539,7 +473,7 @@ const harfbuzz = harfbuzzArchive();
539
473
  if (!sdl || !freetype || !harfbuzz) {
540
474
  fail("one of the SDL3/FreeType/HarfBuzz archives was not found after building");
541
475
  }
542
- const libraries = [sdl, freetype, harfbuzz, ...(ffmpegLibraries ?? [])];
476
+ const libraries = [sdl, freetype, harfbuzz];
543
477
 
544
478
  mkdirSync(outDir, { recursive: true });
545
479
  rmSync(output, { force: true });
@@ -0,0 +1,245 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Build the media extension archive:
4
+ *
5
+ * runtime/lib/<os>-<arch>/media.a
6
+ *
7
+ * The GUI engine (`gui.a`) deliberately contains no decoder. This archive adds
8
+ * `<video>`/`<audio>` support: it bundles the FFmpeg-backed player from
9
+ * `runtime/ext_media/media.cpp` together with a minimal static FFmpeg, and the
10
+ * `media` extension links it only when the program asks for it. A program built
11
+ * with `--ext gui` alone therefore stays FFmpeg-free.
12
+ *
13
+ * Steps:
14
+ * 1. fetch the pinned FFmpeg release tarball into `vendor/ffmpeg`,
15
+ * 2. configure and build a minimal static FFmpeg into
16
+ * `vendor/ffmpeg-build/dist` (reused on later runs),
17
+ * 3. compile the player against those headers,
18
+ * 4. merge the player object and the FFmpeg libraries into `media.a`
19
+ * (libtool on macOS, `ar -M` elsewhere).
20
+ *
21
+ * Environment:
22
+ * xbintsc_CXX C++ compiler override (default: clang++ next to clang)
23
+ * xbintsc_AR archiver override (default: llvm-ar next to clang, then ar)
24
+ * FFMPEG_VERSION FFmpeg release (default: 7.1)
25
+ *
26
+ * Windows is not supported yet: FFmpeg needs its own `configure`/`make`, and an
27
+ * MSVC-compatible build has not been produced (see `doc/gui/known-issues.md`).
28
+ */
29
+
30
+ import { existsSync, mkdirSync, renameSync, rmSync } from "node:fs";
31
+ import { availableParallelism } from "node:os";
32
+ import { spawnSync, type SpawnSyncReturns } from "node:child_process";
33
+ import { dirname, join, resolve } from "node:path";
34
+ import { fileURLToPath } from "node:url";
35
+ import { platformSlug } from "../src/driver/paths.js";
36
+ import { probeClang, realRunner } from "../src/driver/toolchain.js";
37
+ import { resolveToolchain } from "../src/driver/toolchain-provider.js";
38
+
39
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
40
+ const runtimeDir = join(root, "runtime");
41
+ const sourceDir = join(runtimeDir, "ext_media");
42
+ const vendorDir = join(root, "vendor");
43
+ const ffmpegSrc = join(vendorDir, "ffmpeg");
44
+ const ffmpegBuild = join(vendorDir, "ffmpeg-build");
45
+ const ffmpegInstall = join(ffmpegBuild, "dist");
46
+ const objDir = join(root, "build", "media-obj");
47
+ const outDir = join(runtimeDir, "lib", platformSlug());
48
+ const output = join(outDir, "media.a");
49
+
50
+ const FFMPEG_VERSION = process.env.FFMPEG_VERSION || "7.1";
51
+ const FFMPEG_URL = `https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.xz`;
52
+
53
+ /* A deliberately minimal FFmpeg: containers and decoders for the common web
54
+ * media formats, file access only, no network, no encoder, no filter graph.
55
+ * Disabling everything first keeps the static libraries small and the build
56
+ * short. `--disable-x86asm` drops the nasm/yasm build dependency (the C
57
+ * fallbacks are plenty for playback). This configuration is LGPL-only, so the
58
+ * resulting archive stays redistributable. */
59
+ const FFMPEG_COMPONENTS: readonly string[] = [
60
+ "--enable-protocol=file",
61
+ "--enable-demuxer=mov,matroska,avi,flv,mpegts,mp3,ogg,wav,flac,aac,m4v,asf,mpeg,mpegvideo,h264,hevc,vc1",
62
+ "--enable-decoder=h264,hevc,vp8,vp9,av1,aac,aac_latm,mp3,mp3float,opus,vorbis,flac,alac,pcm_s16le,pcm_s24le,pcm_s32le,pcm_u8,pcm_f32le,pcm_alaw,pcm_mulaw,ac3,eac3,wmav2,mjpeg",
63
+ "--enable-parser=h264,hevc,vp8,vp9,av1,aac,aac_latm,mpegaudio,opus,vorbis,mpeg4video,mjpeg,flac",
64
+ "--enable-bsf=h264_mp4toannexb,hevc_mp4toannexb,aac_adtstoasc,vp9_superframe,vp9_superframe_split,extract_extradata",
65
+ ];
66
+
67
+ const FFMPEG_LIBRARY_NAMES = ["libavformat.a", "libavcodec.a", "libswscale.a", "libswresample.a", "libavutil.a"];
68
+
69
+ function fail(message: string): never {
70
+ console.error(`xbintsc: ${message}`);
71
+ process.exit(1);
72
+ }
73
+
74
+ function run(
75
+ command: string,
76
+ args: readonly string[],
77
+ options: { input?: string; quietStderr?: boolean; cwd?: string } = {},
78
+ ): SpawnSyncReturns<string> {
79
+ const quiet = options.quietStderr === true;
80
+ const result = spawnSync(command, args, {
81
+ stdio: options.input !== undefined ? ["pipe", "inherit", "inherit"] : quiet ? ["ignore", "inherit", "pipe"] : "inherit",
82
+ encoding: "utf8",
83
+ input: options.input,
84
+ cwd: options.cwd,
85
+ });
86
+ if (result.status !== 0) {
87
+ if (quiet && result.stderr) process.stderr.write(result.stderr);
88
+ fail(`${command} ${args.join(" ")} failed with status ${result.status ?? "?"}`);
89
+ }
90
+ return result;
91
+ }
92
+
93
+ function isRunnable(candidate: string): boolean {
94
+ return !spawnSync(candidate, ["--version"], { encoding: "utf8" }).error;
95
+ }
96
+
97
+ function firstAvailable(candidates: readonly (string | undefined)[]): string | undefined {
98
+ for (const candidate of candidates) {
99
+ if (candidate && isRunnable(candidate)) return candidate;
100
+ }
101
+ return undefined;
102
+ }
103
+
104
+ /** The tool that sits next to `clang` in the same LLVM installation. */
105
+ function siblingTool(clangPath: string, name: string): string | undefined {
106
+ if (!clangPath.includes("/") && !clangPath.includes("\\")) return undefined;
107
+ const candidate = join(dirname(clangPath), `${name}.exe`);
108
+ if (existsSync(candidate)) return candidate;
109
+ const bare = join(dirname(clangPath), name);
110
+ return existsSync(bare) ? bare : undefined;
111
+ }
112
+
113
+ /** Resolve `clang++` from the same LLVM as the driver's clang. */
114
+ function findClangxx(clangPath: string): string {
115
+ const override = process.env.xbintsc_CXX;
116
+ if (override) {
117
+ const probe = probeClang(realRunner, override);
118
+ if (!probe.responds) fail(`xbintsc_CXX ${override} does not respond to --version`);
119
+ return override;
120
+ }
121
+ const sibling = siblingTool(clangPath, "clang++");
122
+ if (sibling) return sibling;
123
+ const onPath = firstAvailable(["clang++"]);
124
+ if (onPath) return onPath;
125
+ fail("no clang++ found next to the resolved clang or on PATH; install LLVM or set xbintsc_CXX");
126
+ }
127
+
128
+ /** Resolve the archiver that matches the compiler, preferring LLVM's own. */
129
+ function findArchiver(clangPath: string): string {
130
+ const sibling = siblingTool(clangPath, "llvm-ar");
131
+ const archiver = firstAvailable([process.env.xbintsc_AR, sibling, "llvm-ar", "ar"]);
132
+ if (!archiver) fail("no archiver found; install llvm-ar or set xbintsc_AR");
133
+ return archiver;
134
+ }
135
+
136
+ function driverClang(): string {
137
+ try {
138
+ return resolveToolchain().clang;
139
+ } catch (error) {
140
+ fail(error instanceof Error ? error.message : String(error));
141
+ }
142
+ }
143
+
144
+ /** Download the FFmpeg release tarball and extract it under `vendor/ffmpeg`. */
145
+ function ensureFfmpegSource(): void {
146
+ if (existsSync(join(ffmpegSrc, "configure"))) return;
147
+ const tarball = join(vendorDir, "ffmpeg.tar.xz");
148
+ console.log(`xbintsc: fetching FFmpeg ${FFMPEG_VERSION} from ${FFMPEG_URL}`);
149
+ mkdirSync(vendorDir, { recursive: true });
150
+ run("curl", ["-sL", "--fail", "-o", tarball, FFMPEG_URL]);
151
+ run("tar", ["-xf", tarball, "-C", vendorDir]);
152
+ const extracted = join(vendorDir, `ffmpeg-${FFMPEG_VERSION}`);
153
+ if (!existsSync(extracted)) fail(`expected ffmpeg-${FFMPEG_VERSION} after extracting`);
154
+ rmSync(ffmpegSrc, { recursive: true, force: true });
155
+ renameSync(extracted, ffmpegSrc);
156
+ rmSync(tarball, { force: true });
157
+ }
158
+
159
+ function ffmpegStaticLibraries(): string[] | undefined {
160
+ const libraries = FFMPEG_LIBRARY_NAMES.map((name) => join(ffmpegInstall, "lib", name));
161
+ return libraries.every((library) => existsSync(library)) ? libraries : undefined;
162
+ }
163
+
164
+ if (process.platform === "win32") {
165
+ fail(
166
+ "the media extension is not available on Windows yet: FFmpeg needs a POSIX " +
167
+ "configure/make build (see doc/gui/known-issues.md)",
168
+ );
169
+ }
170
+
171
+ /* One toolchain for the player and FFmpeg: the same clang the driver uses, its
172
+ * sibling clang++, and the matching llvm-ar. Mixing another C++ runtime or
173
+ * object format would break the final link. */
174
+ const clang = driverClang();
175
+ const clangxx = findClangxx(clang);
176
+ const archiver = findArchiver(clang);
177
+
178
+ /* -- 1. FFmpeg source ------------------------------------------------------ */
179
+
180
+ ensureFfmpegSource();
181
+
182
+ /* -- 2. minimal static FFmpeg --------------------------------------------- */
183
+
184
+ let ffmpegLibraries = ffmpegStaticLibraries();
185
+ if (!ffmpegLibraries) {
186
+ console.log(`xbintsc: building minimal static FFmpeg ${FFMPEG_VERSION} with ${clang}`);
187
+ mkdirSync(ffmpegBuild, { recursive: true });
188
+ run(join(ffmpegSrc, "configure"), [
189
+ `--prefix=${ffmpegInstall}`,
190
+ "--enable-static", "--disable-shared",
191
+ "--disable-programs", "--disable-doc",
192
+ "--disable-htmlpages", "--disable-manpages", "--disable-podpages", "--disable-txtpages",
193
+ "--disable-avdevice", "--disable-avfilter", "--disable-postproc",
194
+ "--disable-everything",
195
+ "--enable-avformat", "--enable-avcodec", "--enable-avutil", "--enable-swscale", "--enable-swresample",
196
+ "--disable-network", "--disable-autodetect", "--disable-x86asm", "--disable-debug", "--enable-pic",
197
+ `--cc=${clang}`,
198
+ `--cxx=${clangxx}`,
199
+ ...FFMPEG_COMPONENTS,
200
+ ], { cwd: ffmpegBuild });
201
+ run("make", ["-j", String(availableParallelism())], { cwd: ffmpegBuild });
202
+ run("make", ["install"], { cwd: ffmpegBuild });
203
+ ffmpegLibraries = ffmpegStaticLibraries();
204
+ if (!ffmpegLibraries) fail("FFmpeg build did not produce the expected static libraries");
205
+ } else {
206
+ console.log(`xbintsc: reusing existing ${ffmpegInstall}/lib`);
207
+ }
208
+
209
+ /* -- 3. player object ------------------------------------------------------ */
210
+
211
+ /* FFmpeg installs its headers under a prefix (rather than in the source tree)
212
+ * because the *source* root contains a file literally named `version` that
213
+ * would shadow the C++ standard header for any `.cpp` that includes it. */
214
+ mkdirSync(objDir, { recursive: true });
215
+ const object = join(objDir, "media.o");
216
+ const playerSource = join(sourceDir, "media.cpp");
217
+ console.log("xbintsc: compiling media.cpp");
218
+ run(clangxx, [
219
+ "-std=c++17", "-O2", "-Wall", "-Wextra", "-fPIC",
220
+ "-I", runtimeDir,
221
+ "-I", join(ffmpegInstall, "include"),
222
+ "-c", playerSource,
223
+ "-o", object,
224
+ ]);
225
+
226
+ /* -- 4. merge into media.a ------------------------------------------------- */
227
+
228
+ mkdirSync(outDir, { recursive: true });
229
+ rmSync(output, { force: true });
230
+ if (process.platform === "darwin") {
231
+ run("libtool", ["-static", "-o", output, object, ...ffmpegLibraries], { quietStderr: true });
232
+ } else {
233
+ /* `ar -M` (GNU/LLVM) merges archives through an MRI script. */
234
+ const mri = (path: string): string => path.replace(/\\/g, "/");
235
+ const script = [
236
+ `create ${mri(output)}`,
237
+ `addmod ${mri(object)}`,
238
+ ...ffmpegLibraries.map((library) => `addlib ${mri(library)}`),
239
+ "save",
240
+ "end",
241
+ ].join("\n");
242
+ run(archiver, ["-M"], { input: script });
243
+ }
244
+
245
+ console.log(`xbintsc: wrote ${output}`);