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
@@ -0,0 +1,507 @@
1
+ /*
2
+ * xbintsc media extension — `<video>`/`<audio>` decoding.
3
+ *
4
+ * This translation unit is the only place that knows about FFmpeg. It is
5
+ * compiled into the prebuilt `media.a` by `scripts/build-media.ts` and linked
6
+ * only when the `media` extension is enabled; the GUI engine itself is
7
+ * decoder-free (see `../ext_gui/media_api.h`).
8
+ *
9
+ * Threading: the engine is single-threaded and its values live on the main
10
+ * thread, so nothing here may run on an audio callback. Instead the decoder is
11
+ * pushed from `tick()` on the main thread: it writes PCM into the
12
+ * `xtgui::AudioSink` supplied by the GUI (an SDL audio stream opened in push
13
+ * mode), and SDL pulls from its queued buffer on its own thread.
14
+ * `queued_seconds()` keeps the buffer shallow so there is no unbounded latency.
15
+ */
16
+
17
+ #include "../ext_gui/media.h"
18
+
19
+ #include <algorithm>
20
+ #include <chrono>
21
+ #include <cmath>
22
+ #include <cstdio>
23
+ #include <cstring>
24
+ #include <memory>
25
+ #include <unordered_map>
26
+ #include <vector>
27
+
28
+ extern "C" {
29
+ #include <libavcodec/avcodec.h>
30
+ #include <libavformat/avformat.h>
31
+ #include <libavutil/channel_layout.h>
32
+ #include <libavutil/opt.h>
33
+ #include <libswresample/swresample.h>
34
+ #include <libswscale/swscale.h>
35
+ }
36
+
37
+ namespace xtgui {
38
+
39
+ namespace {
40
+
41
+ std::unordered_map<uint64_t, MediaPlayer*>& PlayerTable() {
42
+ static std::unordered_map<uint64_t, MediaPlayer*> table;
43
+ return table;
44
+ }
45
+
46
+ /** Audio format everything is resampled to before it reaches the sink. */
47
+ constexpr int kAudioRate = 48000;
48
+ constexpr int kAudioChannels = 2;
49
+ /** How much audio to keep buffered ahead of the play head. */
50
+ constexpr double kAudioAhead = 0.25;
51
+ /** Minimum gap between `timeupdate` events. */
52
+ constexpr double kTimeUpdateIntervalMs = 250.0;
53
+
54
+ /** Monotonic wall clock, the reference for playback timing. */
55
+ double NowMs() {
56
+ static const auto start = std::chrono::steady_clock::now();
57
+ return std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count();
58
+ }
59
+
60
+ uint64_t NextId() {
61
+ static uint64_t next = 1;
62
+ return next++;
63
+ }
64
+
65
+ struct QueuedFrame {
66
+ Image image;
67
+ double pts = 0;
68
+ };
69
+
70
+ class FfmpegPlayer final : public MediaPlayer {
71
+ public:
72
+ explicit FfmpegPlayer(std::unique_ptr<AudioSink> audio) : audio_(std::move(audio)) {}
73
+
74
+ ~FfmpegPlayer() override {
75
+ PlayerTable().erase(id_);
76
+ if (sws_) sws_freeContext(sws_);
77
+ if (swr_) swr_free(&swr_);
78
+ if (vframe_) av_frame_free(&vframe_);
79
+ if (aframe_) av_frame_free(&aframe_);
80
+ if (pkt_) av_packet_free(&pkt_);
81
+ if (vctx_) avcodec_free_context(&vctx_);
82
+ if (actx_) avcodec_free_context(&actx_);
83
+ if (fmt_) avformat_close_input(&fmt_);
84
+ }
85
+
86
+ bool Open(const std::string& path, std::string* error) {
87
+ source_ = path;
88
+ if (avformat_open_input(&fmt_, path.c_str(), nullptr, nullptr) < 0) {
89
+ if (error) *error = "cannot open media: " + path;
90
+ return false;
91
+ }
92
+ if (avformat_find_stream_info(fmt_, nullptr) < 0) {
93
+ if (error) *error = "cannot read media metadata: " + path;
94
+ return false;
95
+ }
96
+ duration_ = fmt_->duration > 0 ? static_cast<double>(fmt_->duration) / AV_TIME_BASE : 0.0;
97
+
98
+ vstream_ = av_find_best_stream(fmt_, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);
99
+ astream_ = av_find_best_stream(fmt_, AVMEDIA_TYPE_AUDIO, -1, -1, nullptr, 0);
100
+ if (vstream_ < 0 && astream_ < 0) {
101
+ if (error) *error = "no audio or video stream: " + path;
102
+ return false;
103
+ }
104
+ if (vstream_ >= 0 && !OpenVideo(fmt_->streams[vstream_])) {
105
+ vstream_ = -1;
106
+ }
107
+ if (astream_ >= 0 && !OpenAudio(fmt_->streams[astream_])) {
108
+ astream_ = -1;
109
+ }
110
+ if (vstream_ < 0 && astream_ < 0) {
111
+ if (error) *error = "no supported stream: " + path;
112
+ return false;
113
+ }
114
+ pkt_ = av_packet_alloc();
115
+ vframe_ = av_frame_alloc();
116
+ aframe_ = av_frame_alloc();
117
+
118
+ /* A file whose default duration is unknown falls back to the last
119
+ * timestamp of the video stream, which is enough for the `duration`
120
+ * property and for end-of-stream detection. */
121
+ if (duration_ <= 0 && vstream_ >= 0) {
122
+ AVStream* vs = fmt_->streams[vstream_];
123
+ if (vs->duration > 0) duration_ = vs->duration * av_q2d(vs->time_base);
124
+ }
125
+ id_ = NextId();
126
+ PlayerTable()[id_] = this;
127
+ pending_events_ |= kMediaEventLoadedMetadata;
128
+ return true;
129
+ }
130
+
131
+ uint64_t id() const override { return id_; }
132
+ const std::string& source() const override { return source_; }
133
+ bool has_video() const override { return vstream_ >= 0; }
134
+ bool has_audio() const override { return astream_ >= 0; }
135
+ double duration() const override { return duration_; }
136
+
137
+ bool paused() const override { return paused_; }
138
+ bool ended() const override { return ended_; }
139
+ double current_time() const override {
140
+ if (paused_) return clock_base_pts_;
141
+ return clock_base_pts_ + (NowMs() - clock_base_ms_) / 1000.0;
142
+ }
143
+ double volume() const override { return volume_; }
144
+ bool muted() const override { return muted_; }
145
+ bool loop() const override { return loop_; }
146
+ bool autoplay() const override { return autoplay_; }
147
+
148
+ void play() override {
149
+ if (ended_) Seek(0);
150
+ if (!paused_) return;
151
+ paused_ = false;
152
+ clock_base_ms_ = NowMs();
153
+ if (audio_ready_) audio_->resume();
154
+ pending_events_ |= kMediaEventPlay;
155
+ }
156
+
157
+ void pause() override {
158
+ if (paused_) return;
159
+ clock_base_pts_ = current_time();
160
+ paused_ = true;
161
+ if (audio_ready_) audio_->pause();
162
+ pending_events_ |= kMediaEventPause;
163
+ }
164
+
165
+ void seek(double seconds) override { Seek(seconds); }
166
+
167
+ void set_volume(double value) override {
168
+ volume_ = std::min(1.0, std::max(0.0, value));
169
+ ApplyGain();
170
+ }
171
+ void set_muted(bool value) override {
172
+ muted_ = value;
173
+ ApplyGain();
174
+ }
175
+ void set_loop(bool value) override { loop_ = value; }
176
+ void set_autoplay(bool value) override { autoplay_ = value; }
177
+
178
+ int frame_width() const override { return vctx_ ? vctx_->width : 0; }
179
+ int frame_height() const override { return vctx_ ? vctx_->height : 0; }
180
+ const Image* frame() const override { return current_valid_ ? &current_ : nullptr; }
181
+ uint64_t frame_generation() const override { return generation_; }
182
+
183
+ unsigned tick(double now_ms) override {
184
+ unsigned events = pending_events_;
185
+ pending_events_ = 0;
186
+ if (vstream_ < 0 && astream_ < 0) return events;
187
+ if (!started_ && autoplay_) {
188
+ started_ = true;
189
+ play();
190
+ events |= pending_events_;
191
+ pending_events_ = 0;
192
+ }
193
+ if (paused_ || ended_) return events;
194
+ if (now_ms - last_timeupdate_ms_ >= kTimeUpdateIntervalMs) {
195
+ events |= kMediaEventTimeUpdate;
196
+ last_timeupdate_ms_ = now_ms;
197
+ }
198
+ const double t = current_time();
199
+ PumpVideo(t);
200
+ PumpAudio();
201
+ events |= CheckEnded(now_ms);
202
+ return events;
203
+ }
204
+
205
+ void invalidate_texture() override {}
206
+
207
+ private:
208
+ bool OpenVideo(AVStream* stream) {
209
+ const AVCodec* codec = avcodec_find_decoder(stream->codecpar->codec_id);
210
+ if (!codec) return false;
211
+ vctx_ = avcodec_alloc_context3(codec);
212
+ if (!vctx_) return false;
213
+ if (avcodec_parameters_to_context(vctx_, stream->codecpar) < 0) return false;
214
+ vctx_->pkt_timebase = stream->time_base;
215
+ if (avcodec_open2(vctx_, codec, nullptr) < 0) return false;
216
+ return true;
217
+ }
218
+
219
+ bool OpenAudio(AVStream* stream) {
220
+ const AVCodec* codec = avcodec_find_decoder(stream->codecpar->codec_id);
221
+ if (!codec) return false;
222
+ actx_ = avcodec_alloc_context3(codec);
223
+ if (!actx_) return false;
224
+ if (avcodec_parameters_to_context(actx_, stream->codecpar) < 0) return false;
225
+ actx_->pkt_timebase = stream->time_base;
226
+ if (avcodec_open2(actx_, codec, nullptr) < 0) return false;
227
+
228
+ AVChannelLayout out_layout;
229
+ av_channel_layout_default(&out_layout, kAudioChannels);
230
+ if (swr_alloc_set_opts2(&swr_, &out_layout, AV_SAMPLE_FMT_FLT, kAudioRate,
231
+ &actx_->ch_layout, actx_->sample_fmt, actx_->sample_rate,
232
+ 0, nullptr) < 0 ||
233
+ !swr_ || swr_init(swr_) < 0) {
234
+ return false;
235
+ }
236
+ av_channel_layout_uninit(&out_layout);
237
+
238
+ /* Audio is best effort: a headless CI box has no device. Video still
239
+ * plays, and audio is silently dropped. */
240
+ audio_ready_ = audio_ != nullptr && audio_->open();
241
+ if (audio_ready_) ApplyGain();
242
+ return true;
243
+ }
244
+
245
+ void ApplyGain() {
246
+ if (audio_ready_) audio_->set_gain(muted_ ? 0.0f : static_cast<float>(volume_));
247
+ }
248
+
249
+ double queued_seconds() const {
250
+ if (!audio_ready_) return 0.0;
251
+ return audio_->queued_seconds();
252
+ }
253
+
254
+ double stream_pts(const AVStream* stream, int64_t ts) const {
255
+ return static_cast<double>(ts) * av_q2d(stream->time_base);
256
+ }
257
+
258
+ void Seek(double seconds) {
259
+ double target = seconds;
260
+ if (duration_ > 0) target = std::min(duration_, std::max(0.0, target));
261
+ if (target < 0) target = 0;
262
+ clock_base_pts_ = target;
263
+ clock_base_ms_ = NowMs();
264
+ ended_ = false;
265
+ started_ = true;
266
+
267
+ if (fmt_) {
268
+ const int seek_stream = vstream_ >= 0 ? vstream_ : astream_;
269
+ const int64_t ts = static_cast<int64_t>(target / av_q2d(fmt_->streams[seek_stream]->time_base));
270
+ if (av_seek_frame(fmt_, seek_stream, ts, AVSEEK_FLAG_BACKWARD) < 0) {
271
+ avformat_flush(fmt_);
272
+ }
273
+ if (vctx_) avcodec_flush_buffers(vctx_);
274
+ if (actx_) avcodec_flush_buffers(actx_);
275
+ }
276
+ if (audio_ready_) audio_->clear();
277
+ video_queue_.clear();
278
+ current_valid_ = false;
279
+ drop_before_ = vstream_ >= 0 ? target : -1;
280
+ reader_eof_ = false;
281
+ video_flushed_ = false;
282
+ audio_flushed_ = false;
283
+ video_drained_ = false;
284
+ audio_drained_ = false;
285
+ }
286
+
287
+ bool ReadPacket() {
288
+ const int ret = av_read_frame(fmt_, pkt_);
289
+ if (ret < 0) {
290
+ reader_eof_ = true;
291
+ return false;
292
+ }
293
+ if (pkt_->stream_index == vstream_) {
294
+ avcodec_send_packet(vctx_, pkt_);
295
+ DrainVideo();
296
+ } else if (pkt_->stream_index == astream_) {
297
+ avcodec_send_packet(actx_, pkt_);
298
+ DrainAudio();
299
+ }
300
+ av_packet_unref(pkt_);
301
+ return true;
302
+ }
303
+
304
+ void DrainVideo() {
305
+ while (avcodec_receive_frame(vctx_, vframe_) == 0) {
306
+ const double pts = stream_pts(fmt_->streams[vstream_], vframe_->best_effort_timestamp);
307
+ Image image;
308
+ ConvertVideo(vframe_, &image);
309
+ video_queue_.push_back({std::move(image), pts});
310
+ }
311
+ }
312
+
313
+ void ConvertVideo(const AVFrame* frame, Image* out) {
314
+ const int w = frame->width;
315
+ const int h = frame->height;
316
+ if (!sws_ || sws_width_ != w || sws_height_ != h || sws_pix_fmt_ != frame->format) {
317
+ if (sws_) sws_freeContext(sws_);
318
+ sws_ = sws_getContext(w, h, static_cast<AVPixelFormat>(frame->format), w, h, AV_PIX_FMT_RGBA,
319
+ SWS_BILINEAR, nullptr, nullptr, nullptr);
320
+ sws_width_ = w;
321
+ sws_height_ = h;
322
+ sws_pix_fmt_ = frame->format;
323
+ }
324
+ if (!sws_) return;
325
+ out->width = w;
326
+ out->height = h;
327
+ out->pixels.resize(static_cast<size_t>(w) * h * 4);
328
+ uint8_t* dst[4] = {out->pixels.data(), nullptr, nullptr, nullptr};
329
+ int dst_linesize[4] = {w * 4, 0, 0, 0};
330
+ sws_scale(sws_, frame->data, frame->linesize, 0, h, dst, dst_linesize);
331
+ }
332
+
333
+ void DrainAudio() {
334
+ while (avcodec_receive_frame(actx_, aframe_) == 0) {
335
+ if (!audio_ready_) continue;
336
+ const int capacity = swr_get_out_samples(swr_, aframe_->nb_samples);
337
+ if (capacity <= 0) continue;
338
+ audio_buf_.resize(static_cast<size_t>(capacity) * kAudioChannels * sizeof(float));
339
+ uint8_t* out = audio_buf_.data();
340
+ const int got = swr_convert(swr_, &out, capacity,
341
+ reinterpret_cast<const uint8_t* const*>(aframe_->extended_data),
342
+ aframe_->nb_samples);
343
+ if (got > 0) {
344
+ const int bytes = got * kAudioChannels * static_cast<int>(sizeof(float));
345
+ audio_->write(audio_buf_.data(), static_cast<size_t>(bytes));
346
+ }
347
+ }
348
+ }
349
+
350
+ void PumpVideo(double t) {
351
+ if (vstream_ < 0 || video_drained_) return;
352
+ while (true) {
353
+ if (video_queue_.empty()) {
354
+ if (reader_eof_) {
355
+ if (!video_flushed_) {
356
+ avcodec_send_packet(vctx_, nullptr);
357
+ video_flushed_ = true;
358
+ DrainVideo();
359
+ continue;
360
+ }
361
+ video_drained_ = true;
362
+ return;
363
+ }
364
+ if (!ReadPacket() && video_queue_.empty() && reader_eof_) continue;
365
+ if (video_queue_.empty()) continue;
366
+ }
367
+ QueuedFrame& front = video_queue_.front();
368
+ if (drop_before_ >= 0 && front.pts < drop_before_ - 0.001) {
369
+ video_queue_.erase(video_queue_.begin());
370
+ continue;
371
+ }
372
+ if (front.pts <= t + 0.01) {
373
+ current_ = std::move(front.image);
374
+ current_valid_ = true;
375
+ ++generation_;
376
+ video_queue_.erase(video_queue_.begin());
377
+ drop_before_ = -1;
378
+ } else {
379
+ return;
380
+ }
381
+ }
382
+ }
383
+
384
+ void PumpAudio() {
385
+ if (astream_ < 0 || audio_drained_) return;
386
+ while (queued_seconds() < kAudioAhead) {
387
+ if (reader_eof_) {
388
+ if (!audio_flushed_) {
389
+ avcodec_send_packet(actx_, nullptr);
390
+ audio_flushed_ = true;
391
+ DrainAudio();
392
+ continue;
393
+ }
394
+ audio_drained_ = true;
395
+ return;
396
+ }
397
+ if (!ReadPacket()) return;
398
+ }
399
+ }
400
+
401
+ unsigned CheckEnded(double now_ms) {
402
+ if (ended_) return 0;
403
+ if (!reader_eof_) return 0;
404
+ if (vstream_ >= 0 && !video_drained_) return 0;
405
+ if (astream_ >= 0 && !audio_drained_) return 0;
406
+ if (astream_ >= 0 && queued_seconds() > 0.05) return 0;
407
+ if (vstream_ >= 0 && duration_ > 0 && current_time() < duration_ - 0.05) return 0;
408
+ if (loop_) {
409
+ Seek(0);
410
+ play();
411
+ return kMediaEventPlay;
412
+ }
413
+ ended_ = true;
414
+ paused_ = true;
415
+ clock_base_pts_ = duration_ > 0 ? duration_ : current_time();
416
+ last_timeupdate_ms_ = now_ms;
417
+ return kMediaEventEnded;
418
+ }
419
+
420
+ uint64_t id_ = 0;
421
+ std::string source_;
422
+ AVFormatContext* fmt_ = nullptr;
423
+ AVCodecContext* vctx_ = nullptr;
424
+ AVCodecContext* actx_ = nullptr;
425
+ int vstream_ = -1;
426
+ int astream_ = -1;
427
+ AVPacket* pkt_ = nullptr;
428
+ AVFrame* vframe_ = nullptr;
429
+ AVFrame* aframe_ = nullptr;
430
+ SwsContext* sws_ = nullptr;
431
+ SwrContext* swr_ = nullptr;
432
+ int sws_width_ = 0;
433
+ int sws_height_ = 0;
434
+ int sws_pix_fmt_ = -1;
435
+
436
+ std::vector<QueuedFrame> video_queue_;
437
+ Image current_;
438
+ bool current_valid_ = false;
439
+ uint64_t generation_ = 0;
440
+ double drop_before_ = -1;
441
+
442
+ std::unique_ptr<AudioSink> audio_;
443
+ bool audio_ready_ = false;
444
+ std::vector<uint8_t> audio_buf_;
445
+
446
+ bool reader_eof_ = false;
447
+ bool video_flushed_ = false;
448
+ bool audio_flushed_ = false;
449
+ bool video_drained_ = false;
450
+ bool audio_drained_ = false;
451
+
452
+ double duration_ = 0;
453
+ double volume_ = 1.0;
454
+ bool muted_ = false;
455
+ bool loop_ = false;
456
+ bool autoplay_ = false;
457
+ bool paused_ = true;
458
+ bool ended_ = false;
459
+ bool started_ = false;
460
+ double clock_base_pts_ = 0;
461
+ double clock_base_ms_ = 0;
462
+ double last_timeupdate_ms_ = -1e9;
463
+ unsigned pending_events_ = 0;
464
+ };
465
+
466
+ } // namespace
467
+
468
+ MediaPlayer* OpenMediaPlayer(const char* path, std::string* error,
469
+ std::unique_ptr<AudioSink> audio) {
470
+ std::unique_ptr<FfmpegPlayer> player(new FfmpegPlayer(std::move(audio)));
471
+ if (!player->Open(path, error)) return nullptr;
472
+ return player.release();
473
+ }
474
+
475
+ MediaPlayer* MediaPlayerById(uint64_t id) {
476
+ auto& table = PlayerTable();
477
+ const auto it = table.find(id);
478
+ return it == table.end() ? nullptr : it->second;
479
+ }
480
+
481
+ void ShutdownMediaPlayers() {
482
+ /* Move the table out before destroying it: `~FfmpegPlayer` erases its own
483
+ * entry, and a destructor mutating the container it is being cleared from
484
+ * would invalidate the clearing loop. With an empty table the erases are
485
+ * no-ops. */
486
+ auto pending = std::move(PlayerTable());
487
+ PlayerTable().clear();
488
+ }
489
+
490
+ } // namespace xtgui
491
+
492
+ extern "C" XtMediaPlayerHandle xt_media_backend_open(const char* path, char* error,
493
+ size_t error_size, void* audio_sink) {
494
+ std::unique_ptr<xtgui::AudioSink> audio(static_cast<xtgui::AudioSink*>(audio_sink));
495
+ std::string message;
496
+ xtgui::MediaPlayer* player = xtgui::OpenMediaPlayer(path != nullptr ? path : "", &message, std::move(audio));
497
+ if (player == nullptr && error != nullptr && error_size > 0) {
498
+ std::snprintf(error, error_size, "%s", message.c_str());
499
+ }
500
+ return player;
501
+ }
502
+
503
+ extern "C" XtMediaPlayerHandle xt_media_backend_by_id(uint64_t id) {
504
+ return xtgui::MediaPlayerById(id);
505
+ }
506
+
507
+ extern "C" void xt_media_backend_shutdown(void) { xtgui::ShutdownMediaPlayers(); }
@@ -0,0 +1,35 @@
1
+ /*
2
+ * xbintsc media extension — registration bridge.
3
+ *
4
+ * The decoders live in `media.a`, a prebuilt C++ archive. A static archive is
5
+ * only searched for members that satisfy an undefined symbol, so a backend that
6
+ * merely had a constructor would never be extracted. This tiny C translation
7
+ * unit is compiled and linked *directly* (it is the media extension's
8
+ * `runtimeSources()` entry): its constructor references every backend entry
9
+ * point, which pulls the archive member in, and installs the table into the GUI
10
+ * engine before `main` runs.
11
+ *
12
+ * The bridge is intentionally C-only and has no FFmpeg (or SDL) dependency. If
13
+ * the `gui` extension is not enabled, `xt_media_install` is unresolved; the
14
+ * registry rejects that combination first with a "media requires gui"
15
+ * diagnostic (see `src/extensions/registry.ts`).
16
+ */
17
+
18
+ #include "../ext_gui/media_api.h"
19
+
20
+ static const XtMediaApi kMediaApi = {
21
+ xt_media_backend_open,
22
+ xt_media_backend_by_id,
23
+ xt_media_backend_shutdown,
24
+ };
25
+
26
+ #if defined(__GNUC__) || defined(__clang__)
27
+ __attribute__((constructor)) static void xt_media_register(void) {
28
+ xt_media_install(&kMediaApi);
29
+ }
30
+ #elif defined(_MSC_VER)
31
+ /* COFF has no `constructor` attribute. `media.lib` is not produced on Windows
32
+ * yet; keep the table referenced so the translation unit is not empty. */
33
+ void xt_media_register(void);
34
+ void xt_media_register(void) { xt_media_install(&kMediaApi); }
35
+ #endif
@@ -32,9 +32,9 @@
32
32
  /** True when `c` separates path segments on this platform. */
