xbintsc 0.3.79 → 0.3.87

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 (201) hide show
  1. package/README.md +13 -5
  2. package/README.zh-CN.md +13 -6
  3. package/dist/src/ast/common.d.ts +23 -0
  4. package/dist/src/cli/main.js +68 -24
  5. package/dist/src/cli/main.js.map +1 -1
  6. package/dist/src/codegen/generator/calls/invocation.js +5 -1
  7. package/dist/src/codegen/generator/calls/invocation.js.map +1 -1
  8. package/dist/src/codegen/generator/context.d.ts +50 -0
  9. package/dist/src/codegen/generator/context.js +124 -13
  10. package/dist/src/codegen/generator/context.js.map +1 -1
  11. package/dist/src/codegen/generator/debug.d.ts +33 -0
  12. package/dist/src/codegen/generator/debug.js +97 -0
  13. package/dist/src/codegen/generator/debug.js.map +1 -0
  14. package/dist/src/codegen/generator/expressions/assignment.js +9 -5
  15. package/dist/src/codegen/generator/expressions/assignment.js.map +1 -1
  16. package/dist/src/codegen/generator/expressions/operators.js +35 -9
  17. package/dist/src/codegen/generator/expressions/operators.js.map +1 -1
  18. package/dist/src/codegen/generator/expressions/primary.js +5 -2
  19. package/dist/src/codegen/generator/expressions/primary.js.map +1 -1
  20. package/dist/src/codegen/generator/generator.d.ts +3 -1
  21. package/dist/src/codegen/generator/generator.js +3 -1
  22. package/dist/src/codegen/generator/generator.js.map +1 -1
  23. package/dist/src/codegen/generator/module.d.ts +1 -0
  24. package/dist/src/codegen/generator/module.js +76 -6
  25. package/dist/src/codegen/generator/module.js.map +1 -1
  26. package/dist/src/codegen/generator/numeric.d.ts +26 -0
  27. package/dist/src/codegen/generator/numeric.js +127 -0
  28. package/dist/src/codegen/generator/numeric.js.map +1 -0
  29. package/dist/src/codegen/generator/state.d.ts +17 -0
  30. package/dist/src/codegen/generator/state.js +5 -0
  31. package/dist/src/codegen/generator/state.js.map +1 -1
  32. package/dist/src/codegen/generator/statements/control-flow.js +1 -1
  33. package/dist/src/codegen/generator/statements/control-flow.js.map +1 -1
  34. package/dist/src/codegen/generator/statements/dispatch.js +3 -0
  35. package/dist/src/codegen/generator/statements/dispatch.js.map +1 -1
  36. package/dist/src/codegen/generator/statements/variables.js +8 -2
  37. package/dist/src/codegen/generator/statements/variables.js.map +1 -1
  38. package/dist/src/codegen/generator/tables.d.ts +7 -0
  39. package/dist/src/codegen/generator/tables.js +25 -0
  40. package/dist/src/codegen/generator/tables.js.map +1 -1
  41. package/dist/src/driver/bundler/merge.js +41 -5
  42. package/dist/src/driver/bundler/merge.js.map +1 -1
  43. package/dist/src/driver/compiler.d.ts +22 -3
  44. package/dist/src/driver/compiler.js +49 -58
  45. package/dist/src/driver/compiler.js.map +1 -1
  46. package/dist/src/driver/fingerprint.d.ts +27 -0
  47. package/dist/src/driver/fingerprint.js +88 -0
  48. package/dist/src/driver/fingerprint.js.map +1 -0
  49. package/dist/src/driver/toolchain-provider.d.ts +9 -2
  50. package/dist/src/driver/toolchain-provider.js +15 -15
  51. package/dist/src/driver/toolchain-provider.js.map +1 -1
  52. package/dist/src/driver/toolchain.d.ts +58 -1
  53. package/dist/src/driver/toolchain.js +169 -22
  54. package/dist/src/driver/toolchain.js.map +1 -1
  55. package/dist/src/extensions/gui/index.js +10 -2
  56. package/dist/src/extensions/gui/index.js.map +1 -1
  57. package/dist/src/parser/expressions.js +20 -1
  58. package/dist/src/parser/expressions.js.map +1 -1
  59. package/dist/tests/cli/main.test.js +41 -1
  60. package/dist/tests/cli/main.test.js.map +1 -1
  61. package/dist/tests/codegen/expressions.test.js +14 -3
  62. package/dist/tests/codegen/expressions.test.js.map +1 -1
  63. package/dist/tests/codegen/llvm.test.js +23 -0
  64. package/dist/tests/codegen/llvm.test.js.map +1 -1
  65. package/dist/tests/driver/toolchain-provider.test.js +49 -2
  66. package/dist/tests/driver/toolchain-provider.test.js.map +1 -1
  67. package/dist/tests/driver/toolchain.test.js +62 -3
  68. package/dist/tests/driver/toolchain.test.js.map +1 -1
  69. package/dist/tests/e2e/child-process.test.d.ts +10 -0
  70. package/dist/tests/e2e/child-process.test.js +59 -0
  71. package/dist/tests/e2e/child-process.test.js.map +1 -0
  72. package/dist/tests/e2e/compile-objects-classes.test.js +47 -0
  73. package/dist/tests/e2e/compile-objects-classes.test.js.map +1 -1
  74. package/dist/tests/e2e/error-stack.test.d.ts +9 -0
  75. package/dist/tests/e2e/error-stack.test.js +94 -0
  76. package/dist/tests/e2e/error-stack.test.js.map +1 -0
  77. package/dist/tests/e2e/gui-example-svg.test.d.ts +21 -0
  78. package/dist/tests/e2e/gui-example-svg.test.js +105 -0
  79. package/dist/tests/e2e/gui-example-svg.test.js.map +1 -0
  80. package/dist/tests/e2e/gui-transform.test.d.ts +7 -0
  81. package/dist/tests/e2e/gui-transform.test.js +198 -0
  82. package/dist/tests/e2e/gui-transform.test.js.map +1 -0
  83. package/dist/tests/e2e/string-concat.test.d.ts +9 -0
  84. package/dist/tests/e2e/string-concat.test.js +102 -0
  85. package/dist/tests/e2e/string-concat.test.js.map +1 -0
  86. package/dist/tests/helpers.d.ts +9 -0
  87. package/dist/tests/helpers.js +26 -3
  88. package/dist/tests/helpers.js.map +1 -1
  89. package/dist/tests/parser/expressions.test.js +7 -0
  90. package/dist/tests/parser/expressions.test.js.map +1 -1
  91. package/dist/tests/version.test.d.ts +1 -0
  92. package/dist/tests/version.test.js +24 -0
  93. package/dist/tests/version.test.js.map +1 -0
  94. package/doc/DESIGN.md +20 -12
  95. package/doc/ai/cli.md +7 -0
  96. package/doc/ai/contributing.md +7 -1
  97. package/doc/ai/extensions.md +5 -1
  98. package/doc/ai/language-support.md +4 -4
  99. package/doc/ai/troubleshooting.md +39 -2
  100. package/doc/ai/zh-CN/cli.md +2 -0
  101. package/doc/ai/zh-CN/contributing.md +6 -2
  102. package/doc/ai/zh-CN/extensions.md +5 -1
  103. package/doc/ai/zh-CN/language-support.md +4 -4
  104. package/doc/ai/zh-CN/troubleshooting.md +35 -2
  105. package/doc/gui/animation.md +53 -2
  106. package/doc/gui/api.md +1 -1
  107. package/doc/gui/dom.md +6 -1
  108. package/doc/gui/input.md +4 -1
  109. package/doc/gui/known-issues.md +16 -4
  110. package/doc/gui/layout.md +36 -0
  111. package/doc/gui/rendering.md +15 -0
  112. package/doc/gui.md +32 -3
  113. package/doc/implemented.md +4 -4
  114. package/doc/requirements.md +13 -4
  115. package/doc/unimplemented.md +11 -10
  116. package/doc/zh-CN/DESIGN.md +15 -9
  117. package/doc/zh-CN/gui/animation.md +49 -2
  118. package/doc/zh-CN/gui/api.md +1 -1
  119. package/doc/zh-CN/gui/dom.md +6 -1
  120. package/doc/zh-CN/gui/input.md +2 -0
  121. package/doc/zh-CN/gui/known-issues.md +12 -2
  122. package/doc/zh-CN/gui/layout.md +31 -0
  123. package/doc/zh-CN/gui/rendering.md +11 -0
  124. package/doc/zh-CN/gui.md +28 -3
  125. package/doc/zh-CN/implemented.md +4 -4
  126. package/doc/zh-CN/requirements.md +12 -3
  127. package/doc/zh-CN/unimplemented.md +10 -9
  128. package/package.json +3 -1
  129. package/runtime/ext_gui/document.cpp +21 -3
  130. package/runtime/ext_gui/document.h +4 -1
  131. package/runtime/ext_gui/dom_api_node.cpp +6 -4
  132. package/runtime/ext_gui/layout.cpp +78 -1
  133. package/runtime/ext_gui/layout.h +21 -0
  134. package/runtime/ext_gui/paint.cpp +48 -15
  135. package/runtime/ext_gui/paint.h +8 -2
  136. package/runtime/ext_gui/renderer.cpp +4 -2
  137. package/runtime/ext_gui/renderer_image.cpp +2 -1
  138. package/runtime/ext_gui/renderer_internal.h +6 -1
  139. package/runtime/ext_gui/renderer_text.cpp +37 -15
  140. package/runtime/ext_gui/style.h +10 -0
  141. package/runtime/ext_gui/style_print.cpp +6 -0
  142. package/runtime/ext_gui/style_values.cpp +43 -0
  143. package/runtime/ext_gui/transform.cpp +412 -0
  144. package/runtime/ext_gui/transform.h +112 -0
  145. package/runtime/ext_gui/window.cpp +6 -4
  146. package/runtime/ext_node/buffer/parts/construction.inc +2 -2
  147. package/runtime/ext_node/buffer/parts/methods.inc +1 -1
  148. package/runtime/ext_node/buffer/parts/search.inc +1 -1
  149. package/runtime/ext_node/child_process/child_process.c +25 -0
  150. package/runtime/ext_node/fs/fd_ops.c +1 -1
  151. package/runtime/rt.h +17 -1
  152. package/runtime/rt_internal.h +47 -4
  153. package/runtime/xt_alloc.c +14 -2
  154. package/runtime/xt_bigint/conversions.inc +1 -1
  155. package/runtime/xt_builtins.c +136 -14
  156. package/runtime/xt_containers/access.inc +17 -16
  157. package/runtime/xt_containers/objects.inc +4 -3
  158. package/runtime/xt_io/exception.inc +171 -2
  159. package/runtime/xt_io/inspect.inc +15 -13
  160. package/runtime/xt_io.c +5 -0
  161. package/runtime/xt_stdlib/arrays.inc +2 -2
  162. package/runtime/xt_stdlib/dispatch.inc +2 -2
  163. package/runtime/xt_stdlib/strings.inc +33 -21
  164. package/runtime/xt_stdlib2/array-statics.inc +2 -1
  165. package/runtime/xt_stdlib2/arrays.inc +1 -1
  166. package/runtime/xt_stdlib2/error.inc +22 -6
  167. package/runtime/xt_stdlib2/json.inc +3 -2
  168. package/runtime/xt_stdlib2/number-string-statics.inc +3 -1
  169. package/runtime/xt_stdlib2/regexp-match.inc +48 -33
  170. package/runtime/xt_stdlib2/strings.inc +23 -14
  171. package/runtime/xt_symbol.c +1 -1
  172. package/runtime/xt_values/comparison.inc +2 -2
  173. package/runtime/xt_values/constructors.inc +10 -5
  174. package/runtime/xt_values/conversions.inc +4 -3
  175. package/runtime/xt_values/strings.inc +78 -4
  176. package/scripts/bench.ts +172 -0
  177. package/scripts/build-runtime.ts +2 -1
  178. package/scripts/check-version.ts +53 -0
  179. package/src/ast/common.ts +23 -0
  180. package/src/cli/main.ts +68 -26
  181. package/src/codegen/generator/calls/invocation.ts +5 -0
  182. package/src/codegen/generator/context.ts +129 -18
  183. package/src/codegen/generator/debug.ts +132 -0
  184. package/src/codegen/generator/expressions/assignment.ts +9 -5
  185. package/src/codegen/generator/expressions/operators.ts +47 -8
  186. package/src/codegen/generator/expressions/primary.ts +5 -2
  187. package/src/codegen/generator/generator.ts +18 -2
  188. package/src/codegen/generator/module.ts +78 -6
  189. package/src/codegen/generator/numeric.ts +158 -0
  190. package/src/codegen/generator/state.ts +22 -0
  191. package/src/codegen/generator/statements/control-flow.ts +1 -1
  192. package/src/codegen/generator/statements/dispatch.ts +3 -0
  193. package/src/codegen/generator/statements/variables.ts +8 -2
  194. package/src/codegen/generator/tables.ts +26 -0
  195. package/src/driver/bundler/merge.ts +39 -5
  196. package/src/driver/compiler.ts +68 -56
  197. package/src/driver/fingerprint.ts +85 -0
  198. package/src/driver/toolchain-provider.ts +17 -16
  199. package/src/driver/toolchain.ts +210 -21
  200. package/src/extensions/gui/index.ts +10 -2
  201. package/src/parser/expressions.ts +20 -1
