sci2-ts 0.0.0-stage → 0.2.2

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 (357) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/LICENSE +21 -0
  3. package/README.md +113 -2
  4. package/apps/viewer/editor.html +43 -0
  5. package/apps/viewer/explore.html +27 -0
  6. package/apps/viewer/index.html +26 -0
  7. package/apps/viewer/package.json +27 -0
  8. package/apps/viewer/play.html +26 -0
  9. package/apps/viewer/plugin.d.ts +18 -0
  10. package/apps/viewer/plugin.js +75 -0
  11. package/apps/viewer/plugin.ts +84 -0
  12. package/apps/viewer/src/adlib.d.ts +20 -0
  13. package/apps/viewer/src/adlib.js +55 -0
  14. package/apps/viewer/src/adlib.ts +62 -0
  15. package/apps/viewer/src/app.d.ts +5 -0
  16. package/apps/viewer/src/app.js +5 -0
  17. package/apps/viewer/src/app.ts +5 -0
  18. package/apps/viewer/src/asset-index.d.ts +22 -0
  19. package/apps/viewer/src/asset-index.js +74 -0
  20. package/apps/viewer/src/asset-index.ts +90 -0
  21. package/apps/viewer/src/audio.d.ts +28 -0
  22. package/apps/viewer/src/audio.js +102 -0
  23. package/apps/viewer/src/audio.ts +110 -0
  24. package/apps/viewer/src/editor-handles.d.ts +63 -0
  25. package/apps/viewer/src/editor-handles.js +332 -0
  26. package/apps/viewer/src/editor-handles.ts +368 -0
  27. package/apps/viewer/src/editor-languages.d.ts +23 -0
  28. package/apps/viewer/src/editor-languages.js +157 -0
  29. package/apps/viewer/src/editor-languages.ts +135 -0
  30. package/apps/viewer/src/editor.css +213 -0
  31. package/apps/viewer/src/editor.d.ts +1 -0
  32. package/apps/viewer/src/editor.js +393 -0
  33. package/apps/viewer/src/editor.ts +412 -0
  34. package/apps/viewer/src/explore/classes.d.ts +7 -0
  35. package/apps/viewer/src/explore/classes.js +63 -0
  36. package/apps/viewer/src/explore/classes.ts +61 -0
  37. package/apps/viewer/src/explore/context.d.ts +33 -0
  38. package/apps/viewer/src/explore/context.js +96 -0
  39. package/apps/viewer/src/explore/context.ts +124 -0
  40. package/apps/viewer/src/explore/explore.css +93 -0
  41. package/apps/viewer/src/explore/explore.d.ts +1 -0
  42. package/apps/viewer/src/explore/explore.js +102 -0
  43. package/apps/viewer/src/explore/explore.ts +104 -0
  44. package/apps/viewer/src/explore/inspect.d.ts +5 -0
  45. package/apps/viewer/src/explore/inspect.js +208 -0
  46. package/apps/viewer/src/explore/inspect.ts +279 -0
  47. package/apps/viewer/src/explore/live.d.ts +22 -0
  48. package/apps/viewer/src/explore/live.js +318 -0
  49. package/apps/viewer/src/explore/live.ts +365 -0
  50. package/apps/viewer/src/explore/map.d.ts +11 -0
  51. package/apps/viewer/src/explore/map.js +182 -0
  52. package/apps/viewer/src/explore/map.ts +193 -0
  53. package/apps/viewer/src/files.d.ts +21 -0
  54. package/apps/viewer/src/files.js +69 -0
  55. package/apps/viewer/src/files.ts +72 -0
  56. package/apps/viewer/src/game-profile.d.ts +19 -0
  57. package/apps/viewer/src/game-profile.js +4 -0
  58. package/apps/viewer/src/game-profile.ts +26 -0
  59. package/apps/viewer/src/main.d.ts +1 -0
  60. package/apps/viewer/src/main.js +473 -0
  61. package/apps/viewer/src/main.ts +527 -0
  62. package/apps/viewer/src/music.d.ts +14 -0
  63. package/apps/viewer/src/music.js +32 -0
  64. package/apps/viewer/src/music.ts +34 -0
  65. package/apps/viewer/src/opl-worklet.d.ts +1 -0
  66. package/apps/viewer/src/opl-worklet.js +47 -0
  67. package/apps/viewer/src/opl-worklet.ts +55 -0
  68. package/apps/viewer/src/overlay.d.ts +31 -0
  69. package/apps/viewer/src/overlay.js +103 -0
  70. package/apps/viewer/src/overlay.ts +123 -0
  71. package/apps/viewer/src/play.css +14 -0
  72. package/apps/viewer/src/play.d.ts +1 -0
  73. package/apps/viewer/src/play.js +179 -0
  74. package/apps/viewer/src/play.ts +177 -0
  75. package/apps/viewer/src/saves.d.ts +10 -0
  76. package/apps/viewer/src/saves.js +50 -0
  77. package/apps/viewer/src/saves.ts +51 -0
  78. package/apps/viewer/src/session.d.ts +91 -0
  79. package/apps/viewer/src/session.js +301 -0
  80. package/apps/viewer/src/session.ts +321 -0
  81. package/apps/viewer/src/sound-preview.d.ts +7 -0
  82. package/apps/viewer/src/sound-preview.js +214 -0
  83. package/apps/viewer/src/sound-preview.ts +228 -0
  84. package/apps/viewer/src/style.css +64 -0
  85. package/apps/viewer/vite.config.ts +25 -0
  86. package/bin/sci-ts.mjs +9 -0
  87. package/lib/994.sc +49 -0
  88. package/lib/995.sc +53 -0
  89. package/lib/996.sc +299 -0
  90. package/lib/997.sc +198 -0
  91. package/lib/998.sc +338 -0
  92. package/lib/999.sc +189 -0
  93. package/lib/resources.d.ts +2 -0
  94. package/lib/resources.js +84 -0
  95. package/lib/resources.ts +88 -0
  96. package/lib/system.sh +62 -0
  97. package/package.json +75 -4
  98. package/packages/content/room.schema.json +267 -0
  99. package/packages/content/src/index.d.ts +3 -0
  100. package/packages/content/src/index.js +3 -0
  101. package/packages/content/src/index.ts +3 -0
  102. package/packages/content/src/room.d.ts +111 -0
  103. package/packages/content/src/room.js +886 -0
  104. package/packages/content/src/room.ts +1011 -0
  105. package/packages/content/src/target.d.ts +80 -0
  106. package/packages/content/src/target.js +1 -0
  107. package/packages/content/src/target.ts +60 -0
  108. package/packages/content/src/yarn.d.ts +90 -0
  109. package/packages/content/src/yarn.js +214 -0
  110. package/packages/content/src/yarn.ts +261 -0
  111. package/packages/sci/src/analysis/game.d.ts +90 -0
  112. package/packages/sci/src/analysis/game.js +221 -0
  113. package/packages/sci/src/analysis/game.ts +305 -0
  114. package/packages/sci/src/analysis/runtime.d.ts +58 -0
  115. package/packages/sci/src/analysis/runtime.js +133 -0
  116. package/packages/sci/src/analysis/runtime.ts +163 -0
  117. package/packages/sci/src/audio/adlib.d.ts +73 -0
  118. package/packages/sci/src/audio/adlib.js +280 -0
  119. package/packages/sci/src/audio/adlib.ts +329 -0
  120. package/packages/sci/src/audio/index.d.ts +43 -0
  121. package/packages/sci/src/audio/index.js +99 -0
  122. package/packages/sci/src/audio/index.ts +122 -0
  123. package/packages/sci/src/audio/midi.d.ts +92 -0
  124. package/packages/sci/src/audio/midi.js +242 -0
  125. package/packages/sci/src/audio/midi.ts +262 -0
  126. package/packages/sci/src/audio/opl2.d.ts +74 -0
  127. package/packages/sci/src/audio/opl2.js +283 -0
  128. package/packages/sci/src/audio/opl2.ts +283 -0
  129. package/packages/sci/src/audio/sol.d.ts +32 -0
  130. package/packages/sci/src/audio/sol.js +81 -0
  131. package/packages/sci/src/audio/sol.ts +100 -0
  132. package/packages/sci/src/audio/write.d.ts +35 -0
  133. package/packages/sci/src/audio/write.js +91 -0
  134. package/packages/sci/src/audio/write.ts +108 -0
  135. package/packages/sci/src/gfx/cel.d.ts +36 -0
  136. package/packages/sci/src/gfx/cel.js +82 -0
  137. package/packages/sci/src/gfx/cel.ts +110 -0
  138. package/packages/sci/src/gfx/palette.d.ts +24 -0
  139. package/packages/sci/src/gfx/palette.js +49 -0
  140. package/packages/sci/src/gfx/palette.ts +56 -0
  141. package/packages/sci/src/gfx/pic-file.d.ts +46 -0
  142. package/packages/sci/src/gfx/pic-file.js +114 -0
  143. package/packages/sci/src/gfx/pic-file.ts +155 -0
  144. package/packages/sci/src/gfx/pic.d.ts +27 -0
  145. package/packages/sci/src/gfx/pic.js +34 -0
  146. package/packages/sci/src/gfx/pic.ts +53 -0
  147. package/packages/sci/src/gfx/quantize.d.ts +42 -0
  148. package/packages/sci/src/gfx/quantize.js +199 -0
  149. package/packages/sci/src/gfx/quantize.ts +218 -0
  150. package/packages/sci/src/gfx/view-file.d.ts +62 -0
  151. package/packages/sci/src/gfx/view-file.js +182 -0
  152. package/packages/sci/src/gfx/view-file.ts +233 -0
  153. package/packages/sci/src/gfx/view.d.ts +28 -0
  154. package/packages/sci/src/gfx/view.js +45 -0
  155. package/packages/sci/src/gfx/view.ts +62 -0
  156. package/packages/sci/src/index.d.ts +38 -0
  157. package/packages/sci/src/index.js +38 -0
  158. package/packages/sci/src/index.ts +38 -0
  159. package/packages/sci/src/motion/pathfinding.d.ts +40 -0
  160. package/packages/sci/src/motion/pathfinding.js +140 -0
  161. package/packages/sci/src/motion/pathfinding.ts +147 -0
  162. package/packages/sci/src/resource/archive.d.ts +16 -0
  163. package/packages/sci/src/resource/archive.js +51 -0
  164. package/packages/sci/src/resource/archive.ts +56 -0
  165. package/packages/sci/src/resource/lzs.d.ts +12 -0
  166. package/packages/sci/src/resource/lzs.js +64 -0
  167. package/packages/sci/src/resource/lzs.ts +66 -0
  168. package/packages/sci/src/resource/manager.d.ts +45 -0
  169. package/packages/sci/src/resource/manager.js +158 -0
  170. package/packages/sci/src/resource/manager.ts +184 -0
  171. package/packages/sci/src/resource/map.d.ts +18 -0
  172. package/packages/sci/src/resource/map.js +34 -0
  173. package/packages/sci/src/resource/map.ts +44 -0
  174. package/packages/sci/src/resource/types.d.ts +37 -0
  175. package/packages/sci/src/resource/types.js +45 -0
  176. package/packages/sci/src/resource/types.ts +57 -0
  177. package/packages/sci/src/script/assembly.d.ts +67 -0
  178. package/packages/sci/src/script/assembly.js +748 -0
  179. package/packages/sci/src/script/assembly.ts +714 -0
  180. package/packages/sci/src/script/compiler.d.ts +75 -0
  181. package/packages/sci/src/script/compiler.js +993 -0
  182. package/packages/sci/src/script/compiler.ts +1040 -0
  183. package/packages/sci/src/script/disasm.d.ts +81 -0
  184. package/packages/sci/src/script/disasm.js +318 -0
  185. package/packages/sci/src/script/disasm.ts +323 -0
  186. package/packages/sci/src/script/kernel-names.d.ts +6 -0
  187. package/packages/sci/src/script/kernel-names.js +165 -0
  188. package/packages/sci/src/script/kernel-names.ts +165 -0
  189. package/packages/sci/src/script/opcodes.d.ts +22 -0
  190. package/packages/sci/src/script/opcodes.js +45 -0
  191. package/packages/sci/src/script/opcodes.ts +72 -0
  192. package/packages/sci/src/script/script.d.ts +56 -0
  193. package/packages/sci/src/script/script.js +63 -0
  194. package/packages/sci/src/script/script.ts +111 -0
  195. package/packages/sci/src/script/sexpr.d.ts +45 -0
  196. package/packages/sci/src/script/sexpr.js +151 -0
  197. package/packages/sci/src/script/sexpr.ts +139 -0
  198. package/packages/sci/src/script/vocab.d.ts +15 -0
  199. package/packages/sci/src/script/vocab.js +44 -0
  200. package/packages/sci/src/script/vocab.ts +48 -0
  201. package/packages/sci/src/script/world.d.ts +25 -0
  202. package/packages/sci/src/script/world.js +65 -0
  203. package/packages/sci/src/script/world.ts +70 -0
  204. package/packages/sci/src/text/font.d.ts +37 -0
  205. package/packages/sci/src/text/font.js +103 -0
  206. package/packages/sci/src/text/font.ts +125 -0
  207. package/packages/sci/src/text/message.d.ts +69 -0
  208. package/packages/sci/src/text/message.js +171 -0
  209. package/packages/sci/src/text/message.ts +205 -0
  210. package/packages/sci/src/vm/kernels/arrays.d.ts +19 -0
  211. package/packages/sci/src/vm/kernels/arrays.js +182 -0
  212. package/packages/sci/src/vm/kernels/arrays.ts +164 -0
  213. package/packages/sci/src/vm/kernels/audio.d.ts +70 -0
  214. package/packages/sci/src/vm/kernels/audio.js +347 -0
  215. package/packages/sci/src/vm/kernels/audio.ts +349 -0
  216. package/packages/sci/src/vm/kernels/core.d.ts +5 -0
  217. package/packages/sci/src/vm/kernels/core.js +87 -0
  218. package/packages/sci/src/vm/kernels/core.ts +91 -0
  219. package/packages/sci/src/vm/kernels/graphics.d.ts +141 -0
  220. package/packages/sci/src/vm/kernels/graphics.js +467 -0
  221. package/packages/sci/src/vm/kernels/graphics.ts +502 -0
  222. package/packages/sci/src/vm/kernels/index.d.ts +8 -0
  223. package/packages/sci/src/vm/kernels/index.js +29 -0
  224. package/packages/sci/src/vm/kernels/index.ts +32 -0
  225. package/packages/sci/src/vm/kernels/lists.d.ts +6 -0
  226. package/packages/sci/src/vm/kernels/lists.js +127 -0
  227. package/packages/sci/src/vm/kernels/lists.ts +111 -0
  228. package/packages/sci/src/vm/kernels/motion.d.ts +7 -0
  229. package/packages/sci/src/vm/kernels/motion.js +204 -0
  230. package/packages/sci/src/vm/kernels/motion.ts +194 -0
  231. package/packages/sci/src/vm/kernels/palette.d.ts +82 -0
  232. package/packages/sci/src/vm/kernels/palette.js +292 -0
  233. package/packages/sci/src/vm/kernels/palette.ts +303 -0
  234. package/packages/sci/src/vm/kernels/saves.d.ts +45 -0
  235. package/packages/sci/src/vm/kernels/saves.js +123 -0
  236. package/packages/sci/src/vm/kernels/saves.ts +149 -0
  237. package/packages/sci/src/vm/kernels/system.d.ts +62 -0
  238. package/packages/sci/src/vm/kernels/system.js +110 -0
  239. package/packages/sci/src/vm/kernels/system.ts +128 -0
  240. package/packages/sci/src/vm/kernels/text.d.ts +13 -0
  241. package/packages/sci/src/vm/kernels/text.js +338 -0
  242. package/packages/sci/src/vm/kernels/text.ts +328 -0
  243. package/packages/sci/src/vm/memory.d.ts +138 -0
  244. package/packages/sci/src/vm/memory.js +168 -0
  245. package/packages/sci/src/vm/memory.ts +266 -0
  246. package/packages/sci/src/vm/replace.d.ts +11 -0
  247. package/packages/sci/src/vm/replace.js +14 -0
  248. package/packages/sci/src/vm/replace.ts +16 -0
  249. package/packages/sci/src/vm/savegame.d.ts +89 -0
  250. package/packages/sci/src/vm/savegame.js +161 -0
  251. package/packages/sci/src/vm/savegame.ts +188 -0
  252. package/packages/sci/src/vm/value.d.ts +17 -0
  253. package/packages/sci/src/vm/value.js +12 -0
  254. package/packages/sci/src/vm/value.ts +25 -0
  255. package/packages/sci/src/vm/vm.d.ts +155 -0
  256. package/packages/sci/src/vm/vm.js +1053 -0
  257. package/packages/sci/src/vm/vm.ts +914 -0
  258. package/tools/adlib-render.d.ts +1 -0
  259. package/tools/adlib-render.js +49 -0
  260. package/tools/adlib-render.ts +42 -0
  261. package/tools/analyze.d.ts +1 -0
  262. package/tools/analyze.js +25 -0
  263. package/tools/analyze.ts +25 -0
  264. package/tools/art/build.d.ts +34 -0
  265. package/tools/art/build.js +175 -0
  266. package/tools/art/build.ts +153 -0
  267. package/tools/art.d.ts +1 -0
  268. package/tools/art.js +31 -0
  269. package/tools/art.ts +29 -0
  270. package/tools/asm.d.ts +1 -0
  271. package/tools/asm.js +61 -0
  272. package/tools/asm.ts +55 -0
  273. package/tools/cli.d.ts +1 -0
  274. package/tools/cli.js +99 -0
  275. package/tools/cli.ts +100 -0
  276. package/tools/editor-server.d.ts +29 -0
  277. package/tools/editor-server.js +162 -0
  278. package/tools/editor-server.ts +176 -0
  279. package/tools/explore.d.ts +1 -0
  280. package/tools/explore.js +305 -0
  281. package/tools/explore.ts +250 -0
  282. package/tools/game/audio.d.ts +19 -0
  283. package/tools/game/audio.js +143 -0
  284. package/tools/game/audio.ts +130 -0
  285. package/tools/game/build.d.ts +46 -0
  286. package/tools/game/build.js +259 -0
  287. package/tools/game/build.ts +283 -0
  288. package/tools/game/defaults.d.ts +26 -0
  289. package/tools/game/defaults.js +82 -0
  290. package/tools/game/defaults.ts +83 -0
  291. package/tools/game/font.d.ts +3 -0
  292. package/tools/game/font.js +124 -0
  293. package/tools/game/font.ts +126 -0
  294. package/tools/game/kit.d.ts +7 -0
  295. package/tools/game/kit.js +7 -0
  296. package/tools/game/kit.ts +10 -0
  297. package/tools/game/new.d.ts +8 -0
  298. package/tools/game/new.js +39 -0
  299. package/tools/game/new.ts +38 -0
  300. package/tools/game/target.d.ts +7 -0
  301. package/tools/game/target.js +41 -0
  302. package/tools/game/target.ts +42 -0
  303. package/tools/game/template/resources.ts.tmpl +43 -0
  304. package/tools/game/template/rooms/1.room.yaml +9 -0
  305. package/tools/game/template/rooms/1.yarn +17 -0
  306. package/tools/game/template/scripts/0.sc +24 -0
  307. package/tools/game.d.ts +1 -0
  308. package/tools/game.js +77 -0
  309. package/tools/game.ts +70 -0
  310. package/tools/mod-build.d.ts +28 -0
  311. package/tools/mod-build.js +173 -0
  312. package/tools/mod-build.ts +179 -0
  313. package/tools/mod-target.d.ts +16 -0
  314. package/tools/mod-target.js +26 -0
  315. package/tools/mod-target.ts +32 -0
  316. package/tools/mod.d.ts +1 -0
  317. package/tools/mod.js +47 -0
  318. package/tools/mod.ts +45 -0
  319. package/tools/msg.d.ts +1 -0
  320. package/tools/msg.js +40 -0
  321. package/tools/msg.ts +36 -0
  322. package/tools/node-files.d.ts +3 -0
  323. package/tools/node-files.js +34 -0
  324. package/tools/node-files.ts +33 -0
  325. package/tools/patches.d.ts +3 -0
  326. package/tools/patches.js +13 -0
  327. package/tools/patches.ts +13 -0
  328. package/tools/pic-build.d.ts +9 -0
  329. package/tools/pic-build.js +26 -0
  330. package/tools/pic-build.ts +27 -0
  331. package/tools/pic.d.ts +1 -0
  332. package/tools/pic.js +64 -0
  333. package/tools/pic.ts +61 -0
  334. package/tools/play.d.ts +1 -0
  335. package/tools/play.js +54 -0
  336. package/tools/play.ts +54 -0
  337. package/tools/png.d.ts +16 -0
  338. package/tools/png.js +124 -0
  339. package/tools/png.ts +123 -0
  340. package/tools/res.d.ts +1 -0
  341. package/tools/res.js +48 -0
  342. package/tools/res.ts +50 -0
  343. package/tools/scenario.d.ts +1 -0
  344. package/tools/scenario.js +74 -0
  345. package/tools/scenario.ts +80 -0
  346. package/tools/site/build.d.ts +8 -0
  347. package/tools/site/build.js +110 -0
  348. package/tools/site/build.ts +116 -0
  349. package/tools/site/markdown.d.ts +11 -0
  350. package/tools/site/markdown.js +88 -0
  351. package/tools/site/markdown.ts +84 -0
  352. package/tools/view-build.d.ts +34 -0
  353. package/tools/view-build.js +47 -0
  354. package/tools/view-build.ts +71 -0
  355. package/tools/view.d.ts +1 -0
  356. package/tools/view.js +54 -0
  357. package/tools/view.ts +52 -0
