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,993 @@
1
+ import { read, show, ReadError } from "./sexpr.js";
2
+ export class CompileError extends Error {
3
+ file;
4
+ line;
5
+ constructor(message, file, line) {
6
+ super(`${file}:${line}: ${message}`);
7
+ this.file = file;
8
+ this.line = line;
9
+ }
10
+ }
11
+ /** The object header slots, in order; every class and instance begins with them. */
12
+ export const OBJECT_HEADER = ["-objID-", "-size-", "-propDict-", "-methDict-", "-classScript-", "-species-", "-super-", "-info-", "name"];
13
+ const BUILTIN_DEFINES = { TRUE: 1, FALSE: 0, NULL: 0 };
14
+ export function compileScripts(sources, ctx) {
15
+ const units = sources.map((s) => declare(s, ctx));
16
+ const byNumber = new Map();
17
+ for (const u of units) {
18
+ const other = byNumber.get(u.number);
19
+ if (other)
20
+ throw new CompileError(`script ${u.number} is also ${other.file}`, u.file, 1);
21
+ byNumber.set(u.number, u);
22
+ }
23
+ // Classes: numbered superclasses first, then in script order, skipping numbers used
24
+ // elsewhere (so a root class like Obj gets 0).
25
+ const classes = new Map();
26
+ for (const u of [...units].sort((a, b) => a.number - b.number)) {
27
+ for (const o of u.objects) {
28
+ if (o.kind !== "class")
29
+ continue;
30
+ if (classes.has(o.name) || ctx.externalClass?.(o.name))
31
+ throw new CompileError(`class ${o.name} is defined twice`, u.file, o.line);
32
+ classes.set(o.name, { obj: o, unit: u });
33
+ }
34
+ }
35
+ const taken = new Set(ctx.takenSpecies ?? []);
36
+ let nextSpecies = 0;
37
+ const number = (c, seen) => {
38
+ if (c.obj.species !== 0xffff)
39
+ return;
40
+ if (seen.has(c.obj.name))
41
+ throw new CompileError(`class ${c.obj.name} is its own superclass`, c.unit.file, c.obj.line);
42
+ seen.add(c.obj.name);
43
+ const sup = c.obj.superName ? classes.get(c.obj.superName) : undefined;
44
+ if (sup)
45
+ number(sup, seen);
46
+ while (taken.has(nextSpecies))
47
+ nextSpecies++;
48
+ c.obj.species = nextSpecies;
49
+ taken.add(nextSpecies);
50
+ };
51
+ for (const c of classes.values())
52
+ number(c, new Set());
53
+ // Globals: script 0's variables, then those other scripts declare with (global ...), in
54
+ // script order; they all live in script 0.
55
+ const globals = new Map();
56
+ const zero = byNumber.get(0);
57
+ const declared = [...units].sort((a, b) => a.number - b.number).flatMap((u) => u.globals.map((v) => ({ v, u })));
58
+ if (declared.length && !zero) {
59
+ throw new CompileError("(global ...) needs script 0 to be compiled with it (globals live there)", declared[0].u.file, declared[0].v.line);
60
+ }
61
+ for (const { v, u } of declared) {
62
+ const other = zero.locals.find((x) => x.name === v.name);
63
+ if (other)
64
+ throw new CompileError(`global ${v.name} is declared twice`, u.file, v.line);
65
+ zero.locals.push(v);
66
+ }
67
+ if (zero) {
68
+ let i = 0;
69
+ for (const v of zero.locals)
70
+ (globals.set(v.name, { index: i, size: v.size }), (i += v.size));
71
+ }
72
+ // Procedures other scripts can call.
73
+ const publicProcs = new Map();
74
+ for (const u of units) {
75
+ for (const p of u.publics)
76
+ if (u.procedures.some((f) => f.name === p.name))
77
+ publicProcs.set(p.name, { script: u.number, index: p.index });
78
+ }
79
+ const project = { ctx, classes, globals, publicProcs, layouts: new Map(), sent: [] };
80
+ const compiled = units.map((u) => ({
81
+ file: u.file,
82
+ number: u.number,
83
+ assembly: new ScriptCompiler(u, project).compile(),
84
+ classes: new Map(u.objects.filter((o) => o.kind === "class").map((o) => [o.name, o.species])),
85
+ variables: u.locals.flatMap((v) => (v.size === 1 ? [v.name] : Array.from({ length: v.size }, (_, i) => `${v.name}[${i}]`))),
86
+ }));
87
+ // Selectors sent that nothing here defines: probably misspelt.
88
+ if (ctx.onWarning) {
89
+ const defined = new Set(OBJECT_HEADER);
90
+ for (const u of units) {
91
+ for (const o of u.objects) {
92
+ for (const p of o.props)
93
+ defined.add(p.name);
94
+ for (const m of o.methods)
95
+ defined.add(m.name);
96
+ }
97
+ }
98
+ const reported = new Set();
99
+ for (const s of project.sent) {
100
+ if (defined.has(s.name) || reported.has(s.name))
101
+ continue;
102
+ reported.add(s.name);
103
+ ctx.onWarning({ message: `nothing defines ${s.name} (no class or object has a property or method by that name)`, file: s.file, line: s.line });
104
+ }
105
+ }
106
+ return compiled;
107
+ }
108
+ /** Reads a source file's top-level forms into declarations. */
109
+ function declare(src, ctx) {
110
+ const unit = { file: src.file, number: -1, defines: new Map(), locals: [], globals: [], publics: [], externs: new Map(), objects: [], procedures: [] };
111
+ const fail = (msg, line, file = src.file) => {
112
+ throw new CompileError(msg, file, line);
113
+ };
114
+ const parse = (text, file) => {
115
+ try {
116
+ return read(text);
117
+ }
118
+ catch (e) {
119
+ if (e instanceof ReadError)
120
+ fail(e.message.replace(/^line \d+: /, ""), e.line, file);
121
+ throw e;
122
+ }
123
+ };
124
+ const topLevel = (forms, file, included) => {
125
+ for (const form of forms) {
126
+ if (form.kind !== "list" || form.items[0]?.kind !== "sym")
127
+ fail(`expected a declaration like (script ...), got ${show(form)}`, form.line, file);
128
+ const [head, ...rest] = form.items;
129
+ const line = form.line;
130
+ const sym = (n, what) => (n?.kind === "sym" ? n.name : fail(`expected ${what}`, n?.line ?? line, file));
131
+ const num = (n, what) => (n?.kind === "num" ? n.value : fail(`expected ${what}`, n?.line ?? line, file));
132
+ const onlyInScript = () => included && fail(`(${head.name}) can't be in an included file`, line, file);
133
+ switch (head.name) {
134
+ case "script":
135
+ onlyInScript();
136
+ if (unit.number >= 0)
137
+ fail("a second (script N)", line);
138
+ unit.number = num(rest[0], "the script's number");
139
+ break;
140
+ case "include": {
141
+ const name = rest[0]?.kind === "str" ? rest[0].value : sym(rest[0], "a file name");
142
+ const text = ctx.include?.(name, file);
143
+ if (text === undefined)
144
+ fail(`can't find ${name} to include`, line, file);
145
+ topLevel(parse(text, name), name, true);
146
+ break;
147
+ }
148
+ case "define":
149
+ unit.defines.set(sym(rest[0], "a name"), rest[1] ?? fail("(define NAME value)", line, file));
150
+ break;
151
+ case "enum": {
152
+ // (enum [start] A B (= C 10) D): A = start (0 by default), B = A + 1, ...
153
+ let i = 0;
154
+ let names = rest;
155
+ if (names[0]?.kind === "num")
156
+ (i = names[0].value), (names = names.slice(1));
157
+ for (const n of names) {
158
+ if (n.kind === "list" && n.items[0]?.kind === "sym" && n.items[0].name === "=") {
159
+ i = constValue(n.items[2], unit.defines, (m) => fail(m, n.line, file));
160
+ unit.defines.set(sym(n.items[1], "a name"), { kind: "num", value: i++, line: n.line });
161
+ }
162
+ else
163
+ unit.defines.set(sym(n, "a name"), { kind: "num", value: i++, line: n.line });
164
+ }
165
+ break;
166
+ }
167
+ case "extern":
168
+ for (let i = 0; i < rest.length; i += 3) {
169
+ unit.externs.set(sym(rest[i], "a procedure name"), { script: num(rest[i + 1], "a script number"), index: num(rest[i + 2], "an export index") });
170
+ }
171
+ break;
172
+ case "public":
173
+ onlyInScript();
174
+ for (let i = 0; i < rest.length; i += 2) {
175
+ unit.publics.push({ name: sym(rest[i], "a name"), index: num(rest[i + 1], "an export index"), line: rest[i].line });
176
+ }
177
+ break;
178
+ case "local":
179
+ onlyInScript();
180
+ unit.locals.push(...variables(rest, (m, l) => fail(m, l)));
181
+ break;
182
+ case "global":
183
+ onlyInScript();
184
+ unit.globals.push(...variables(rest, (m, l) => fail(m, l)));
185
+ break;
186
+ case "class":
187
+ case "instance":
188
+ onlyInScript();
189
+ unit.objects.push(object(head.name, rest, line, (m, l) => fail(m, l)));
190
+ break;
191
+ case "procedure": {
192
+ onlyInScript();
193
+ const sig = rest[0];
194
+ if (sig?.kind === "sym")
195
+ break; // a forward declaration
196
+ unit.procedures.push(func(sig, rest.slice(1), line, (m, l) => fail(m, l)));
197
+ break;
198
+ }
199
+ default:
200
+ fail(`unknown declaration (${head.name} ...)`, line, file);
201
+ }
202
+ }
203
+ };
204
+ topLevel(parse(src.text, src.file), src.file, false);
205
+ if (unit.number < 0)
206
+ fail("missing (script N)", 1);
207
+ return unit;
208
+ }
209
+ /** `a [b 4] (= c 5) [d 2] = (1 2)`: names, array sizes, initial values. */
210
+ function variables(nodes, fail) {
211
+ const out = [];
212
+ for (let i = 0; i < nodes.length; i++) {
213
+ const n = nodes[i];
214
+ if (n.kind === "sym" && n.name !== "=") {
215
+ const v = { name: n.name, size: 1, init: [], line: n.line };
216
+ if (nodes[i + 1]?.kind === "sym" && nodes[i + 1].name === "=") {
217
+ v.init = [nodes[i + 2] ?? fail("= needs a value", n.line)];
218
+ i += 2;
219
+ }
220
+ out.push(v);
221
+ }
222
+ else if (n.kind === "index") {
223
+ const [name, size] = n.items;
224
+ if (name?.kind !== "sym" || size?.kind !== "num")
225
+ fail("an array is [name size]", n.line);
226
+ const v = { name: name.name, size: size.value, init: [], line: n.line };
227
+ if (nodes[i + 1]?.kind === "sym" && nodes[i + 1].name === "=") {
228
+ const values = nodes[i + 2];
229
+ v.init = values?.kind === "list" || values?.kind === "index" ? values.items : [values ?? fail("= needs values", n.line)];
230
+ if (v.init.length > v.size)
231
+ fail(`${v.name} has ${v.size} elements, but ${v.init.length} values`, n.line);
232
+ i += 2;
233
+ }
234
+ out.push(v);
235
+ }
236
+ else if (n.kind === "list" && n.items[0]?.kind === "sym" && n.items[0].name === "=" && n.items[1]?.kind === "sym") {
237
+ out.push({ name: n.items[1].name, size: 1, init: [n.items[2] ?? fail("(= name value)", n.line)], line: n.line });
238
+ }
239
+ else
240
+ fail(`expected a variable name, got ${show(n)}`, n.line);
241
+ }
242
+ return out;
243
+ }
244
+ /** `(name a b &tmp t [buf 4])` and a body. */
245
+ function func(sig, body, line, fail) {
246
+ if (sig?.kind !== "list" || sig.items[0]?.kind !== "sym")
247
+ return fail("expected (name params...)", sig?.line ?? line);
248
+ const [name, ...rest] = sig.items;
249
+ const tmp = rest.findIndex((n) => n.kind === "sym" && n.name === "&tmp");
250
+ const params = (tmp < 0 ? rest : rest.slice(0, tmp)).map((n) => (n.kind === "sym" ? n.name : fail(`expected a parameter name, got ${show(n)}`, n.line)));
251
+ const temps = tmp < 0 ? [] : variables(rest.slice(tmp + 1), fail);
252
+ for (const t of temps)
253
+ if (t.init.length)
254
+ fail("temporaries can't have initial values", t.line);
255
+ return { name: name.name, params, temps, body, line: sig.line };
256
+ }
257
+ function object(kind, rest, line, fail) {
258
+ const [name, ...more] = rest;
259
+ if (name?.kind !== "sym")
260
+ return fail(`expected (${kind} Name of Class ...)`, line);
261
+ const o = { kind: kind, name: name.name, props: [], methods: [], line, species: 0xffff };
262
+ let i = 0;
263
+ if (more[0]?.kind === "sym" && (more[0].name === "of" || more[0].name === "kindof")) {
264
+ if (more[1]?.kind !== "sym")
265
+ fail(`${o.name}: expected a class name after ${more[0].name}`, line);
266
+ o.superName = more[1].name;
267
+ i = 2;
268
+ }
269
+ else if (kind === "instance")
270
+ fail(`instance ${o.name} needs "of Class"`, line);
271
+ for (const n of more.slice(i)) {
272
+ if (n.kind !== "list" || n.items[0]?.kind !== "sym")
273
+ fail(`${o.name}: expected (properties ...) or (method ...), got ${show(n)}`, n.line);
274
+ const [head, ...body] = n.items;
275
+ if (head.name === "properties") {
276
+ for (let k = 0; k < body.length; k += 2) {
277
+ const p = body[k];
278
+ if (p.kind !== "sym")
279
+ fail(`expected a property name, got ${show(p)}`, p.line);
280
+ o.props.push({ name: p.name, value: body[k + 1] ?? fail(`property ${p.name} needs a value`, p.line), line: p.line });
281
+ }
282
+ }
283
+ else if (head.name === "method") {
284
+ const m = func(body[0], body.slice(1), n.line, fail);
285
+ if (o.methods.some((x) => x.name === m.name))
286
+ fail(`${o.name} has two ${m.name} methods`, n.line);
287
+ o.methods.push(m);
288
+ }
289
+ else
290
+ fail(`${o.name}: expected (properties ...) or (method ...), got (${head.name} ...)`, n.line);
291
+ }
292
+ return o;
293
+ }
294
+ /** A constant expression: numbers, defines and arithmetic on them. */
295
+ function constValue(n, defines, fail, depth = 0) {
296
+ if (depth > 50)
297
+ fail("a define refers to itself");
298
+ if (n.kind === "num")
299
+ return n.value;
300
+ if (n.kind === "sym") {
301
+ if (n.name in BUILTIN_DEFINES)
302
+ return BUILTIN_DEFINES[n.name];
303
+ const d = defines.get(n.name);
304
+ if (!d)
305
+ return fail(`${n.name} isn't a constant`);
306
+ return constValue(d, defines, fail, depth + 1);
307
+ }
308
+ if (n.kind === "list" && n.items[0]?.kind === "sym") {
309
+ const op = n.items[0].name;
310
+ const args = n.items.slice(1).map((a) => constValue(a, defines, fail, depth + 1));
311
+ const fold = (f) => args.reduce(f);
312
+ switch (op) {
313
+ case "+": return fold((a, b) => a + b);
314
+ case "-": return args.length === 1 ? -args[0] : fold((a, b) => a - b);
315
+ case "*": return fold((a, b) => a * b);
316
+ case "/": return fold((a, b) => Math.trunc(a / b));
317
+ case "|": return fold((a, b) => a | b);
318
+ case "&": return fold((a, b) => a & b);
319
+ case "^": return fold((a, b) => a ^ b);
320
+ case "<<": return fold((a, b) => a << b);
321
+ case ">>": return fold((a, b) => a >> b);
322
+ case "~": return ~args[0] & 0xffff;
323
+ }
324
+ }
325
+ return fail(`${show(n)} isn't a constant`);
326
+ }
327
+ const SEND_SELECTOR = /^([A-Za-z_-][\w-]*)[:?]$/;
328
+ /** A message in a send starts with a selector (`x:`, `x?`) or `[expr]`, a selector computed at run time. */
329
+ const isMessageHead = (n) => (n?.kind === "sym" && SEND_SELECTOR.test(n.name)) || (n?.kind === "index" && n.items.length === 1);
330
+ const BINARY = {
331
+ "+": "add", "-": "sub", "*": "mul", "/": "div", mod: "mod", "<<": "shl", ">>": "shr", "&": "and", "|": "or", "^": "xor",
332
+ };
333
+ const COMPARE = {
334
+ "==": "eq?", "!=": "ne?", "<": "lt?", ">": "gt?", "<=": "le?", ">=": "ge?", "u<": "ult?", "u>": "ugt?", "u<=": "ule?", "u>=": "uge?",
335
+ };
336
+ const COMPOUND = { "+=": "+", "-=": "-", "*=": "*", "/=": "/", "|=": "|", "&=": "&", "^=": "^", "<<=": "<<", ">>=": ">>" };
337
+ class ScriptCompiler {
338
+ unit;
339
+ project;
340
+ out = [];
341
+ strings = new Map();
342
+ labels = 0;
343
+ fn;
344
+ locals = new Map();
345
+ procedures;
346
+ objects;
347
+ constructor(unit, project) {
348
+ this.unit = unit;
349
+ this.project = project;
350
+ let i = 0;
351
+ for (const v of unit.locals) {
352
+ if (this.locals.has(v.name))
353
+ this.fail(`variable ${v.name} is declared twice`, v.line);
354
+ this.locals.set(v.name, { index: i, size: v.size });
355
+ i += v.size;
356
+ }
357
+ this.procedures = new Set(unit.procedures.map((p) => p.name));
358
+ this.objects = new Map(unit.objects.map((o) => [o.name, o]));
359
+ }
360
+ fail(message, line) {
361
+ throw new CompileError(message, this.unit.file, line);
362
+ }
363
+ compile() {
364
+ const u = this.unit;
365
+ const head = [`; compiled from ${u.file}`, `script ${u.number}`];
366
+ // Locals: initial values (numbers, strings, objects in this script).
367
+ const localValues = [];
368
+ for (const v of u.locals) {
369
+ for (let k = 0; k < v.size; k++)
370
+ localValues.push(v.init[k] ? this.initValue(v.init[k]) : "0");
371
+ }
372
+ if (localValues.length)
373
+ head.push(`locals ${localValues.join(" ")}`);
374
+ // Exports.
375
+ const exports = [];
376
+ for (const p of u.publics) {
377
+ if (!this.procedures.has(p.name) && !this.objects.has(p.name))
378
+ this.fail(`public ${p.name} is neither a procedure nor an object here`, p.line);
379
+ if (exports[p.index] !== undefined)
380
+ this.fail(`export ${p.index} is used twice`, p.line);
381
+ exports[p.index] = p.name;
382
+ }
383
+ if (exports.length)
384
+ head.push("exports", ...Array.from(exports, (e) => ` ${e ?? 0}`));
385
+ // Objects.
386
+ const body = [];
387
+ for (const o of u.objects)
388
+ body.push(...this.objectLines(o));
389
+ // Code.
390
+ const code = [];
391
+ for (const o of u.objects) {
392
+ const owner = this.ownerOf(o);
393
+ for (const m of o.methods)
394
+ code.push(...this.function(m, `${o.name}::${m.name}`, owner));
395
+ }
396
+ for (const p of u.procedures)
397
+ code.push(...this.function(p, p.name, undefined));
398
+ const strings = [...this.strings].map(([text, label]) => ` ${label} ${quote(text)}`);
399
+ return [...head, ...body, ...(strings.length ? ["strings", ...strings] : []), "code", ...code, ""].join("\n");
400
+ }
401
+ /** A class's layout: inherited properties first, then its own. */
402
+ layout(name, line) {
403
+ const cached = this.project.layouts.get(name);
404
+ if (cached)
405
+ return cached;
406
+ const local = this.project.classes.get(name);
407
+ let layout;
408
+ if (local) {
409
+ const { obj } = local;
410
+ const props = obj.superName ? this.layout(obj.superName, obj.line).props.map((p) => ({ ...p })) : [];
411
+ for (const p of obj.props) {
412
+ if (OBJECT_HEADER.includes(p.name))
413
+ continue;
414
+ const inherited = props.find((x) => x.name === p.name);
415
+ if (inherited)
416
+ inherited.value = p.value;
417
+ else
418
+ props.push({ name: p.name, value: p.value });
419
+ }
420
+ layout = { species: obj.species, props };
421
+ }
422
+ else {
423
+ const ext = this.project.ctx.externalClass?.(name);
424
+ if (!ext)
425
+ this.fail(`unknown class ${name}`, line);
426
+ layout = { species: ext.species, props: ext.properties.map((p) => ({ ...p })) };
427
+ }
428
+ this.project.layouts.set(name, layout);
429
+ return layout;
430
+ }
431
+ ownerOf(o) {
432
+ const layout = this.objectLayout(o);
433
+ const superSpecies = o.kind === "instance" ? this.layout(o.superName, o.line).species : o.superName ? this.layout(o.superName, o.line).species : undefined;
434
+ return { obj: o, layout: [...OBJECT_HEADER, ...layout.map((p) => p.name)], superSpecies };
435
+ }
436
+ /** An object's properties after the header, with the values it gives them. */
437
+ objectLayout(o) {
438
+ if (o.kind === "class")
439
+ return this.layout(o.name, o.line).props;
440
+ const props = this.layout(o.superName, o.line).props.map((p) => ({ ...p }));
441
+ for (const p of o.props) {
442
+ if (p.name === "name")
443
+ continue;
444
+ const slot = props.find((x) => x.name === p.name);
445
+ if (!slot)
446
+ this.fail(`${o.name}: ${o.superName} has no property ${p.name}`, p.line);
447
+ slot.value = p.value;
448
+ }
449
+ return props;
450
+ }
451
+ objectLines(o) {
452
+ const lines = [""];
453
+ const own = o.props.find((p) => p.name === "name");
454
+ const name = own ? this.initValue(own.value) : `@${this.string(o.name)}`;
455
+ if (o.kind === "class") {
456
+ const sup = o.superName ? `$${this.layout(o.superName, o.line).species.toString(16)}` : "-";
457
+ lines.push(`class ${o.name} of ${sup} species ${o.species}${o.superName ? ` ; ${o.superName}` : ""}`);
458
+ lines.push(` header ${OBJECT_HEADER.join(" ")}`, ` name ${name}`);
459
+ for (const p of this.objectLayout(o))
460
+ lines.push(` prop ${p.name} ${this.propValue(p.value)}`);
461
+ }
462
+ else {
463
+ const layout = this.objectLayout(o);
464
+ lines.push(`instance ${o.name} of $${this.layout(o.superName, o.line).species.toString(16)} ; ${o.superName}`);
465
+ lines.push(` layout ${[...OBJECT_HEADER, ...layout.map((p) => p.name)].join(" ")}`, ` name ${name}`);
466
+ for (const p of layout)
467
+ lines.push(` ${p.name} ${this.propValue(p.value)}`);
468
+ }
469
+ for (const m of o.methods)
470
+ lines.push(` method ${m.name} ${o.name}::${m.name}`);
471
+ return lines;
472
+ }
473
+ propValue(v) {
474
+ return typeof v === "number" ? String(toWord(v)) : this.initValue(v);
475
+ }
476
+ /** A value fixed when the script loads: a number, a string, a selector, or an object in this script. */
477
+ initValue(n) {
478
+ if (n.kind === "str")
479
+ return `@${this.string(n.value)}`;
480
+ if (n.kind === "sel") {
481
+ this.project.sent.push({ name: n.name, file: this.unit.file, line: n.line });
482
+ return `#${n.name}`;
483
+ }
484
+ if (n.kind === "sym" && this.objects.has(n.name) && this.objects.get(n.name).kind === "instance")
485
+ return `@${n.name}`;
486
+ return String(toWord(constValue(n, this.unit.defines, (m) => this.fail(m, n.line))));
487
+ }
488
+ string(text) {
489
+ let label = this.strings.get(text);
490
+ if (!label)
491
+ this.strings.set(text, (label = `s_${this.strings.size}`));
492
+ return label;
493
+ }
494
+ label() {
495
+ return `.L${++this.labels}`;
496
+ }
497
+ // --- Functions ---
498
+ function(f, label, owner) {
499
+ this.out.length = 0;
500
+ const params = new Map(f.params.map((p, i) => [p, i + 1]));
501
+ const temps = new Map();
502
+ let size = 0;
503
+ for (const t of f.temps)
504
+ (temps.set(t.name, { index: size, size: t.size }), (size += t.size));
505
+ this.fn = { func: f, owner, params, temps, loops: [], switches: 0 };
506
+ this.emit(`${label}:`);
507
+ if (size)
508
+ this.emit(` link ${size}`);
509
+ this.body(f.body);
510
+ this.emit(" ret");
511
+ this.fn = undefined;
512
+ return ["", ...this.out];
513
+ }
514
+ emit(line) {
515
+ this.out.push(line);
516
+ }
517
+ op(line) {
518
+ this.out.push(` ${line}`);
519
+ }
520
+ body(nodes) {
521
+ for (const n of nodes)
522
+ this.expr(n);
523
+ }
524
+ /** What a name means here: variables first, then properties, then everything else. */
525
+ resolve(name, line) {
526
+ const fn = this.fn;
527
+ const temp = fn.temps.get(name);
528
+ if (temp)
529
+ return { kind: "var", type: "t", index: temp.index, size: temp.size };
530
+ const param = fn.params.get(name);
531
+ if (param !== undefined)
532
+ return { kind: "var", type: "p", index: param, size: 1 };
533
+ if (fn.owner?.layout.includes(name))
534
+ return { kind: "prop", name };
535
+ const local = this.locals.get(name);
536
+ if (local)
537
+ return { kind: "var", type: this.unit.number === 0 ? "g" : "l", ...local };
538
+ const global = this.project.globals.get(name);
539
+ if (global)
540
+ return { kind: "var", type: "g", ...global };
541
+ if (name === "self")
542
+ return { kind: "self" };
543
+ if (name === "argc")
544
+ return { kind: "argc" };
545
+ const define = this.unit.defines.get(name);
546
+ if (define)
547
+ return { kind: "const", node: define };
548
+ if (name in BUILTIN_DEFINES)
549
+ return { kind: "const", node: { kind: "num", value: BUILTIN_DEFINES[name], line } };
550
+ const obj = this.objects.get(name);
551
+ if (obj?.kind === "instance")
552
+ return { kind: "object", label: name };
553
+ if (obj?.kind === "class" || this.project.classes.has(name) || this.project.ctx.externalClass?.(name)) {
554
+ return { kind: "class", species: this.layout(name, line).species, name };
555
+ }
556
+ return this.fail(`unknown name ${name}`, line);
557
+ }
558
+ varOp(action, stack, ref, indexed = false) {
559
+ // Script 0's own variables are the globals; elsewhere, its locals.
560
+ const type = ref.type === "g" && this.unit.number === 0 ? "l" : ref.type;
561
+ return `${action}${stack ? "s" : "a"}${type}${indexed ? "i" : ""} ${ref.index}`;
562
+ }
563
+ /** Evaluates into acc. */
564
+ expr(n) {
565
+ switch (n.kind) {
566
+ case "num": return this.op(`ldi ${toSigned(n.value)}`);
567
+ case "str": return this.op(`lofsa @${this.string(n.value)}`);
568
+ case "sel":
569
+ this.project.sent.push({ name: n.name, file: this.unit.file, line: n.line });
570
+ return this.op(`ldi #${n.name}`);
571
+ case "addr": return this.address(n.name, n.index, n.line);
572
+ case "index": {
573
+ const [arr, i] = n.items;
574
+ if (arr?.kind !== "sym" || !i || n.items.length !== 2)
575
+ this.fail("an array element is [name index]", n.line);
576
+ const ref = this.arrayRef(arr.name, n.line);
577
+ this.expr(i);
578
+ return this.op(this.varOp("l", false, ref, true));
579
+ }
580
+ case "sym": {
581
+ if (n.name === "&rest" || SEND_SELECTOR.test(n.name))
582
+ this.fail(`${n.name} only makes sense in a call or send`, n.line);
583
+ const ref = this.resolve(n.name, n.line);
584
+ switch (ref.kind) {
585
+ case "var": return this.op(this.varOp("l", false, ref));
586
+ case "prop": return this.op(`pToa ${ref.name}`);
587
+ case "const": return this.op(`ldi ${toSigned(constValue(ref.node, this.unit.defines, (m) => this.fail(m, n.line)))}`);
588
+ case "object": return this.op(`lofsa @${ref.label}`);
589
+ case "class": return this.op(`class $${ref.species.toString(16)}`);
590
+ case "self": return this.op("selfID");
591
+ case "argc": return this.op("lap 0");
592
+ }
593
+ return;
594
+ }
595
+ case "list": return this.form(n);
596
+ }
597
+ }
598
+ /** Pushes a value, in one instruction where there is one. */
599
+ push(n) {
600
+ if (n.kind === "num" || (n.kind === "sym" && this.isConst(n.name))) {
601
+ const v = n.kind === "num" ? n.value : constValue({ ...n }, this.unit.defines, (m) => this.fail(m, n.line));
602
+ const w = toWord(v);
603
+ return this.op(w === 0 ? "push0" : w === 1 ? "push1" : w === 2 ? "push2" : `pushi ${toSigned(w)}`);
604
+ }
605
+ if (n.kind === "sel") {
606
+ this.project.sent.push({ name: n.name, file: this.unit.file, line: n.line });
607
+ return this.op(`pushi #${n.name}`);
608
+ }
609
+ if (n.kind === "str")
610
+ return this.op(`lofss @${this.string(n.value)}`);
611
+ if (n.kind === "sym") {
612
+ const ref = this.resolve(n.name, n.line);
613
+ if (ref.kind === "var")
614
+ return this.op(this.varOp("l", true, ref));
615
+ if (ref.kind === "prop")
616
+ return this.op(`pTos ${ref.name}`);
617
+ if (ref.kind === "object")
618
+ return this.op(`lofss @${ref.label}`);
619
+ if (ref.kind === "self")
620
+ return this.op("pushSelf");
621
+ }
622
+ this.expr(n);
623
+ this.op("push");
624
+ }
625
+ isConst(name) {
626
+ if (!this.fn)
627
+ return true;
628
+ const ref = (() => {
629
+ try {
630
+ return this.resolve(name, 0);
631
+ }
632
+ catch {
633
+ return undefined;
634
+ }
635
+ })();
636
+ return ref?.kind === "const";
637
+ }
638
+ arrayRef(name, line) {
639
+ const ref = this.resolve(name, line);
640
+ if (ref.kind !== "var")
641
+ this.fail(`${name} isn't a variable, so it can't be indexed`, line);
642
+ return ref;
643
+ }
644
+ /** @name or @[name i]: the address of a variable, for kernels that write into it. */
645
+ address(name, index, line) {
646
+ const ref = this.arrayRef(name, line);
647
+ const kind = { g: 0, l: 1, t: 2, p: 3 }[ref.type === "g" && this.unit.number === 0 ? "l" : ref.type];
648
+ if (index)
649
+ this.expr(index);
650
+ this.op(`lea ${(kind | (index ? 8 : 0)) << 1} ${ref.index}`);
651
+ }
652
+ form(n) {
653
+ const [head, ...args] = n.items;
654
+ if (!head)
655
+ this.fail("() is empty", n.line);
656
+ // A send: (target sel: ...), or (target [s] ...) with the selector in s.
657
+ if (isMessageHead(args[0]))
658
+ return this.send(head, args, n.line);
659
+ if (head.kind !== "sym") {
660
+ if (head.kind === "list" || head.kind === "index")
661
+ this.fail(`expected a selector after ${show(head)} (a send)`, n.line);
662
+ this.fail(`can't call ${show(head)}`, n.line);
663
+ }
664
+ const name = head.name;
665
+ const arity = (min, max = min) => {
666
+ if (args.length < min || args.length > max)
667
+ this.fail(`(${name} ...) takes ${min === max ? min : `${min} to ${max === Infinity ? "any number of" : max}`} argument(s)`, n.line);
668
+ };
669
+ switch (name) {
670
+ case "send": {
671
+ if (!args[0])
672
+ this.fail("(send target sel: ...)", n.line);
673
+ return this.send(args[0], args.slice(1), n.line);
674
+ }
675
+ case "=":
676
+ arity(2);
677
+ return this.assign(args[0], args[1], n.line);
678
+ case "++":
679
+ case "--":
680
+ arity(1);
681
+ return this.increment(args[0], name === "++" ? "+" : "-", n.line);
682
+ case "not":
683
+ arity(1);
684
+ this.expr(args[0]);
685
+ return this.op("not");
686
+ case "~":
687
+ arity(1);
688
+ this.expr(args[0]);
689
+ return this.op("bnot");
690
+ case "and":
691
+ case "or": {
692
+ arity(1, Infinity);
693
+ const end = this.label();
694
+ args.forEach((a, i) => {
695
+ this.expr(a);
696
+ if (i < args.length - 1)
697
+ this.op(`${name === "and" ? "bnt" : "bt"} ${end}`);
698
+ });
699
+ return this.emit(`${end}:`);
700
+ }
701
+ case "if": return this.ifForm(args, n.line);
702
+ case "cond": return this.cond(args, n.line);
703
+ case "switch": return this.switchForm(args, n.line);
704
+ case "switchto": {
705
+ // (switchto value (body for 0...) (body for 1...) ...): cases by position.
706
+ if (!args.length)
707
+ this.fail("(switchto value (body...)...)", n.line);
708
+ const cases = args.slice(1).map((c, i) => {
709
+ if (c.kind !== "list")
710
+ this.fail("a switchto case is (body...)", c.line);
711
+ return { kind: "list", items: [{ kind: "num", value: i, line: c.line }, ...c.items], line: c.line };
712
+ });
713
+ return this.switchForm([args[0], ...cases], n.line);
714
+ }
715
+ case "while": {
716
+ arity(1, Infinity);
717
+ const top = this.label(), end = this.label();
718
+ this.emit(`${top}:`);
719
+ this.expr(args[0]);
720
+ this.op(`bnt ${end}`);
721
+ this.loop(top, end, () => this.body(args.slice(1)));
722
+ this.op(`jmp ${top}`);
723
+ return this.emit(`${end}:`);
724
+ }
725
+ case "repeat": {
726
+ const top = this.label(), end = this.label();
727
+ this.emit(`${top}:`);
728
+ this.loop(top, end, () => this.body(args));
729
+ this.op(`jmp ${top}`);
730
+ return this.emit(`${end}:`);
731
+ }
732
+ case "for": {
733
+ arity(3, Infinity);
734
+ const [init, test, step] = args;
735
+ const list = (x) => (x.kind === "list" && (x.items.length === 0 || x.items[0].kind === "list") ? x.items : [x]);
736
+ this.body(list(init));
737
+ const top = this.label(), next = this.label(), end = this.label();
738
+ this.emit(`${top}:`);
739
+ this.expr(test);
740
+ this.op(`bnt ${end}`);
741
+ this.loop(next, end, () => this.body(args.slice(3)));
742
+ this.emit(`${next}:`);
743
+ this.body(list(step));
744
+ this.op(`jmp ${top}`);
745
+ return this.emit(`${end}:`);
746
+ }
747
+ case "break":
748
+ case "continue": {
749
+ arity(0);
750
+ const loop = this.fn.loops[this.fn.loops.length - 1];
751
+ if (!loop)
752
+ this.fail(`(${name}) outside a loop`, n.line);
753
+ // Leave the values of switches entered since the loop began.
754
+ for (let i = loop.switches; i < this.fn.switches; i++)
755
+ this.op("toss");
756
+ return this.op(`jmp ${name === "break" ? loop.brk : loop.cont}`);
757
+ }
758
+ case "return":
759
+ arity(0, 1);
760
+ if (args[0])
761
+ this.expr(args[0]);
762
+ return this.op("ret");
763
+ }
764
+ if (name in COMPOUND) {
765
+ arity(2);
766
+ const [target, value] = args;
767
+ return this.assign(target, { kind: "list", items: [{ kind: "sym", name: COMPOUND[name], line: n.line }, target, value], line: n.line }, n.line);
768
+ }
769
+ if (name in BINARY) {
770
+ if (name === "-" && args.length === 1) {
771
+ this.expr(args[0]);
772
+ return this.op("neg");
773
+ }
774
+ arity(2, name === "-" || name === "/" || name === "mod" || name === "<<" || name === ">>" ? 2 : Infinity);
775
+ this.expr(args[0]);
776
+ for (const a of args.slice(1)) {
777
+ this.op("push");
778
+ this.expr(a);
779
+ this.op(BINARY[name]);
780
+ }
781
+ return;
782
+ }
783
+ if (name in COMPARE) {
784
+ arity(2);
785
+ this.push(args[0]);
786
+ this.expr(args[1]);
787
+ return this.op(COMPARE[name]);
788
+ }
789
+ return this.call(name, args, n.line);
790
+ }
791
+ loop(cont, brk, body) {
792
+ this.fn.loops.push({ brk, cont, switches: this.fn.switches });
793
+ body();
794
+ this.fn.loops.pop();
795
+ }
796
+ ifForm(args, line) {
797
+ if (!args.length)
798
+ this.fail("(if condition ...)", line);
799
+ const split = args.findIndex((a) => a.kind === "sym" && a.name === "else");
800
+ const then = split < 0 ? args.slice(1) : args.slice(1, split);
801
+ const otherwise = split < 0 ? undefined : args.slice(split + 1);
802
+ const elseLabel = this.label(), end = this.label();
803
+ this.expr(args[0]);
804
+ this.op(`bnt ${otherwise ? elseLabel : end}`);
805
+ this.body(then);
806
+ if (otherwise) {
807
+ this.op(`jmp ${end}`);
808
+ this.emit(`${elseLabel}:`);
809
+ this.body(otherwise);
810
+ }
811
+ this.emit(`${end}:`);
812
+ }
813
+ cond(clauses, line) {
814
+ const end = this.label();
815
+ for (const c of clauses) {
816
+ if (c.kind !== "list" || !c.items.length)
817
+ this.fail("a cond clause is (condition body...)", c.line ?? line);
818
+ const [test, ...body] = c.items;
819
+ if (test.kind === "sym" && test.name === "else") {
820
+ this.body(body);
821
+ break;
822
+ }
823
+ const next = this.label();
824
+ this.expr(test);
825
+ this.op(`bnt ${next}`);
826
+ this.body(body);
827
+ this.op(`jmp ${end}`);
828
+ this.emit(`${next}:`);
829
+ }
830
+ this.emit(`${end}:`);
831
+ }
832
+ switchForm(args, line) {
833
+ if (!args.length)
834
+ this.fail("(switch value (case body...)...)", line);
835
+ const end = this.label();
836
+ this.expr(args[0]);
837
+ this.op("push");
838
+ this.fn.switches++;
839
+ for (const c of args.slice(1)) {
840
+ if (c.kind !== "list" || !c.items.length)
841
+ this.fail("a switch case is (value body...)", c.line ?? line);
842
+ const [value, ...body] = c.items;
843
+ if (value.kind === "sym" && value.name === "else") {
844
+ this.body(body);
845
+ break;
846
+ }
847
+ const next = this.label();
848
+ this.op("dup");
849
+ this.expr(value);
850
+ this.op("eq?");
851
+ this.op(`bnt ${next}`);
852
+ this.body(body);
853
+ this.op(`jmp ${end}`);
854
+ this.emit(`${next}:`);
855
+ }
856
+ this.fn.switches--;
857
+ this.emit(`${end}:`);
858
+ this.op("toss");
859
+ }
860
+ assign(target, value, line) {
861
+ if (target.kind === "index") {
862
+ const [arr, i] = target.items;
863
+ if (arr?.kind !== "sym" || !i)
864
+ this.fail("an array element is [name index]", line);
865
+ const ref = this.arrayRef(arr.name, line);
866
+ this.push(value);
867
+ this.expr(i);
868
+ return this.op(this.varOp("s", false, ref, true));
869
+ }
870
+ if (target.kind !== "sym")
871
+ this.fail(`can't assign to ${show(target)}`, line);
872
+ const ref = this.resolve(target.name, line);
873
+ this.expr(value);
874
+ if (ref.kind === "var")
875
+ return this.op(this.varOp("s", false, ref));
876
+ if (ref.kind === "prop")
877
+ return this.op(`aTop ${ref.name}`);
878
+ this.fail(`can't assign to ${target.name}`, line);
879
+ }
880
+ increment(target, dir, line) {
881
+ if (target.kind === "index") {
882
+ const [arr, i] = target.items;
883
+ if (arr?.kind !== "sym" || !i)
884
+ this.fail("an array element is [name index]", line);
885
+ const ref = this.arrayRef(arr.name, line);
886
+ this.expr(i);
887
+ return this.op(this.varOp(dir, false, ref, true));
888
+ }
889
+ if (target.kind !== "sym")
890
+ this.fail(`can't change ${show(target)}`, line);
891
+ const ref = this.resolve(target.name, line);
892
+ if (ref.kind === "var")
893
+ return this.op(this.varOp(dir, false, ref));
894
+ if (ref.kind === "prop")
895
+ return this.op(`${dir === "+" ? "ipToa" : "dpToa"} ${ref.name}`);
896
+ this.fail(`can't change ${target.name}`, line);
897
+ }
898
+ /**
899
+ * Splits arguments from a trailing `&rest` (the caller's arguments after its named ones) or
900
+ * `&rest param` (from that parameter on).
901
+ */
902
+ restSplit(args, line) {
903
+ const at = args.findIndex((a) => a.kind === "sym" && a.name === "&rest");
904
+ if (at < 0)
905
+ return { explicit: args };
906
+ const from = args[at + 1];
907
+ const fromParam = from?.kind === "sym" ? this.fn.params.get(from.name) : undefined;
908
+ if (args.length > at + (fromParam === undefined ? 1 : 2))
909
+ this.fail("&rest goes last", line);
910
+ return { explicit: args.slice(0, at), rest: fromParam ?? this.fn.func.params.length + 1 };
911
+ }
912
+ /** Pushes arguments; returns the frame size in bytes (what &rest adds is counted at run time). */
913
+ pushArgs(args) {
914
+ for (const a of args.explicit)
915
+ this.push(a);
916
+ if (args.rest !== undefined)
917
+ this.op(`&rest ${args.rest}`);
918
+ return args.explicit.length * 2;
919
+ }
920
+ send(target, messages, line) {
921
+ let frame = 0;
922
+ for (let i = 0; i < messages.length;) {
923
+ const sel = messages[i];
924
+ if (!isMessageHead(sel))
925
+ this.fail(`expected a selector like x: or init:, got ${show(sel)}`, sel.line);
926
+ let j = i + 1;
927
+ while (j < messages.length && !isMessageHead(messages[j]))
928
+ j++;
929
+ const args = this.restSplit(messages.slice(i + 1, j), sel.line);
930
+ if (sel.kind === "sym") {
931
+ const name = SEND_SELECTOR.exec(sel.name)[1];
932
+ this.project.sent.push({ name, file: this.unit.file, line: sel.line });
933
+ this.op(`pushi #${name}`);
934
+ }
935
+ else
936
+ this.push(sel.items[0]);
937
+ this.push({ kind: "num", value: args.explicit.length, line });
938
+ frame += 4 + this.pushArgs(args);
939
+ i = j;
940
+ }
941
+ if (!messages.length)
942
+ this.fail("a send needs at least one selector", line);
943
+ if (target.kind === "sym" && target.name === "self")
944
+ return this.op(`self ${frame}`);
945
+ if (target.kind === "sym" && target.name === "super") {
946
+ const sup = this.fn.owner?.superSpecies;
947
+ if (sup === undefined)
948
+ this.fail("super: this object has no superclass", line);
949
+ return this.op(`super $${sup.toString(16)} ${frame}`);
950
+ }
951
+ this.expr(target);
952
+ this.op(`send ${frame}`);
953
+ }
954
+ call(name, args, line) {
955
+ const local = this.procedures.has(name);
956
+ const ext = local ? undefined : this.unit.externs.get(name) ?? this.project.publicProcs.get(name);
957
+ const kernel = local || ext ? -1 : this.project.ctx.kernelNames.indexOf(name);
958
+ if (!local && !ext && kernel < 0) {
959
+ if (this.objects.has(name) || this.project.classes.has(name))
960
+ this.fail(`${name} is an object: send it a message, like (${name} init:)`, line);
961
+ this.fail(`unknown procedure or kernel ${name}`, line);
962
+ }
963
+ const split = this.restSplit(args, line);
964
+ this.push({ kind: "num", value: split.explicit.length, line });
965
+ const frame = this.pushArgs(split);
966
+ if (local)
967
+ this.op(`call ${name} ${frame}`);
968
+ else if (ext)
969
+ this.op(ext.script === 0 ? `callb ${ext.index} ${frame}` : `calle ${ext.script} ${ext.index} ${frame}`);
970
+ else
971
+ this.op(`callk ${name} ${frame}`);
972
+ }
973
+ }
974
+ const toWord = (v) => ((v % 0x10000) + 0x10000) % 0x10000;
975
+ const toSigned = (v) => {
976
+ const w = toWord(v);
977
+ return w >= 0x8000 ? w - 0x10000 : w;
978
+ };
979
+ function quote(text) {
980
+ let s = '"';
981
+ for (const ch of text) {
982
+ const c = ch.charCodeAt(0);
983
+ if (ch === '"' || ch === "\\")
984
+ s += `\\${ch}`;
985
+ else if (ch === "\n")
986
+ s += "\\n";
987
+ else if (c >= 0x20 && c < 0x7f)
988
+ s += ch;
989
+ else
990
+ s += `\\x${(c & 0xff).toString(16).padStart(2, "0")}`;
991
+ }
992
+ return `${s}"`;
993
+ }