package/doc/DESIGN.md CHANGED
@@ -41,16 +41,21 @@ xt_value fn(xt_value env, int32_t argc, xt_value *argv);
41
41
 
42
42
  `env` threads captured variables by reference through boxes, so direct calls and
43
43
  closure calls share a single code path. JavaScript semantics that are awkward to
44
- inline (`+` coercion, relational comparison, property access, inspection) are
45
- delegated to `@xt_*` runtime calls.
44
+ inline (`+` coercion, property access, inspection) are delegated to `@xt_*`
45
+ runtime calls. Numeric arithmetic and comparison are the exception: codegen emits
46
+ the double fast path inline (`numeric.ts`) and calls the runtime for the coercing
47
+ cases.
46
48
 
47
49
  ### Runtime
48
50
 
49
- `runtime/xt_runtime.c` implements strings, objects, arrays, closures, arithmetic,
50
- comparison, exceptions and Node-like `console.log` inspection. It uses a
51
- non-moving mark-sweep collector behind `xt_alloc` (explicit roots, subsystem
52
- root providers and a conservative C-stack scan), so the collector's internals
53
- stay isolated from the compiler.
51
+ `runtime/` implements strings, objects, arrays, closures, arithmetic, comparison,
52
+ exceptions and Node-like `console.log` inspection. It is split by concern into
53
+ translation units (`xt_values.c`, `xt_containers.c`, `xt_stdlib.c`,
54
+ `xt_builtins.c`, `xt_io.c`, `xt_alloc.c`, …), each `#include`-ing its
55
+ implementation fragments (`.inc`) so files stay small without changing the link
56
+ surface. It uses a non-moving mark-sweep collector behind `xt_alloc` (explicit
57
+ roots, subsystem root providers and a conservative C-stack scan), so the
58
+ collector's internals stay isolated from the compiler.
54
59
 
