xbintsc 0.3.46 → 0.3.61

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 (131) hide show
  1. package/AGENTS.md +95 -0
  2. package/README.md +25 -0
  3. package/README.zh-CN.md +23 -0
  4. package/dist/src/cli/hints.d.ts +54 -0
  5. package/dist/src/cli/hints.js +165 -0
  6. package/dist/src/cli/hints.js.map +1 -0
  7. package/dist/src/cli/main.js +73 -9
  8. package/dist/src/cli/main.js.map +1 -1
  9. package/dist/src/codegen/generator/tables.d.ts +26 -0
  10. package/dist/src/codegen/generator/tables.js +64 -12
  11. package/dist/src/codegen/generator/tables.js.map +1 -1
  12. package/dist/src/diagnostics/source-text.d.ts +22 -0
  13. package/dist/src/diagnostics/source-text.js +76 -0
  14. package/dist/src/diagnostics/source-text.js.map +1 -0
  15. package/dist/src/driver/bundler/graph.js +2 -1
  16. package/dist/src/driver/bundler/graph.js.map +1 -1
  17. package/dist/src/driver/compiler.js +28 -3
  18. package/dist/src/driver/compiler.js.map +1 -1
  19. package/dist/src/driver/config.d.ts +7 -0
  20. package/dist/src/driver/config.js.map +1 -1
  21. package/dist/src/driver/win-entry.d.ts +21 -0
  22. package/dist/src/driver/win-entry.js +52 -0
  23. package/dist/src/driver/win-entry.js.map +1 -0
  24. package/dist/src/lexer/scanner/strings.js +16 -3
  25. package/dist/src/lexer/scanner/strings.js.map +1 -1
  26. package/dist/tests/cli/hints.test.d.ts +9 -0
  27. package/dist/tests/cli/hints.test.js +143 -0
  28. package/dist/tests/cli/hints.test.js.map +1 -0
  29. package/dist/tests/cli/main.test.js +6 -4
  30. package/dist/tests/cli/main.test.js.map +1 -1
  31. package/dist/tests/codegen/llvm.test.js +17 -2
  32. package/dist/tests/codegen/llvm.test.js.map +1 -1
  33. package/dist/tests/e2e/gui-example-browser.test.d.ts +12 -0
  34. package/dist/tests/e2e/gui-example-browser.test.js +164 -0
  35. package/dist/tests/e2e/gui-example-browser.test.js.map +1 -0
  36. package/dist/tests/e2e/gui-example.test.d.ts +14 -0
  37. package/dist/tests/e2e/gui-example.test.js +76 -0
  38. package/dist/tests/e2e/gui-example.test.js.map +1 -0
  39. package/dist/tests/e2e/gui-helpers.d.ts +39 -4
  40. package/dist/tests/e2e/gui-helpers.js +38 -9
  41. package/dist/tests/e2e/gui-helpers.js.map +1 -1
  42. package/dist/tests/e2e/gui-layout.test.js +155 -0
  43. package/dist/tests/e2e/gui-layout.test.js.map +1 -1
  44. package/dist/tests/e2e/gui-lifecycle.test.js +10 -1
  45. package/dist/tests/e2e/gui-lifecycle.test.js.map +1 -1
  46. package/dist/tests/e2e/gui-paint.test.js +44 -4
  47. package/dist/tests/e2e/gui-paint.test.js.map +1 -1
  48. package/dist/tests/e2e/gui-script.test.js +8 -4
  49. package/dist/tests/e2e/gui-script.test.js.map +1 -1
  50. package/dist/tests/helpers.js +3 -2
  51. package/dist/tests/helpers.js.map +1 -1
  52. package/dist/tests/lexer/strings.test.js +14 -2
  53. package/dist/tests/lexer/strings.test.js.map +1 -1
  54. package/doc/DESIGN.md +117 -0
  55. package/doc/ai/README.md +63 -0
  56. package/doc/ai/build-recipe.md +137 -0
  57. package/doc/ai/cli.md +142 -0
  58. package/doc/ai/contributing.md +196 -0
  59. package/doc/ai/extensions.md +148 -0
  60. package/doc/ai/language-support.md +152 -0
  61. package/doc/ai/troubleshooting.md +163 -0
  62. package/doc/ai/zh-CN/README.md +56 -0
  63. package/doc/ai/zh-CN/build-recipe.md +132 -0
  64. package/doc/ai/zh-CN/cli.md +127 -0
  65. package/doc/ai/zh-CN/contributing.md +173 -0
  66. package/doc/ai/zh-CN/extensions.md +139 -0
  67. package/doc/ai/zh-CN/language-support.md +147 -0
  68. package/doc/ai/zh-CN/troubleshooting.md +150 -0
  69. package/doc/gui-scripts.md +350 -0
  70. package/doc/gui.md +752 -0
  71. package/doc/icon.md +265 -0
  72. package/doc/implemented.md +373 -0
  73. package/doc/node-implemented.md +588 -0
  74. package/doc/node-unimplemented.md +167 -0
  75. package/doc/post/announce.md +43 -0
  76. package/doc/requirements.md +145 -0
  77. package/doc/unimplemented.md +286 -0
  78. package/doc/xbintsc.config.schema.json +71 -0
  79. package/doc/zh-CN/DESIGN.md +104 -0
  80. package/doc/zh-CN/gui-scripts.md +329 -0
  81. package/doc/zh-CN/gui.md +656 -0
  82. package/doc/zh-CN/icon.md +241 -0
  83. package/doc/zh-CN/implemented.md +365 -0
  84. package/doc/zh-CN/node-implemented.md +533 -0
  85. package/doc/zh-CN/node-unimplemented.md +141 -0
  86. package/doc/zh-CN/plan-require-node-modules.md +284 -0
  87. package/doc/zh-CN/post/announce.md +47 -0
  88. package/doc/zh-CN/requirements.md +134 -0
  89. package/doc/zh-CN/unimplemented.md +247 -0
  90. package/llms.txt +45 -0
  91. package/package.json +4 -1
  92. package/runtime/ext_gui/dom_api.cpp +19 -0
  93. package/runtime/ext_gui/dom_api_node.cpp +4 -1
  94. package/runtime/ext_gui/dom_api_node_read.cpp +12 -0
  95. package/runtime/ext_gui/gui.cpp +45 -3
  96. package/runtime/ext_gui/gui_engine.h +15 -0
  97. package/runtime/ext_gui/gui_events.cpp +10 -3
  98. package/runtime/ext_gui/layout.cpp +6 -0
  99. package/runtime/ext_gui/layout.h +4 -0
  100. package/runtime/ext_gui/layout_flex.cpp +13 -5
  101. package/runtime/ext_gui/layout_flow.cpp +134 -4
  102. package/runtime/ext_gui/layout_flow_internal.h +41 -2
  103. package/runtime/ext_gui/layout_inline.cpp +11 -3
  104. package/runtime/ext_gui/paint.cpp +6 -0
  105. package/runtime/ext_gui/renderer.cpp +13 -11
  106. package/runtime/ext_gui/renderer_image.cpp +12 -8
  107. package/runtime/ext_gui/renderer_shaders.h +131 -4
  108. package/runtime/ext_gui/renderer_shaders_data.h +1809 -0
  109. package/runtime/ext_gui/renderer_text.cpp +12 -8
  110. package/runtime/ext_gui/script.cpp +12 -2
  111. package/runtime/ext_gui/shaders.hlsl +98 -0
  112. package/runtime/ext_gui/spirv/fill.frag +19 -0
  113. package/runtime/ext_gui/spirv/fill.vert +42 -0
  114. package/runtime/ext_gui/spirv/image.frag +16 -0
  115. package/runtime/ext_gui/spirv/quad.vert +30 -0
  116. package/runtime/ext_gui/spirv/text.frag +16 -0
  117. package/runtime/ext_gui/text.cpp +14 -0
  118. package/runtime/ext_gui/window.cpp +12 -0
  119. package/runtime/rt.h +10 -0
  120. package/scripts/build-gui-shaders.mjs +204 -0
  121. package/scripts/build-gui.ts +35 -0
  122. package/scripts/check-file-length.ts +5 -1
  123. package/src/cli/hints.ts +194 -0
  124. package/src/cli/main.ts +82 -9
  125. package/src/codegen/generator/tables.ts +60 -14
  126. package/src/diagnostics/source-text.ts +78 -0
  127. package/src/driver/bundler/graph.ts +2 -1
  128. package/src/driver/compiler.ts +29 -3
  129. package/src/driver/config.ts +7 -0
  130. package/src/driver/win-entry.ts +59 -0
  131. package/src/lexer/scanner/strings.ts +16 -3