@@ -0,0 +1,748 @@
1
+ import { decodeInstruction, disassemble } from "./disasm.js";
2
+ import { opcodes } from "./opcodes.js";
3
+ import { ObjectSlot, readString } from "./script.js";
4
+ export class AsmError extends Error {
5
+ line;
6
+ constructor(message, line) {
7
+ super(line ? `line ${line}: ${message}` : message);
8
+ this.line = line;
9
+ }
10
+ }
11
+ const hex = (n, w = 4) => n.toString(16).padStart(w, "0");
12
+ const RELATIVE = (k) => k === "R";
13
+ const SIZED = (k) => k === "W" || k === "S" || k === "R";
14
+ /** Opcodes whose default form is 16-bit. */
15
+ const WORD_DEFAULT = new Set(["send", "self", "lofsa", "lofss"]);
16
+ const HEAP_OPERAND = new Set(["lofsa", "lofss"]);
17
+ const HEADER_SLOTS = ObjectSlot.Name + 1;
18
+ /** Instructions whose operand is a property of self, as a byte offset. */
19
+ const PROPERTY_OPS = new Set(["pToa", "aTop", "pTos", "sTop", "ipToa", "dpToa", "ipTos", "dpTos"]);
20
+ /**
21
+ * The name for a property byte offset in a layout, if it maps back unambiguously (so the
22
+ * assembler reproduces the same offset). Names never look like numbers or labels.
23
+ */
24
+ function propertyName(layout, offset, sel) {
25
+ if (!layout || offset % 2 || offset / 2 >= layout.length)
26
+ return undefined;
27
+ const selector = layout[offset / 2];
28
+ if (layout.indexOf(selector) !== layout.lastIndexOf(selector))
29
+ return undefined;
30
+ const name = sel(selector);
31
+ return /^[A-Za-z_-][\w-]*$/.test(name) && !name.startsWith("sel_") ? name : undefined;
32
+ }
33
+ /** Would these operand values fit the byte form? */
34
+ function fitsSmall(kinds, values) {
35
+ return kinds.every((k, i) => {
36
+ const v = values[i];
37
+ if (k === "W")
38
+ return v >= 0 && v <= 0xff;
39
+ if (k === "S" || k === "R")
40
+ return v >= -128 && v <= 127;
41
+ return true;
42
+ });
43
+ }
44
+ // --- Script → text ----------------------------------------------------------------------
45
+ export function toAssembly(script, ctx) {
46
+ const { code, heap } = script;
47
+ const cw = (o) => code[o] | (code[o + 1] << 8);
48
+ const hw = (o) => heap[o] | (heap[o + 1] << 8);
49
+ const sel = (n) => ctx.selectorNames[n] ?? `sel_${n}`;
50
+ const out = [`script ${script.number}`];
51
+ // Heap labels: objects by name, strings by offset.
52
+ const heapReloc = hw(0);
53
+ const heapRelocs = new Set(Array.from({ length: hw(heapReloc) }, (_, i) => hw(heapReloc + 2 + i * 2)));
54
+ const codeReloc = cw(0);
55
+ const codeRelocs = new Set(Array.from({ length: cw(codeReloc) }, (_, i) => cw(codeReloc + 2 + i * 2)));
56
+ const objectsEnd = script.objects.length ? script.objects.at(-1).offset + script.objects.at(-1).properties.length * 2 : 4 + script.locals.length * 2;
57
+ const strings = [];
58
+ for (let p = objectsEnd + 2; p < heapReloc;) {
59
+ let e = p;
60
+ while (e < heapReloc && heap[e] !== 0)
61
+ e++;
62
+ strings.push({ offset: p, bytes: heap.subarray(p, e) });
63
+ p = e + 1;
64
+ }
65
+ const objectLabels = uniqueObjectLabels(script.objects);
66
+ const heapLabel = (offset) => {
67
+ const o = script.objects.findIndex((x) => x.offset === offset);
68
+ if (o >= 0)
69
+ return `@${objectLabels[o]}`;
70
+ const s = strings.find((x) => x.offset === offset);
71
+ if (s)
72
+ return `@s_${hex(offset)}`;
73
+ // Inside something: the nearest label before it, plus an offset.
74
+ const before = [...script.objects.map((x, i) => ({ at: x.offset, label: objectLabels[i] })), ...strings.map((x) => ({ at: x.offset, label: `s_${hex(x.offset)}` }))]
75
+ .filter((x) => x.at <= offset).sort((a, b) => b.at - a.at)[0];
76
+ if (before)
77
+ return `@${before.label}+${offset - before.at}`;
78
+ return `@${offset}`;
79
+ };
80
+ // Code labels: functions from the disassembler, jump targets from a linear sweep.
81
+ const fns = disassemble(script, ctx);
82
+ const labels = new Map();
83
+ for (const f of fns)
84
+ labels.set(f.entry, f.label);
85
+ let dictEnd = 8 + script.exports.length * 2;
86
+ for (const o of script.objects) {
87
+ if (o.isClass)
88
+ dictEnd = o.properties[ObjectSlot.PropDict] + o.properties.length * 2;
89
+ const md = o.properties[ObjectSlot.MethDict];
90
+ dictEnd = md + 2 + cw(md) * 4;
91
+ }
92
+ const sweep = [];
93
+ for (let pc = dictEnd; pc < codeReloc;) {
94
+ const ins = decodeInstruction(code, pc);
95
+ sweep.push(ins);
96
+ pc += ins.length;
97
+ }
98
+ for (const ins of sweep)
99
+ if (ins.target !== undefined && !labels.has(ins.target))
100
+ labels.set(ins.target, ins.name === "call" ? `proc_${hex(ins.target)}` : `.L${hex(ins.target)}`);
101
+ const codeLabel = (offset) => labels.get(offset) ?? `${offset}`;
102
+ // Locals
103
+ if (script.locals.length)
104
+ out.push(`locals ${script.locals.map((v) => (v > 9 ? `$${hex(v)}` : `${v}`)).join(" ")}`);
105
+ // Exports
106
+ out.push("exports");
107
+ for (const e of script.exports) {
108
+ if (e.kind === "empty")
109
+ out.push(" 0");
110
+ else if (e.kind === "object")
111
+ out.push(` ${heapLabel(e.offset)}`);
112
+ else
113
+ out.push(` ${codeLabel(e.offset)}`);
114
+ }
115
+ // Objects
116
+ for (const [i, o] of script.objects.entries()) {
117
+ const label = objectLabels[i];
118
+ const species = o.isClass ? o.species : o.superclass;
119
+ const superName = (n) => (n === 0xffff ? "-" : ctx.className(n) ?? `$${hex(n)}`);
120
+ const propSels = o.isClass ? o.propSelectors : ctx.classPropSelectors(o.superclass);
121
+ out.push("");
122
+ out.push(o.isClass ? `class ${label} of ${superName(o.superclass)} species ${species}` : `instance ${label} of ${superName(o.superclass)}`);
123
+ const slotValue = (slot) => {
124
+ const at = o.offset + slot * 2;
125
+ const v = o.properties[slot];
126
+ return heapRelocs.has(at) ? heapLabel(v) : v > 9 ? `$${hex(v)}` : `${v}`;
127
+ };
128
+ out.push(` classScript ${slotValue(ObjectSlot.ClassScript)}`);
129
+ out.push(` info ${slotValue(ObjectSlot.Info)}`);
130
+ out.push(` name ${slotValue(ObjectSlot.Name)}`);
131
+ if (o.isClass) {
132
+ // A class declares its own layout: each property with its selector.
133
+ const headerSels = o.propSelectors.slice(0, HEADER_SLOTS);
134
+ out.push(` header ${headerSels.map(sel).join(" ")}`);
135
+ for (let slot = HEADER_SLOTS; slot < o.properties.length; slot++)
136
+ out.push(` prop ${sel(o.propSelectors[slot])} ${slotValue(slot)}`);
137
+ }
138
+ else {
139
+ for (let slot = HEADER_SLOTS; slot < o.properties.length; slot++) {
140
+ const s = propSels?.[slot];
141
+ out.push(` ${s === undefined ? `#${slot}` : sel(s)} ${slotValue(slot)}`);
142
+ }
143
+ }
144
+ for (const m of o.methods)
145
+ out.push(` method ${sel(m.selector)} ${codeLabel(m.offset)}`);
146
+ }
147
+ // Strings
148
+ out.push("", "strings");
149
+ for (const s of strings)
150
+ out.push(` s_${hex(s.offset)} ${quote(s.bytes)}`);
151
+ // Code
152
+ out.push("", "code");
153
+ const selectorPushes = new Set();
154
+ for (const f of fns)
155
+ for (const ins of f.instructions)
156
+ if (ins.name === "pushi" && f.comments.get(ins.offset) === sel(ins.operands[0]))
157
+ selectorPushes.add(ins.offset);
158
+ // Property operands are named against the layout of the code's owner (self). The owner
159
+ // follows the rule the assembler uses: a label `Obj::method` sets it, an `owner Obj` line
160
+ // sets it (emitted for procedures, whose labels don't say), `.L` labels keep it, and any
161
+ // other label clears it.
162
+ const fnAt = new Map(fns.map((f) => [f.entry, f]));
163
+ const labelOf = new Map(script.objects.map((o, i) => [o, objectLabels[i]]));
164
+ const layoutOf = (o) => (o.isClass ? o.propSelectors : ctx.classPropSelectors(o.superclass));
165
+ let owner;
166
+ for (const ins of sweep) {
167
+ const label = labels.get(ins.offset);
168
+ if (label) {
169
+ out.push(label.startsWith(".") ? `${label}:` : `\n${label}:`);
170
+ if (!label.startsWith(".")) {
171
+ const fnOwner = fnAt.get(ins.offset)?.owner;
172
+ const prefix = label.includes("::") ? label.slice(0, label.lastIndexOf("::")) : undefined;
173
+ const byLabel = prefix === undefined ? undefined : script.objects.find((o) => labelOf.get(o) === prefix);
174
+ owner = byLabel;
175
+ if (fnOwner && fnOwner !== byLabel) {
176
+ out.push(` owner ${labelOf.get(fnOwner)}`);
177
+ owner = fnOwner;
178
+ }
179
+ }
180
+ }
181
+ const info = opcodes[ins.opcode];
182
+ const small = (code[ins.offset] & 1) === 1;
183
+ const hasSized = info.operands.some(SIZED);
184
+ let suffix = "";
185
+ if (hasSized) {
186
+ const defaultSmall = WORD_DEFAULT.has(ins.name) ? false : fitsSmall(info.operands, ins.operands);
187
+ if (small !== defaultSmall)
188
+ suffix = small ? ".b" : ".w";
189
+ }
190
+ else if (small)
191
+ suffix = ".b"; // the size bit on an opcode without sized operands
192
+ const args = info.operands.map((k, i) => {
193
+ const v = ins.operands[i];
194
+ if (RELATIVE(k))
195
+ return codeLabel(ins.target);
196
+ if (i === 0 && HEAP_OPERAND.has(ins.name))
197
+ return codeRelocs.has(ins.offset + 1) ? heapLabel(v) : `${v}`;
198
+ if (i === 0 && ins.name === "pushi" && selectorPushes.has(ins.offset))
199
+ return `#${sel(v)}`;
200
+ if (i === 0 && ins.name === "callk") {
201
+ const name = ctx.kernelNames[v];
202
+ return name && ctx.kernelNames.indexOf(name) === ctx.kernelNames.lastIndexOf(name) ? name : `${v}`;
203
+ }
204
+ if (i === 0 && (ins.name === "class" || ins.name === "super"))
205
+ return ctx.className(v) ?? `$${hex(v)}`;
206
+ if (i === 0 && PROPERTY_OPS.has(ins.name) && owner) {
207
+ const name = propertyName(layoutOf(owner), v, sel);
208
+ if (name)
209
+ return name;
210
+ }
211
+ return k === "S" ? `${v}` : v > 9 ? `$${hex(v)}` : `${v}`;
212
+ });
213
+ out.push(` ${ins.name}${suffix}${args.length ? ` ${args.join(" ")}` : ""}`);
214
+ }
215
+ return `${out.join("\n")}\n`;
216
+ }
217
+ /** Object labels: names, disambiguated when a script reuses one. */
218
+ function uniqueObjectLabels(objects) {
219
+ const seen = new Map();
220
+ return objects.map((o) => {
221
+ const n = seen.get(o.name) ?? 0;
222
+ seen.set(o.name, n + 1);
223
+ const safe = /^[A-Za-z_][\w:$-]*$/.test(o.name) ? o.name : `obj_${hex(o.offset)}`;
224
+ return n ? `${safe}~${n}` : safe;
225
+ });
226
+ }
227
+ function quote(bytes) {
228
+ let s = '"';
229
+ for (const b of bytes) {
230
+ if (b === 0x22)
231
+ s += '\\"';
232
+ else if (b === 0x5c)
233
+ s += "\\\\";
234
+ else if (b === 0x0a)
235
+ s += "\\n";
236
+ else if (b >= 0x20 && b < 0x7f)
237
+ s += String.fromCharCode(b);
238
+ else
239
+ s += `\\x${hex(b, 2)}`;
240
+ }
241
+ return `${s}"`;
242
+ }
243
+ function tokenize(text) {
244
+ const lines = [];
245
+ text.split("\n").forEach((raw, i) => {
246
+ const tokens = [];
247
+ for (let p = 0; p < raw.length;) {
248
+ const c = raw[p];
249
+ if (c === ";")
250
+ break;
251
+ if (c === " " || c === "\t" || c === "\r") {
252
+ p++;
253
+ continue;
254
+ }
255
+ if (c === '"') {
256
+ let e = p + 1;
257
+ while (e < raw.length && raw[e] !== '"')
258
+ e += raw[e] === "\\" ? 2 : 1;
259
+ tokens.push(raw.slice(p, e + 1));
260
+ p = e + 1;
261
+ continue;
262
+ }
263
+ let e = p;
264
+ while (e < raw.length && !" \t\r;".includes(raw[e]))
265
+ e++;
266
+ tokens.push(raw.slice(p, e));
267
+ p = e;
268
+ }
269
+ if (tokens.length)
270
+ lines.push({ no: i + 1, tokens });
271
+ });
272
+ return lines;
273
+ }
274
+ function unquote(token, line) {
275
+ if (!token.startsWith('"') || !token.endsWith('"') || token.length < 2)
276
+ throw new AsmError(`expected a string, got ${token}`, line);
277
+ const out = [];
278
+ for (let i = 1; i < token.length - 1; i++) {
279
+ const c = token[i];
280
+ if (c !== "\\") {
281
+ out.push(c.charCodeAt(0) & 0xff);
282
+ continue;
283
+ }
284
+ const n = token[++i];
285
+ if (n === "n")
286
+ out.push(0x0a);
287
+ else if (n === "x") {
288
+ out.push(parseInt(token.slice(i + 1, i + 3), 16));
289
+ i += 2;
290
+ }
291
+ else
292
+ out.push((n ?? "").charCodeAt(0));
293
+ }
294
+ return Uint8Array.from(out);
295
+ }
296
+ export function assemble(text, ctx) {
297
+ const lines = tokenize(text);
298
+ const selectorIds = new Map(ctx.selectorNames.map((n, i) => [n, i]));
299
+ const selector = (name, line) => {
300
+ let id = selectorIds.get(name) ?? (name.startsWith("sel_") ? Number(name.slice(4)) : undefined);
301
+ if (id === undefined && ctx.newSelector && /^[A-Za-z_-][\w-]*$/.test(name))
302
+ selectorIds.set(name, (id = ctx.newSelector(name)));
303
+ if (id === undefined || Number.isNaN(id))
304
+ throw new AsmError(`unknown selector ${name}`, line);
305
+ return id;
306
+ };
307
+ const number = (t, line) => {
308
+ const v = t.startsWith("$") ? parseInt(t.slice(1), 16) : t.startsWith("-$") ? -parseInt(t.slice(2), 16) : Number(t);
309
+ if (!Number.isFinite(v) || t === "")
310
+ throw new AsmError(`expected a number, got ${t}`, line);
311
+ return v;
312
+ };
313
+ const value = (t, line) => {
314
+ if (t.startsWith("#") && t.length > 1)
315
+ return { kind: "num", value: selector(t.slice(1), line) };
316
+ if (t.startsWith("@")) {
317
+ const [label, off] = t.slice(1).split("+");
318
+ return { kind: "heap", label: label, offset: off ? Number(off) : 0 };
319
+ }
320
+ if (/^-?(\$[0-9a-fA-F]+|\d+)$/.test(t))
321
+ return { kind: "num", value: number(t, line) };
322
+ return { kind: "code", label: t };
323
+ };
324
+ const classNumber = (t, line) => {
325
+ if (t === "-")
326
+ return 0xffff;
327
+ if (t.startsWith("$"))
328
+ return number(t, line);
329
+ const local = objects.find((o) => o.isClass && o.label === t);
330
+ const n = local ? local.species : ctx.classSpecies(t);
331
+ if (n === undefined)
332
+ throw new AsmError(`unknown class ${t}`, line);
333
+ return n;
334
+ };
335
+ let scriptNumber;
336
+ let locals = [];
337
+ const exports = [];
338
+ const objects = [];
339
+ const strings = [];
340
+ const instructions = [];
341
+ const codeLabels = new Map(); // label → instruction index
342
+ let section = "";
343
+ let current;
344
+ let owner;
345
+ const opcodeByName = new Map(opcodes.map((o, i) => [o.name, i]));
346
+ const kernelIds = new Map(ctx.kernelNames.map((n, i) => [n, i]));
347
+ for (const { no, tokens } of lines) {
348
+ const [head, ...rest] = tokens;
349
+ // Keywords double as property names (`script`, `code`...): inside an object, a line
350
+ // with a value is a property. Section headers stand alone.
351
+ const inObject = section === "object";
352
+ if (head === "script" && !inObject && section === "") {
353
+ scriptNumber = number(rest[0] ?? "", no);
354
+ continue;
355
+ }
356
+ if (head === "locals" && !inObject && section === "") {
357
+ locals = rest.map((t) => value(t, no));
358
+ continue;
359
+ }
360
+ if ((head === "exports" || head === "strings" || head === "code") && rest.length === 0) {
361
+ section = head;
362
+ current = undefined;
363
+ continue;
364
+ }
365
+ // In code, `class Name` is the instruction that loads a class object.
366
+ if ((head === "class" || head === "instance") && rest[1] === "of" && section !== "code") {
367
+ section = "object";
368
+ const [label, of, superName, speciesKw, speciesNo] = rest;
369
+ if (!label || of !== "of" || !superName)
370
+ throw new AsmError(`expected: ${head} Name of Super`, no);
371
+ const isClass = head === "class";
372
+ if (isClass && speciesKw !== "species")
373
+ throw new AsmError("classes need: species N", no);
374
+ current = {
375
+ line: no, label, isClass, superclass: 0, species: isClass ? number(speciesNo ?? "", no) : 0xffff,
376
+ classScript: { kind: "num", value: 0 }, info: { kind: "num", value: isClass ? 0x8000 : 0 }, name: { kind: "num", value: 0 },
377
+ props: new Map(), propSelectors: [], methods: [], heapOffset: 0, size: 0,
378
+ };
379
+ // Resolved after all objects are read (a class may be declared later in the file).
380
+ current.superName = superName;
381
+ objects.push(current);
382
+ continue;
383
+ }
384
+ if (section === "exports") {
385
+ exports.push(value(head, no));
386
+ continue;
387
+ }
388
+ if (section === "strings") {
389
+ if (!rest[0])
390
+ throw new AsmError("expected: label \"text\"", no);
391
+ strings.push({ label: head, bytes: unquote(rest[0], no), line: no });
392
+ continue;
393
+ }
394
+ if (section === "object" && current) {
395
+ const o = current;
396
+ if (head === "classScript")
397
+ o.classScript = value(rest[0] ?? "", no);
398
+ else if (head === "info")
399
+ o.info = value(rest[0] ?? "", no);
400
+ else if (head === "name")
401
+ o.name = value(rest[0] ?? "", no);
402
+ else if (head === "header")
403
+ o.headerSelectors = rest.map((t) => selector(t, no));
404
+ else if (head === "layout")
405
+ o.layoutSelectors = rest.map((t) => selector(t, no));
406
+ else if (head === "method")
407
+ o.methods.push({ selector: selector(rest[0] ?? "", no), label: rest[1] ?? "" });
408
+ else if (head === "prop") {
409
+ if (!o.isClass)
410
+ throw new AsmError("only classes declare new properties with `prop`", no);
411
+ o.propSelectors.push(selector(rest[0] ?? "", no));
412
+ o.props.set(HEADER_SLOTS + o.propSelectors.length - 1, value(rest[1] ?? "", no));
413
+ }
414
+ else {
415
+ // An instance's property, by name (resolved against its class later) or #slot.
416
+ o.named ??= [];
417
+ o.named.push({ name: head, value: value(rest[0] ?? "", no), line: no });
418
+ }
419
+ continue;
420
+ }
421
+ if (section === "code") {
422
+ if (head.endsWith(":") && rest.length === 0) {
423
+ const label = head.slice(0, -1);
424
+ if (codeLabels.has(label))
425
+ throw new AsmError(`duplicate label ${label}`, no);
426
+ codeLabels.set(label, instructions.length);
427
+ // `Obj::method` code runs with self = Obj; local labels keep the owner; others clear it.
428
+ if (!label.startsWith("."))
429
+ owner = label.includes("::") ? label.slice(0, label.lastIndexOf("::")) : undefined;
430
+ continue;
431
+ }
432
+ if (head === "owner") {
433
+ if (!rest[0])
434
+ throw new AsmError("expected: owner ObjectOrClass", no);
435
+ owner = rest[0];
436
+ continue;
437
+ }
438
+ const [mnemonic, form] = head.split(".");
439
+ const opcode = opcodeByName.get(mnemonic);
440
+ if (opcode === undefined)
441
+ throw new AsmError(`unknown instruction ${head}`, no);
442
+ const info = opcodes[opcode];
443
+ if (rest.length !== info.operands.length)
444
+ throw new AsmError(`${mnemonic} takes ${info.operands.length} operand(s)`, no);
445
+ const args = info.operands.map((k, i) => {
446
+ const t = rest[i];
447
+ if (k === "R")
448
+ return { label: t, heap: false, offset: 0 };
449
+ if (t.startsWith("@")) {
450
+ const [label, off] = t.slice(1).split("+");
451
+ return { label: label, heap: true, offset: off ? Number(off) : 0 };
452
+ }
453
+ if (t.startsWith("#"))
454
+ return selector(t.slice(1), no);
455
+ if (i === 0 && mnemonic === "callk" && !/^-?\$?\d/.test(t)) {
456
+ const id = kernelIds.get(t);
457
+ if (id === undefined)
458
+ throw new AsmError(`unknown kernel ${t}`, no);
459
+ return id;
460
+ }
461
+ if (i === 0 && (mnemonic === "class" || mnemonic === "super") && !/^\$?\d/.test(t))
462
+ return classNumber(t, no);
463
+ if (i === 0 && PROPERTY_OPS.has(mnemonic) && !/^-?\$?\d/.test(t))
464
+ return { property: t };
465
+ return number(t, no);
466
+ });
467
+ instructions.push({ line: no, name: mnemonic, opcode, forced: form === "b" ? true : form === "w" ? false : undefined, args, owner, offset: 0, small: false });
468
+ continue;
469
+ }
470
+ throw new AsmError(`unexpected ${head}`, no);
471
+ }
472
+ if (scriptNumber === undefined)
473
+ throw new AsmError("missing `script N`");
474
+ // --- Resolve object layouts --------------------------------------------------------
475
+ for (const o of objects) {
476
+ o.superclass = classNumber(o.superName, o.line);
477
+ if (o.isClass) {
478
+ const header = o.headerSelectors ?? ctx.classPropSelectors(o.superclass)?.slice(0, HEADER_SLOTS);
479
+ if (!header)
480
+ throw new AsmError(`class ${o.label} needs a header line (its superclass layout is unknown)`, o.line);
481
+ o.propSelectors = [...header, ...o.propSelectors];
482
+ }
483
+ else {
484
+ const sels = o.layoutSelectors ?? ctx.classPropSelectors(o.superclass) ?? objects.find((c) => c.isClass && c.species === o.superclass)?.propSelectors;
485
+ if (!sels)
486
+ throw new AsmError(`unknown layout for the class of ${o.label}`, o.line);
487
+ o.propSelectors = [...sels];
488
+ // Unlisted properties keep the class's defaults.
489
+ const cls = objects.find((c) => c.isClass && c.species === o.superclass);
490
+ if (cls)
491
+ for (const [slot, v] of cls.props)
492
+ o.props.set(slot, v);
493
+ else
494
+ ctx.classDefaults(o.superclass)?.forEach((v, slot) => slot >= HEADER_SLOTS && o.props.set(slot, { kind: "num", value: v }));
495
+ for (const { name, value: v, line } of o.named ?? []) {
496
+ const slot = name.startsWith("#") ? Number(name.slice(1)) : sels.indexOf(selector(name, line));
497
+ if (slot < HEADER_SLOTS || slot >= sels.length)
498
+ throw new AsmError(`${o.label} has no property ${name}`, line);
499
+ o.props.set(slot, v);
500
+ }
501
+ }
502
+ o.size = o.propSelectors.length;
503
+ }
504
+ // --- Property names: byte offsets in the owner's layout -------------------------------
505
+ for (const ins of instructions) {
506
+ const a = ins.args[0];
507
+ if (!a || typeof a !== "object" || !("property" in a))
508
+ continue;
509
+ if (!ins.owner)
510
+ throw new AsmError(`${ins.name} ${a.property}: no owner here (add \`owner Name\` or put it in a Name::method)`, ins.line);
511
+ const local = objects.find((o) => o.label === ins.owner);
512
+ const species = local ? undefined : ctx.classSpecies(ins.owner);
513
+ const layout = local?.propSelectors ?? (species === undefined ? undefined : ctx.classPropSelectors(species));
514
+ if (!layout)
515
+ throw new AsmError(`unknown owner ${ins.owner}`, ins.line);
516
+ const slot = layout.indexOf(selector(a.property, ins.line));
517
+ if (slot < 0)
518
+ throw new AsmError(`${ins.owner} has no property ${a.property}`, ins.line);
519
+ ins.args[0] = slot * 2;
520
+ }
521
+ // --- Heap layout ---------------------------------------------------------------------
522
+ const heapLabels = new Map();
523
+ let hp = 4 + locals.length * 2;
524
+ for (const o of objects) {
525
+ o.heapOffset = hp;
526
+ if (heapLabels.has(o.label))
527
+ throw new AsmError(`duplicate object ${o.label}`, o.line);
528
+ heapLabels.set(o.label, hp);
529
+ hp += o.size * 2;
530
+ }
531
+ hp += 2; // the 0 word ending the object list
532
+ const stringOffsets = [];
533
+ for (const s of strings) {
534
+ if (heapLabels.has(s.label))
535
+ throw new AsmError(`duplicate label ${s.label}`, s.line);
536
+ heapLabels.set(s.label, hp);
537
+ stringOffsets.push(hp);
538
+ hp += s.bytes.length + 1;
539
+ }
540
+ const heapRelocOffset = hp;
541
+ const heapAddress = (label, offset, line) => {
542
+ const base = heapLabels.get(label) ?? (/^\d+$/.test(label) ? Number(label) : undefined);
543
+ if (base === undefined)
544
+ throw new AsmError(`unknown heap label @${label}`, line);
545
+ return base + offset;
546
+ };
547
+ // --- Code layout: dictionaries, then instructions (with jump relaxation) -----------------
548
+ let cp = 8 + exports.length * 2;
549
+ const propDictAt = new Map(), methDictAt = new Map();
550
+ for (const o of objects) {
551
+ if (o.isClass) {
552
+ propDictAt.set(o, cp);
553
+ cp += o.size * 2;
554
+ }
555
+ methDictAt.set(o, cp);
556
+ cp += 2 + o.methods.length * 4;
557
+ }
558
+ const codeStart = cp;
559
+ const labelOffset = (label, line) => {
560
+ if (/^\d+$/.test(label))
561
+ return Number(label);
562
+ const idx = codeLabels.get(label);
563
+ if (idx === undefined)
564
+ throw new AsmError(`unknown code label ${label}`, line);
565
+ return idx < instructions.length ? instructions[idx].offset : codeEnd;
566
+ };
567
+ let codeEnd = codeStart;
568
+ const sizeOf = (ins) => {
569
+ const info = opcodes[ins.opcode];
570
+ let size = 1;
571
+ for (const k of info.operands)
572
+ size += k === "B" ? 1 : k === "U" ? 2 : ins.small ? 1 : 2;
573
+ return size;
574
+ };
575
+ // Start with each instruction's default/forced form; unforced relative jumps start small
576
+ // and grow until everything fits (sizes only grow, so this terminates).
577
+ for (const ins of instructions) {
578
+ const info = opcodes[ins.opcode];
579
+ if (!info.operands.some(SIZED))
580
+ ins.small = ins.forced ?? false;
581
+ else if (ins.forced !== undefined)
582
+ ins.small = ins.forced;
583
+ else if (WORD_DEFAULT.has(ins.name))
584
+ ins.small = false;
585
+ else if (info.operands.some(RELATIVE))
586
+ ins.small = true;
587
+ else
588
+ ins.small = fitsSmall(info.operands, ins.args.map((a) => (typeof a === "number" ? a : 0x7fff)));
589
+ }
590
+ for (let pass = 0;; pass++) {
591
+ let pc = codeStart;
592
+ for (const ins of instructions) {
593
+ ins.offset = pc;
594
+ pc += sizeOf(ins);
595
+ }
596
+ codeEnd = pc;
597
+ let grew = false;
598
+ for (const ins of instructions) {
599
+ const info = opcodes[ins.opcode];
600
+ if (!ins.small || ins.forced !== undefined || !info.operands.some(RELATIVE))
601
+ continue;
602
+ const target = labelOffset(ins.args[0].label, ins.line);
603
+ const rel = target - (ins.offset + sizeOf(ins));
604
+ if (rel < -128 || rel > 127) {
605
+ ins.small = false;
606
+ grew = true;
607
+ }
608
+ }
609
+ if (!grew)
610
+ break;
611
+ if (pass > 1000)
612
+ throw new AsmError("jump relaxation did not settle");
613
+ }
614
+ // --- Emit code -----------------------------------------------------------------------------
615
+ const codeRelocs = [];
616
+ const codeBytes = [];
617
+ const w16 = (arr, at, v) => { arr[at] = v & 0xff; arr[at + 1] = (v >> 8) & 0xff; };
618
+ const resolveValue = (v, line) => {
619
+ if (v.kind === "num")
620
+ return { value: v.value & 0xffff, heap: false };
621
+ if (v.kind === "heap")
622
+ return { value: heapAddress(v.label, v.offset, line), heap: true };
623
+ return { value: labelOffset(v.label, line), heap: false };
624
+ };
625
+ w16(codeBytes, 0, 0); // relocation table offset, patched below
626
+ w16(codeBytes, 2, 0);
627
+ w16(codeBytes, 4, 0);
628
+ w16(codeBytes, 6, exports.length);
629
+ exports.forEach((e, i) => {
630
+ const at = 8 + i * 2;
631
+ if (e.kind === "num") {
632
+ w16(codeBytes, at, e.value);
633
+ return;
634
+ }
635
+ if (e.kind === "heap") {
636
+ w16(codeBytes, at, heapAddress(e.label, e.offset, 0));
637
+ codeRelocs.push(at);
638
+ return;
639
+ }
640
+ // A bare name: an object if there is one, else a code label.
641
+ if (heapLabels.has(e.label) && objects.some((o) => o.label === e.label)) {
642
+ w16(codeBytes, at, heapLabels.get(e.label));
643
+ codeRelocs.push(at);
644
+ }
645
+ else
646
+ w16(codeBytes, at, labelOffset(e.label, 0));
647
+ });
648
+ for (const o of objects) {
649
+ if (o.isClass)
650
+ o.propSelectors.forEach((s, i) => w16(codeBytes, propDictAt.get(o) + i * 2, s));
651
+ const md = methDictAt.get(o);
652
+ w16(codeBytes, md, o.methods.length);
653
+ o.methods.forEach((m, i) => {
654
+ w16(codeBytes, md + 2 + i * 4, m.selector);
655
+ w16(codeBytes, md + 4 + i * 4, labelOffset(m.label, o.line));
656
+ });
657
+ }
658
+ for (const ins of instructions) {
659
+ const info = opcodes[ins.opcode];
660
+ let pc = ins.offset;
661
+ codeBytes[pc++] = (ins.opcode << 1) | (ins.small ? 1 : 0);
662
+ const next = ins.offset + sizeOf(ins);
663
+ info.operands.forEach((k, i) => {
664
+ const a = ins.args[i];
665
+ let v;
666
+ if (typeof a === "number")
667
+ v = a;
668
+ else if ("property" in a)
669
+ throw new AsmError(`unresolved property ${a.property}`, ins.line);
670
+ else if (a.heap) {
671
+ v = heapAddress(a.label, a.offset, ins.line);
672
+ if (ins.small)
673
+ throw new AsmError(`${ins.name} with a heap address can't be .b (it's relocated)`, ins.line);
674
+ codeRelocs.push(pc);
675
+ }
676
+ else
677
+ v = k === "R" ? labelOffset(a.label, ins.line) - next : labelOffset(a.label, ins.line);
678
+ const width = k === "B" ? 1 : k === "U" ? 2 : ins.small ? 1 : 2;
679
+ if (width === 1) {
680
+ const ok = k === "W" || k === "B" ? v >= 0 && v <= 0xff : v >= -128 && v <= 127;
681
+ if (!ok)
682
+ throw new AsmError(`${ins.name}: ${v} doesn't fit in a byte`, ins.line);
683
+ codeBytes[pc++] = v & 0xff;
684
+ }
685
+ else {
686
+ if (v < -0x8000 || v > 0xffff)
687
+ throw new AsmError(`${ins.name}: ${v} doesn't fit in 16 bits`, ins.line);
688
+ w16(codeBytes, pc, v & 0xffff);
689
+ pc += 2;
690
+ }
691
+ });
692
+ }
693
+ const codeRelocOffset = codeEnd;
694
+ w16(codeBytes, 0, codeRelocOffset);
695
+ codeRelocs.sort((a, b) => a - b);
696
+ w16(codeBytes, codeRelocOffset, codeRelocs.length);
697
+ codeRelocs.forEach((r, i) => w16(codeBytes, codeRelocOffset + 2 + i * 2, r));
698
+ // --- Emit heap -----------------------------------------------------------------------------
699
+ const heapBytes = [];
700
+ const heapRelocs = [];
701
+ w16(heapBytes, 0, heapRelocOffset);
702
+ w16(heapBytes, 2, locals.length);
703
+ locals.forEach((v, i) => {
704
+ const r = resolveValue(v, 0);
705
+ w16(heapBytes, 4 + i * 2, r.value);
706
+ if (r.heap)
707
+ heapRelocs.push(4 + i * 2);
708
+ });
709
+ for (const o of objects) {
710
+ const slot = (n, v) => {
711
+ const at = o.heapOffset + n * 2;
712
+ if (typeof v === "number") {
713
+ w16(heapBytes, at, v);
714
+ return;
715
+ }
716
+ const r = resolveValue(v, o.line);
717
+ w16(heapBytes, at, r.value);
718
+ if (r.heap)
719
+ heapRelocs.push(at);
720
+ };
721
+ slot(ObjectSlot.Magic, 0x1234);
722
+ slot(ObjectSlot.Size, o.size);
723
+ // Instances point both dictionary slots at their method dictionary.
724
+ slot(ObjectSlot.PropDict, o.isClass ? propDictAt.get(o) : methDictAt.get(o));
725
+ slot(ObjectSlot.MethDict, methDictAt.get(o));
726
+ slot(ObjectSlot.ClassScript, o.classScript);
727
+ slot(ObjectSlot.Species, o.species);
728
+ slot(ObjectSlot.Super, o.superclass);
729
+ slot(ObjectSlot.Info, o.info);
730
+ slot(ObjectSlot.Name, o.name);
731
+ for (let n = HEADER_SLOTS; n < o.size; n++)
732
+ slot(n, o.props.get(n) ?? 0);
733
+ }
734
+ const objectsEnd = objects.length ? objects.at(-1).heapOffset + objects.at(-1).size * 2 : 4 + locals.length * 2;
735
+ w16(heapBytes, objectsEnd, 0);
736
+ strings.forEach((s, i) => {
737
+ const at = stringOffsets[i];
738
+ s.bytes.forEach((b, j) => (heapBytes[at + j] = b));
739
+ heapBytes[at + s.bytes.length] = 0;
740
+ });
741
+ heapRelocs.sort((a, b) => a - b);
742
+ w16(heapBytes, heapRelocOffset, heapRelocs.length);
743
+ heapRelocs.forEach((r, i) => w16(heapBytes, heapRelocOffset + 2 + i * 2, r));
744
+ const fill = (a) => Uint8Array.from({ length: a.length }, (_, i) => a[i] ?? 0);
745
+ return { number: scriptNumber, code: fill(codeBytes), heap: fill(heapBytes) };
746
+ }
747
+ /** Reads the heap string at an address (for tools that show assembled output). */
748
+ export const heapString = (heap, offset) => readString(heap, offset);