55
60
  ### Replacing llc
56
61
 
@@ -103,15 +108,18 @@ found on `PATH` (the MSVC ABI when LLVM + the Visual Studio C++ build tools are
103
108
  installed), and each manifest declares any platform-specific linker flags. See
104
109
  `examples/extensions/` and `runtime/xt_ext.h` / `runtime/xt_ext.rs`.
105
110
 
106
- ## Self-hosting roadmap
111
+ ## Self-hosting
107
112
 
108
- The compiler itself is written in TypeScript and emits IR text, and `runtime` is
109
- already decoupled from the compiler. Later, the runtime can be rewritten in TS
110
- and the compiler can compile itself, gradually reaching self-hosting.
113
+ The compiler compiles itself: `xbintsc build src/cli/main.ts --ext node` produces
114
+ a working `xbintsc` binary, and the emitted IR is a fixpoint from the first
115
+ generation onward. CI builds those binaries for every supported platform and
116
+ checks the fixpoint. The runtime stays in C; rewriting it in TypeScript is an
117
+ option for later, not a prerequisite for self-hosting.
111
118
 
112
119
  ## Tests
113
120
 
114
121
  Organised by module: `tests/lexer`, `tests/parser`, `tests/binder`,
115
122
  `tests/codegen`, `tests/driver`, `tests/extensions`, `tests/cli`, `tests/e2e`.
116
- When `clang` is present, e2e tests really compile and run binaries; otherwise
123
+ When a supported clang (16+, the same version gate `build` applies) is present,
124
+ e2e tests really compile and run binaries; otherwise
117
125
  they are skipped automatically.
package/doc/ai/cli.md CHANGED
@@ -36,6 +36,8 @@ unknown command prints an error plus the help text and exits 1.
36
36
  --app-id <id> macOS bundle identifier (e.g. com.example.demo)
37
37
  --force Ignore the incremental cache
38
38
  --verbose Print progress information