@@ -0,0 +1,247 @@
1
+ # xbintsc 未实现语法与功能
2
+
3
+ > 语言 / Language:[English](../unimplemented.md) | **简体中文**
4
+
5
+ 本文档基于对源码(`src/`、`runtime/`)与测试(`tests/`)的逐文件核对整理,列出**当前未实现 / 仅部分实现 / 语义与标准不符**的语法与功能。
6
+
7
+ 判定标准(按严重程度):
8
+
9
+ 1. **未解析**:解析器直接报语法错误,无法进入代码生成。
10
+ 2. **解析但代码生成报错**:AST / 绑定能建立,但 `codegen` 报 `UnsupportedFeature`。
11
+ 3. **解析且能编译,但语义未实现或有偏差**:能产出二进制,但运行结果不符合 ECMAScript / TypeScript 语义。
12
+
13
+ > 近期已补齐:**ECMAScript 语义 + 差分测试** —— 数字格式化 / 强制转换(`toFixed` / `toPrecision` / `toExponential`、十六 / 八 / 二进制解析、完整的 `+` 与关系运算 `ToPrimitive`)、`JSON.stringify` 省略规则、`Array.prototype.sort` 默认稳定字符串序、整数键插入序、整链可选链短路、`finally` 在提前 `return` / `break` / `continue` 时执行、对象键插入序、感知 UTF-8 且能展示循环引用的 `console` 输出、`Array.prototype.splice`、`String.prototype.match` / `lastIndexOf` / 带 limit 的 `split`、正则捕获组(`exec` / `match` / `replace` / `split` / `search`)、不可变数组方法(`toReversed` / `toSorted` / `toSpliced` / `with`)、`Object.getOwnPropertyNames` / `groupBy`、全局 URI 函数、标签语句、`#private` 字段 / 方法 / 静态成员、标记模板(含 raw 字符串与 `String.raw`)、相对模块的 `import * as ns`。测试套件新增了**差分测试脚手架**:每个用例同时经 xbintsc 与 Node 运行并逐字节比较输出。
14
+
15
+ > 更早已补齐:`class` 声明 / 类表达式、`new` / `this`、继承 `extends` / `super`、`instanceof`、方法 / 静态成员 / 实例字段 / getter 与 setter / 构造器参数属性、`async` / `await` + `Promise`、`import` / `export` 多文件打包(含 `.js` → `.ts` 说明符解析与命名空间导入)、`Map` / `Set` / `Date` / `RegExp` / `JSON` / `BigInt`、解构绑定与解构参数、`enum` / `const enum`,以及**自举**(产出的 LLVM IR 达到逐字节不动点)。
16
+
17
+ ---
18
+
19
+ ## 1. 语句层未实现
20
+
21
+ ### 1.1 解析但代码生成报 `UnsupportedFeature`
22
+
23
+ | 语法 | 现状 | 说明 |
24
+ | --- | --- | --- |
25
+ | `namespace` / `module` 声明 | 解析 ✓,代码生成 ✗ | → "does not yet support this statement (module declaration)" |
26
+
27
+ > 标签语句(`label: statement`、`break label`、`continue label`)已实现,包括非循环语句上的标签。
28
+
29
+ ### 1.2 解析器不支持(直接语法错误)
30
+
31
+ 目前未知有语句级别的缺口。解析器有意保持宽松,接受绝大多数 TypeScript 语句语法;不支持的形式会在代码生成阶段报错(见上)。
32
+
33
+ ---
34
+
35
+ ## 2. 表达式层未实现
36
+
37
+ ### 2.1 解析但代码生成报 `UnsupportedFeature`
38
+
39
+ 无——生成器已实现。
40
+
41
+ > 标记模板已实现,含 raw 字符串与 `String.raw`。生成器(`function*`、`yield`、`yield*`)
42
+ > 基于有栈协程实现,支持 `next`/`throw`/`return`、`for...of`、展开与委托;
43
+ > `import.meta` 可解析,但无运行时取值;异步生成器尚未支持。
44
+
45
+ ### 2.2 解析器不支持
46
+
47
+ 目前未知有表达式级别的缺口。
48
+
49
+ ### 2.3 运算符未实现
50
+
51
+ | 运算符 | 现状 | 说明 |
52
+ | --- | --- | --- |
53
+ | `new.target` | ✗ | 未实现 |
54
+ | `typeof` / `void` / `in` / `delete` / `instanceof` | ✓ | 映射到运行时辅助函数(`xt_typeof`、`xt_in`、`xt_delete`、`xt_instance_of`) |
55
+
56
+ ---
57
+
58
+ ## 3. 标准库未实现
59
+
60
+ ### 3.1 已实现(概要)
61
+
62
+ - 数组:`push` `pop` `shift` `unshift` `join` `slice` `splice` `indexOf` `lastIndexOf` `includes` `concat` `reverse` `forEach` `map` `filter` `reduce` `reduceRight` `find` `findIndex` `findLast` `findLastIndex` `some` `every` `sort` `flat` `flatMap` `fill` `copyWithin` `at` `keys` `values` `entries`,以及不可变的 `toReversed` / `toSorted` / `toSpliced` / `with`
63
+ - 字符串:`charAt` `charCodeAt` `codePointAt` `indexOf` `lastIndexOf` `includes` `startsWith` `endsWith` `slice` `substring` `substr` `split` `match` `replace` `replaceAll` `search` `toUpperCase` `toLowerCase` `trim` `trimStart` `trimEnd` `padStart` `padEnd` `repeat` `concat` `at` `localeCompare` `valueOf`
64
+ - `Math`:完整函数与常量
65
+ - `Object.keys` / `values` / `entries` / `assign` / `getOwnPropertyNames` / `groupBy`,对象展开 `{...obj}`
66
+ - 全局函数:`parseInt` `parseFloat` `isNaN` `isFinite` `Number` `String` `Boolean` `encodeURI` `decodeURI` `encodeURIComponent` `decodeURIComponent`
67
+ - `console.log` / `info` / `warn` / `error` / `dir` / `trace` / `assert` / `count` / `group` / `table` / `time`
68
+
69
+ ### 3.2 已补齐
70
+
71
+ - `JSON.parse` / `JSON.stringify`
72
+ - `Date`、`RegExp`(POSIX ERE 子集,支持捕获组)、`Map`、`Set`、`Promise`、`BigInt`
73
+ - `Array` 静态(`isArray/of/from`)、`Object` 静态、`Number` 静态、`String` 静态(`fromCharCode` / `fromCodePoint` / `raw`)
74
+ - `Error` 家族(`Error`、`TypeError`、`RangeError`、`SyntaxError`、`ReferenceError`、`EvalError`、`URIError`、`AggregateError`):支持 `new`、`instanceof`、`extends`、`String(err)` / `err.toString()` 与 `.stack`
75
+
76
+ ### 3.3 仍未实现
77
+
78
+ | 类别 | 现状 |
79
+ | --- | --- |
80
+ | `Symbol` 构造器与 symbol 原始值 | ✓ `Symbol(description)`、著名符号、`Symbol.for` / `Symbol.keyFor`、symbol 属性键与 `Object.getOwnPropertySymbols` |
81
+ | `String.prototype.normalize` | ✗ 未实现 |
82
+ | `structuredClone` | ✗ 未实现 |
83
+ | 内置错误子类(`TypeError`、`RangeError` 等) | ✓ 一等公民构造器与原型;整个家族 `instanceof Error` 成立 |
84
+ | `AggregateError` 构造器 | ✓ `new AggregateError(errors, message)`;`Promise.any` 现以其作为 rejection |
85
+ | 迭代器协议 / `Symbol.iterator` / `for...of` 自定义可迭代 | ✓ 数组、字符串、`Map`、`Set`、生成器以及任何暴露 `[Symbol.iterator]()` 的对象均可在 `for...of` / 展开中使用 |
86
+ | 生成器(`function*`、`yield`、`yield*`、`next`/`throw`/`return`) | ✓ 基于有栈协程实现;支持 `for...of`、展开与委托。`return()` 直接结束,不执行 `finally` |
87
+ | 定时器 | ✓ 核心事件循环提供 `setTimeout` / `clearTimeout` / `setInterval` / `clearInterval`(返回数字 id,无 `Timeout` 对象;无 `setImmediate` / `queueMicrotask`) |
88
+ | I/O / 进程等宿主 API | 仅通过扩展(如 Node `fs`)提供 |
89
+
90
+ ### 3.4 类型化数组
91
+
92
+ 已实现:`Uint8Array`、`Int8Array`、`Uint8ClampedArray`、`Uint16Array`、
93
+ `Int16Array`、`Uint32Array`、`Int32Array`、`Float32Array`、`Float64Array`,支持
94
+ `new X(n)` / `new X(数组样)`、`X.from` / `X.of`、`BYTES_PER_ELEMENT`、按类型强制的
95
+ 元素写入,以及 `fill` / `set` / `slice` / `subarray` / `join` / `toString` /
96
+ `indexOf` / `lastIndexOf` / `includes` / `forEach` / `map` / `filter` / `every` /
97
+ `some` / `find` / `findIndex` / `reduce` / `reverse` / `sort` / `copyWithin` /
98
+ `at` / `keys` / `values` / `entries`。已知差异:
99
+
100
+ | 功能 | 现状 |
101
+ | --- | --- |
102
+ | `subarray` | ~ 返回副本,而非共享缓冲区上的视图 |
103
+ | `keys` / `values` / `entries` | ~ 返回数组,而非迭代器对象 |
104
+ | `ArrayBuffer` / `DataView` / `.buffer` / `.byteOffset` 语义 | ✗ 无底层缓冲区类型;`.byteOffset` 恒为 0 |
105
+ | `BigInt64Array` / `BigUint64Array` | ✗ 未实现 |
106
+ | `instanceof Uint8Array` | ✗ 全局构造器不是一等公民值,`x instanceof Uint8Array` 会报 `Cannot find name 'Uint8Array'` |
107
+ | 基于 `Buffer` / `ArrayBuffer` 的类型化数组视图 | ✗ 构造器仅能从长度 / 数组样 / 类型化数组复制 |
108
+
109
+ ---
110
+
111
+ ## 4. 模块系统
112
+
113
+ | 功能 | 现状 |
114
+ | --- | --- |
115
+ | 多文件 / 模块解析与链接 | ✓ 驱动层 AST 打包(`src/driver/modules.ts`):解析每个模块、按模块前缀重命名顶层符号、改写引用后合并为单文件重新绑定 |
116
+ | 具名导入导出 | ✓ `import { a, b as c }` / `export { a as b }` / `export const/let/var/function/class` |
117
+ | 默认导入导出 | ✓ `export default` / `import d from` |
118
+ | 再导出 `export { x } from` / `export * from` | ✓(`export *` 复制依赖模块的导出) |
119
+ | 命名空间导入 `import * as ns` | ✓ 降级为持有全部导出的合成对象字面量 |
120
+ | 循环依赖 | ✗ 直接报错(不做循环初始化语义) |
121
+ | 实时绑定(live bindings) | ✗ 命名空间对象与导入绑定是模块求值时的快照 |
122
+ | ESM `node_modules` 包 | ✓ 裸说明符沿目录树解析,支持 `exports` / `module` / `main`、作用域包与子路径;包源码与相对模块一样被打包 |
123
+ | CommonJS `require` / `module.exports` | ✓ 仅限 `node_modules` 内:打包器为每个 CommonJS 包注入合成的 `module` / `exports`,将 `require("x")` 降级为依赖的导出对象,并把外部/内置 `require` 提升为 ESM `import`。用户代码(`node_modules` 之外)的 `require()` 仍会报错并提示改用 ESM `import` |
124
+
125
+ > 扩展模块(如 `fs`)可通过裸名称或 `node:` 前缀的 `import` 引入 —— `import { readFileSync } from "fs"` / `import path from "path"` —— 并解析到运行时入口(具名、默认与命名空间形式均可)。相对模块与 `node_modules` 中的 ESM/CJS 包均在驱动层打包;CJS 包内的 `require` 会静态解析并内联。
126
+
127
+ ---
128
+
129
+ ## 5. 函数特性未实现(或语义缺失)
130
+
131
+ | 特性 | 解析 | 语义 |
132
+ | --- | --- | --- |
133
+ | 默认参数 `function f(a = 5)` | ✓ | ✓ 已实现 |
134
+ | 剩余参数 `function f(...args)` | ✓ | ✓ 已实现 |
135
+ | 调用参数展开 `f(...args)` | ✓ | ✓ 已实现 |
136
+ | 解构参数 / 解构绑定 | ✓ | ✓ 已实现 |
137
+ | `arguments` 对象 | ✓(隐式) | ✓ 已实现(箭头函数取的是自身参数,而非外层函数的 `arguments`,与 JS 不同) |
138
+ | `this` 绑定 / 方法调用语义 | ✓ | ✓ 已实现(`this` 作为函数首个 ABI 参数线程化;箭头函数词法继承) |
139
+ | `async` / `await` / Promise | ✓ | ✓ 已实现(同步微任务模型) |
140
+ | `new.target` | ✗ | 未实现 |
141
+ | 生成器 / 迭代器 / `yield` | ✓ 已实现 | `function*`、`yield`、`yield*`、`.next/.throw/.return`;不支持异步生成器 |
142
+ | 闭包 `arity` | — | `xt_closure_arity` 恒为 -1,未填充 |
143
+ | `fn.call` / `fn.apply` / `fn.bind` | ✓ | ✓ 已实现(绑定闭包不跟踪部分参数的 `length`) |
144
+ | `fn.name` / `fn.length` | ✓ | ✓ 已实现(从声明 / 赋值 / 属性键推断;绑定函数为 `"bound ..."` 并调整 arity) |
145
+ | 内置方法一等公民(`typeof arr.map`、`const f = arr.push`、`obj.method?.()`) | ✓ | ✓ 作为**未绑定**方法值实现:读取 `arr.map` 得到函数,`arr.map?.(cb)` 与 `obj.method?.()` 会把接收者作为 `this` 绑定,脱离接收者调用(`const f = arr.map; f(cb)`)会像 JavaScript 一样抛错。方法的 `name`/`length` 也一并暴露 |
146
+ | `fn.toString()` | ~ | 返回原生占位字符串(`function name() { [native code] }`),而非原始源码文本 |
147
+
148
+ ---
149
+
150
+ ## 6. 类与面向对象
151
+
152
+ | 特性 | 现状 |
153
+ | --- | --- |
154
+ | `class` 声明 / 类表达式 | ✓ 已实现 |
155
+ | 构造函数 `constructor` | ✓ |
156
+ | 实例字段 / 属性声明 | ✓ |
157
+ | 方法 | ✓ |
158
+ | `static` 字段 / 方法 | ✓ |
159
+ | 继承 `extends` / `super` | ✓(单级正确;`super` 取 `this` 原型的原型,理论缺陷见第 8 节) |
160
+ | 原型链 / 方法查找 | ✓ |
161
+ | `instanceof` | ✓ |
162
+ | `new` / 实例化 | ✓ |
163
+ | `get` / `set` 访问器 | ✓ 已实现 |
164
+ | 参数属性 `constructor(public x: T)` | ✓ 已实现 |
165
+ | 私有字段 `#x` | ✓ 已实现(以字面量 `#x` 作为键存储;无访问控制强制) |
166
+ | `enum` / `const enum` | ✓ 已实现(正向 + 反向映射) |
167
+ | `private` / `protected` / `public` / `readonly` 修饰符 | ✗ 无访问控制(擦除) |
168
+ | `abstract` / `implements` | ✗(擦除) |
169
+ | 父子类 `#x` 同名 | ✗ 可能混用(同一字面量键) |
170
+
171
+ ---
172
+
173
+ ## 7. 类型系统未实现(仅解析、不检查)
174
+
175
+ 类型语法被解析为 AST 后在绑定 / 代码生成阶段**整体擦除**,不做任何类型检查:
176
+
177
+ - 类型注解、返回类型、类型参数、类型别名、接口、泛型约束等:解析 ✓,检查 ✗。
178
+ - 检查器诊断码(`TypeMismatch` `NotCallable` `PropertyNotFound` `ArgumentCountMismatch`)已定义但**未使用**。
179
+ - `as` / `satisfies` / 非空断言 `!`:直接擦除,不做任何断言语义。
180
+ - 泛型:无运行时实例化,类型参数被忽略。
181
+ - 可选链 `?.` 的类型窄化:无(运行时短路已实现,但无类型层面的窄化)。
182
+
183
+ ---
184
+
185
+ ## 8. 运行时 / 语义与标准不符(已知偏差)
186
+
187
+ 这些特性**能编译、能运行**,但结果不完全符合 ECMAScript:
188
+
189
+ | 项 | 偏差 |
190
+ | --- | --- |
191
+ | 字符串 `length` | 按 UTF-8 字节计数,而非 UTF-16 码元(`"\u00e9".length` 报 1 而非 2;`"\u{1F600}".length` 报 4 而非 2;`codePointAt` 同样有偏差) |
192
+ | `Object.getPrototypeOf({})` | 返回 `undefined`,而非 `Object.prototype` 对象 |
193
+ | 全局正则 `lastIndex` | `/g`、`/y` 正则的 `test` / `exec` 不推进也不读取调用方设置的 `lastIndex` |
194
+ | `String.normalize` / `structuredClone` | 未实现 |
195
+ | 内置方法一等公民 | 以未绑定方法值实现(见第 5 节);`fn.toString()` 返回占位字符串而非源码文本 |
196
+ | `for...in` | 枚举对象 / 数组 / 字符串的自身键;不含原型链属性 |
197
+ | 数组越界 / 稀疏 | 越界访问返回 `undefined`;对 `arr.length` 赋值会截断 / 扩展,但不区分稀疏空洞 |
198
+ | 内存管理 | 非移动标记-清扫回收器(在 `xt_alloc` 之后):显式根、子系统根提供者与保守 C 栈扫描。单线程、stop-the-world;无移动/压缩与分代 |
199
+ | 函数 `arity` / 调用参数个数 | 无参数个数校验;`fn.length` 报告声明的 arity,但调用不做校验 |
200
+ | `async` / `await` | **同步微任务模型**:`await` 在已 settle 的 promise 上同步继续。定时器与套接字仅在程序主体执行完后由事件循环运行,因此无法等待由定时器回调 settle 的 promise |
201
+ | `super` | `super.x` / `super(...)` 取 `this` 原型的原型;单级继承正确,继承深度 > 1 时可能不准确 |
202
+ | `Error.stack` | 未捕获 |
203
+ | 模块实时绑定 | 命名空间导入与导入绑定为快照(见第 4 节) |
204
+ | `import.meta` | 可解析但无取值 |
205
+
206
+ > 数字格式化、宽松相等、`+` / 关系运算 `ToPrimitive`、整链可选链、`finally` 提前退出、对象键顺序等均已对齐 Node,并由差分测试覆盖,故不再列为偏差。
207
+
208
+ ---
209
+
210
+ ## 9. 工具链 / 平台 / 工程未实现
211
+
212
+ | 项 | 现状 |
213
+ | --- | --- |
214
+ | GC(垃圾回收) | ✓ `xt_alloc.c` 中的非移动标记-清扫,按存活堆阈值(`XT_GC_THRESHOLD`)或 `xt_gc_collect()` 触发;显式根 + 根提供者 + 保守活动栈扫描。无压缩、弱引用或后台回收 |
215
+ | 自举(self-hosting) | ✓ 编译器已能自编译:`xbintsc build src/cli/main.ts` 可产出可用二进制,且从第 1 代起产出的 IR 保持稳定。运行时仍为 C |
216
+ | 类型检查器 | ✗ 仅定义诊断码,无 checker |
217
+ | 完整标准库 | 部分:Math / JSON / Date / Map / Set / RegExp / `Error` / `BigInt` / `Symbol` 已实现;String.normalize / structuredClone 缺失 |
218
+ | 多文件模块打包 | 部分:相对路径 `.ts` 打包、ESM `node_modules` 包、命名空间导入、裸说明符扩展模块导入已实现;循环依赖 / CommonJS / 实时绑定未实现 |
219
+ | 真正的异步运行时 / 事件循环 | ✗(Promise 为同步微任务模型) |
220
+ | Windows 二进制产物验证 | 构建层已适配(`.exe` 后缀、链接参数分支),并在 CI 中验证 |
221
+ | 精确的 ECMAScript 数值 / 字符串 / 比较语义 | 部分,见第 8 节 |
222
+
223
+ ---
224
+
225
+ ## 10. 速查:未实现 / 部分实现清单
226
+
227
+ ```
228
+ 未实现(语句):namespace/module 声明
229
+
230
+ 未实现(表达式):new.target、import.meta 取值
231
+
232
+ 未实现(函数):`fn.toString()` 源码文本、异步生成器
233
+
234
+ 未实现(类/面向对象):abstract/implements、访问控制、父子类 #x 同名
235
+
236
+ 未实现(标准库):String.normalize、structuredClone
237
+
238
+ 未实现(模块):循环依赖、实时绑定、动态 `require(expr)`、
239
+ `require.resolve` / `require.cache`、`__dirname` / `__filename`
240
+
241
+ 未实现(类型系统):类型检查、泛型实例化、断言语义、可选链类型窄化
242
+
243
+ 未实现(运行时):真正的异步事件循环、UTF-16 length、
244
+ Object.prototype 身份、全局正则 lastIndex
245
+
246
+ 未实现(工程):类型检查器
247
+ ```
package/llms.txt ADDED
@@ -0,0 +1,45 @@
1
+ # xbintsc
2
+
3
+ > xbintsc compiles a subset of TypeScript directly to native binaries: it parses
4
+ > TypeScript itself, lowers the program to LLVM IR text, and drives clang to
5
+ > produce a standalone executable linked against a small C runtime. There is no
6
+ > Node.js, V8 or TypeScript compiler inside the produced binary. It runs on
7
+ > macOS, Linux and Windows (x64 and arm64), compiles itself, and ships prebuilt
8
+ > archives per platform. A clang 16+ toolchain is required to link binaries;
9
+ > `xbintsc emit` (LLVM IR only) needs nothing.
10
+
11
+ ## Start here
12
+
13
+ - [AGENTS.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/AGENTS.md): entry point for an agent working in the repository — what xbintsc is, the repository map, the build gate and the rules
14
+ - [doc/ai/README.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/ai/README.md): task-oriented AI guide — the three facts that decide everything, and which page to read for your task
15
+ - [README.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/README.md): the project's human overview
16
+
17
+ ## Using the compiler
18
+
19
+ - [doc/ai/build-recipe.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/ai/build-recipe.md): exact commands to run, build, inspect IR and configure a project, with the program shape that compiles
20
+ - [doc/ai/cli.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/ai/cli.md): every CLI flag and its parsing quirks, project config fields, environment variables, incremental-cache behaviour, diagnostic codes, programmatic API
21
+ - [doc/ai/language-support.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/ai/language-support.md): the supported TypeScript subset as tables — statements, expressions, classes, async, the erased type system, standard library gaps, modules, runtime deviations
22
+ - [doc/ai/extensions.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/ai/extensions.md): Node built-in modules and the `--ext node` rule, the GUI extension, bundling third-party packages, C++/Rust native extensions via manifest
23
+ - [doc/ai/troubleshooting.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/ai/troubleshooting.md): the clang version requirement and every frequent error message with its cause and fix
24
+ - [doc/requirements.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/requirements.md): per-platform toolchain prerequisites
25
+ - [doc/xbintsc.config.schema.json](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/xbintsc.config.schema.json): JSON schema for `xbintsc.config.json`
26
+
27
+ ## Changing the compiler
28
+
29
+ - [doc/ai/contributing.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/ai/contributing.md): test suites, the self-hosting fixpoint invariant, the runtime ABI and value model, adding extensions or diagnostics, documentation duties
30
+ - [doc/DESIGN.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/DESIGN.md): architecture and design rationale
31
+ - [doc/implemented.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/implemented.md): everything implemented, by compiler stage — the canonical reference
32
+ - [doc/unimplemented.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/unimplemented.md): unsupported syntax, missing semantics and known deviations, with a quick-reference list
33
+ - [doc/node-implemented.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/node-implemented.md): Node module coverage of the `node` extension
34
+ - [doc/node-unimplemented.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/node-unimplemented.md): what the `node` extension is still missing
35
+ - [doc/gui.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/gui.md): the self-hosted GPU HTML/CSS renderer
36
+ - [doc/gui-scripts.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/gui-scripts.md): the GUI scripting surface
37
+ - [doc/icon.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/icon.md): application icons and project config packaging
38
+ - [examples/extensions/README.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/examples/extensions/README.md): authoring C++ and Rust native extensions
39
+
40
+ ## Optional
41
+
42
+ - [doc/zh-CN/](https://github.com/zy445566/xbintsc/tree/main/doc/zh-CN): Chinese translations of the canonical documents
43
+ - [doc/ai/zh-CN/](https://github.com/zy445566/xbintsc/tree/main/doc/ai/zh-CN): Chinese translations of the AI guide
44
+ - [doc/post/announce.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/doc/post/announce.md): announcement / project write-up
45
+ - [README.zh-CN.md](https://raw.githubusercontent.com/zy445566/xbintsc/main/README.zh-CN.md): the project overview in Chinese
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xbintsc",
3
- "version": "0.3.46",
3
+ "version": "0.3.61",
4
4
  "description": "xbintsc - a TypeScript binary compiler (TypeScript -> LLVM IR -> native binary)",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -27,6 +27,9 @@
27
27
  "runtime",
28
28
  "scripts",
29
29
  "bin",
30
+ "doc",
31
+ "AGENTS.md",
32
+ "llms.txt",
30
33
  "README.md"
31
34
  ],
32
35
  "scripts": {
@@ -136,6 +136,25 @@ xt_value xt_gui_node_handle(XtGuiWindow *win, const xtgui::Node *node) {
136
136
  return handle;
137
137
  }
138
138
 
139
+ void xt_gui_node_handle_forget(XtGuiWindow *win, const xtgui::Node *node) {
140
+ if (win == nullptr || node == nullptr) return;
141
+ /* The node is about to be destroyed (a removal, or `innerHTML = …` dropping
142
+ * the old children). Drop its slot as well as its handle: the slot is what
143
+ * `resolve_node` reads, and leaving a freed pointer there is a
144
+ * use-after-free the next time any handle is resolved. Existing handles keep
145
+ * resolving to "stale" and no-op, which is the documented tombstone
146
+ * behaviour. */
147
+ auto index = win->node_index.find(node);
148
+ if (index != win->node_index.end()) {
149
+ int slot = index->second;
150
+ if (slot >= 0 && slot < (int)win->node_order.size()) {
151
+ win->node_order[(size_t)slot] = nullptr;
152
+ }
153
+ win->node_index.erase(index);
154
+ }
155
+ win->node_handles.erase(node);
156
+ }
157
+
139
158
  xt_value xt_gui_document_handle(XtGuiWindow *win) {
140
159
  if (win == nullptr) return XT_UNDEFINED;
141
160
  if (!XT_IS_OBJECT(win->document_object)) {
@@ -177,7 +177,10 @@ METHOD(node_replace_child) {
177
177
  xtgui::Node *newChild = resolve_node(xt_arg(argc, argv, 0), nullptr);
178
178
  xtgui::Node *oldChild = resolve_node(xt_arg(argc, argv, 1), nullptr);
179
179
  if (newChild == nullptr || oldChild == nullptr) return XT_UNDEFINED;
180
- return xt_gui_node_handle(win, win->document->replaceChild(node, newChild, oldChild));
180
+ /* `replaceChild` frees the node it replaces, so its slot may not survive. */
181
+ xtgui::Node *replaced = win->document->replaceChild(node, newChild, oldChild);
182
+ if (replaced != oldChild) xt_gui_node_handle_forget(win, oldChild);
183
+ return xt_gui_node_handle(win, replaced);
181
184
  }
182
185
 
183
186
  METHOD(node_remove) {
@@ -25,6 +25,13 @@ static void collect_text(const xtgui::Node *node, std::string &out) {
25
25
  for (const std::unique_ptr<xtgui::Node> &child : node->children) collect_text(child.get(), out);
26
26
  }
27
27
 
28
+ /** Forget the handles of `node` and every descendant: they are about to be
29
+ * freed, and a slot left behind would dangle. */
30
+ static void forget_subtree(XtGuiWindow *win, const xtgui::Node *node) {
31
+ xt_gui_node_handle_forget(win, node);
32
+ for (const std::unique_ptr<xtgui::Node> &child : node->children) forget_subtree(win, child.get());
33
+ }
34
+
28
35
  #define METHOD(name) xt_value name(xt_value self, xt_value env, int32_t argc, xt_value *argv)
29
36
 
30
37
  METHOD(node_tag_name_get) {
@@ -105,6 +112,7 @@ METHOD(node_text_content_set) {
105
112
  std::vector<xtgui::Node *> existing;
106
113
  for (const std::unique_ptr<xtgui::Node> &child : node->children) existing.push_back(child.get());
107
114
  for (xtgui::Node *child : existing) win->document->removeChild(node, child);
115
+ for (xtgui::Node *child : existing) forget_subtree(win, child);
108
116
  if (!text.empty()) node->addText(text);
109
117
  mark_dirty(win);
110
118
  return XT_UNDEFINED;
@@ -132,6 +140,10 @@ METHOD(node_inner_html_set) {
132
140
  std::vector<xtgui::Node *> existing;
133
141
  for (const std::unique_ptr<xtgui::Node> &child : node->children) existing.push_back(child.get());
134
142
  for (xtgui::Node *child : existing) win->document->removeChild(node, child);
143
+ /* The old children are dropped here (only `removeChild` keeps a detached node
144
+ * alive), so forget them *and everything below them* before they are freed:
145
+ * a leftover slot would be a dangling pointer for the next handle lookup. */
146
+ for (xtgui::Node *child : existing) forget_subtree(win, child);
135
147
  std::unique_ptr<xtgui::Node> fragment = xtgui::xt_html_parse(html);
136
148
  for (std::unique_ptr<xtgui::Node> &child : fragment->children) node->append(std::move(child));
137
149
  mark_dirty(win);
@@ -64,16 +64,44 @@ void xt_gui_apply_icon(SDL_Window *window) {
64
64
  SDL_DestroySurface(surface);
65
65
  }
66
66
 
67
+ /* -- garbage collector roots ---------------------------------------------- */
68
+
69
+ /**
70
+ * GC root provider: the engine caches `xt_value`s in C++ containers that the
71
+ * mark-sweep collector cannot see — per-window element handles, element
72
+ * listeners, the document handle and the queued/currently-running
73
+ * `requestAnimationFrame` callbacks. Rooting the window object alone does not
74
+ * reach these (they are not its properties), so without this provider a
75
+ * collection frees values the engine still uses and the next frame or DOM read
76
+ * crashes. The process-wide AOT `<script>` registry registers its own
77
+ * provider (`xt_gui_script_gc_scan`).
78
+ */
79
+ static void xt_gui_gc_scan_roots(void) {
80
+ for (int i = 0; i < g_window_count; i++) {
81
+ const XtGuiWindow &win = g_windows[i];
82
+ xt_gc_mark_value(win.object);
83
+ xt_gc_mark_value(win.document_object);
84
+ for (const auto &entry : win.node_handles) xt_gc_mark_value(entry.second);
85
+ for (const auto &entry : win.node_listeners) {
86
+ for (const XtGuiNodeListener &listener : entry.second) xt_gc_mark_value(listener.fn);
87
+ }
88
+ for (const XtGuiAnimationFrame &frame : win.animation_frames) xt_gc_mark_value(frame.fn);
89
+ for (const XtGuiAnimationFrame &frame : win.running_frames) xt_gc_mark_value(frame.fn);
90
+ }
91
+ }
92
+
67
93
  /* -- initialisation ------------------------------------------------------- */
68
94
 
69
- static int ensure_init(void) {
70
- if (g_sdl_initialized) return 1;
95
+ static int ensure_init(void) { if (g_sdl_initialized) return 1;
96
+ xt_gc_register_root_provider(xt_gui_gc_scan_roots);
71
97
  if (!SDL_Init(SDL_INIT_VIDEO)) {
72
98
  fprintf(stderr, "xt_gui: SDL_Init failed: %s\n", SDL_GetError());
73
99
  return 0;
74
100
  }
75
101
  /* Accept every shader format so the same code runs on Metal, Vulkan and
76
- * D3D12; the backend only needs the one it supports. */
102
+ * D3D12; the backend only needs the one it supports. The engine ships MSL,
103
+ * SPIR-V and DXIL shaders accordingly, and picks the matching one in
104
+ * `renderer_shaders.h`. */
77
105
  SDL_GPUShaderFormat formats =
78
106
  SDL_GPU_SHADERFORMAT_MSL | SDL_GPU_SHADERFORMAT_SPIRV | SDL_GPU_SHADERFORMAT_DXIL;
79
107
  g_device = SDL_CreateGPUDevice(formats, false, NULL);
@@ -100,6 +128,12 @@ static XtGuiWindow *window_by_object(xt_value object) {
100
128
 
101
129
  using namespace guidev;
102
130
 
131
+ const char *xt_gui_driver(void) {
132
+ if (g_device == NULL) return "";
133
+ const char *driver = SDL_GetGPUDeviceDriver(g_device);
134
+ return driver != NULL ? driver : "";
135
+ }
136
+
103
137
  XtGuiWindow *xt_gui_window_from_this(xt_value thisValue) {
104
138
  return window_by_object(thisValue);
105
139
  }
@@ -204,6 +238,14 @@ extern "C" xt_value xt_gui_create_window(int32_t argc, xt_value *argv) {
204
238
  xt_object_set(object, xt_string_from_cstr("__xt_gui_index"), xt_number((double)index));
205
239
  xt_object_set(object, xt_string_from_cstr("__xt_gui_html"), xt_string_from_cstr(""));
206
240
  xt_object_set(object, xt_string_from_cstr("__xt_gui_events"), xt_object_new());
241
+ /* The engine owns a graph of values on the window's behalf — element
242
+ * handles (`node_handles`), their event listeners and the queued animation
243
+ * frame closures. Most of them are referenced from TypeScript too, but not
244
+ * all: a handle the script queried once and dropped (or one only reachable
245
+ * through an ancestor element) would otherwise be collected while the engine
246
+ * still uses it, which is a use-after-free. Rooting the window object makes
247
+ * everything hanging off it reachable. */
248
+ xt_gc_add_root(&record->object);
207
249
 
208
250
  return object;
209
251
  }
@@ -24,6 +24,9 @@ xt_value xt_gui_run(int32_t argc, xt_value *argv);
24
24
  xt_value xt_gui_quit(int32_t argc, xt_value *argv);
25
25
  /** `__registerScript(id, fn)`: record an AOT-compiled `<script>` body. */
26
26
  xt_value xt_register_script(int32_t argc, xt_value *argv);
27
+ /** Which SDL_GPU backend the device is using (`"vulkan"`, `"direct3d12"`,
28
+ * `"metal"`, …), or `""` before the device exists. */
29
+ const char *xt_gui_driver(void);
27
30
 
28
31
  #ifdef __cplusplus
29
32
  } /* extern "C" */
@@ -84,6 +87,11 @@ struct XtGuiWindow {
84
87
  int struct_dirty = 0;
85
88
  /** Callbacks queued for the next frame, plus the id counter. */
86
89
  std::vector<XtGuiAnimationFrame> animation_frames;
90
+ /** The batch currently running: its callbacks have been swapped out of
91
+ * `animation_frames`, and a collection triggered by one of them must still
92
+ * see the rest. Kept on the record (not a local) so the GC root provider can
93
+ * mark it. Empty except while `xt_gui_run_animation_frames` runs. */
94
+ std::vector<XtGuiAnimationFrame> running_frames;
87
95
  int next_animation_frame_id = 1;
88
96
  };
89
97
 
@@ -93,6 +101,10 @@ XtGuiWindow *xt_gui_window_from_this(xt_value thisValue);
93
101
  xt_value xt_gui_document_handle(XtGuiWindow *win);
94
102
  /** Node handle for `node` (created and cached on first use). */
95
103
  xt_value xt_gui_node_handle(XtGuiWindow *win, const xtgui::Node *node);
104
+ /** Forget a node's slot and handle because it is about to be destroyed (a
105
+ * removal that frees it, or `innerHTML = …` dropping the old children). Without
106
+ * this the node table would keep a dangling pointer. */
107
+ void xt_gui_node_handle_forget(XtGuiWindow *win, const xtgui::Node *node);
96
108
  /** Drop every cached handle/listener and bump the document generation. */
97
109
  void xt_gui_handles_reset(XtGuiWindow *win);
98
110
  /** Deliver `type` to element listeners along the propagation path. */
@@ -105,6 +117,9 @@ void xt_gui_flush_dom(XtGuiWindow *win);
105
117
  /** Run every registered `<script data-xt-id>` body found in the document, in
106
118
  * document order, passing `(windowHandle, documentHandle)`. */
107
119
  void xt_gui_run_scripts(XtGuiWindow *win);
120
+ /** GC root provider: mark every AOT-compiled `<script>` function in the
121
+ * process-wide registry. Registered with the collector by `xt_register_script`. */
122
+ void xt_gui_script_gc_scan(void);
108
123
  /** Shared prototype carrying the window methods. */
109
124
  xt_value xt_gui_window_proto(void);
110
125
  /** Release GPU claim + destroy the window and mark the record closed. */
@@ -220,15 +220,18 @@ void handle_event(const SDL_Event *event) {
220
220
 
221
221
  void xt_gui_run_animation_frames(XtGuiWindow *win, double timestamp_ms) {
222
222
  if (win == NULL || win->animation_frames.empty()) return;
223
- /* Swap the queue out first: a callback that calls requestAnimationFrame
224
- * again schedules for the *next* frame, not this one. */
225
- std::vector<XtGuiAnimationFrame> frames;
223
+ /* Swap the queue into a member, not a local: the GC root provider marks
224
+ * `running_frames` too, so a collection triggered while one callback runs
225
+ * cannot free the rest of the batch. Re-queueing from a callback lands in
226
+ * `animation_frames` and runs on the *next* frame, not this one. */
227
+ std::vector<XtGuiAnimationFrame> &frames = win->running_frames;
226
228
  frames.swap(win->animation_frames);
227
229
  for (const XtGuiAnimationFrame &frame : frames) {
228
230
  if (!win->open) break;
229
231
  xt_value arg = xt_number(timestamp_ms);
230
232
  xt_call_with_this(frame.fn, win->object, 1, &arg);
231
233
  }
234
+ frames.clear();
232
235
  }
233
236
 
234
237
  void xt_gui_render_window(XtGuiWindow *win) {
@@ -244,6 +247,10 @@ void xt_gui_render_window(XtGuiWindow *win) {
244
247
  /* Animation-frame callbacks run before layout, so any DOM mutation they make
245
248
  * is picked up by this same frame. */
246
249
  xt_gui_run_animation_frames(win, now);
250
+ /* A callback may have closed the window (directly or through the
251
+ * auto-close guard): `xt_gui_quit_window` released the document and the GPU
252
+ * geometry, so there is nothing left to lay out or paint. */
253
+ if (!win->open) return;
247
254
  if (win->document != nullptr && win->document->advance(delta)) win->geometry.dirty = 1;
248
255
 
249
256
  /* A DOM mutation from a handler invalidates the tree: restyle/relayout
@@ -96,6 +96,12 @@ void LayoutTree::compute(const Node *root, const std::unordered_map<const Node *
96
96
  root_->node = root;
97
97
  root_->style = &root_style_;
98
98
  root_->display = Display::Block;
99
+ /* The initial containing block: absolute boxes with no positioned ancestor
100
+ * resolve their insets against the viewport. */
101
+ root_->cb_x = 0;
102
+ root_->cb_y = 0;
103
+ root_->cb_width = viewportWidth;
104
+ root_->cb_height = viewportHeight;
99
105
  if (root != nullptr) {
100
106
  if (root->type == NodeType::Document) {
101
107
  for (const std::unique_ptr<Node> &child : root->children) {
@@ -59,6 +59,10 @@ struct LayoutBox {
59
59
  float content_x = 0, content_y = 0, content_width = 0, content_height = 0;
60
60
  float baseline = 0;
61
61
 
62
+ /* The padding box of the nearest positioned ancestor (the viewport when there
63
+ * is none): the containing block `position: absolute` resolves against. */
64
+ float cb_x = 0, cb_y = 0, cb_width = 0, cb_height = 0;
65
+
62
66
  LayoutBox *parent = nullptr;
63
67
  std::vector<std::unique_ptr<LayoutBox>> children;
64
68
  };
@@ -19,9 +19,12 @@ float Layouter::layoutFlex(LayoutBox *container, float contentWidth, float defin
19
19
  containerStyle.flex_direction == FlexDirection::ColumnReverse;
20
20
  float gap = px(containerStyle.gap, contentWidth, containerStyle.font_size, 0.0f);
21
21
 
22
+ /* An absolutely positioned child is out of flow: it takes no part in the flex
23
+ * algorithm and is placed against the container's padding box instead. */
22
24
  std::vector<LayoutBox *> items;
23
25
  for (const std::unique_ptr<LayoutBox> &child : container->children) {
24
- if (child->display != Display::None) items.push_back(child.get());
26
+ if (child->display == Display::None || isAbsoluteChild(child.get())) continue;
27
+ items.push_back(child.get());
25
28
  }
26
29
  if (items.empty()) return 0;
27
30
  size_t count = items.size();
@@ -71,6 +74,7 @@ float Layouter::layoutFlex(LayoutBox *container, float contentWidth, float defin
71
74
  for (size_t i = 0; i < count; i++) {
72
75
  float contentW = mainSizes[i] - horizontalEdges(items[i]);
73
76
  if (contentW < 0) contentW = 0;
77
+ setContainingBlock(items[i], containingBlockAncestor(items[i]));
74
78
  layoutBlock(items[i], container->content_x, container->content_y, contentWidth,
75
79
  definiteHeight, contentW);
76
80
  cross[i] = verticalEdges(items[i]) + items[i]->height;
@@ -98,8 +102,8 @@ float Layouter::layoutFlex(LayoutBox *container, float contentWidth, float defin
98
102
  for (size_t k = 0; k < count; k++) {
99
103
  size_t i = reverse ? (count - 1 - k) : k;
100
104
  float crossOffset = crossOffsetFor(containerStyle.align_items, crossSize, cross[i]);
101
- float dx = cursor - items[i]->x;
102
- float dy = container->content_y + crossOffset - items[i]->y;
105
+ float dx = cursor + items[i]->margin_left - items[i]->x;
106
+ float dy = container->content_y + crossOffset + items[i]->margin_top - items[i]->y;
103
107
  translate(items[i], dx, dy);
104
108
  cursor += mainSizes[i] + between;
105
109
  }
@@ -111,6 +115,7 @@ float Layouter::layoutFlex(LayoutBox *container, float contentWidth, float defin
111
115
  std::vector<float> outer(count, 0.0f);
112
116
  float sumBase = 0;
113
117
  for (size_t i = 0; i < count; i++) {
118
+ setContainingBlock(items[i], containingBlockAncestor(items[i]));
114
119
  layoutBlock(items[i], container->content_x, container->content_y, contentWidth, definiteHeight);
115
120
  outer[i] = verticalEdges(items[i]) + items[i]->height;
116
121
  sumBase += outer[i];
@@ -142,8 +147,11 @@ float Layouter::layoutFlex(LayoutBox *container, float contentWidth, float defin
142
147
  float cursor = container->content_y + offset;
143
148
  for (size_t k = 0; k < count; k++) {
144
149
  size_t i = reverse ? (count - 1 - k) : k;
145
- float dx = container->content_x - items[i]->x;
146
- float dy = cursor - items[i]->y;
150
+ float dx = container->content_x + items[i]->margin_left - items[i]->x;
151
+ /* Margins keep the item's outer box on the main axis, exactly like block
152
+ * flow — a negative `margin-top` pulls the item up instead of being
153
+ * silently dropped. */
154
+ float dy = cursor + items[i]->margin_top - items[i]->y;
147
155
  translate(items[i], dx, dy);
148
156
  cursor += mainSizes[i] + between;
149
157
  }