33
33
  static int xt_path_is_sep(char c) {
34
34
  #if defined(_WIN32)
35
- return c == '/' || c == '\\';
35
+ return c == XT_ARTIFACT_PATH_SEPARATOR || c == '\\';
36
36
  #else
37
- return c == '/';
37
+ return c == XT_ARTIFACT_PATH_SEPARATOR;
38
38
  #endif
39
39
  }
40
40
 
@@ -57,7 +57,7 @@ static int xt_path_is_absolute(const char *path) {
57
57
  if (path[0] != '\0' && path[1] == ':') return root == 3;
58
58
  return xt_path_is_sep(path[0]) ? 1 : 0;
59
59
  #else
60
- return path[0] == '/';
60
+ return path[0] == XT_ARTIFACT_PATH_SEPARATOR;
61
61
  #endif
62
62
  }
63
63
 
@@ -129,11 +129,11 @@ static void xt_path_normalize(const char *input, char *out, size_t size) {
129
129
 
130
130
  size_t position = 0;
131
131
  for (size_t p = 0; p < prefixLen && position + 1 < size; p++) out[position++] = prefix[p];
132
- if (absolute && position + 1 < size) out[position++] = '/';
132
+ if (absolute && position + 1 < size) out[position++] = XT_ARTIFACT_PATH_SEPARATOR;
133
133
  for (size_t s = 0; s < count; s++) {
134
134
  size_t segmentLength = strlen(segments[s]);
135
135
  if (position + segmentLength + 2 > size) break;
136
- if (s > 0) out[position++] = '/';
136
+ if (s > 0) out[position++] = XT_ARTIFACT_PATH_SEPARATOR;
137
137
  memcpy(out + position, segments[s], segmentLength);
138
138
  position += segmentLength;
139
139
  }
@@ -325,14 +325,14 @@ static xt_value xt_path_relative(const char *from, const char *to) {
325
325
  result[0] = '\0';
326
326
  size_t position = 0;
327
327
  for (size_t i = common; i < fromParts.count && position + 3 < sizeof(result); i++) {
328
- if (position > 0) result[position++] = '/';
328
+ if (position > 0) result[position++] = XT_ARTIFACT_PATH_SEPARATOR;
329
329
  result[position++] = '.';
330
330
  result[position++] = '.';
331
331
  }
332
332
  for (size_t i = common; i < toParts.count; i++) {
333
333
  size_t segmentLength = strlen(toParts.segments[i]);
334
334
  if (position + segmentLength + 2 > sizeof(result)) break;
335
- if (position > 0) result[position++] = '/';
335
+ if (position > 0) result[position++] = XT_ARTIFACT_PATH_SEPARATOR;
336
336
  memcpy(result + position, toParts.segments[i], segmentLength);
337
337
  position += segmentLength;
338
338
  }
@@ -58,9 +58,11 @@ xt_value xt_url_path_to_file_url(int32_t argc, xt_value *argv) {
58
58
  const char *prefix = "file://";
59
59
  for (; *prefix && j < sizeof(href) - 1; prefix++) href[j++] = *prefix;
60
60
  #if defined(_WIN32)
61
- if (j < sizeof(href) - 1) href[j++] = '/';
61
+ if (j < sizeof(href) - 1) href[j++] = XT_ARTIFACT_PATH_SEPARATOR;
62
62
  #endif
63
- for (const char *p = path; *p && j < sizeof(href) - 1; p++) href[j++] = (*p == '\\') ? '/' : *p;
63
+ for (const char *p = path; *p && j < sizeof(href) - 1; p++) {
64
+ href[j++] = (*p == '\\') ? XT_ARTIFACT_PATH_SEPARATOR : *p;
65
+ }
64
66
  href[j] = 0;
65
67
 
66
68
  xt_value url = xt_object_new();
package/runtime/rt.h CHANGED
@@ -31,6 +31,19 @@ extern "C" {
31
31
 
32
32
  typedef uint64_t xt_value;
33
33
 
34
+ /* Separator for paths the runtime renders into program-visible artifacts: the
35
+ * `path` module's results and `file://` URLs. This is deliberately NOT selected
36
+ * by `#if defined(_WIN32)`: every host must emit the same bytes (Windows
37
+ * accepts `/` everywhere), which is what keeps output comparable across
38
+ * platforms and reproducible in the self-hosting fixpoint. Native filesystem
39
+ * paths — diagnostics, `import.meta.filename`, `process.cwd()` — keep the host
40
+ * separator and never use this constant. */
41
+ #define XT_ARTIFACT_PATH_SEPARATOR '/'
42
+
43
+ #if XT_ARTIFACT_PATH_SEPARATOR != '/'
44
+ #error "XT_ARTIFACT_PATH_SEPARATOR is the canonical artifact separator; it must not vary by platform"
45
+ #endif
46
+
34
47
  /* Tag layout (top 16 bits). Values whose tag bits are not 0xFFF8.. are
35
48
  * doubles, so ordinary numbers travel unboxed. */
36
49
  #define XT_TAG_MASK 0xFFFF000000000000ULL
@@ -221,6 +234,8 @@ xt_value xt_object_ctor(int32_t argc, xt_value *argv);
221
234
  xt_value xt_call_method(xt_value target, xt_value name, int32_t argc, xt_value *argv);
222
235
  /** Implements `Math.<name>(...)`; the name is interned by the compiler. */
223
236
  xt_value xt_math_call(xt_value name, int32_t argc, xt_value *argv);
237
+ /* The first-class `Math` object (function members as native closures). */
238
+ xt_value xt_math_object(int32_t argc, xt_value *argv);
224
239
  /** Implements `JSON.parse` / `JSON.stringify`. */
225
240
  xt_value xt_json_parse(int32_t argc, xt_value *argv);
226
241
  xt_value xt_json_stringify(int32_t argc, xt_value *argv);
@@ -270,7 +270,8 @@ static const char *const XT_OBJECT_STATICS[] = {
270
270
  "keys", "values", "entries", "assign", "getOwnPropertyNames",
271
271
  "getOwnPropertySymbols", "groupBy", "freeze", "isFrozen", "fromEntries",
272
272
  "getPrototypeOf", "setPrototypeOf", "hasOwn", "is", "create",
273
- "defineProperty", "getOwnPropertyDescriptor", "getOwnPropertyDescriptors",
273
+ "defineProperty", "defineProperties", "getOwnPropertyDescriptor",
274
+ "getOwnPropertyDescriptors",
274
275
  };
275
276
  static const char *const XT_NUMBER_STATICS[] = {
276
277
  "isInteger", "isSafeInteger", "isFinite", "isNaN", "parseFloat", "parseInt",