39
+ --time Print per-phase build timings
40
+ --debug Emit debug info (DWARF/CodeView) and keep frame pointers
39
41
  ```
40
42
 
41
43
  Parsing details that occasionally surprise:
@@ -46,6 +48,11 @@ Parsing details that occasionally surprise:
46
48
  `run`, or is a positional for the other commands.
47
49
  - `-o` consumes the next argument unconditionally; the other value flags leave
48
50
  the value unset when the next argument starts with `-`.
51
+ - `-o`/`output` may name a directory that does not exist yet: the driver creates
52
+ the parent directories of the output path. (`--out` likewise creates its
53
+ directory.) Without that, an explicit path such as `-o dist/app.exe` where
54
+ `dist/` was missing used to fail inside the linker, since clang — not xbintsc —
55
+ is what writes the executable.
49
56
 
50
57
  ## Project config
51
58
 
@@ -38,6 +38,7 @@ it. Release tags are created by `release.yml`, never by the hook.
38
38
  | `npm run test:watch` | vitest in watch mode |
39
39
  | `npm run coverage` | V8 coverage of the TypeScript compiler (`src/`) |
40
40
  | `npm run coverage:runtime` | LLVM coverage of the C runtime (`runtime/`) |
41
+ | `npm run bench` | build and time the fixed benchmark suite (optionally vs Node) |
41
42
 
42
43
  Unit tests live one directory per module under `tests/` (`lexer`, `parser`,
43
44
  `binder`, `codegen`, `driver`, `extensions`, `cli`, `e2e`) and mirror the source
@@ -103,7 +104,12 @@ diff scratch/ref.ll scratch/gen2.ll
103
104
  in [../../src/codegen/values.ts](../../src/codegen/values.ts) and
104
105
  [../../runtime/rt.h](../../runtime/rt.h); change both together or not at all.
105
106
  - Awkward JS semantics (`+` coercion, relational comparison, property access,
106
- inspection) are delegated to `@xt_*` runtime calls rather than inlined.
107
+ inspection) are delegated to `@xt_*` runtime calls. Numeric arithmetic and
108
+ comparison are the exception: codegen inlines the two-double fast path
109
+ (`src/codegen/generator/numeric.ts`) and calls the runtime for everything else.
110
+ - String literals are interned: each distinct literal is materialised once in
111
+ `main`, stored in a GC-rooted global, and loaded at each use. Property names
112
+ therefore never allocate per access.
107
113
  - The GC is a non-moving mark-sweep collector: new heap objects go through
108
114
  `xt_alloc`, and a value must be reachable from an explicit root slot, a
109
115
  registered root provider (the event loop, the microtask queue) or the
@@ -76,7 +76,11 @@ with elements and CSS (see `examples/gui/media.ts`). Video frames and audio PCM
76
76
  are decoded on the main thread. `<canvas>` is supported too: `getContext("2d")`
77
77
  returns a context whose fills, paths, text and `drawImage` are rasterised by a
78
78
  self-written CPU rasteriser, and element handles expose `canvas` drawing plus
79
- `width`/`height`. If that archive is missing for
79
+ `width`/`height`. CSS `transform`/`transform-origin` work as paint-time
80
+ effects, so a box (and the text, images and children in it) can be translated,
81
+ scaled and rotated without reflowing anything — animate it per frame from
82
+ `requestAnimationFrame` or with `transition: transform` (see
83
+ `examples/gui/pelican-svg`). If that archive is missing for
80
84
  your platform, the build fails with an actionable message; design and roadmap
81
85
  live in [../gui.md](../gui.md) (overview; the engine is documented by function
82
86
  under [`doc/gui/`](../gui/)), and the scripting surface in
@@ -45,8 +45,8 @@ Canonical detail (always current, much longer):
45
45
  | Destructuring (bindings, parameters, nested) | ✅ | defaults + rest supported |
46
46
  | `delete`, `in`, `instanceof`, `typeof` | ✅ | |
47
47
  | Comma operator, `void` | ✅ | |
48
- | `new.target` | 🚫 | not implemented |
49
- | `import.meta` | 🚫 | parsed but has no value |
48
+ | `new.target` | 🚫 | parsed, then rejected with a clear `UnsupportedFeature` |
49
+ | `import.meta` | ✅ | `url`, `dirname`, `filename` (resolved from the executable path) |
50
50
  | `super` | ⚠️ | single-level inheritance correct; depth > 1 may be inaccurate |
51
51
 
52
52
  ## Functions, classes, objects
@@ -103,7 +103,7 @@ URI functions.
103
103
  | Missing or different | Status |
104
104
  | --- | --- |
105
105
  | `String.prototype.normalize`, `structuredClone` | 🚫 |
106
- | `Error.stack` capture | 🚫 (not captured) |
106
+ | `Error.stack` capture | ⚠️ function-level frames (name/file/line), no columns; generator bodies are omitted |
107
107
  | `Object.getPrototypeOf({})` | ⚠️ returns `undefined`, not `Object.prototype` |
108
108
  | Global RegExp `lastIndex` | ⚠️ `test`/`exec` ignore it for `/g` and `/y` |
109
109
  | Sparse array holes, array out-of-bounds | ⚠️ holes are not tracked distinctly; out-of-bounds reads give `undefined` |
@@ -122,7 +122,7 @@ See section 3 of [../unimplemented.md](../unimplemented.md) for the full list.
122
122
  | `require()` / CommonJS | 🚫 | rejected with a hint to use `import` |
123
123
  | Circular dependencies | 🚫 | |
124
124
  | Live bindings | ⚠️ | imports and namespace members are snapshots |
125
- | `import.meta`, `__dirname`, `__filename` | 🚫 | |
125
+ | `__dirname`, `__filename` | 🚫 | use `import.meta.dirname` / `import.meta.filename` |
126
126
 
127
127
  ## Strings, numbers, memory
128
128
 
@@ -22,8 +22,12 @@ D:\...\build\app.ll:382:37: error: '@.str.0' defined with type '[6 x i8]*' but e
22
22
 
23
23
  The compiler emits LLVM IR that relies on opaque pointers; typed pointers were
24
24
  removed in LLVM 16 ([LLVM release notes](https://github.com/llvm/llvm-project/blob/release/15.x/llvm/docs/ReleaseNotes.rst#changes-to-the-llvm-ir)).
25
- Confirm with `clang --version`; if the machine has an old system clang, install a
26
- newer LLVM and point xbintsc at it:
25
+ `xbintsc build`/`run` check the version up front and stop with "reports clang
26
+ 15.0.2, but xbintsc requires clang 16 or newer", and `xbintsc doctor` prints
27
+ `status : ok` or `status : too old` (exiting non-zero in the latter case). The
28
+ search prefers a supported clang — including the LLVM bundled with Visual Studio
29
+ on Windows — so an old `/usr/bin/clang` is skipped when a newer one is installed.
30
+ To choose one explicitly:
27
31
 
28
32
  ```bash
29
33
  export xbintsc_CLANG=/usr/lib/llvm-18/bin/clang # PowerShell: $env:xbintsc_CLANG = "C:\Program Files\LLVM\bin\clang.exe"
@@ -118,6 +122,39 @@ described in [../gui.md](../gui.md).
118
122
  Windows, embedding a PE icon needs `llvm-rc` or `windres`; `doctor` reports which
119
123
  one was found, and a missing one is only fatal when an icon was requested.
120
124
 
125
+ ## Reading a stack trace
126
+
127
+ `Error.stack` is captured by codegen, not from the C runtime's native frames:
128
+ every non-generator function pushes `{name, file, line}` on entry and pops it
129
+ before returning. Frames look like
130
+
131
+ ```
132
+ Error: boom
133
+ at inner (app.ts:12)
134
+ at module (app.ts:3)
135
+ ```
136
+
137
+ Lines are function declaration lines, except that the innermost frame is moved
138
+ to the `new Error(...)` line; there are no column numbers. Generator bodies are
139
+ not tracked because they switch stacks. A fatal native signal prints
140
+ `xbintsc: fatal signal …` followed by the same frames, so a crash without a
141
+ debugger still shows which JavaScript functions were running.
142
+
143
+ ## Debugging with gdb / lldb / WinDbg
144
+
145
+ `xbintsc build app.ts --debug` emits LLVM debug metadata and passes `-g
146
+ -fno-omit-frame-pointer` to clang, so the object carries DWARF (Linux/macOS) or
147
+ CodeView (Windows) line tables. Function symbols keep their source names
148
+ (`xt_fn_3_add`) and every statement has a line location:
149
+
150
+ ```bash
151
+ xbintsc build app.ts --debug
152
+ gdb ./build/app # or: lldb ./build/app, or open the .exe in WinDbg
153
+ ```
154
+
155
+ The binary is larger and is cached separately from a normal build (the debug
156
+ flag is part of the cache key). `Error.stack` works without `--debug`.
157
+
121
158
  ## Build behaviour that looks wrong but is not
122
159
 
123
160
  ### The build printed `(cached)` and did nothing
@@ -33,6 +33,8 @@
33
33
  --app-id <id> macOS bundle 标识(如 com.example.demo)
34
34
  --force 忽略增量缓存
35
35
  --verbose 打印进度信息
