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