@gtkx/codegen 0.21.0

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 (340) hide show
  1. package/LICENSE +373 -0
  2. package/dist/dsl/context.d.ts +104 -0
  3. package/dist/dsl/context.d.ts.map +1 -0
  4. package/dist/dsl/context.js +140 -0
  5. package/dist/dsl/context.js.map +1 -0
  6. package/dist/dsl/emit.d.ts +28 -0
  7. package/dist/dsl/emit.d.ts.map +1 -0
  8. package/dist/dsl/emit.js +41 -0
  9. package/dist/dsl/emit.js.map +1 -0
  10. package/dist/dsl/identifier.d.ts +28 -0
  11. package/dist/dsl/identifier.d.ts.map +1 -0
  12. package/dist/dsl/identifier.js +45 -0
  13. package/dist/dsl/identifier.js.map +1 -0
  14. package/dist/dsl/imports.d.ts +58 -0
  15. package/dist/dsl/imports.d.ts.map +1 -0
  16. package/dist/dsl/imports.js +107 -0
  17. package/dist/dsl/imports.js.map +1 -0
  18. package/dist/dsl/module.d.ts +53 -0
  19. package/dist/dsl/module.d.ts.map +1 -0
  20. package/dist/dsl/module.js +76 -0
  21. package/dist/dsl/module.js.map +1 -0
  22. package/dist/ffi/pipeline.d.ts +19 -0
  23. package/dist/ffi/pipeline.d.ts.map +1 -0
  24. package/dist/ffi/pipeline.js +107 -0
  25. package/dist/ffi/pipeline.js.map +1 -0
  26. package/dist/fingerprint.d.ts +30 -0
  27. package/dist/fingerprint.d.ts.map +1 -0
  28. package/dist/fingerprint.js +33 -0
  29. package/dist/fingerprint.js.map +1 -0
  30. package/dist/gi-store.d.ts +34 -0
  31. package/dist/gi-store.d.ts.map +1 -0
  32. package/dist/gi-store.js +154 -0
  33. package/dist/gi-store.js.map +1 -0
  34. package/dist/gir/boxed.d.ts +66 -0
  35. package/dist/gir/boxed.d.ts.map +1 -0
  36. package/dist/gir/boxed.js +49 -0
  37. package/dist/gir/boxed.js.map +1 -0
  38. package/dist/gir/callback.d.ts +15 -0
  39. package/dist/gir/callback.d.ts.map +1 -0
  40. package/dist/gir/callback.js +17 -0
  41. package/dist/gir/callback.js.map +1 -0
  42. package/dist/gir/class.d.ts +49 -0
  43. package/dist/gir/class.d.ts.map +1 -0
  44. package/dist/gir/class.js +42 -0
  45. package/dist/gir/class.js.map +1 -0
  46. package/dist/gir/enum.d.ts +39 -0
  47. package/dist/gir/enum.d.ts.map +1 -0
  48. package/dist/gir/enum.js +24 -0
  49. package/dist/gir/enum.js.map +1 -0
  50. package/dist/gir/field.d.ts +27 -0
  51. package/dist/gir/field.d.ts.map +1 -0
  52. package/dist/gir/field.js +19 -0
  53. package/dist/gir/field.js.map +1 -0
  54. package/dist/gir/function.d.ts +47 -0
  55. package/dist/gir/function.d.ts.map +1 -0
  56. package/dist/gir/function.js +28 -0
  57. package/dist/gir/function.js.map +1 -0
  58. package/dist/gir/namespace.d.ts +65 -0
  59. package/dist/gir/namespace.d.ts.map +1 -0
  60. package/dist/gir/namespace.js +60 -0
  61. package/dist/gir/namespace.js.map +1 -0
  62. package/dist/gir/parameter.d.ts +82 -0
  63. package/dist/gir/parameter.d.ts.map +1 -0
  64. package/dist/gir/parameter.js +66 -0
  65. package/dist/gir/parameter.js.map +1 -0
  66. package/dist/gir/parse.d.ts +78 -0
  67. package/dist/gir/parse.d.ts.map +1 -0
  68. package/dist/gir/parse.js +155 -0
  69. package/dist/gir/parse.js.map +1 -0
  70. package/dist/gir/primitives.d.ts +36 -0
  71. package/dist/gir/primitives.d.ts.map +1 -0
  72. package/dist/gir/primitives.js +109 -0
  73. package/dist/gir/primitives.js.map +1 -0
  74. package/dist/gir/property.d.ts +29 -0
  75. package/dist/gir/property.d.ts.map +1 -0
  76. package/dist/gir/property.js +16 -0
  77. package/dist/gir/property.js.map +1 -0
  78. package/dist/gir/qualified-name.d.ts +37 -0
  79. package/dist/gir/qualified-name.d.ts.map +1 -0
  80. package/dist/gir/qualified-name.js +39 -0
  81. package/dist/gir/qualified-name.js.map +1 -0
  82. package/dist/gir/qualify.d.ts +32 -0
  83. package/dist/gir/qualify.d.ts.map +1 -0
  84. package/dist/gir/qualify.js +60 -0
  85. package/dist/gir/qualify.js.map +1 -0
  86. package/dist/gir/repository.d.ts +82 -0
  87. package/dist/gir/repository.d.ts.map +1 -0
  88. package/dist/gir/repository.js +122 -0
  89. package/dist/gir/repository.js.map +1 -0
  90. package/dist/gir/signal.d.ts +22 -0
  91. package/dist/gir/signal.d.ts.map +1 -0
  92. package/dist/gir/signal.js +20 -0
  93. package/dist/gir/signal.js.map +1 -0
  94. package/dist/gir/size.d.ts +67 -0
  95. package/dist/gir/size.d.ts.map +1 -0
  96. package/dist/gir/size.js +87 -0
  97. package/dist/gir/size.js.map +1 -0
  98. package/dist/gir/type-ref.d.ts +79 -0
  99. package/dist/gir/type-ref.d.ts.map +1 -0
  100. package/dist/gir/type-ref.js +101 -0
  101. package/dist/gir/type-ref.js.map +1 -0
  102. package/dist/index.d.ts +12 -0
  103. package/dist/index.d.ts.map +1 -0
  104. package/dist/index.js +12 -0
  105. package/dist/index.js.map +1 -0
  106. package/dist/jsx-store.d.ts +22 -0
  107. package/dist/jsx-store.d.ts.map +1 -0
  108. package/dist/jsx-store.js +40 -0
  109. package/dist/jsx-store.js.map +1 -0
  110. package/dist/react/compounds-meta.d.ts +18 -0
  111. package/dist/react/compounds-meta.d.ts.map +1 -0
  112. package/dist/react/compounds-meta.js +93 -0
  113. package/dist/react/compounds-meta.js.map +1 -0
  114. package/dist/react/compounds.d.ts +27 -0
  115. package/dist/react/compounds.d.ts.map +1 -0
  116. package/dist/react/compounds.js +164 -0
  117. package/dist/react/compounds.js.map +1 -0
  118. package/dist/react/internal.d.ts +14 -0
  119. package/dist/react/internal.d.ts.map +1 -0
  120. package/dist/react/internal.js +79 -0
  121. package/dist/react/internal.js.map +1 -0
  122. package/dist/react/jsx.d.ts +22 -0
  123. package/dist/react/jsx.d.ts.map +1 -0
  124. package/dist/react/jsx.js +134 -0
  125. package/dist/react/jsx.js.map +1 -0
  126. package/dist/react/pipeline.d.ts +13 -0
  127. package/dist/react/pipeline.d.ts.map +1 -0
  128. package/dist/react/pipeline.js +25 -0
  129. package/dist/react/pipeline.js.map +1 -0
  130. package/dist/react/props.d.ts +38 -0
  131. package/dist/react/props.d.ts.map +1 -0
  132. package/dist/react/props.js +231 -0
  133. package/dist/react/props.js.map +1 -0
  134. package/dist/react/slot-props.d.ts +10 -0
  135. package/dist/react/slot-props.d.ts.map +1 -0
  136. package/dist/react/slot-props.js +56 -0
  137. package/dist/react/slot-props.js.map +1 -0
  138. package/dist/react/widgets.d.ts +83 -0
  139. package/dist/react/widgets.d.ts.map +1 -0
  140. package/dist/react/widgets.js +152 -0
  141. package/dist/react/widgets.js.map +1 -0
  142. package/dist/runner.d.ts +55 -0
  143. package/dist/runner.d.ts.map +1 -0
  144. package/dist/runner.js +60 -0
  145. package/dist/runner.js.map +1 -0
  146. package/dist/store-fs.d.ts +109 -0
  147. package/dist/store-fs.d.ts.map +1 -0
  148. package/dist/store-fs.js +98 -0
  149. package/dist/store-fs.js.map +1 -0
  150. package/dist/transpile.d.ts +32 -0
  151. package/dist/transpile.d.ts.map +1 -0
  152. package/dist/transpile.js +63 -0
  153. package/dist/transpile.js.map +1 -0
  154. package/dist/typecheck-store.d.ts +41 -0
  155. package/dist/typecheck-store.d.ts.map +1 -0
  156. package/dist/typecheck-store.js +102 -0
  157. package/dist/typecheck-store.js.map +1 -0
  158. package/dist/writers/alias.d.ts +13 -0
  159. package/dist/writers/alias.d.ts.map +1 -0
  160. package/dist/writers/alias.js +17 -0
  161. package/dist/writers/alias.js.map +1 -0
  162. package/dist/writers/async.d.ts +22 -0
  163. package/dist/writers/async.d.ts.map +1 -0
  164. package/dist/writers/async.js +48 -0
  165. package/dist/writers/async.js.map +1 -0
  166. package/dist/writers/boxed-constructor.d.ts +26 -0
  167. package/dist/writers/boxed-constructor.d.ts.map +1 -0
  168. package/dist/writers/boxed-constructor.js +85 -0
  169. package/dist/writers/boxed-constructor.js.map +1 -0
  170. package/dist/writers/boxed-field-accessor.d.ts +18 -0
  171. package/dist/writers/boxed-field-accessor.d.ts.map +1 -0
  172. package/dist/writers/boxed-field-accessor.js +260 -0
  173. package/dist/writers/boxed-field-accessor.js.map +1 -0
  174. package/dist/writers/boxed-layout.d.ts +27 -0
  175. package/dist/writers/boxed-layout.d.ts.map +1 -0
  176. package/dist/writers/boxed-layout.js +128 -0
  177. package/dist/writers/boxed-layout.js.map +1 -0
  178. package/dist/writers/boxed.d.ts +20 -0
  179. package/dist/writers/boxed.d.ts.map +1 -0
  180. package/dist/writers/boxed.js +71 -0
  181. package/dist/writers/boxed.js.map +1 -0
  182. package/dist/writers/callables.d.ts +124 -0
  183. package/dist/writers/callables.d.ts.map +1 -0
  184. package/dist/writers/callables.js +283 -0
  185. package/dist/writers/callables.js.map +1 -0
  186. package/dist/writers/callback.d.ts +16 -0
  187. package/dist/writers/callback.d.ts.map +1 -0
  188. package/dist/writers/callback.js +38 -0
  189. package/dist/writers/callback.js.map +1 -0
  190. package/dist/writers/class-struct-record.d.ts +31 -0
  191. package/dist/writers/class-struct-record.d.ts.map +1 -0
  192. package/dist/writers/class-struct-record.js +67 -0
  193. package/dist/writers/class-struct-record.js.map +1 -0
  194. package/dist/writers/class-struct.d.ts +22 -0
  195. package/dist/writers/class-struct.d.ts.map +1 -0
  196. package/dist/writers/class-struct.js +85 -0
  197. package/dist/writers/class-struct.js.map +1 -0
  198. package/dist/writers/class.d.ts +16 -0
  199. package/dist/writers/class.d.ts.map +1 -0
  200. package/dist/writers/class.js +307 -0
  201. package/dist/writers/class.js.map +1 -0
  202. package/dist/writers/constant.d.ts +15 -0
  203. package/dist/writers/constant.d.ts.map +1 -0
  204. package/dist/writers/constant.js +23 -0
  205. package/dist/writers/constant.js.map +1 -0
  206. package/dist/writers/constants.d.ts +15 -0
  207. package/dist/writers/constants.d.ts.map +1 -0
  208. package/dist/writers/constants.js +23 -0
  209. package/dist/writers/constants.js.map +1 -0
  210. package/dist/writers/constructor-props.d.ts +32 -0
  211. package/dist/writers/constructor-props.d.ts.map +1 -0
  212. package/dist/writers/constructor-props.js +110 -0
  213. package/dist/writers/constructor-props.js.map +1 -0
  214. package/dist/writers/enum.d.ts +21 -0
  215. package/dist/writers/enum.d.ts.map +1 -0
  216. package/dist/writers/enum.js +48 -0
  217. package/dist/writers/enum.js.map +1 -0
  218. package/dist/writers/function.d.ts +40 -0
  219. package/dist/writers/function.d.ts.map +1 -0
  220. package/dist/writers/function.js +127 -0
  221. package/dist/writers/function.js.map +1 -0
  222. package/dist/writers/gtype-binding.d.ts +19 -0
  223. package/dist/writers/gtype-binding.d.ts.map +1 -0
  224. package/dist/writers/gtype-binding.js +40 -0
  225. package/dist/writers/gtype-binding.js.map +1 -0
  226. package/dist/writers/inheritance.d.ts +48 -0
  227. package/dist/writers/inheritance.d.ts.map +1 -0
  228. package/dist/writers/inheritance.js +90 -0
  229. package/dist/writers/inheritance.js.map +1 -0
  230. package/dist/writers/interface.d.ts +16 -0
  231. package/dist/writers/interface.d.ts.map +1 -0
  232. package/dist/writers/interface.js +155 -0
  233. package/dist/writers/interface.js.map +1 -0
  234. package/dist/writers/method.d.ts +150 -0
  235. package/dist/writers/method.d.ts.map +1 -0
  236. package/dist/writers/method.js +510 -0
  237. package/dist/writers/method.js.map +1 -0
  238. package/dist/writers/param-classify.d.ts +93 -0
  239. package/dist/writers/param-classify.d.ts.map +1 -0
  240. package/dist/writers/param-classify.js +163 -0
  241. package/dist/writers/param-classify.js.map +1 -0
  242. package/dist/writers/property-accessor.d.ts +19 -0
  243. package/dist/writers/property-accessor.d.ts.map +1 -0
  244. package/dist/writers/property-accessor.js +117 -0
  245. package/dist/writers/property-accessor.js.map +1 -0
  246. package/dist/writers/registration.d.ts +27 -0
  247. package/dist/writers/registration.d.ts.map +1 -0
  248. package/dist/writers/registration.js +30 -0
  249. package/dist/writers/registration.js.map +1 -0
  250. package/dist/writers/return-wrap.d.ts +39 -0
  251. package/dist/writers/return-wrap.d.ts.map +1 -0
  252. package/dist/writers/return-wrap.js +152 -0
  253. package/dist/writers/return-wrap.js.map +1 -0
  254. package/dist/writers/runtime-override.d.ts +18 -0
  255. package/dist/writers/runtime-override.d.ts.map +1 -0
  256. package/dist/writers/runtime-override.js +44 -0
  257. package/dist/writers/runtime-override.js.map +1 -0
  258. package/dist/writers/signal.d.ts +21 -0
  259. package/dist/writers/signal.d.ts.map +1 -0
  260. package/dist/writers/signal.js +260 -0
  261. package/dist/writers/signal.js.map +1 -0
  262. package/dist/writers/ts-type.d.ts +50 -0
  263. package/dist/writers/ts-type.d.ts.map +1 -0
  264. package/dist/writers/ts-type.js +75 -0
  265. package/dist/writers/ts-type.js.map +1 -0
  266. package/dist/writers/types-ts.d.ts +27 -0
  267. package/dist/writers/types-ts.d.ts.map +1 -0
  268. package/dist/writers/types-ts.js +97 -0
  269. package/dist/writers/types-ts.js.map +1 -0
  270. package/dist/writers/value.d.ts +89 -0
  271. package/dist/writers/value.d.ts.map +1 -0
  272. package/dist/writers/value.js +402 -0
  273. package/dist/writers/value.js.map +1 -0
  274. package/package.json +55 -0
  275. package/src/dsl/context.ts +146 -0
  276. package/src/dsl/emit.ts +42 -0
  277. package/src/dsl/identifier.ts +52 -0
  278. package/src/dsl/imports.ts +109 -0
  279. package/src/dsl/module.ts +75 -0
  280. package/src/ffi/pipeline.ts +113 -0
  281. package/src/fingerprint.ts +50 -0
  282. package/src/gi-store.ts +199 -0
  283. package/src/gir/boxed.ts +94 -0
  284. package/src/gir/callback.ts +26 -0
  285. package/src/gir/class.ts +79 -0
  286. package/src/gir/enum.ts +55 -0
  287. package/src/gir/field.ts +40 -0
  288. package/src/gir/function.ts +67 -0
  289. package/src/gir/namespace.ts +118 -0
  290. package/src/gir/parameter.ts +120 -0
  291. package/src/gir/parse.ts +171 -0
  292. package/src/gir/primitives.ts +137 -0
  293. package/src/gir/property.ts +39 -0
  294. package/src/gir/qualified-name.ts +55 -0
  295. package/src/gir/qualify.ts +64 -0
  296. package/src/gir/repository.ts +149 -0
  297. package/src/gir/signal.ts +36 -0
  298. package/src/gir/size.ts +129 -0
  299. package/src/gir/type-ref.ts +180 -0
  300. package/src/index.ts +16 -0
  301. package/src/jsx-store.ts +53 -0
  302. package/src/react/compounds-meta.ts +99 -0
  303. package/src/react/compounds.ts +204 -0
  304. package/src/react/internal.ts +91 -0
  305. package/src/react/jsx.ts +155 -0
  306. package/src/react/pipeline.ts +29 -0
  307. package/src/react/props.ts +307 -0
  308. package/src/react/slot-props.ts +57 -0
  309. package/src/react/widgets.ts +185 -0
  310. package/src/runner.ts +89 -0
  311. package/src/store-fs.ts +157 -0
  312. package/src/transpile.ts +80 -0
  313. package/src/typecheck-store.ts +136 -0
  314. package/src/writers/alias.ts +19 -0
  315. package/src/writers/async.ts +44 -0
  316. package/src/writers/boxed-constructor.ts +105 -0
  317. package/src/writers/boxed-field-accessor.ts +306 -0
  318. package/src/writers/boxed-layout.ts +166 -0
  319. package/src/writers/boxed.ts +77 -0
  320. package/src/writers/callables.ts +339 -0
  321. package/src/writers/callback.ts +40 -0
  322. package/src/writers/class-struct-record.ts +75 -0
  323. package/src/writers/class-struct.ts +96 -0
  324. package/src/writers/class.ts +384 -0
  325. package/src/writers/constant.ts +29 -0
  326. package/src/writers/constructor-props.ts +130 -0
  327. package/src/writers/enum.ts +53 -0
  328. package/src/writers/function.ts +136 -0
  329. package/src/writers/gtype-binding.ts +48 -0
  330. package/src/writers/inheritance.ts +104 -0
  331. package/src/writers/interface.ts +168 -0
  332. package/src/writers/method.ts +678 -0
  333. package/src/writers/param-classify.ts +178 -0
  334. package/src/writers/property-accessor.ts +139 -0
  335. package/src/writers/registration.ts +42 -0
  336. package/src/writers/return-wrap.ts +180 -0
  337. package/src/writers/runtime-override.ts +59 -0
  338. package/src/writers/signal.ts +318 -0
  339. package/src/writers/ts-type.ts +115 -0
  340. package/src/writers/value.ts +485 -0