36
+ --time 打印各构建阶段耗时
37
+ --debug 生成调试信息(DWARF/CodeView)并保留帧指针
36
38
  ```
37
39
 
38
40
  少数容易踩的解析细节:
@@ -35,6 +35,7 @@ git config core.hooksPath .githooks
35
35
  | `npm run test:watch` | vitest 监听模式 |
36
36
  | `npm run coverage` | TypeScript 编译器(`src/`)的 V8 覆盖率 |
37
37
  | `npm run coverage:runtime` | C 运行时(`runtime/`)的 LLVM 覆盖率 |
38
+ | `npm run bench` | 构建并计时固定基准套件(可与 Node 对比) |
38
39
 
39
40
  单元测试在 `tests/` 下按模块一个目录(`lexer`、`parser`、`binder`、`codegen`、
40
41
  `driver`、`extensions`、`cli`、`e2e`),与源码树镜像对应。边界用例放在它覆盖的模块
@@ -90,8 +91,11 @@ diff scratch/ref.ll scratch/gen2.ll
90
91
  - 每个值都是一个 64 位字:double 不装箱,其余都是带标签指针(16 位标签 + 48 位载荷)。
91
92
  该表示只定义一次:[../../../src/codegen/values.ts](../../../src/codegen/values.ts) 与
92
93
  [../../../runtime/rt.h](../../../runtime/rt.h);要改就一起改,否则别动。
93
- - 别扭的 JS 语义(`+` 强转、关系比较、属性访问、打印)委托给 `@xt_*` 运行时调用,而
94
- 不是内联。
94
+ - 别扭的 JS 语义(`+` 强转、关系比较、属性访问、打印)委托给 `@xt_*` 运行时调用。
95
+ 数值算术与比较是例外:codegen 内联双 double 快路径
96
+ (`src/codegen/generator/numeric.ts`),其余情况调用运行时。
97
+ - 字符串字面量会被驻留:每个不同字面量只在 `main` 中创建一次,存入 GC 根全局,
98
+ 每次使用只做一次 load;属性名因此不再逐次分配。
95
99
  - GC 是非移动标记清扫:新堆对象走 `xt_alloc`;值存活期间必须能从显式根槽、已注册的
96
100
  根提供者(事件循环、微任务队列)或 C 栈保守扫描到达。只存在于未被扫描的 C 局部
97
101
  变量里的值就是 bug。
@@ -69,7 +69,11 @@ import { createWindow, run } from "gui";
69
69
  `loadedmetadata`/`play`/`pause`/`ended`/`timeupdate`/`error` 事件可让程序驱动播放。
70
70
  没有内置控件——用元素和 CSS 自绘控件(见 `examples/gui/media.ts`);视频帧与音频 PCM
71
71
  都在主线程解码。也支持 `<canvas>`:`getContext("2d")` 返回的上下文,其填充、路径、
72
- 文本与 `drawImage` 由自研 CPU 光栅器处理,元素句柄还暴露 `width`/`height`。因为主体是 C++,它以各平台预编译的 `gui.a`/`gui.lib` 形式分发。如果该
72
+ 文本与 `drawImage` 由自研 CPU 光栅器处理,元素句柄还暴露 `width`/`height`。CSS
73
+ `transform`/`transform-origin` 作为绘制期效果可用,因此一个盒子(以及其中的文本、
74
+ 图像与子元素)可以被平移、缩放与旋转而不触发任何重排——可以用
75
+ `requestAnimationFrame` 逐帧驱动,或写成 `transition: transform`(见
76
+ `examples/gui/pelican-svg`)。因为主体是 C++,它以各平台预编译的 `gui.a`/`gui.lib` 形式分发。如果该
73
77
  平台的归档不存在,构建会以明确的提示失败;设计与路线图见 [../../zh-CN/gui.md](../../zh-CN/gui.md)(总览;引擎按功能拆分在 [`doc/zh-CN/gui/`](../../zh-CN/gui/) 下),
74
78
  脚本接口见 [../../zh-CN/gui-scripts.md](../../zh-CN/gui-scripts.md)。媒体播放是
75
79
  Windows 唯一不支持的:Windows 的 `gui.a` 不构建 FFmpeg,媒体槽位为空操作。Canvas API
@@ -42,8 +42,8 @@ xbintsc 编译 TypeScript 的一个**实用子集**。本页用来判断你能
42
42
  | 解构(绑定、参数、嵌套) | ✅ | 支持默认值与剩余 |
43
43
  | `delete`、`in`、`instanceof`、`typeof` | ✅ | |
44
44
  | 逗号运算符、`void` | ✅ | |
45
- | `new.target` | 🚫 | 未实现 |
46
- | `import.meta` | 🚫 | 能解析,但没有值 |
45
+ | `new.target` | 🚫 | 能解析,但会给出明确的 `UnsupportedFeature` |
46
+ | `import.meta` | ✅ | `url`、`dirname`、`filename`(由可执行文件路径解析) |
47
47
  | `super` | ⚠️ | 单层继承正确;继承深度 > 1 可能不准 |
48
48
 
49
49
  ## 函数、类与对象
@@ -98,7 +98,7 @@ xbintsc 编译 TypeScript 的一个**实用子集**。本页用来判断你能
98
98
  | 缺失或行为不同 | 状态 |
99
99
  | --- | --- |
100
100
  | `String.prototype.normalize`、`structuredClone` | 🚫 |
101
- | `Error.stack` 采集 | 🚫(不采集) |
101
+ | `Error.stack` 采集 | ⚠️ 函数级帧(名称/文件/行号),没有列号;生成器函数体不产生帧 |
102
102
  | `Object.getPrototypeOf({})` | ⚠️ 返回 `undefined`,而非 `Object.prototype` |
103
103
  | 全局 RegExp `lastIndex` | ⚠️ `/g`、`/y` 下 `test`/`exec` 忽略它 |
104
104
  | 稀疏数组空洞、数组越界 | ⚠️ 不区分空洞;越界读得 `undefined` |
@@ -117,7 +117,7 @@ xbintsc 编译 TypeScript 的一个**实用子集**。本页用来判断你能
117
117
  | `require()` / CommonJS | 🚫 | 被拒绝,并提示改用 `import` |
118
118
  | 循环依赖 | 🚫 | |
119
119
  | 实时绑定 | ⚠️ | 导入与命名空间成员都是快照 |
120
- | `import.meta`、`__dirname`、`__filename` | 🚫 | |
120
+ | `__dirname`、`__filename` | 🚫 | 请使用 `import.meta.dirname` / `import.meta.filename` |
121
121
 
122
122
  ## 字符串、数字与内存
123
123
 
@@ -21,8 +21,11 @@ D:\...\build\app.ll:382:37: error: '@.str.0' defined with type '[6 x i8]*' but e
21
21
 
22
22
  编译器生成的 LLVM IR 依赖不透明指针(opaque pointers),而有类型指针在 LLVM 16
23
23
  中被移除([LLVM 发布说明](https://github.com/llvm/llvm-project/blob/release/15.x/llvm/docs/ReleaseNotes.rst#changes-to-the-llvm-ir))。
24
- 用 `clang --version` 确认;如果机器上的系统 clang 太旧,装一个新版 LLVM 并让 xbintsc
25
- 用它:
24
+ `xbintsc build`/`run` 现在会先检查版本,直接停止并提示 "reports clang 15.0.2,
25
+ but xbintsc requires clang 16 or newer";`xbintsc doctor` 会打印
26
+ `status : ok` 或 `status : too old`(后者以非零码退出)。搜索工具链时会优先选择受支持
27
+ 的 clang——包括 Windows 上 Visual Studio 自带的 LLVM——所以装了新版时旧的
28
+ `/usr/bin/clang` 会被跳过。也可以显式指定:
26
29
 
27
30
  ```powershell