@@ -0,0 +1,306 @@
1
+ import { toCamelCase, toIdentifier } from "@gtkx/utils";
2
+ import type { ModuleContext } from "../dsl/context.js";
3
+ import { indent } from "../dsl/emit.js";
4
+ import type { GirField } from "../gir/field.js";
5
+ import type { FieldSlot } from "../gir/size.js";
6
+ import type { GirTypeRef } from "../gir/type-ref.js";
7
+ import { type BoxedFieldSlot, computeBoxedFieldSlots } from "./boxed-layout.js";
8
+ import { typeRefIsClassStruct } from "./class-struct-record.js";
9
+ import { wrapReturnValue } from "./return-wrap.js";
10
+ import { renderTsType } from "./ts-type.js";
11
+ import { renderFfiType } from "./value.js";
12
+
13
+ /**
14
+ * Renders the `get` / `set` accessor pair for a single boxed field.
15
+ *
16
+ * The accessor reads through the runtime's `read(handle, ffiType, offset)`
17
+ * (or `write` for the setter), with bitfield members threading through a
18
+ * read-modify-write sequence into their shared storage word. Fields whose
19
+ * type cannot be marshaled at present (callbacks, varargs, inline arrays
20
+ * of complex types) are skipped silently.
21
+ *
22
+ * @param context - The module context
23
+ * @param slot - The field plus its layout slot
24
+ * @param claimedNames - Names already used by emitted methods
25
+ */
26
+ export const renderBoxedFieldAccessor = (
27
+ context: ModuleContext,
28
+ slot: BoxedFieldSlot,
29
+ claimedNames: ReadonlySet<string>,
30
+ siblingFields: readonly GirField[],
31
+ ): string | undefined => {
32
+ const { field } = slot;
33
+ if (field.private) return undefined;
34
+ if (!field.readable && !field.writable) return undefined;
35
+ if (field.callback !== undefined) return undefined;
36
+ if (field.type === undefined) return undefined;
37
+ if (typeRefIsClassStruct(context, field.type)) return undefined;
38
+ const jsName = toIdentifier(toCamelCase(field.name));
39
+ if (claimedNames.has(jsName)) return undefined;
40
+ if (jsName === "constructor") return undefined;
41
+
42
+ const structArray = renderStructArrayAccessor(context, { field, jsName, slot: slot.slot, siblingFields });
43
+ if (structArray !== undefined) return structArray;
44
+
45
+ if (!isAccessorEligibleType(field.type)) {
46
+ const tsType = renderTsType(context, field.type, false);
47
+ const modifier = field.writable ? "declare" : "declare readonly";
48
+ return `${modifier} ${jsName}: ${tsType};`;
49
+ }
50
+
51
+ const ffiType = renderFfiType(context, field.type, "none");
52
+ const tsType = renderTsType(context, field.type, false);
53
+ const accessorOptions: AccessorOptions = {
54
+ context,
55
+ jsName,
56
+ tsType,
57
+ ffiType,
58
+ slot: slot.slot,
59
+ fieldType: field.type,
60
+ };
61
+ const blocks: string[] = [getterBlock(accessorOptions)];
62
+ if (field.writable) {
63
+ blocks.push(setterBlock(accessorOptions));
64
+ }
65
+ return blocks.join("\n\n");
66
+ };
67
+
68
+ const isAccessorEligibleType = (ref: GirTypeRef): boolean => {
69
+ switch (ref.kind) {
70
+ case "primitive":
71
+ return ref.category !== "void" && ref.category !== "unichar";
72
+ case "named":
73
+ return true;
74
+ case "array":
75
+ return ref.fixedSize !== undefined;
76
+ case "list":
77
+ case "hashtable":
78
+ return false;
79
+ case "callback":
80
+ case "varargs":
81
+ return false;
82
+ }
83
+ };
84
+
85
+ const resolveInlineStructFields = (
86
+ context: ModuleContext,
87
+ ref: GirTypeRef | undefined,
88
+ ): readonly GirField[] | undefined => {
89
+ if (ref === undefined || ref.kind !== "named") return undefined;
90
+ if (ref.cType?.endsWith("*") === true) return undefined;
91
+ const resolved = context.repository.resolveNamed(ref.namespaceName ?? context.namespace.name, ref.typeName);
92
+ if (resolved === undefined || resolved.kind !== "boxed") return undefined;
93
+ if (resolved.value.cType?.endsWith("*") === true) return undefined;
94
+ if (resolved.value.fields.length === 0) return undefined;
95
+ return resolved.value.fields;
96
+ };
97
+
98
+ const arrayLengthExpression = (
99
+ arrayRef: Extract<GirTypeRef, { kind: "array" }>,
100
+ siblingFields: readonly GirField[],
101
+ ): string | undefined => {
102
+ if (arrayRef.fixedSize !== undefined) return String(arrayRef.fixedSize);
103
+ if (arrayRef.lengthParameterIndex === undefined) return undefined;
104
+ const lengthField = siblingFields.filter((field) => !field.private)[arrayRef.lengthParameterIndex];
105
+ if (lengthField === undefined) return undefined;
106
+ return `this.${toIdentifier(toCamelCase(lengthField.name))}`;
107
+ };
108
+
109
+ const renderElementReadObject = (context: ModuleContext, fields: readonly GirField[], baseOffset: number): string => {
110
+ const { slots } = computeBoxedFieldSlots(context, fields);
111
+ const entries: string[] = [];
112
+ for (const { field, slot } of slots) {
113
+ if (field.private || field.type === undefined || field.callback !== undefined) continue;
114
+ const jsName = toIdentifier(toCamelCase(field.name));
115
+ const offset = baseOffset + slot.byteOffset;
116
+ const nested = resolveInlineStructFields(context, field.type);
117
+ if (nested !== undefined) {
118
+ entries.push(`${jsName}: ${renderElementReadObject(context, nested, offset)}`);
119
+ continue;
120
+ }
121
+ if (!isAccessorEligibleType(field.type)) continue;
122
+ const ffi = renderFfiType(context, field.type, "none");
123
+ if (slot.bitWidth === undefined) {
124
+ entries.push(
125
+ `${jsName}: read(__array, ${ffi}, __base + ${offset}) as ${renderTsType(context, field.type, false)}`,
126
+ );
127
+ continue;
128
+ }
129
+ const shift = slot.bitOffset ?? 0;
130
+ entries.push(
131
+ `${jsName}: (((read(__array, ${ffi}, __base + ${offset}) as number) >>> ${shift}) & ${bitMask(slot.bitWidth)})`,
132
+ );
133
+ }
134
+ return `{ ${entries.join(", ")} }`;
135
+ };
136
+
137
+ type ElementWriteOptions = {
138
+ readonly fields: readonly GirField[];
139
+ readonly baseOffset: number;
140
+ readonly valuePath: string;
141
+ readonly out: string[];
142
+ };
143
+
144
+ const appendElementWriteStatements = (context: ModuleContext, options: ElementWriteOptions): void => {
145
+ const { fields, baseOffset, valuePath, out } = options;
146
+ const { slots } = computeBoxedFieldSlots(context, fields);
147
+ for (const { field, slot } of slots) {
148
+ if (field.private || field.type === undefined || field.callback !== undefined) continue;
149
+ const valueExpr = `${valuePath}.${toIdentifier(toCamelCase(field.name))}`;
150
+ const offset = baseOffset + slot.byteOffset;
151
+ const nested = resolveInlineStructFields(context, field.type);
152
+ if (nested !== undefined) {
153
+ appendElementWriteStatements(context, { fields: nested, baseOffset: offset, valuePath: valueExpr, out });
154
+ continue;
155
+ }
156
+ if (!isAccessorEligibleType(field.type)) continue;
157
+ const ffi = renderFfiType(context, field.type, "none");
158
+ if (slot.bitWidth === undefined) {
159
+ out.push(`write(__array, ${ffi}, __base + ${offset}, ${valueExpr});`);
160
+ continue;
161
+ }
162
+ const mask = bitMask(slot.bitWidth);
163
+ const shift = slot.bitOffset ?? 0;
164
+ out.push(
165
+ `write(__array, ${ffi}, __base + ${offset}, ((read(__array, ${ffi}, __base + ${offset}) & ~(${mask} << ${shift})) | ((Number(${valueExpr}) & ${mask}) << ${shift})) >>> 0);`,
166
+ );
167
+ }
168
+ };
169
+
170
+ type StructArrayTarget = {
171
+ readonly field: GirField;
172
+ readonly jsName: string;
173
+ readonly slot: FieldSlot;
174
+ readonly siblingFields: readonly GirField[];
175
+ };
176
+
177
+ type StructArrayAccessorOptions = {
178
+ readonly context: ModuleContext;
179
+ readonly jsName: string;
180
+ readonly tsType: string;
181
+ readonly bufferType: string;
182
+ readonly offset: number;
183
+ readonly lengthExpr: string;
184
+ readonly elementSize: number;
185
+ readonly elementFields: readonly GirField[];
186
+ };
187
+
188
+ const structArrayGetterBlock = (options: StructArrayAccessorOptions): string => {
189
+ const { context, jsName, tsType, bufferType, offset, lengthExpr, elementSize, elementFields } = options;
190
+ const element = renderElementReadObject(context, elementFields, 0);
191
+ const loop = [`const __base = __index * ${elementSize};`, `__result.push(${element});`].join("\n");
192
+ const body = [
193
+ `const __array = read(getHandle(this), ${bufferType}, ${offset});`,
194
+ `const __result: ${tsType} = [];`,
195
+ `for (let __index = 0; __index < ${lengthExpr}; __index++) {`,
196
+ indent(loop, 1),
197
+ "}",
198
+ "return __result;",
199
+ ].join("\n");
200
+ return `get ${jsName}(): ${tsType} {\n${indent(body, 1)}\n}`;
201
+ };
202
+
203
+ const structArraySetterBlock = (options: StructArrayAccessorOptions): string => {
204
+ const { context, jsName, tsType, bufferType, offset, elementSize, elementFields } = options;
205
+ const writes: string[] = [];
206
+ appendElementWriteStatements(context, {
207
+ fields: elementFields,
208
+ baseOffset: 0,
209
+ valuePath: "__element",
210
+ out: writes,
211
+ });
212
+ const loop = [`const __element = __value[__index];`, `const __base = __index * ${elementSize};`, ...writes].join(
213
+ "\n",
214
+ );
215
+ const body = [
216
+ `const __array = read(getHandle(this), ${bufferType}, ${offset});`,
217
+ `for (let __index = 0; __index < __value.length; __index++) {`,
218
+ indent(loop, 1),
219
+ "}",
220
+ `write(getHandle(this), ${bufferType}, ${offset}, __array);`,
221
+ ].join("\n");
222
+ return `set ${jsName}(__value: ${tsType}) {\n${indent(body, 1)}\n}`;
223
+ };
224
+
225
+ const renderStructArrayAccessor = (context: ModuleContext, target: StructArrayTarget): string | undefined => {
226
+ const { field, jsName, slot, siblingFields } = target;
227
+ const arrayRef = field.type;
228
+ if (arrayRef === undefined || arrayRef.kind !== "array" || slot.bitWidth !== undefined) return undefined;
229
+ const elementFields = resolveInlineStructFields(context, arrayRef.element);
230
+ if (elementFields === undefined) return undefined;
231
+ const lengthExpr = arrayLengthExpression(arrayRef, siblingFields);
232
+ if (lengthExpr === undefined) return undefined;
233
+ const elementSize = computeBoxedFieldSlots(context, elementFields).size;
234
+ if (elementSize === 0) return undefined;
235
+
236
+ context.addNativeImport("read");
237
+ context.addRuntimeImport("getHandle");
238
+ context.addRuntimeImport("t");
239
+ const options: StructArrayAccessorOptions = {
240
+ context,
241
+ jsName,
242
+ tsType: renderTsType(context, arrayRef, false),
243
+ bufferType: `t.struct("borrowed", ${lengthExpr} * ${elementSize})`,
244
+ offset: slot.byteOffset,
245
+ lengthExpr,
246
+ elementSize,
247
+ elementFields,
248
+ };
249
+ const blocks: string[] = [];
250
+ if (field.readable) blocks.push(structArrayGetterBlock(options));
251
+ if (field.writable) {
252
+ context.addNativeImport("write");
253
+ blocks.push(structArraySetterBlock(options));
254
+ }
255
+ return blocks.length === 0 ? undefined : blocks.join("\n\n");
256
+ };
257
+
258
+ type AccessorOptions = {
259
+ readonly context: ModuleContext;
260
+ readonly jsName: string;
261
+ readonly tsType: string;
262
+ readonly ffiType: string;
263
+ readonly slot: FieldSlot;
264
+ readonly fieldType: GirTypeRef;
265
+ };
266
+
267
+ const getterBlock = (options: AccessorOptions): string => {
268
+ const { context, jsName, tsType, ffiType, slot, fieldType } = options;
269
+ context.addNativeImport("read");
270
+ context.addRuntimeImport("getHandle");
271
+ if (slot.bitWidth === undefined) {
272
+ const valueExpression = `read(getHandle(this), ${ffiType}, ${slot.byteOffset})`;
273
+ const wrapped = wrapReturnValue(context, {
274
+ ref: fieldType,
275
+ transfer: "none",
276
+ nullable: false,
277
+ valueExpression,
278
+ });
279
+ const body = `return ${wrapped};`;
280
+ return `get ${jsName}(): ${tsType} {\n${indent(body, 1)}\n}`;
281
+ }
282
+ const mask = bitMask(slot.bitWidth);
283
+ const shift = slot.bitOffset ?? 0;
284
+ const body = `const __unit = read(getHandle(this), ${ffiType}, ${slot.byteOffset}) as number;\nreturn (((__unit >>> ${shift}) & ${mask}) >>> 0) as ${tsType};`;
285
+ return `get ${jsName}(): ${tsType} {\n${indent(body, 1)}\n}`;
286
+ };
287
+
288
+ const setterBlock = (options: AccessorOptions): string => {
289
+ const { context, jsName, tsType, ffiType, slot } = options;
290
+ context.addNativeImport("write");
291
+ context.addRuntimeImport("getHandle");
292
+ if (slot.bitWidth === undefined) {
293
+ const body = `write(getHandle(this), ${ffiType}, ${slot.byteOffset}, value);`;
294
+ return `set ${jsName}(value: ${tsType}) {\n${indent(body, 1)}\n}`;
295
+ }
296
+ context.addNativeImport("read");
297
+ const mask = bitMask(slot.bitWidth);
298
+ const shift = slot.bitOffset ?? 0;
299
+ const body = `const __unit = read(getHandle(this), ${ffiType}, ${slot.byteOffset}) as number;\nconst __next = ((__unit & ~(${mask} << ${shift})) | ((Number(value) & ${mask}) << ${shift})) >>> 0;\nwrite(getHandle(this), ${ffiType}, ${slot.byteOffset}, __next);`;
300
+ return `set ${jsName}(value: ${tsType}) {\n${indent(body, 1)}\n}`;
301
+ };
302
+
303
+ const bitMask = (width: number): number => {
304
+ if (width >= 32) return 0xffffffff;
305
+ return (1 << width) - 1;
306
+ };
@@ -0,0 +1,166 @@
1
+ import type { ModuleContext } from "../dsl/context.js";
2
+ import type { GirField } from "../gir/field.js";
3
+ import { primitiveCategory } from "../gir/primitives.js";
4
+ import type { ResolvedNamed } from "../gir/repository.js";
5
+ import {
6
+ computeFieldSlots,
7
+ type FieldLayout,
8
+ type FieldLayoutInput,
9
+ type FieldSlot,
10
+ layoutOfPrimitive,
11
+ } from "../gir/size.js";
12
+ import type { GirTypeRef, NamedTypeRef } from "../gir/type-ref.js";
13
+
14
+ const POINTER_LAYOUT: FieldLayout = { size: 8, align: 8 };
15
+
16
+ /**
17
+ * Computed slot for a single boxed field, ready for accessor emission.
18
+ */
19
+ export type BoxedFieldSlot = {
20
+ readonly field: GirField;
21
+ readonly slot: FieldSlot;
22
+ };
23
+
24
+ /**
25
+ * Computes per-field storage slots for a boxed record.
26
+ *
27
+ * Walks every field (private fields included so their storage occupies
28
+ * space) and resolves each to a primitive-size {@link FieldLayoutInput}.
29
+ * Private fields are returned with their slots so callers can filter on
30
+ * `field.private` while still seeing the layout-correct offsets of public
31
+ * fields after them.
32
+ *
33
+ * @param context - The module context
34
+ * @param fields - The boxed's field list in declaration order
35
+ */
36
+ export const computeBoxedFieldSlots = (
37
+ context: ModuleContext,
38
+ fields: readonly GirField[],
39
+ isUnion = false,
40
+ ): { readonly slots: readonly BoxedFieldSlot[]; readonly size: number } => {
41
+ const inputs: FieldLayoutInput[] = [];
42
+ for (const field of fields) {
43
+ inputs.push(fieldLayoutInput(context, field, new Set()));
44
+ }
45
+ const result = computeFieldSlots(inputs, isUnion);
46
+ const slots = result.slots.map((slot, index) => {
47
+ const field = fields[index];
48
+ if (field === undefined) {
49
+ throw new Error("computeBoxedFieldSlots: parallel arrays diverged");
50
+ }
51
+ return { field, slot };
52
+ });
53
+ return { slots, size: result.size };
54
+ };
55
+
56
+ const fieldLayoutInput = (context: ModuleContext, field: GirField, visited: ReadonlySet<string>): FieldLayoutInput => {
57
+ if (field.callback !== undefined) {
58
+ return { layout: POINTER_LAYOUT, bits: undefined };
59
+ }
60
+ if (field.type === undefined) {
61
+ return { layout: POINTER_LAYOUT, bits: undefined };
62
+ }
63
+ return { layout: layoutOfType(context, field.type, visited), bits: field.bits };
64
+ };
65
+
66
+ const layoutOfType = (context: ModuleContext, ref: GirTypeRef, visited: ReadonlySet<string>): FieldLayout => {
67
+ switch (ref.kind) {
68
+ case "primitive":
69
+ return layoutOfPrimitive(ref.category);
70
+ case "named":
71
+ return layoutOfNamed(context, ref, visited);
72
+ case "array":
73
+ return arrayLayout(context, ref, visited);
74
+ case "list":
75
+ case "hashtable":
76
+ case "callback":
77
+ case "varargs":
78
+ return POINTER_LAYOUT;
79
+ }
80
+ };
81
+
82
+ const arrayLayout = (
83
+ context: ModuleContext,
84
+ ref: Extract<GirTypeRef, { kind: "array" }>,
85
+ visited: ReadonlySet<string>,
86
+ ): FieldLayout => {
87
+ if (ref.fixedSize === undefined) return POINTER_LAYOUT;
88
+ const elementLayout = layoutOfType(context, ref.element, visited);
89
+ return { size: elementLayout.size * ref.fixedSize, align: elementLayout.align };
90
+ };
91
+
92
+ const layoutOfNamed = (context: ModuleContext, ref: NamedTypeRef, visited: ReadonlySet<string>): FieldLayout => {
93
+ if (ref.cType?.endsWith("*") === true) return POINTER_LAYOUT;
94
+ const namespaceName = ref.namespaceName ?? context.namespace.name;
95
+ const resolved = context.repository.resolveNamed(namespaceName, ref.typeName);
96
+ if (resolved === undefined) return POINTER_LAYOUT;
97
+ return layoutOfResolved(context, resolved, visited);
98
+ };
99
+
100
+ const layoutOfResolved = (
101
+ context: ModuleContext,
102
+ resolved: ResolvedNamed,
103
+ visited: ReadonlySet<string>,
104
+ ): FieldLayout => {
105
+ switch (resolved.kind) {
106
+ case "class":
107
+ case "interface":
108
+ return POINTER_LAYOUT;
109
+ case "boxed":
110
+ return layoutOfBoxedRecord(context, resolved, visited);
111
+ case "enum":
112
+ return layoutOfPrimitive("int32");
113
+ case "callback":
114
+ return POINTER_LAYOUT;
115
+ case "alias":
116
+ return resolveAliasLayout(context, resolved, visited);
117
+ }
118
+ };
119
+
120
+ const layoutOfBoxedRecord = (
121
+ context: ModuleContext,
122
+ resolved: Extract<ResolvedNamed, { kind: "boxed" }>,
123
+ visited: ReadonlySet<string>,
124
+ ): FieldLayout => {
125
+ if (resolved.value.cType?.endsWith("*") === true) return POINTER_LAYOUT;
126
+ const key = `${resolved.namespace.name}.${resolved.value.name}`;
127
+ if (visited.has(key)) return POINTER_LAYOUT;
128
+ const cached = recordLayoutCache.get(key);
129
+ if (cached !== undefined) return cached;
130
+ const nextVisited = new Set(visited);
131
+ nextVisited.add(key);
132
+ const inputs: FieldLayoutInput[] = [];
133
+ for (const field of resolved.value.fields) {
134
+ if (field.callback !== undefined) {
135
+ inputs.push({ layout: POINTER_LAYOUT, bits: undefined });
136
+ continue;
137
+ }
138
+ if (field.type === undefined) {
139
+ inputs.push({ layout: POINTER_LAYOUT, bits: undefined });
140
+ continue;
141
+ }
142
+ inputs.push({ layout: layoutOfType(context, field.type, nextVisited), bits: field.bits });
143
+ }
144
+ if (inputs.length === 0) return POINTER_LAYOUT;
145
+ const { size } = computeFieldSlots(inputs, resolved.value.isUnion);
146
+ const align = Math.max(1, ...inputs.map((input) => input.layout.align));
147
+ const layout: FieldLayout = { size, align };
148
+ recordLayoutCache.set(key, layout);
149
+ return layout;
150
+ };
151
+
152
+ const recordLayoutCache = new Map<string, FieldLayout>();
153
+
154
+ const resolveAliasLayout = (
155
+ context: ModuleContext,
156
+ resolved: Extract<ResolvedNamed, { kind: "alias" }>,
157
+ visited: ReadonlySet<string>,
158
+ ): FieldLayout => {
159
+ const ref = resolved.targetRef;
160
+ if (ref === undefined) return POINTER_LAYOUT;
161
+ if (ref.kind === "named") {
162
+ const primitive = primitiveCategory(ref.typeName);
163
+ if (primitive !== undefined) return layoutOfPrimitive(primitive);
164
+ }
165
+ return layoutOfType(context, ref, visited);
166
+ };
@@ -0,0 +1,77 @@
1
+ import type { ModuleContext } from "../dsl/context.js";
2
+ import { indent } from "../dsl/emit.js";
3
+ import type { GirBoxed } from "../gir/boxed.js";
4
+ import { renderBoxedConstructor, renderBoxedConstructorPropsInterface } from "./boxed-constructor.js";
5
+ import { renderBoxedFieldAccessor } from "./boxed-field-accessor.js";
6
+ import { computeBoxedFieldSlots } from "./boxed-layout.js";
7
+ import { type Callables, dedupeCallables, emitBindings, renderPlainTypeMembers } from "./callables.js";
8
+ import { isClassStructRecord } from "./class-struct-record.js";
9
+ import { renderGetTypeReference } from "./gtype-binding.js";
10
+ import { appendNativeClassRegistration } from "./registration.js";
11
+
12
+ /**
13
+ * Emits a class declaration plus optional boxed registration for a
14
+ * `<record>`, `<union>`, or `<glib:boxed>`.
15
+ *
16
+ * Walks constructors, static functions, and methods just like
17
+ * {@link emitClass}, attaching them to the boxed class. Field accessors
18
+ * and construction metadata are emitted by their dedicated writers.
19
+ *
20
+ * Vtable records (`glib:is-gtype-struct-for`) are skipped here — they are
21
+ * consumed by the owning class's vtable registration. Class- and
22
+ * interface-struct records (`GTypeClass`, `GEnumClass`, …) are likewise skipped
23
+ * as they are runtime vtables rather than marshallable values.
24
+ *
25
+ * @param context - The module context
26
+ * @param boxed - The boxed to emit
27
+ */
28
+ export const emitBoxed = (context: ModuleContext, boxed: GirBoxed): void => {
29
+ if (!boxed.introspectable) return;
30
+ if (boxed.kind === "vtable") return;
31
+ if (boxed.name.length === 0) return;
32
+ if (isClassStructRecord(context.namespace.name, boxed)) return;
33
+ const className = boxed.name;
34
+ const callables: Callables = {
35
+ constructors: dedupeCallables(boxed.constructors),
36
+ functions: dedupeCallables(boxed.functions),
37
+ methods: dedupeCallables(boxed.methods),
38
+ };
39
+ emitBindings(context, callables);
40
+
41
+ const members = renderBoxedMembers(context, boxed, className, callables);
42
+ const body = members.map((member) => indent(member, 1)).join("\n\n");
43
+ context.module.appendDeclaration(`export class ${className} {\n${body}\n}`);
44
+ context.module.appendDeclaration(renderBoxedConstructorPropsInterface(context, boxed, className));
45
+
46
+ const getTypeRef =
47
+ boxed.glibGetType === undefined
48
+ ? undefined
49
+ : renderGetTypeReference(context, boxed.glibGetType, boxed.glibTypeName);
50
+ appendNativeClassRegistration(context, {
51
+ className,
52
+ role: "boxed",
53
+ getTypeRef,
54
+ });
55
+ };
56
+
57
+ const renderBoxedMembers = (
58
+ context: ModuleContext,
59
+ boxed: GirBoxed,
60
+ className: string,
61
+ callables: Callables,
62
+ ): readonly string[] => {
63
+ const { members, claimedNames } = renderPlainTypeMembers({
64
+ context,
65
+ className,
66
+ callables,
67
+ hasGType: boxed.glibGetType !== undefined,
68
+ wrap: "boxed",
69
+ });
70
+ members.unshift(renderBoxedConstructor(context, boxed, className));
71
+ const { slots } = computeBoxedFieldSlots(context, boxed.fields, boxed.isUnion);
72
+ for (const slot of slots) {
73
+ const block = renderBoxedFieldAccessor(context, slot, claimedNames, boxed.fields);
74
+ if (block !== undefined) members.push(block);
75
+ }
76
+ return members;
77
+ };