28
31
  $env:xbintsc_CLANG = "C:\Program Files\LLVM\bin\clang.exe"
@@ -110,6 +113,36 @@ clang 自身失败。抛出的消息包含完整的 clang 命令与 stderr——
110
113
  `llvm-rc` 或 `windres`;`doctor` 会报告找到了哪个,只有在确实请求了图标时缺失才是
111
114
  致命错误。
112
115
 
116
+ ## 怎么看堆栈
117
+
118
+ `Error.stack` 由 codegen 采集,而不是依赖 C 运行时的原生帧:每个非生成器函数
119
+ 在入口压入 `{名称, 文件, 行号}`,返回前弹出。帧形如
120
+
121
+ ```
122
+ Error: boom
123
+ at inner (app.ts:12)
124
+ at module (app.ts:3)
125
+ ```
126
+
127
+ 行号取函数声明行;最内层帧会更新为 `new Error(...)` 所在行。没有列号;生成器
128
+ 函数体因为会切换栈而不记录。原生致命信号会先打印 `xbintsc: fatal signal …`,
129
+ 再打印同一组帧,所以即使没有调试器也能看出崩溃时正在运行哪些 JS 函数。
130
+
131
+ ## 用 gdb / lldb / WinDbg 调试
132
+
133
+ `xbintsc build app.ts --debug` 会生成 LLVM 调试元数据,并把 `-g
134
+ -fno-omit-frame-pointer` 传给 clang,因此目标文件带有 DWARF(Linux/macOS)或
135
+ CodeView(Windows)行号表。函数符号保留源码名(如 `xt_fn_3_add`),每条语句都有
136
+ 对应的行位置:
137
+
138
+ ```bash
139
+ xbintsc build app.ts --debug
140
+ gdb ./build/app # 或 lldb ./build/app,Windows 用 WinDbg 打开 .exe
141
+ ```
142
+
143
+ 调试版二进制更大,并且与普通构建分开缓存(debug 标志参与缓存键)。`Error.stack`
144
+ 不需要 `--debug` 也能工作。
145
+
113
146
  ## 看起来不对、其实正常的构建行为
114
147
 
115
148
  ### 构建打印了 `(cached)` 并什么都没做
@@ -31,8 +31,10 @@ the engine advances the clock with real frame deltas (`XtDocument::advance`),
31
31
  and `win.advance(ms)` lets tests step it deterministically.
32
32
 
33
33
  - Supported properties: `background-color`, `color`, `border-color`,
34
- `border-radius`; `transition: all` covers them. Structural/layout-affecting
35
- properties are not animated yet (that would relayout every frame).
34
+ `border-radius` and `transform`; `transition: all` covers them. A
35
+ layout-affecting property (width, position, …) is still not animated — that
36
+ would reflow every frame — but `transform` is paint-time, so animating it costs
37
+ one matrix per animated box per frame ([Layout](./layout.md#transforms-m15)).
36
38
  - Both the `transition` shorthand and the `transition-property` / `-duration` /
37
39
  `-delay` / `-timing-function` longhands are parsed; time values accept `s` and
38
40
  `ms`; timing functions are `linear`, `ease`, `ease-in`, `ease-out` and
@@ -42,5 +44,54 @@ and `win.advance(ms)` lets tests step it deterministically.
42
44
  - `computedStyle()` reports the displayed (interpolated) value, so transitions
43
45
  are directly observable in tests.
44
46
 
47
+ `transform` lists interpolate **element-wise** (`document.cpp`,
48
+ `xt_transform_interpolate`), which is what makes the common pair
49
+ `translateY(-4px) rotate(-0.35deg)` <-> `none` glide instead of jumping: the
50
+ missing side is filled with identity ops of the same kind. Two lists whose shapes
51
+ or units do not line up (`rotate(10deg)` vs `rotate(10rad)`, or a different number
52
+ of functions) are not interpolated at all — the transition snaps to the target,
53
+ which is CSS's discrete fallback rather than a wrong-looking sweep.
54
+
55
+ ## CSS transforms (M15)
56
+
57
+ `transform` and `transform-origin` are supported as *paint-time* effects
58
+ (`translate`/`translate3d`/`translateX`/`translateY`,
59
+ `scale`/`scale3d`/`scaleX`/`scaleY`, `rotate`/`rotateZ`, `matrix`; the `3d`
60
+ spellings' `z` argument is parsed and dropped, since the engine is 2D;
61
+ `deg`/`rad`/`grad`/`turn`; `%`, `px`, `em`, `rem`, `vw`, `vh`). Layout
62
+ is not affected: geometry stays where flow put it, and only the painted result
63
+ moves, so a size, a scroll position or a sibling's position never depends on an
64
+ animation frame. See [Layout](./layout.md#transforms-m15) for the composition
65
+ rules and [Rendering](./rendering.md) for how it reaches the GPU.
66
+
67
+ Two ways to drive one:
68
+
69
+ ```ts
70
+ // 1. Per frame — a pose from the frame clock. Nothing reflows: the string is
71
+ // parsed into matrices and the display list picks them up in the same frame.
72
+ const tick = (t: number): void => {
73
+ const s = t / 1000;
74
+ art.style.setProperty(
75
+ "transform",
76
+ "translateY(" + (Math.sin(s * 2.4) * 5).toFixed(2) + "px) " +
77
+ "rotate(" + (Math.sin(s * 1.15) * 1.1).toFixed(2) + "deg)",
78
+ );
79
+ win.requestAnimationFrame(tick);
80
+ };
81
+ ```
82
+
83
+ ```css
84
+ /* 2. From a state change — a transitioned transform, like any other property. */
85
+ #caption {
86
+ transform-origin: 50% 100%;
87
+ transition: transform 260ms ease-out;
88
+ }
89
+ #caption:hover { transform: translateY(-2px) scale(1.03); }
90
+ ```
91
+
92
+ Both are exercised by `examples/gui/pelican-svg` (per-frame sway plus a
93
+ hover transition on the caption) and pinned by
94
+ `tests/e2e/gui-transform.test.ts`.
95
+
45
96
  Still to do for animation: `@keyframes` animations and `cubic-bezier(...)`.
46
97
 
package/doc/gui/api.md CHANGED
@@ -60,7 +60,7 @@ exposes read-only hooks:
60
60
  ```ts
61
61
  win.computedStyle(selector, property) // e.g. ("#main", "width") -> "60%"
62
62
  win.queryCount(selector) // number of matching elements
63
- win.getBoundingClientRect(selector) // { x, y, width, height } (border box)
63
+ win.getBoundingClientRect(selector) // { x, y, width, height } (border box, transformed)
64
64
  win.documentTree() // serialized DOM (debugging)
65
65
  win.layoutTree() // serialized layout boxes (debugging)
66
66
  win.paintCount() // number of shapes in the display list
package/doc/gui/dom.md CHANGED
@@ -20,7 +20,12 @@ pseudo-classes `:first-child`, `:last-child`, `:only-child`, `:empty`,
20
20
  stateful `:hover` and `:focus` (see [Input](./input.md)). Values:
21
21
  lengths (`px`, `%`, `em`, `rem`, `vw`, `vh`, `pt`, `pc`, `in`, `cm`, `mm`, `q`),
22
22
  colors (hex, `rgb()`/`rgba()`, a named subset), numbers, keywords and shorthands
23
- (`margin`/`padding`/`border`/`flex`).
23
+ (`margin`/`padding`/`border`/`flex`). `transform` (`translate`/`translate3d`/
24
+ `translateX`/`translateY`, `scale`/`scale3d`/`scaleX`/`scaleY`, `rotate`/
25
+ `rotateZ`, `matrix`; the `3d` spellings' `z` argument is dropped) and
26
+ `transform-origin` are parsed into the computed style as a list of operations
27
+ plus two (unresolved) origin lengths; an unsupported function invalidates the
28
+ whole declaration ([Layout](./layout.md#transforms-m15)).
24
29
 
25
30
  **Cascade & computed style** (`runtime/ext_gui/style.{h,cpp}`): a small built-in
26
31
  UA stylesheet, author rules sorted by `(!important, specificity, source order)`,
package/doc/gui/input.md CHANGED
@@ -6,7 +6,10 @@
6
6
 
7
7
  `LayoutTree::hitTest` returns the deepest box containing a point (probing later
8
8
  siblings first so the topmost element wins), and `xt_dom_describe` turns the
9
- element into the `div#id.class` descriptor carried by event payloads.
9
+ element into the `div#id.class` descriptor carried by event payloads. A
10
+ transformed box maps the point through its inverse matrix first, so a rotated,
11
+ scaled or translated element is hit exactly where it is drawn — and `:hover`
12
+ follows the artwork, not the box flow left behind.
10
13
 
11
14
  - SDL pointer/wheel/key events are routed to the owning window, hit tested, and
12
15
  delivered to the handlers registered with `on`. `mousedown` also moves focus.
@@ -48,11 +48,23 @@
48
48
  rectangles/text/images, so `<svg>` subtrees would need a whole second
49
49
  geometry+paint path. Vector art therefore has to arrive as an SVG/PNG file or
50
50
  be drawn from boxes, as `examples/gui/pelican-bike` does.
51
+ - **Windows: the C++ runtime must not be named on the link line.** `gui.lib` is
52
+ built with the *static* CRT (`CMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded`) and
53
+ every engine object records `libcpmt` as a dependent library, which is also
54
+ clang's Windows default and what the xbintsc runtime objects use
55
+ (`scripts/build-gui.ts`). Asking for the DLL runtime as well — the gui
56
+ extension used to pass `-lmsvcprt` — makes the two copies of the same symbols
57
+ collide: `LNK2038: mismatch detected for 'RuntimeLibrary'` plus a wall of
58
+ `LNK2005: already defined`, surfaced as `clang: linker command failed with exit
59
+ code 1169`. `src/extensions/gui/index.ts` now lists only the OS libraries
60
+ (`user32`, `gdi32`, `d3d12`, …). A Windows link is also the only place the
61
+ MSVC environment matters, so build from an x64 Native Tools prompt.
51
62
  - Windows: an MSVC-compatible `gui.lib` (COFF objects + `ar -M`/`llvm-ar`) is
52
- produced by `scripts/build-gui.ts`, but it has not been validated in CI yet,
53
- so the Windows step is provisional (the job stays green) and `package` skips
54
- it. It also needs an SDL3 build with the D3D12/DXIL backend (DXIL requires
55
- `dxc`).
63
+ produced by `scripts/build-gui.ts` but is not validated in CI, so the Windows
64
+ step is provisional (the job stays green) and `package` skips it. Building and
65
+ running `examples/gui/pelican-svg` and `examples/gui/pelican-bike` on a
66
+ developer machine works. It also needs an SDL3 build with the D3D12/DXIL
67
+ backend (DXIL requires `dxc`).
56
68
  - **Media on Windows:** the media decoders (FFmpeg) are only built and merged
57
69
  into the POSIX `gui.a`; the Windows `gui.lib` is produced without them, so
58
70
  `<video>`/`<audio>` are inert there (failures fire `error`). Enabling it needs
package/doc/gui/layout.md CHANGED
@@ -66,6 +66,42 @@ flex items from non-whitespace text, full `z-index` stacking contexts, and
66
66
  `float` affecting block children (only inline content wraps; a block must
67
67
  `clear`).
68
68
 
69
+ ## Transforms (M15)
70
+
71
+ `transform` and `transform-origin` are a **paint-time** effect: the box model
72
+ above is untouched (nothing reflows, no geometry is recomputed), and a second
73
+ pass after `xt_layout_run` gives every `LayoutBox` a `Matrix2D world` — the
74
+ transform of the whole ancestor chain — plus its `inverse`:
75
+
76
+ - **Only element boxes contribute.** A text box shares its parent element's
77
+ `style` pointer, so letting both contribute would apply the element's list
78
+ twice; the pass composes an element's own list, its ancestors', and nothing
79
+ else.
80
+ - **`transform-origin` is resolved here**, not in the cascade: the computed style
81
+ keeps the two (possibly `%`) `Length`s (default `50% 50%`) and layout resolves
82
+ them against the *border box*, applying `T(origin) · M · T(-origin)`. That is
83
+ why `computedStyle(el, "transform-origin")` still reports `50% 92%`. The
84
+ keywords are positional as in CSS: `top` alone is `center top`, and the
85
+ keyword pair may be given in either order (`bottom right` = `right bottom`).
86
+ - **Lengths resolve against the box**: `%` in `translate()` is a share of the
87
+ border box's width/height, `em`/`rem` against the root font size (16px), and
88
+ `vw`/`vh` against the viewport.
89
+ - **Composition order** follows CSS: the list applies right to left, so
90
+ `rotate(90deg) translate(10px)` moves the box *down* the screen, not right.
91
+
92
+ Everything downstream follows the matrices:
93
+
94
+ | Consumer | Behaviour |
95
+ | --- | --- |
96
+ | Paint | each op carries the matrix (see [Rendering](./rendering.md#paint-and-display-list-m4am4b)); a clipped subtree clips to the bounding box of its padding box through the matrix |
97
+ | Hit testing | `hitTestBox` maps the point through each box's inverse before the containment test, so a rotated or scaled element is clickable where it is *drawn* |
98
+ | `getBoundingClientRect` | the axis-aligned bounding box of the **transformed** border box (`xt_layout_transformed_bounds`) |
99
+ | Layout geometry | unchanged — `layoutTree()` and `offsetWidth`/`offsetHeight` keep reporting the untransformed box |
100
+
101
+ Unsupported transform functions (`skew`, `perspective`, …) invalidate the whole
102
+ declaration, so a box is either correctly transformed or not transformed at all
103
+ rather than partially applied.
104
+
69
105
  **Document** (`runtime/ext_gui/document.{h,cpp}`): owns the DOM tree, gathers
70
106
  `<style>` text into one stylesheet, computes styles and layout for a viewport and
71
107
  offers `querySelector`/`querySelectorAll`/`styleOf`/`boxOf`.
@@ -54,6 +54,21 @@ its own GPU scissor rectangle (scaled to device pixels), so painter order and
54
54
  - Geometry is uploaded only when the document or viewport changes
55
55
  (`geometry.dirty`), so steady-state frames are bind-and-draw.
56
56
 
57
+ Each op also carries the `Matrix2D` of the box it came from
58
+ (`runtime/ext_gui/transform.h`, `PaintRect::transform` and friends). It is
59
+ applied while the vertex buffer is built, not in the shader: `pushRect` maps its
60
+ four corners through the matrix (the distance-field `local`/half-extent/radius
61
+ fields stay in the *box* frame, so a rounded corner stays round after a
62
+ rotation), and `pushQuad`/`pushGlyph` map the glyph or image quad the same way.
63
+ That kept the MSL/SPIR-V/DXIL shaders and the pipelines untouched — a transform
64
+ costs a few multiplies per vertex and nothing per pixel. A clip rectangle of a
65
+ transformed box is the bounding box of the transformed padding box, because the
66
+ GPU scissor is axis aligned. Text is the one case that needs more than
67
+ multiplying corners: a scaling transform would magnify an atlas raster, so the
68
+ atlas is asked for glyphs at the device scale **times the matrix's
69
+ magnification** (clamped to 0.5–4x, since the atlas is finite) and the quad is
70
+ divided back into logical units, exactly the way HiDPI scaling already worked.
71
+
57
72
  The window background (`setBackground`) is the render-pass clear colour.
58
73
 
59
74
  Still to do in M4: gradients.
package/doc/gui.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # xbintsc GUI extension (self-hosted HTML/CSS renderer)
2
2
 
3
- Status: **M14** — features (M1–M13) are complete: HTML parsing, CSS selector
3
+ Status: **M15** — features (M1–M14) are complete: HTML parsing, CSS selector
4
4
  matching, the cascade, computed styles and layout (block, inline, Flexbox and
5
5
  CSS positioning) are
6
6
  in place, and the engine *paints*: it builds a display list of rectangles, images
@@ -24,7 +24,13 @@ video frames go through the image texture path and audio to an SDL stream, with
24
24
  the DOM media API exposing `play`/`pause`/`currentTime`/… and media events.
25
25
  **M14** adds `<canvas>` with a self-written 2D software rasteriser (anti-aliased
26
26
  fills, paths, text and `drawImage`) uploaded through the image texture path —
27
- see [Canvas](./gui/canvas.md).
27
+ see [Canvas](./gui/canvas.md). **M15** adds CSS `transform` /
28
+ `transform-origin`: a composited `Matrix2D` per layout box that the display list
29
+ carries into the renderer, so boxes (and the text, images and children inside
30
+ them) can be translated, scaled and rotated without reflowing anything — and
31
+ `transition: transform`, so those poses animate. See
32
+ [Layout](./gui/layout.md#transforms-m15) and
33
+ [Animation](./gui/animation.md#css-transforms-m15).
28
34
  This overview records the locked decisions, the architecture, the milestone plan
29
35
  and the current progress; the engine itself is documented by function in the
30
36
  pages above.
@@ -38,7 +44,7 @@ pages above.
38
44
  - [Media](./gui/media.md) — images, SVG, video and audio
39
45
  - [Canvas](./gui/canvas.md) — `<canvas>` and the 2D context
40
46
  - [Input](./gui/input.md) — hit testing, events and `:hover`/`:focus`
41
- - [Animation](./gui/animation.md) — `requestAnimationFrame` and CSS transitions
47
+ - [Animation](./gui/animation.md) — `requestAnimationFrame`, CSS transitions and transforms
42
48
  - [Runtime integration](./gui/runtime.md) — event loop, native bridge and memory
43
49
  - [Packaging and build](./gui/build.md) — building `gui.a` and running examples
44
50
  - [Known issues and open questions](./gui/known-issues.md)
@@ -240,6 +246,16 @@ engine were Rust.
240
246
  GPU texture through the image path (`renderer_image.cpp`) and the display
241
247
  list carries a `canvas_id` (`paint.*`). See [Canvas](./gui/canvas.md);
242
248
  e2e in `tests/e2e/gui-canvas.test.ts`.
249
+ 15. **M15 — CSS `transform`** ✅
250
+ - `transform`/`transform-origin` parse into a `TransformOp` list
251
+ (`transform.{h,cpp}`, `style.*`), transition like any other paint
252
+ property (`document.*`), and are composed into a per-box `world` matrix
253
+ (plus its inverse) after layout (`layout.*`). Paint carries the matrix on
254
+ every op and the renderer maps quad corners through it, so the shaders are
255
+ untouched. Hit testing and `getBoundingClientRect` follow. See
256
+ [Layout](./gui/layout.md#transforms-m15) and
257
+ [Animation](./gui/animation.md#css-transforms-m15); e2e in
258
+ `tests/e2e/gui-transform.test.ts` and `tests/e2e/gui-example-svg.test.ts`.
243
259
 
244
260
  ## Progress log
245
261
 
@@ -325,4 +341,17 @@ engine were Rust.
325
341
  image texture path (`renderer_image.cpp`) and the display list carries a
326
342
  `canvas_id` (`paint.*`). See [Canvas](./gui/canvas.md); e2e in
327
343
  `tests/e2e/gui-canvas.test.ts`.
344
+ - **M15** ✅ CSS `transform` / `transform-origin`. A new translation unit
345
+ (`transform.{h,cpp}`) parses `translate`/`translateX`/`translateY`/`scale`/
346
+ `scaleX`/`scaleY`/`rotate`/`matrix` (plus `translate3d`/`scale3d`, whose `z`
347
+ argument is dropped) into a `Matrix2D` list, reports them back through
348
+ `computedStyle`, and interpolates them for transitions (a shape or unit change
349
+ snaps). Layout composes a `world` matrix and its inverse per box after the
350
+ normal pass (`applyTransforms`), resolving `transform-origin` against the
351
+ border box — so layout geometry, and therefore flow, never changes. Paint
352
+ attaches the matrix to every shape/text/image op and the renderer maps quad
353
+ corners through it (no shader or uniform change), hit testing maps the point
354
+ through the inverse, and `getBoundingClientRect` returns the transformed AABB.
355
+ `examples/gui/pelican-svg` animates its artwork with it; e2e in
356
+ `tests/e2e/gui-transform.test.ts` and `tests/e2e/gui-example-svg.test.ts`.
328
357