redscript-mc 1.2.30 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (593) hide show
  1. package/.claudeignore +21 -0
  2. package/.github/workflows/ci.yml +1 -0
  3. package/README.md +12 -16
  4. package/README.zh.md +2 -2
  5. package/demo.gif +0 -0
  6. package/dist/cli.js +2 -554
  7. package/dist/compile.js +2 -266
  8. package/dist/index.js +2 -159
  9. package/dist/src/__tests__/budget.test.js +261 -0
  10. package/dist/src/__tests__/cli.test.js +104 -0
  11. package/dist/{__tests__ → src/__tests__}/dce.test.js +11 -47
  12. package/dist/{__tests__ → src/__tests__}/diagnostics.test.js +67 -40
  13. package/dist/src/__tests__/e2e/basic.test.d.ts +8 -0
  14. package/dist/src/__tests__/e2e/basic.test.js +140 -0
  15. package/dist/src/__tests__/e2e/coroutine.test.d.ts +7 -0
  16. package/dist/src/__tests__/e2e/coroutine.test.js +132 -0
  17. package/dist/src/__tests__/e2e/macros.test.d.ts +9 -0
  18. package/dist/src/__tests__/e2e/macros.test.js +182 -0
  19. package/dist/src/__tests__/e2e/migrate.test.d.ts +13 -0
  20. package/dist/src/__tests__/e2e/migrate.test.js +2739 -0
  21. package/dist/src/__tests__/e2e/stdlib-e2e.test.d.ts +10 -0
  22. package/dist/src/__tests__/e2e/stdlib-e2e.test.js +324 -0
  23. package/dist/src/__tests__/enum.test.d.ts +10 -0
  24. package/dist/src/__tests__/enum.test.js +389 -0
  25. package/dist/src/__tests__/generics.test.d.ts +14 -0
  26. package/dist/src/__tests__/generics.test.js +367 -0
  27. package/dist/src/__tests__/hir/desugar.test.js +234 -0
  28. package/dist/src/__tests__/incremental.test.d.ts +5 -0
  29. package/dist/src/__tests__/incremental.test.js +308 -0
  30. package/dist/src/__tests__/lir/lower.test.js +559 -0
  31. package/dist/src/__tests__/lir/types.test.js +185 -0
  32. package/dist/src/__tests__/lir/verify.test.js +221 -0
  33. package/dist/src/__tests__/lsp.test.d.ts +7 -0
  34. package/dist/src/__tests__/lsp.test.js +245 -0
  35. package/dist/{__tests__ → src/__tests__}/mc-integration.test.js +1 -3
  36. package/dist/src/__tests__/mc-version.test.d.ts +10 -0
  37. package/dist/src/__tests__/mc-version.test.js +154 -0
  38. package/dist/src/__tests__/mir/arithmetic.test.js +130 -0
  39. package/dist/src/__tests__/mir/control-flow.test.js +205 -0
  40. package/dist/src/__tests__/mir/verify.test.js +223 -0
  41. package/dist/src/__tests__/modules.test.d.ts +7 -0
  42. package/dist/src/__tests__/modules.test.js +333 -0
  43. package/dist/src/__tests__/optimizer/block_merge.test.js +78 -0
  44. package/dist/src/__tests__/optimizer/branch_simplify.test.js +58 -0
  45. package/dist/src/__tests__/optimizer/constant_fold.test.js +131 -0
  46. package/dist/src/__tests__/optimizer/copy_prop.test.js +91 -0
  47. package/dist/src/__tests__/optimizer/coroutine.test.d.ts +12 -0
  48. package/dist/src/__tests__/optimizer/coroutine.test.js +251 -0
  49. package/dist/src/__tests__/optimizer/dce.test.d.ts +1 -0
  50. package/dist/src/__tests__/optimizer/dce.test.js +76 -0
  51. package/dist/src/__tests__/optimizer/interprocedural.test.d.ts +1 -0
  52. package/dist/src/__tests__/optimizer/interprocedural.test.js +145 -0
  53. package/dist/src/__tests__/optimizer/lir/const_imm.test.d.ts +1 -0
  54. package/dist/src/__tests__/optimizer/lir/const_imm.test.js +138 -0
  55. package/dist/src/__tests__/optimizer/lir/dead_slot.test.d.ts +1 -0
  56. package/dist/src/__tests__/optimizer/lir/dead_slot.test.js +141 -0
  57. package/dist/src/__tests__/optimizer/lir/peephole.test.d.ts +1 -0
  58. package/dist/src/__tests__/optimizer/lir/peephole.test.js +126 -0
  59. package/dist/src/__tests__/optimizer/lir/pipeline.test.d.ts +1 -0
  60. package/dist/src/__tests__/optimizer/lir/pipeline.test.js +84 -0
  61. package/dist/src/__tests__/optimizer/nbt-batch.test.d.ts +1 -0
  62. package/dist/src/__tests__/optimizer/nbt-batch.test.js +110 -0
  63. package/dist/src/__tests__/optimizer/pipeline.test.d.ts +1 -0
  64. package/dist/src/__tests__/optimizer/pipeline.test.js +102 -0
  65. package/dist/src/__tests__/optimizer/selector-cache.test.d.ts +1 -0
  66. package/dist/src/__tests__/optimizer/selector-cache.test.js +103 -0
  67. package/dist/src/__tests__/optimizer/unroll.test.d.ts +1 -0
  68. package/dist/src/__tests__/optimizer/unroll.test.js +206 -0
  69. package/dist/src/__tests__/option.test.d.ts +14 -0
  70. package/dist/src/__tests__/option.test.js +275 -0
  71. package/dist/src/__tests__/parser.test.d.ts +1 -0
  72. package/dist/src/__tests__/repl.test.d.ts +1 -0
  73. package/dist/src/__tests__/schedule.test.d.ts +7 -0
  74. package/dist/src/__tests__/schedule.test.js +98 -0
  75. package/dist/src/__tests__/sourcemap.test.d.ts +7 -0
  76. package/dist/src/__tests__/sourcemap.test.js +227 -0
  77. package/dist/src/__tests__/tuple.test.d.ts +11 -0
  78. package/dist/src/__tests__/tuple.test.js +202 -0
  79. package/dist/src/__tests__/typechecker-strict.test.d.ts +10 -0
  80. package/dist/src/__tests__/typechecker-strict.test.js +197 -0
  81. package/dist/src/__tests__/typechecker.test.d.ts +1 -0
  82. package/dist/{ast → src/ast}/types.d.ts +58 -3
  83. package/dist/src/cache/deps.d.ts +41 -0
  84. package/dist/src/cache/deps.js +158 -0
  85. package/dist/src/cache/incremental.d.ts +35 -0
  86. package/dist/src/cache/incremental.js +165 -0
  87. package/dist/src/cache/index.d.ts +37 -0
  88. package/dist/src/cache/index.js +152 -0
  89. package/dist/{cli.d.ts → src/cli.d.ts} +1 -1
  90. package/dist/src/cli.js +474 -0
  91. package/dist/src/compile.d.ts +37 -0
  92. package/dist/src/compile.js +165 -0
  93. package/dist/{diagnostics → src/diagnostics}/index.d.ts +1 -1
  94. package/dist/{diagnostics → src/diagnostics}/index.js +8 -11
  95. package/dist/src/emit/compile.d.ts +29 -0
  96. package/dist/src/emit/compile.js +143 -0
  97. package/dist/src/emit/index.d.ts +26 -0
  98. package/dist/src/emit/index.js +223 -0
  99. package/dist/src/emit/modules.d.ts +29 -0
  100. package/dist/src/emit/modules.js +492 -0
  101. package/dist/src/emit/sourcemap.d.ts +53 -0
  102. package/dist/src/emit/sourcemap.js +73 -0
  103. package/dist/src/hir/lower.d.ts +15 -0
  104. package/dist/src/hir/lower.js +399 -0
  105. package/dist/src/hir/monomorphize.d.ts +22 -0
  106. package/dist/src/hir/monomorphize.js +379 -0
  107. package/dist/src/hir/types.d.ts +406 -0
  108. package/dist/src/hir/types.js +16 -0
  109. package/dist/src/index.d.ts +39 -0
  110. package/dist/src/index.js +67 -0
  111. package/dist/{lexer → src/lexer}/index.d.ts +1 -1
  112. package/dist/{lexer → src/lexer}/index.js +1 -0
  113. package/dist/src/lir/budget.d.ts +37 -0
  114. package/dist/src/lir/budget.js +280 -0
  115. package/dist/src/lir/lower.d.ts +15 -0
  116. package/dist/src/lir/lower.js +472 -0
  117. package/dist/src/lir/types.d.ts +139 -0
  118. package/dist/src/lir/types.js +11 -0
  119. package/dist/src/lir/verify.d.ts +14 -0
  120. package/dist/src/lir/verify.js +113 -0
  121. package/dist/src/lsp/main.d.ts +8 -0
  122. package/dist/src/lsp/main.js +11 -0
  123. package/dist/src/lsp/server.d.ts +11 -0
  124. package/dist/src/lsp/server.js +352 -0
  125. package/dist/{mc-test → src/mc-test}/runner.js +4 -3
  126. package/dist/src/mir/lower.d.ts +9 -0
  127. package/dist/src/mir/lower.js +1264 -0
  128. package/dist/src/mir/macro.d.ts +22 -0
  129. package/dist/src/mir/macro.js +168 -0
  130. package/dist/src/mir/types.d.ts +191 -0
  131. package/dist/src/mir/types.js +11 -0
  132. package/dist/src/mir/verify.d.ts +16 -0
  133. package/dist/src/mir/verify.js +216 -0
  134. package/dist/src/optimizer/block_merge.d.ts +12 -0
  135. package/dist/src/optimizer/block_merge.js +84 -0
  136. package/dist/src/optimizer/branch_simplify.d.ts +9 -0
  137. package/dist/src/optimizer/branch_simplify.js +28 -0
  138. package/dist/src/optimizer/constant_fold.d.ts +10 -0
  139. package/dist/src/optimizer/constant_fold.js +85 -0
  140. package/dist/src/optimizer/copy_prop.d.ts +9 -0
  141. package/dist/src/optimizer/copy_prop.js +113 -0
  142. package/dist/src/optimizer/coroutine.d.ts +34 -0
  143. package/dist/src/optimizer/coroutine.js +789 -0
  144. package/dist/src/optimizer/dce.d.ts +8 -0
  145. package/dist/src/optimizer/dce.js +156 -0
  146. package/dist/src/optimizer/interprocedural.d.ts +14 -0
  147. package/dist/src/optimizer/interprocedural.js +186 -0
  148. package/dist/src/optimizer/lir/const_imm.d.ts +12 -0
  149. package/dist/src/optimizer/lir/const_imm.js +139 -0
  150. package/dist/src/optimizer/lir/dead_slot.d.ts +14 -0
  151. package/dist/src/optimizer/lir/dead_slot.js +130 -0
  152. package/dist/src/optimizer/lir/peephole.d.ts +21 -0
  153. package/dist/src/optimizer/lir/peephole.js +52 -0
  154. package/dist/src/optimizer/lir/pipeline.d.ts +10 -0
  155. package/dist/src/optimizer/lir/pipeline.js +34 -0
  156. package/dist/src/optimizer/nbt-batch.d.ts +11 -0
  157. package/dist/src/optimizer/nbt-batch.js +51 -0
  158. package/dist/src/optimizer/pipeline.d.ts +14 -0
  159. package/dist/src/optimizer/pipeline.js +58 -0
  160. package/dist/src/optimizer/selector-cache.d.ts +22 -0
  161. package/dist/src/optimizer/selector-cache.js +100 -0
  162. package/dist/src/optimizer/unroll.d.ts +32 -0
  163. package/dist/src/optimizer/unroll.js +348 -0
  164. package/dist/{parser → src/parser}/index.d.ts +8 -0
  165. package/dist/{parser → src/parser}/index.js +204 -14
  166. package/dist/{repl.d.ts → src/repl.d.ts} +1 -1
  167. package/dist/{runtime → src/runtime}/index.js +1 -1
  168. package/dist/{typechecker → src/typechecker}/index.d.ts +4 -0
  169. package/dist/{typechecker → src/typechecker}/index.js +198 -13
  170. package/dist/src/types/mc-version.d.ts +24 -0
  171. package/dist/src/types/mc-version.js +49 -0
  172. package/docs/ROADMAP.md +395 -0
  173. package/docs/compiler-pipeline-redesign.md +2260 -0
  174. package/docs/optimization-ideas.md +1076 -0
  175. package/editors/vscode/out/extension.js +25176 -8000
  176. package/editors/vscode/package-lock.json +90 -6
  177. package/editors/vscode/package.json +3 -2
  178. package/editors/vscode/src/extension.ts +97 -67
  179. package/examples/showcase.mcrs +3 -3
  180. package/package.json +13 -6
  181. package/scripts/postbuild.js +15 -0
  182. package/src/__tests__/budget.test.ts +297 -0
  183. package/src/__tests__/cli.test.ts +8 -220
  184. package/src/__tests__/dce.test.ts +11 -56
  185. package/src/__tests__/diagnostics.test.ts +61 -41
  186. package/src/__tests__/e2e/basic.test.ts +154 -0
  187. package/src/__tests__/e2e/coroutine.test.ts +142 -0
  188. package/src/__tests__/e2e/macros.test.ts +199 -0
  189. package/src/__tests__/e2e/migrate.test.ts +3008 -0
  190. package/src/__tests__/e2e/stdlib-e2e.test.ts +348 -0
  191. package/src/__tests__/enum.test.ts +425 -0
  192. package/src/__tests__/generics.test.ts +390 -0
  193. package/src/__tests__/hir/desugar.test.ts +263 -0
  194. package/src/__tests__/incremental.test.ts +337 -0
  195. package/src/__tests__/lir/lower.test.ts +619 -0
  196. package/src/__tests__/lir/types.test.ts +207 -0
  197. package/src/__tests__/lir/verify.test.ts +249 -0
  198. package/src/__tests__/lsp.test.ts +270 -0
  199. package/src/__tests__/mc-integration.test.ts +1 -2
  200. package/src/__tests__/mc-version.test.ts +178 -0
  201. package/src/__tests__/mir/arithmetic.test.ts +156 -0
  202. package/src/__tests__/mir/control-flow.test.ts +242 -0
  203. package/src/__tests__/mir/verify.test.ts +254 -0
  204. package/src/__tests__/modules.test.ts +365 -0
  205. package/src/__tests__/optimizer/block_merge.test.ts +84 -0
  206. package/src/__tests__/optimizer/branch_simplify.test.ts +64 -0
  207. package/src/__tests__/optimizer/constant_fold.test.ts +145 -0
  208. package/src/__tests__/optimizer/copy_prop.test.ts +99 -0
  209. package/src/__tests__/optimizer/coroutine.test.ts +312 -0
  210. package/src/__tests__/optimizer/dce.test.ts +83 -0
  211. package/src/__tests__/optimizer/interprocedural.test.ts +174 -0
  212. package/src/__tests__/optimizer/lir/const_imm.test.ts +151 -0
  213. package/src/__tests__/optimizer/lir/dead_slot.test.ts +156 -0
  214. package/src/__tests__/optimizer/lir/peephole.test.ts +136 -0
  215. package/src/__tests__/optimizer/lir/pipeline.test.ts +113 -0
  216. package/src/__tests__/optimizer/nbt-batch.test.ts +119 -0
  217. package/src/__tests__/optimizer/pipeline.test.ts +116 -0
  218. package/src/__tests__/optimizer/selector-cache.test.ts +112 -0
  219. package/src/__tests__/optimizer/unroll.test.ts +231 -0
  220. package/src/__tests__/option.test.ts +299 -0
  221. package/src/__tests__/schedule.test.ts +105 -0
  222. package/src/__tests__/sourcemap.test.ts +254 -0
  223. package/src/__tests__/tuple.test.ts +220 -0
  224. package/src/__tests__/typechecker-strict.test.ts +216 -0
  225. package/src/ast/types.ts +39 -3
  226. package/src/cache/deps.ts +132 -0
  227. package/src/cache/incremental.ts +173 -0
  228. package/src/cache/index.ts +135 -0
  229. package/src/cli.ts +111 -195
  230. package/src/compile.ts +6 -162
  231. package/src/diagnostics/index.ts +8 -11
  232. package/src/emit/compile.ts +177 -0
  233. package/src/emit/index.ts +286 -0
  234. package/src/emit/modules.ts +581 -0
  235. package/src/emit/sourcemap.ts +101 -0
  236. package/src/hir/lower.ts +455 -0
  237. package/src/hir/monomorphize.ts +416 -0
  238. package/src/hir/types.ts +228 -0
  239. package/src/index.ts +37 -182
  240. package/src/lexer/index.ts +2 -1
  241. package/src/lir/budget.ts +321 -0
  242. package/src/lir/lower.ts +587 -0
  243. package/src/lir/types.ts +113 -0
  244. package/src/lir/verify.ts +129 -0
  245. package/src/lsp/main.ts +9 -0
  246. package/src/lsp/server.ts +414 -0
  247. package/src/mc-test/runner.ts +4 -3
  248. package/src/mir/lower.ts +1403 -0
  249. package/src/mir/macro.ts +167 -0
  250. package/src/mir/types.ts +117 -0
  251. package/src/mir/verify.ts +218 -0
  252. package/src/optimizer/block_merge.ts +93 -0
  253. package/src/optimizer/branch_simplify.ts +27 -0
  254. package/src/optimizer/constant_fold.ts +88 -0
  255. package/src/optimizer/copy_prop.ts +106 -0
  256. package/src/optimizer/coroutine.ts +996 -0
  257. package/src/optimizer/dce.ts +108 -653
  258. package/src/optimizer/interprocedural.ts +177 -0
  259. package/src/optimizer/lir/const_imm.ts +143 -0
  260. package/src/optimizer/lir/dead_slot.ts +123 -0
  261. package/src/optimizer/lir/peephole.ts +57 -0
  262. package/src/optimizer/lir/pipeline.ts +37 -0
  263. package/src/optimizer/nbt-batch.ts +50 -0
  264. package/src/optimizer/pipeline.ts +59 -0
  265. package/src/optimizer/selector-cache.ts +103 -0
  266. package/src/optimizer/unroll.ts +386 -0
  267. package/src/parser/index.ts +213 -16
  268. package/src/repl.ts +1 -1
  269. package/src/runtime/index.ts +1 -1
  270. package/src/stdlib/math.mcrs +4 -4
  271. package/src/templates/quest.mcrs +4 -4
  272. package/src/typechecker/index.ts +215 -15
  273. package/src/types/mc-version.ts +46 -0
  274. package/tsconfig.json +1 -1
  275. package/dist/__tests__/cli.test.js +0 -278
  276. package/dist/__tests__/codegen.test.js +0 -152
  277. package/dist/__tests__/e2e.test.d.ts +0 -6
  278. package/dist/__tests__/e2e.test.js +0 -1847
  279. package/dist/__tests__/entity-types.test.js +0 -203
  280. package/dist/__tests__/lowering.test.js +0 -1015
  281. package/dist/__tests__/macro.test.d.ts +0 -8
  282. package/dist/__tests__/macro.test.js +0 -305
  283. package/dist/__tests__/nbt.test.js +0 -82
  284. package/dist/__tests__/optimizer-advanced.test.js +0 -124
  285. package/dist/__tests__/optimizer.test.js +0 -149
  286. package/dist/__tests__/runtime.test.js +0 -289
  287. package/dist/__tests__/stdlib-advanced.test.d.ts +0 -4
  288. package/dist/__tests__/stdlib-advanced.test.js +0 -378
  289. package/dist/__tests__/stdlib-bigint.test.d.ts +0 -7
  290. package/dist/__tests__/stdlib-bigint.test.js +0 -428
  291. package/dist/__tests__/stdlib-math.test.d.ts +0 -7
  292. package/dist/__tests__/stdlib-math.test.js +0 -352
  293. package/dist/__tests__/stdlib-vec.test.d.ts +0 -4
  294. package/dist/__tests__/stdlib-vec.test.js +0 -264
  295. package/dist/__tests__/structure-optimizer.test.js +0 -33
  296. package/dist/__tests__/var-allocator.test.js +0 -69
  297. package/dist/codegen/cmdblock/index.d.ts +0 -26
  298. package/dist/codegen/cmdblock/index.js +0 -45
  299. package/dist/codegen/mcfunction/index.d.ts +0 -40
  300. package/dist/codegen/mcfunction/index.js +0 -606
  301. package/dist/codegen/structure/index.d.ts +0 -24
  302. package/dist/codegen/structure/index.js +0 -279
  303. package/dist/codegen/var-allocator.d.ts +0 -45
  304. package/dist/codegen/var-allocator.js +0 -104
  305. package/dist/compile.d.ts +0 -68
  306. package/dist/data/arena/function/__load.mcfunction +0 -6
  307. package/dist/data/arena/function/__tick.mcfunction +0 -2
  308. package/dist/data/arena/function/announce_leaders/else_1.mcfunction +0 -3
  309. package/dist/data/arena/function/announce_leaders/foreach_0/merge_2.mcfunction +0 -1
  310. package/dist/data/arena/function/announce_leaders/foreach_0/then_0.mcfunction +0 -3
  311. package/dist/data/arena/function/announce_leaders/foreach_0.mcfunction +0 -7
  312. package/dist/data/arena/function/announce_leaders/foreach_1/merge_2.mcfunction +0 -1
  313. package/dist/data/arena/function/announce_leaders/foreach_1/then_0.mcfunction +0 -4
  314. package/dist/data/arena/function/announce_leaders/foreach_1.mcfunction +0 -6
  315. package/dist/data/arena/function/announce_leaders/merge_2.mcfunction +0 -1
  316. package/dist/data/arena/function/announce_leaders/then_0.mcfunction +0 -4
  317. package/dist/data/arena/function/announce_leaders.mcfunction +0 -6
  318. package/dist/data/arena/function/arena_tick/merge_2.mcfunction +0 -1
  319. package/dist/data/arena/function/arena_tick/then_0.mcfunction +0 -4
  320. package/dist/data/arena/function/arena_tick.mcfunction +0 -11
  321. package/dist/data/counter/function/__load.mcfunction +0 -5
  322. package/dist/data/counter/function/__tick.mcfunction +0 -2
  323. package/dist/data/counter/function/counter_tick/merge_2.mcfunction +0 -1
  324. package/dist/data/counter/function/counter_tick/then_0.mcfunction +0 -3
  325. package/dist/data/counter/function/counter_tick.mcfunction +0 -11
  326. package/dist/data/gcd2/function/__load.mcfunction +0 -3
  327. package/dist/data/gcd2/function/abs/merge_2.mcfunction +0 -3
  328. package/dist/data/gcd2/function/abs/then_0.mcfunction +0 -5
  329. package/dist/data/gcd2/function/abs.mcfunction +0 -7
  330. package/dist/data/gcd2/function/gcd/loop_body_1.mcfunction +0 -7
  331. package/dist/data/gcd2/function/gcd/loop_check_0.mcfunction +0 -5
  332. package/dist/data/gcd2/function/gcd/loop_exit_2.mcfunction +0 -3
  333. package/dist/data/gcd2/function/gcd.mcfunction +0 -14
  334. package/dist/data/gcd3/function/__load.mcfunction +0 -3
  335. package/dist/data/gcd3/function/abs/merge_2.mcfunction +0 -3
  336. package/dist/data/gcd3/function/abs/then_0.mcfunction +0 -5
  337. package/dist/data/gcd3/function/abs.mcfunction +0 -7
  338. package/dist/data/gcd3/function/gcd/loop_body_1.mcfunction +0 -7
  339. package/dist/data/gcd3/function/gcd/loop_check_0.mcfunction +0 -5
  340. package/dist/data/gcd3/function/gcd/loop_exit_2.mcfunction +0 -3
  341. package/dist/data/gcd3/function/gcd.mcfunction +0 -14
  342. package/dist/data/gcd3/function/test.mcfunction +0 -7
  343. package/dist/data/gcd3nm/function/__load.mcfunction +0 -3
  344. package/dist/data/gcd3nm/function/abs/merge_2.mcfunction +0 -3
  345. package/dist/data/gcd3nm/function/abs/then_0.mcfunction +0 -5
  346. package/dist/data/gcd3nm/function/abs.mcfunction +0 -7
  347. package/dist/data/gcd3nm/function/gcd/loop_body_1.mcfunction +0 -7
  348. package/dist/data/gcd3nm/function/gcd/loop_check_0.mcfunction +0 -5
  349. package/dist/data/gcd3nm/function/gcd/loop_exit_2.mcfunction +0 -3
  350. package/dist/data/gcd3nm/function/gcd.mcfunction +0 -14
  351. package/dist/data/gcd3nm/function/test.mcfunction +0 -7
  352. package/dist/data/gcd_test/function/__load.mcfunction +0 -3
  353. package/dist/data/gcd_test/function/abs/merge_2.mcfunction +0 -3
  354. package/dist/data/gcd_test/function/abs/then_0.mcfunction +0 -5
  355. package/dist/data/gcd_test/function/abs.mcfunction +0 -7
  356. package/dist/data/gcd_test/function/gcd/loop_body_1.mcfunction +0 -7
  357. package/dist/data/gcd_test/function/gcd/loop_check_0.mcfunction +0 -5
  358. package/dist/data/gcd_test/function/gcd/loop_exit_2.mcfunction +0 -3
  359. package/dist/data/gcd_test/function/gcd.mcfunction +0 -14
  360. package/dist/data/isqrttest/function/__load.mcfunction +0 -6
  361. package/dist/data/isqrttest/function/isqrt/loop_body_4.mcfunction +0 -12
  362. package/dist/data/isqrttest/function/isqrt/loop_check_3.mcfunction +0 -5
  363. package/dist/data/isqrttest/function/isqrt/loop_exit_5.mcfunction +0 -3
  364. package/dist/data/isqrttest/function/isqrt/merge_2.mcfunction +0 -4
  365. package/dist/data/isqrttest/function/isqrt/merge_8.mcfunction +0 -6
  366. package/dist/data/isqrttest/function/isqrt/then_0.mcfunction +0 -3
  367. package/dist/data/isqrttest/function/isqrt/then_6.mcfunction +0 -3
  368. package/dist/data/isqrttest/function/isqrt.mcfunction +0 -7
  369. package/dist/data/isqrttest/function/test.mcfunction +0 -6
  370. package/dist/data/mathtest/function/__load.mcfunction +0 -3
  371. package/dist/data/mathtest/function/abs/merge_2.mcfunction +0 -3
  372. package/dist/data/mathtest/function/abs/then_0.mcfunction +0 -5
  373. package/dist/data/mathtest/function/abs.mcfunction +0 -6
  374. package/dist/data/mathtest/function/test.mcfunction +0 -5
  375. package/dist/data/minecraft/tags/function/load.json +0 -5
  376. package/dist/data/minecraft/tags/function/tick.json +0 -5
  377. package/dist/data/mypack/function/__load.mcfunction +0 -13
  378. package/dist/data/mypack/function/_atan_init.mcfunction +0 -2
  379. package/dist/data/mypack/function/abs/merge_2.mcfunction +0 -3
  380. package/dist/data/mypack/function/abs/then_0.mcfunction +0 -5
  381. package/dist/data/mypack/function/abs.mcfunction +0 -6
  382. package/dist/data/mypack/function/atan2_fixed/__sgi_1.mcfunction +0 -2
  383. package/dist/data/mypack/function/atan2_fixed/else_34.mcfunction +0 -3
  384. package/dist/data/mypack/function/atan2_fixed/loop_body_31.mcfunction +0 -19
  385. package/dist/data/mypack/function/atan2_fixed/loop_check_30.mcfunction +0 -5
  386. package/dist/data/mypack/function/atan2_fixed/loop_exit_32.mcfunction +0 -6
  387. package/dist/data/mypack/function/atan2_fixed/merge_11.mcfunction +0 -6
  388. package/dist/data/mypack/function/atan2_fixed/merge_14.mcfunction +0 -3
  389. package/dist/data/mypack/function/atan2_fixed/merge_17.mcfunction +0 -6
  390. package/dist/data/mypack/function/atan2_fixed/merge_2.mcfunction +0 -5
  391. package/dist/data/mypack/function/atan2_fixed/merge_20.mcfunction +0 -5
  392. package/dist/data/mypack/function/atan2_fixed/merge_23.mcfunction +0 -5
  393. package/dist/data/mypack/function/atan2_fixed/merge_26.mcfunction +0 -6
  394. package/dist/data/mypack/function/atan2_fixed/merge_29.mcfunction +0 -4
  395. package/dist/data/mypack/function/atan2_fixed/merge_38.mcfunction +0 -5
  396. package/dist/data/mypack/function/atan2_fixed/merge_41.mcfunction +0 -5
  397. package/dist/data/mypack/function/atan2_fixed/merge_44.mcfunction +0 -5
  398. package/dist/data/mypack/function/atan2_fixed/merge_47.mcfunction +0 -5
  399. package/dist/data/mypack/function/atan2_fixed/merge_5.mcfunction +0 -5
  400. package/dist/data/mypack/function/atan2_fixed/merge_8.mcfunction +0 -3
  401. package/dist/data/mypack/function/atan2_fixed/then_0.mcfunction +0 -5
  402. package/dist/data/mypack/function/atan2_fixed/then_12.mcfunction +0 -3
  403. package/dist/data/mypack/function/atan2_fixed/then_15.mcfunction +0 -5
  404. package/dist/data/mypack/function/atan2_fixed/then_18.mcfunction +0 -5
  405. package/dist/data/mypack/function/atan2_fixed/then_21.mcfunction +0 -3
  406. package/dist/data/mypack/function/atan2_fixed/then_24.mcfunction +0 -3
  407. package/dist/data/mypack/function/atan2_fixed/then_27.mcfunction +0 -6
  408. package/dist/data/mypack/function/atan2_fixed/then_3.mcfunction +0 -3
  409. package/dist/data/mypack/function/atan2_fixed/then_33.mcfunction +0 -5
  410. package/dist/data/mypack/function/atan2_fixed/then_36.mcfunction +0 -5
  411. package/dist/data/mypack/function/atan2_fixed/then_39.mcfunction +0 -5
  412. package/dist/data/mypack/function/atan2_fixed/then_42.mcfunction +0 -3
  413. package/dist/data/mypack/function/atan2_fixed/then_45.mcfunction +0 -5
  414. package/dist/data/mypack/function/atan2_fixed/then_6.mcfunction +0 -3
  415. package/dist/data/mypack/function/atan2_fixed/then_9.mcfunction +0 -5
  416. package/dist/data/mypack/function/atan2_fixed.mcfunction +0 -7
  417. package/dist/data/mypack/function/my_game.mcfunction +0 -10
  418. package/dist/data/quiz/function/__load.mcfunction +0 -16
  419. package/dist/data/quiz/function/__tick.mcfunction +0 -6
  420. package/dist/data/quiz/function/__trigger_quiz_a_dispatch.mcfunction +0 -4
  421. package/dist/data/quiz/function/__trigger_quiz_b_dispatch.mcfunction +0 -4
  422. package/dist/data/quiz/function/__trigger_quiz_c_dispatch.mcfunction +0 -4
  423. package/dist/data/quiz/function/__trigger_quiz_start_dispatch.mcfunction +0 -4
  424. package/dist/data/quiz/function/answer_a.mcfunction +0 -4
  425. package/dist/data/quiz/function/answer_b.mcfunction +0 -4
  426. package/dist/data/quiz/function/answer_c.mcfunction +0 -4
  427. package/dist/data/quiz/function/ask_question/else_1.mcfunction +0 -5
  428. package/dist/data/quiz/function/ask_question/else_4.mcfunction +0 -5
  429. package/dist/data/quiz/function/ask_question/else_7.mcfunction +0 -4
  430. package/dist/data/quiz/function/ask_question/merge_2.mcfunction +0 -1
  431. package/dist/data/quiz/function/ask_question/merge_5.mcfunction +0 -2
  432. package/dist/data/quiz/function/ask_question/merge_8.mcfunction +0 -2
  433. package/dist/data/quiz/function/ask_question/then_0.mcfunction +0 -4
  434. package/dist/data/quiz/function/ask_question/then_3.mcfunction +0 -4
  435. package/dist/data/quiz/function/ask_question/then_6.mcfunction +0 -4
  436. package/dist/data/quiz/function/ask_question.mcfunction +0 -7
  437. package/dist/data/quiz/function/finish_quiz.mcfunction +0 -6
  438. package/dist/data/quiz/function/handle_answer/else_1.mcfunction +0 -5
  439. package/dist/data/quiz/function/handle_answer/else_10.mcfunction +0 -3
  440. package/dist/data/quiz/function/handle_answer/else_16.mcfunction +0 -3
  441. package/dist/data/quiz/function/handle_answer/else_4.mcfunction +0 -3
  442. package/dist/data/quiz/function/handle_answer/else_7.mcfunction +0 -5
  443. package/dist/data/quiz/function/handle_answer/merge_11.mcfunction +0 -2
  444. package/dist/data/quiz/function/handle_answer/merge_14.mcfunction +0 -2
  445. package/dist/data/quiz/function/handle_answer/merge_17.mcfunction +0 -2
  446. package/dist/data/quiz/function/handle_answer/merge_2.mcfunction +0 -8
  447. package/dist/data/quiz/function/handle_answer/merge_5.mcfunction +0 -2
  448. package/dist/data/quiz/function/handle_answer/merge_8.mcfunction +0 -2
  449. package/dist/data/quiz/function/handle_answer/then_0.mcfunction +0 -5
  450. package/dist/data/quiz/function/handle_answer/then_12.mcfunction +0 -5
  451. package/dist/data/quiz/function/handle_answer/then_15.mcfunction +0 -6
  452. package/dist/data/quiz/function/handle_answer/then_3.mcfunction +0 -6
  453. package/dist/data/quiz/function/handle_answer/then_6.mcfunction +0 -5
  454. package/dist/data/quiz/function/handle_answer/then_9.mcfunction +0 -6
  455. package/dist/data/quiz/function/handle_answer.mcfunction +0 -11
  456. package/dist/data/quiz/function/start_quiz.mcfunction +0 -5
  457. package/dist/data/reqtest/function/__load.mcfunction +0 -4
  458. package/dist/data/reqtest/function/_table_init.mcfunction +0 -2
  459. package/dist/data/reqtest/function/no_trig.mcfunction +0 -3
  460. package/dist/data/reqtest/function/use_table.mcfunction +0 -4
  461. package/dist/data/reqtest2/function/__load.mcfunction +0 -3
  462. package/dist/data/reqtest2/function/no_trig.mcfunction +0 -3
  463. package/dist/data/runtime/function/__load.mcfunction +0 -5
  464. package/dist/data/runtime/function/__tick.mcfunction +0 -2
  465. package/dist/data/runtime/function/counter_tick/then_0.mcfunction +0 -3
  466. package/dist/data/runtime/function/counter_tick.mcfunction +0 -13
  467. package/dist/data/shop/function/__load.mcfunction +0 -7
  468. package/dist/data/shop/function/__tick.mcfunction +0 -3
  469. package/dist/data/shop/function/__trigger_shop_buy_dispatch.mcfunction +0 -4
  470. package/dist/data/shop/function/complete_purchase/else_1.mcfunction +0 -5
  471. package/dist/data/shop/function/complete_purchase/else_4.mcfunction +0 -5
  472. package/dist/data/shop/function/complete_purchase/else_7.mcfunction +0 -3
  473. package/dist/data/shop/function/complete_purchase/merge_2.mcfunction +0 -2
  474. package/dist/data/shop/function/complete_purchase/merge_5.mcfunction +0 -2
  475. package/dist/data/shop/function/complete_purchase/merge_8.mcfunction +0 -2
  476. package/dist/data/shop/function/complete_purchase/then_0.mcfunction +0 -4
  477. package/dist/data/shop/function/complete_purchase/then_3.mcfunction +0 -4
  478. package/dist/data/shop/function/complete_purchase/then_6.mcfunction +0 -4
  479. package/dist/data/shop/function/complete_purchase.mcfunction +0 -7
  480. package/dist/data/shop/function/handle_shop_trigger.mcfunction +0 -3
  481. package/dist/data/swap_test/function/__load.mcfunction +0 -3
  482. package/dist/data/swap_test/function/gcd_old/loop_body_1.mcfunction +0 -7
  483. package/dist/data/swap_test/function/gcd_old/loop_check_0.mcfunction +0 -5
  484. package/dist/data/swap_test/function/gcd_old/loop_exit_2.mcfunction +0 -3
  485. package/dist/data/swap_test/function/gcd_old.mcfunction +0 -8
  486. package/dist/data/turret/function/__load.mcfunction +0 -5
  487. package/dist/data/turret/function/__tick.mcfunction +0 -4
  488. package/dist/data/turret/function/__trigger_deploy_turret_dispatch.mcfunction +0 -4
  489. package/dist/data/turret/function/deploy_turret.mcfunction +0 -8
  490. package/dist/data/turret/function/turret_tick/at_1.mcfunction +0 -2
  491. package/dist/data/turret/function/turret_tick/foreach_0.mcfunction +0 -2
  492. package/dist/data/turret/function/turret_tick/foreach_2.mcfunction +0 -2
  493. package/dist/data/turret/function/turret_tick/tick_body.mcfunction +0 -3
  494. package/dist/data/turret/function/turret_tick/tick_skip.mcfunction +0 -1
  495. package/dist/data/turret/function/turret_tick.mcfunction +0 -5
  496. package/dist/gcd2.map.json +0 -15
  497. package/dist/gcd3.map.json +0 -17
  498. package/dist/gcd_test.map.json +0 -15
  499. package/dist/index.d.ts +0 -62
  500. package/dist/ir/builder.d.ts +0 -33
  501. package/dist/ir/builder.js +0 -99
  502. package/dist/ir/types.d.ts +0 -132
  503. package/dist/ir/types.js +0 -15
  504. package/dist/isqrttest.map.json +0 -15
  505. package/dist/lowering/index.d.ts +0 -188
  506. package/dist/lowering/index.js +0 -3403
  507. package/dist/mathtest.map.json +0 -6
  508. package/dist/mypack.map.json +0 -27
  509. package/dist/optimizer/commands.d.ts +0 -38
  510. package/dist/optimizer/commands.js +0 -451
  511. package/dist/optimizer/dce.d.ts +0 -34
  512. package/dist/optimizer/dce.js +0 -639
  513. package/dist/optimizer/passes.d.ts +0 -34
  514. package/dist/optimizer/passes.js +0 -243
  515. package/dist/optimizer/structure.d.ts +0 -9
  516. package/dist/optimizer/structure.js +0 -356
  517. package/dist/pack.mcmeta +0 -6
  518. package/dist/reqtest.map.json +0 -4
  519. package/dist/reqtest2.map.json +0 -4
  520. package/dist/runtime.map.json +0 -7
  521. package/dist/swap_test.map.json +0 -14
  522. package/src/__tests__/codegen.test.ts +0 -161
  523. package/src/__tests__/e2e.test.ts +0 -2039
  524. package/src/__tests__/entity-types.test.ts +0 -236
  525. package/src/__tests__/lowering.test.ts +0 -1185
  526. package/src/__tests__/macro.test.ts +0 -343
  527. package/src/__tests__/nbt.test.ts +0 -58
  528. package/src/__tests__/optimizer-advanced.test.ts +0 -144
  529. package/src/__tests__/optimizer.test.ts +0 -162
  530. package/src/__tests__/runtime.test.ts +0 -305
  531. package/src/__tests__/stdlib-advanced.test.ts +0 -379
  532. package/src/__tests__/stdlib-bigint.test.ts +0 -427
  533. package/src/__tests__/stdlib-math.test.ts +0 -374
  534. package/src/__tests__/stdlib-vec.test.ts +0 -259
  535. package/src/__tests__/structure-optimizer.test.ts +0 -38
  536. package/src/__tests__/var-allocator.test.ts +0 -75
  537. package/src/codegen/cmdblock/index.ts +0 -63
  538. package/src/codegen/mcfunction/index.ts +0 -662
  539. package/src/codegen/structure/index.ts +0 -346
  540. package/src/codegen/var-allocator.ts +0 -104
  541. package/src/ir/builder.ts +0 -116
  542. package/src/ir/types.ts +0 -134
  543. package/src/lowering/index.ts +0 -3876
  544. package/src/optimizer/commands.ts +0 -534
  545. package/src/optimizer/passes.ts +0 -250
  546. package/src/optimizer/structure.ts +0 -450
  547. /package/dist/{__tests__/cli.test.d.ts → src/__tests__/budget.test.d.ts} +0 -0
  548. /package/dist/{__tests__/codegen.test.d.ts → src/__tests__/cli.test.d.ts} +0 -0
  549. /package/dist/{__tests__ → src/__tests__}/compile-all.test.d.ts +0 -0
  550. /package/dist/{__tests__ → src/__tests__}/compile-all.test.js +0 -0
  551. /package/dist/{__tests__ → src/__tests__}/dce.test.d.ts +0 -0
  552. /package/dist/{__tests__ → src/__tests__}/diagnostics.test.d.ts +0 -0
  553. /package/dist/{__tests__ → src/__tests__}/formatter.test.d.ts +0 -0
  554. /package/dist/{__tests__ → src/__tests__}/formatter.test.js +0 -0
  555. /package/dist/{__tests__/entity-types.test.d.ts → src/__tests__/hir/desugar.test.d.ts} +0 -0
  556. /package/dist/{__tests__ → src/__tests__}/lexer.test.d.ts +0 -0
  557. /package/dist/{__tests__ → src/__tests__}/lexer.test.js +0 -0
  558. /package/dist/{__tests__/lowering.test.d.ts → src/__tests__/lir/lower.test.d.ts} +0 -0
  559. /package/dist/{__tests__/mc-syntax.test.d.ts → src/__tests__/lir/types.test.d.ts} +0 -0
  560. /package/dist/{__tests__/nbt.test.d.ts → src/__tests__/lir/verify.test.d.ts} +0 -0
  561. /package/dist/{__tests__ → src/__tests__}/mc-integration.test.d.ts +0 -0
  562. /package/dist/{__tests__/optimizer-advanced.test.d.ts → src/__tests__/mc-syntax.test.d.ts} +0 -0
  563. /package/dist/{__tests__ → src/__tests__}/mc-syntax.test.js +0 -0
  564. /package/dist/{__tests__/optimizer.test.d.ts → src/__tests__/mir/arithmetic.test.d.ts} +0 -0
  565. /package/dist/{__tests__/parser.test.d.ts → src/__tests__/mir/control-flow.test.d.ts} +0 -0
  566. /package/dist/{__tests__/repl.test.d.ts → src/__tests__/mir/verify.test.d.ts} +0 -0
  567. /package/dist/{__tests__/runtime.test.d.ts → src/__tests__/optimizer/block_merge.test.d.ts} +0 -0
  568. /package/dist/{__tests__/structure-optimizer.test.d.ts → src/__tests__/optimizer/branch_simplify.test.d.ts} +0 -0
  569. /package/dist/{__tests__/typechecker.test.d.ts → src/__tests__/optimizer/constant_fold.test.d.ts} +0 -0
  570. /package/dist/{__tests__/var-allocator.test.d.ts → src/__tests__/optimizer/copy_prop.test.d.ts} +0 -0
  571. /package/dist/{__tests__ → src/__tests__}/parser.test.js +0 -0
  572. /package/dist/{__tests__ → src/__tests__}/repl.test.js +0 -0
  573. /package/dist/{__tests__ → src/__tests__}/typechecker.test.js +0 -0
  574. /package/dist/{ast → src/ast}/types.js +0 -0
  575. /package/dist/{builtins → src/builtins}/metadata.d.ts +0 -0
  576. /package/dist/{builtins → src/builtins}/metadata.js +0 -0
  577. /package/dist/{events → src/events}/types.d.ts +0 -0
  578. /package/dist/{events → src/events}/types.js +0 -0
  579. /package/dist/{formatter → src/formatter}/index.d.ts +0 -0
  580. /package/dist/{formatter → src/formatter}/index.js +0 -0
  581. /package/dist/{mc-test → src/mc-test}/client.d.ts +0 -0
  582. /package/dist/{mc-test → src/mc-test}/client.js +0 -0
  583. /package/dist/{mc-test → src/mc-test}/runner.d.ts +0 -0
  584. /package/dist/{mc-test → src/mc-test}/setup.d.ts +0 -0
  585. /package/dist/{mc-test → src/mc-test}/setup.js +0 -0
  586. /package/dist/{mc-validator → src/mc-validator}/index.d.ts +0 -0
  587. /package/dist/{mc-validator → src/mc-validator}/index.js +0 -0
  588. /package/dist/{nbt → src/nbt}/index.d.ts +0 -0
  589. /package/dist/{nbt → src/nbt}/index.js +0 -0
  590. /package/dist/{repl.js → src/repl.js} +0 -0
  591. /package/dist/{runtime → src/runtime}/index.d.ts +0 -0
  592. /package/dist/{types → src/types}/entity-hierarchy.d.ts +0 -0
  593. /package/dist/{types → src/types}/entity-hierarchy.js +0 -0
@@ -0,0 +1,1403 @@
1
+ /**
2
+ * HIR → MIR Lowering — Stage 3 of the RedScript compiler pipeline.
3
+ *
4
+ * Converts structured HIR (if/while/break/continue) into an explicit CFG
5
+ * with 3-address instructions and unlimited fresh temporaries.
6
+ */
7
+
8
+ import type {
9
+ HIRModule, HIRFunction, HIRStmt, HIRBlock, HIRExpr,
10
+ HIRExecuteSubcommand, HIRParam,
11
+ } from '../hir/types'
12
+ import type {
13
+ MIRModule, MIRFunction, MIRBlock, MIRInstr, BlockId,
14
+ Operand, Temp, CmpOp, ExecuteSubcmd, NBTType, SourceLoc,
15
+ } from './types'
16
+ import { detectMacroFunctions, BUILTIN_SET, type MacroFunctionInfo } from './macro'
17
+
18
+ // ---------------------------------------------------------------------------
19
+ // Public API
20
+ // ---------------------------------------------------------------------------
21
+
22
+ export function lowerToMIR(hir: HIRModule, sourceFile?: string): MIRModule {
23
+ // Build struct definitions: name → field names
24
+ const structDefs = new Map<string, string[]>()
25
+ for (const s of hir.structs) {
26
+ structDefs.set(s.name, s.fields.map(f => f.name))
27
+ }
28
+
29
+ // Build enum definitions: enumName → variantName → integer value
30
+ const enumDefs = new Map<string, Map<string, number>>()
31
+ for (const e of hir.enums) {
32
+ const variants = new Map<string, number>()
33
+ for (const v of e.variants) {
34
+ variants.set(v.name, v.value ?? 0)
35
+ }
36
+ enumDefs.set(e.name, variants)
37
+ }
38
+
39
+ // Build impl method info: typeName → methodName → { hasSelf }
40
+ const implMethods = new Map<string, Map<string, { hasSelf: boolean }>>()
41
+ for (const ib of hir.implBlocks) {
42
+ const methods = new Map<string, { hasSelf: boolean }>()
43
+ for (const m of ib.methods) {
44
+ const hasSelf = m.params.length > 0 && m.params[0].name === 'self'
45
+ methods.set(m.name, { hasSelf })
46
+ }
47
+ implMethods.set(ib.typeName, methods)
48
+ }
49
+
50
+ // Pre-scan for macro functions
51
+ const macroInfo = detectMacroFunctions(hir)
52
+
53
+ // Build function param info for call_macro generation at call sites
54
+ const fnParamInfo = new Map<string, HIRParam[]>()
55
+ for (const f of hir.functions) {
56
+ fnParamInfo.set(f.name, f.params)
57
+ }
58
+ for (const ib of hir.implBlocks) {
59
+ for (const m of ib.methods) {
60
+ fnParamInfo.set(`${ib.typeName}::${m.name}`, m.params)
61
+ }
62
+ }
63
+
64
+ const allFunctions: MIRFunction[] = []
65
+ for (const f of hir.functions) {
66
+ const { fn, helpers } = lowerFunction(f, hir.namespace, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, sourceFile)
67
+ allFunctions.push(fn, ...helpers)
68
+ }
69
+
70
+ // Lower impl block methods
71
+ for (const ib of hir.implBlocks) {
72
+ for (const m of ib.methods) {
73
+ const { fn, helpers } = lowerImplMethod(m, ib.typeName, hir.namespace, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, sourceFile)
74
+ allFunctions.push(fn, ...helpers)
75
+ }
76
+ }
77
+
78
+ return {
79
+ functions: allFunctions,
80
+ namespace: hir.namespace,
81
+ objective: `__${hir.namespace}`,
82
+ }
83
+ }
84
+
85
+ // ---------------------------------------------------------------------------
86
+ // Function lowering context
87
+ // ---------------------------------------------------------------------------
88
+
89
+ class FnContext {
90
+ private tempCounter = 0
91
+ private blockCounter = 0
92
+ readonly blocks: MIRBlock[] = []
93
+ private currentBlock: MIRBlock
94
+ /** Stack of (loopHeader, loopExit, continueTo) for break/continue */
95
+ private loopStack: { header: BlockId; exit: BlockId; continueTo: BlockId }[] = []
96
+ /** Extracted helper functions for execute blocks */
97
+ readonly helperFunctions: MIRFunction[] = []
98
+ private readonly namespace: string
99
+ private readonly fnName: string
100
+ /** Struct definitions: struct name → field names */
101
+ readonly structDefs: Map<string, string[]>
102
+ /** Impl method info: typeName → methodName → { hasSelf } */
103
+ readonly implMethods: Map<string, Map<string, { hasSelf: boolean }>>
104
+ /** Struct variable tracking: varName → { typeName, fields: fieldName → temp } */
105
+ readonly structVars = new Map<string, { typeName: string; fields: Map<string, Temp> }>()
106
+ /** Tuple variable tracking: varName → array of element temps (index = slot) */
107
+ readonly tupleVars = new Map<string, Temp[]>()
108
+ /** Macro function info for all functions in the module */
109
+ readonly macroInfo: Map<string, MacroFunctionInfo>
110
+ /** Function parameter info for call_macro generation */
111
+ readonly fnParamInfo: Map<string, HIRParam[]>
112
+ /** Macro params for the current function being lowered */
113
+ readonly currentMacroParams: Set<string>
114
+ /** Enum definitions: enumName → variantName → integer value */
115
+ readonly enumDefs: Map<string, Map<string, number>>
116
+ /** Current source location (set during statement lowering) */
117
+ currentSourceLoc: SourceLoc | undefined = undefined
118
+ /** Source file path for the module being compiled */
119
+ sourceFile: string | undefined = undefined
120
+
121
+ constructor(
122
+ namespace: string,
123
+ fnName: string,
124
+ structDefs: Map<string, string[]> = new Map(),
125
+ implMethods: Map<string, Map<string, { hasSelf: boolean }>> = new Map(),
126
+ macroInfo: Map<string, MacroFunctionInfo> = new Map(),
127
+ fnParamInfo: Map<string, HIRParam[]> = new Map(),
128
+ enumDefs: Map<string, Map<string, number>> = new Map(),
129
+ ) {
130
+ this.namespace = namespace
131
+ this.fnName = fnName
132
+ this.structDefs = structDefs
133
+ this.implMethods = implMethods
134
+ this.macroInfo = macroInfo
135
+ this.fnParamInfo = fnParamInfo
136
+ this.currentMacroParams = macroInfo.get(fnName)?.macroParams ?? new Set()
137
+ this.enumDefs = enumDefs
138
+ const entry = this.makeBlock('entry')
139
+ this.currentBlock = entry
140
+ }
141
+
142
+ freshTemp(): Temp {
143
+ return `t${this.tempCounter++}`
144
+ }
145
+
146
+ private makeBlock(id?: string): MIRBlock {
147
+ const block: MIRBlock = {
148
+ id: id ?? `bb${this.blockCounter++}`,
149
+ instrs: [],
150
+ term: { kind: 'return', value: null }, // placeholder
151
+ preds: [],
152
+ }
153
+ this.blocks.push(block)
154
+ return block
155
+ }
156
+
157
+ newBlock(prefix?: string): MIRBlock {
158
+ return this.makeBlock(prefix ? `${prefix}_${this.blockCounter++}` : undefined)
159
+ }
160
+
161
+ emit(instr: MIRInstr): void {
162
+ if (this.currentSourceLoc && !instr.sourceLoc) {
163
+ instr.sourceLoc = this.currentSourceLoc
164
+ }
165
+ this.currentBlock.instrs.push(instr)
166
+ }
167
+
168
+ terminate(term: MIRInstr): void {
169
+ if (this.currentSourceLoc && !term.sourceLoc) {
170
+ term.sourceLoc = this.currentSourceLoc
171
+ }
172
+ this.currentBlock.term = term
173
+ }
174
+
175
+ switchTo(block: MIRBlock): void {
176
+ this.currentBlock = block
177
+ }
178
+
179
+ current(): MIRBlock {
180
+ return this.currentBlock
181
+ }
182
+
183
+ pushLoop(header: BlockId, exit: BlockId, continueTo?: BlockId): void {
184
+ this.loopStack.push({ header, exit, continueTo: continueTo ?? header })
185
+ }
186
+
187
+ popLoop(): void {
188
+ this.loopStack.pop()
189
+ }
190
+
191
+ currentLoop(): { header: BlockId; exit: BlockId; continueTo: BlockId } | undefined {
192
+ return this.loopStack[this.loopStack.length - 1]
193
+ }
194
+
195
+ getNamespace(): string {
196
+ return this.namespace
197
+ }
198
+
199
+ getFnName(): string {
200
+ return this.fnName
201
+ }
202
+ }
203
+
204
+ // ---------------------------------------------------------------------------
205
+ // Function lowering
206
+ // ---------------------------------------------------------------------------
207
+
208
+ function lowerFunction(
209
+ fn: HIRFunction,
210
+ namespace: string,
211
+ structDefs: Map<string, string[]> = new Map(),
212
+ implMethods: Map<string, Map<string, { hasSelf: boolean }>> = new Map(),
213
+ macroInfo: Map<string, MacroFunctionInfo> = new Map(),
214
+ fnParamInfo: Map<string, HIRParam[]> = new Map(),
215
+ enumDefs: Map<string, Map<string, number>> = new Map(),
216
+ sourceFile?: string,
217
+ ): { fn: MIRFunction; helpers: MIRFunction[] } {
218
+ const ctx = new FnContext(namespace, fn.name, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs)
219
+ ctx.sourceFile = sourceFile
220
+ const fnMacroInfo = macroInfo.get(fn.name)
221
+
222
+ // Create temps for parameters
223
+ const params: { name: Temp; isMacroParam: boolean }[] = fn.params.map(p => {
224
+ const t = ctx.freshTemp()
225
+ return { name: t, isMacroParam: fnMacroInfo?.macroParams.has(p.name) ?? false }
226
+ })
227
+
228
+ // Map parameter names to their temps
229
+ const scope = new Map<string, Temp>()
230
+ fn.params.forEach((p, i) => {
231
+ scope.set(p.name, params[i].name)
232
+ })
233
+
234
+ lowerBlock(fn.body, ctx, scope)
235
+
236
+ // If the current block doesn't have a real terminator, add void return
237
+ const cur = ctx.current()
238
+ if (isPlaceholderTerm(cur.term)) {
239
+ ctx.terminate({ kind: 'return', value: null })
240
+ }
241
+
242
+ // Remove unreachable blocks (dead continuations after return/break/continue)
243
+ const reachable = computeReachable(ctx.blocks, 'entry')
244
+ const liveBlocks = ctx.blocks.filter(b => reachable.has(b.id))
245
+
246
+ // Fill predecessor lists
247
+ computePreds(liveBlocks)
248
+
249
+ const result: MIRFunction = {
250
+ name: fn.name,
251
+ params,
252
+ blocks: liveBlocks,
253
+ entry: 'entry',
254
+ isMacro: fnMacroInfo != null,
255
+ }
256
+
257
+ return { fn: result, helpers: ctx.helperFunctions }
258
+ }
259
+
260
+ function lowerImplMethod(
261
+ method: HIRFunction,
262
+ typeName: string,
263
+ namespace: string,
264
+ structDefs: Map<string, string[]>,
265
+ implMethods: Map<string, Map<string, { hasSelf: boolean }>>,
266
+ macroInfo: Map<string, MacroFunctionInfo> = new Map(),
267
+ fnParamInfo: Map<string, HIRParam[]> = new Map(),
268
+ enumDefs: Map<string, Map<string, number>> = new Map(),
269
+ sourceFile?: string,
270
+ ): { fn: MIRFunction; helpers: MIRFunction[] } {
271
+ const fnName = `${typeName}::${method.name}`
272
+ const ctx = new FnContext(namespace, fnName, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs)
273
+ ctx.sourceFile = sourceFile
274
+ const fields = structDefs.get(typeName) ?? []
275
+ const hasSelf = method.params.length > 0 && method.params[0].name === 'self'
276
+
277
+ const params: { name: Temp; isMacroParam: boolean }[] = []
278
+ const scope = new Map<string, Temp>()
279
+
280
+ if (hasSelf) {
281
+ // Self fields become the first N params (one per struct field)
282
+ const selfFields = new Map<string, Temp>()
283
+ for (const fieldName of fields) {
284
+ const t = ctx.freshTemp()
285
+ params.push({ name: t, isMacroParam: false })
286
+ selfFields.set(fieldName, t)
287
+ }
288
+ ctx.structVars.set('self', { typeName, fields: selfFields })
289
+ // Remaining params (after self)
290
+ for (let i = 1; i < method.params.length; i++) {
291
+ const t = ctx.freshTemp()
292
+ params.push({ name: t, isMacroParam: false })
293
+ scope.set(method.params[i].name, t)
294
+ }
295
+ } else {
296
+ // Static method — regular params
297
+ for (const p of method.params) {
298
+ const t = ctx.freshTemp()
299
+ params.push({ name: t, isMacroParam: false })
300
+ scope.set(p.name, t)
301
+ }
302
+ }
303
+
304
+ lowerBlock(method.body, ctx, scope)
305
+
306
+ const cur = ctx.current()
307
+ if (isPlaceholderTerm(cur.term)) {
308
+ ctx.terminate({ kind: 'return', value: null })
309
+ }
310
+
311
+ const reachable = computeReachable(ctx.blocks, 'entry')
312
+ const liveBlocks = ctx.blocks.filter(b => reachable.has(b.id))
313
+ computePreds(liveBlocks)
314
+
315
+ const result: MIRFunction = {
316
+ name: fnName,
317
+ params,
318
+ blocks: liveBlocks,
319
+ entry: 'entry',
320
+ isMacro: macroInfo.has(fnName),
321
+ }
322
+
323
+ return { fn: result, helpers: ctx.helperFunctions }
324
+ }
325
+
326
+ function isPlaceholderTerm(term: MIRInstr): boolean {
327
+ // Our placeholder is a return null that was set in makeBlock
328
+ return term.kind === 'return' && (term as any).value === null
329
+ }
330
+
331
+ function computeReachable(blocks: MIRBlock[], entry: BlockId): Set<BlockId> {
332
+ const reachable = new Set<BlockId>()
333
+ const queue: BlockId[] = [entry]
334
+ while (queue.length > 0) {
335
+ const id = queue.shift()!
336
+ if (reachable.has(id)) continue
337
+ reachable.add(id)
338
+ const block = blocks.find(b => b.id === id)
339
+ if (block) {
340
+ for (const t of getTermTargets(block.term)) {
341
+ if (!reachable.has(t)) queue.push(t)
342
+ }
343
+ }
344
+ }
345
+ return reachable
346
+ }
347
+
348
+ function computePreds(blocks: MIRBlock[]): void {
349
+ // Clear all preds
350
+ for (const b of blocks) b.preds = []
351
+
352
+ for (const b of blocks) {
353
+ const targets = getTermTargets(b.term)
354
+ for (const t of targets) {
355
+ const target = blocks.find(bb => bb.id === t)
356
+ if (target && !target.preds.includes(b.id)) {
357
+ target.preds.push(b.id)
358
+ }
359
+ }
360
+ }
361
+ }
362
+
363
+ function getTermTargets(term: MIRInstr): BlockId[] {
364
+ switch (term.kind) {
365
+ case 'jump': return [term.target]
366
+ case 'branch': return [term.then, term.else]
367
+ case 'return': return []
368
+ default: return []
369
+ }
370
+ }
371
+
372
+ // ---------------------------------------------------------------------------
373
+ // Block / statement lowering
374
+ // ---------------------------------------------------------------------------
375
+
376
+ function lowerBlock(
377
+ stmts: HIRBlock,
378
+ ctx: FnContext,
379
+ scope: Map<string, Temp>,
380
+ ): void {
381
+ for (const stmt of stmts) {
382
+ lowerStmt(stmt, ctx, scope)
383
+ }
384
+ }
385
+
386
+ function lowerStmt(
387
+ stmt: HIRStmt,
388
+ ctx: FnContext,
389
+ scope: Map<string, Temp>,
390
+ ): void {
391
+ // Propagate source location from HIR statement span
392
+ if (stmt.span && ctx.sourceFile) {
393
+ ctx.currentSourceLoc = { file: ctx.sourceFile, line: stmt.span.line, col: stmt.span.col }
394
+ }
395
+
396
+ switch (stmt.kind) {
397
+ case 'let': {
398
+ if (stmt.init.kind === 'some_lit') {
399
+ // Some(expr) — create option struct vars: has=1, val=expr
400
+ // Use __opt_ prefix so DCE treats these as side-effectful scoreboard writes
401
+ const hasTemp: Temp = `__opt_${stmt.name}_has`
402
+ const valTemp: Temp = `__opt_${stmt.name}_val`
403
+ ctx.emit({ kind: 'const', dst: hasTemp, value: 1 })
404
+ const valOp = lowerExpr(stmt.init.value, ctx, scope)
405
+ ctx.emit({ kind: 'copy', dst: valTemp, src: valOp })
406
+ const fieldTemps = new Map<string, Temp>([['has', hasTemp], ['val', valTemp]])
407
+ ctx.structVars.set(stmt.name, { typeName: '__option', fields: fieldTemps })
408
+ } else if (stmt.init.kind === 'none_lit') {
409
+ // None — create option struct vars: has=0, val=0
410
+ const hasTemp: Temp = `__opt_${stmt.name}_has`
411
+ const valTemp: Temp = `__opt_${stmt.name}_val`
412
+ ctx.emit({ kind: 'const', dst: hasTemp, value: 0 })
413
+ ctx.emit({ kind: 'const', dst: valTemp, value: 0 })
414
+ const fieldTemps = new Map<string, Temp>([['has', hasTemp], ['val', valTemp]])
415
+ ctx.structVars.set(stmt.name, { typeName: '__option', fields: fieldTemps })
416
+ } else if (stmt.init.kind === 'struct_lit') {
417
+ // Struct literal: create per-field temps
418
+ const typeName = (stmt.type?.kind === 'struct') ? stmt.type.name : '__anon'
419
+ const fieldTemps = new Map<string, Temp>()
420
+ for (const field of stmt.init.fields) {
421
+ const val = lowerExpr(field.value, ctx, scope)
422
+ const t = ctx.freshTemp()
423
+ ctx.emit({ kind: 'copy', dst: t, src: val })
424
+ fieldTemps.set(field.name, t)
425
+ }
426
+ ctx.structVars.set(stmt.name, { typeName, fields: fieldTemps })
427
+ } else if (stmt.type?.kind === 'option') {
428
+ // Option<T>-typed let with function call result — use __rf_has/__rf_val convention
429
+ lowerExpr(stmt.init, ctx, scope)
430
+ const hasTemp: Temp = `__opt_${stmt.name}_has`
431
+ const valTemp: Temp = `__opt_${stmt.name}_val`
432
+ ctx.emit({ kind: 'copy', dst: hasTemp, src: { kind: 'temp', name: '__rf_has' } })
433
+ ctx.emit({ kind: 'copy', dst: valTemp, src: { kind: 'temp', name: '__rf_val' } })
434
+ const fieldTemps = new Map<string, Temp>([['has', hasTemp], ['val', valTemp]])
435
+ ctx.structVars.set(stmt.name, { typeName: '__option', fields: fieldTemps })
436
+ } else if (stmt.type?.kind === 'struct') {
437
+ // Struct-typed let with non-literal init (e.g., call returning struct)
438
+ const fields = ctx.structDefs.get(stmt.type.name)
439
+ if (fields) {
440
+ lowerExpr(stmt.init, ctx, scope)
441
+ // Copy from return field slots into struct variable temps
442
+ const fieldTemps = new Map<string, Temp>()
443
+ for (const fieldName of fields) {
444
+ const t = ctx.freshTemp()
445
+ ctx.emit({ kind: 'copy', dst: t, src: { kind: 'temp', name: `__rf_${fieldName}` } })
446
+ fieldTemps.set(fieldName, t)
447
+ }
448
+ ctx.structVars.set(stmt.name, { typeName: stmt.type.name, fields: fieldTemps })
449
+ } else {
450
+ const valOp = lowerExpr(stmt.init, ctx, scope)
451
+ const t = ctx.freshTemp()
452
+ ctx.emit({ kind: 'copy', dst: t, src: valOp })
453
+ scope.set(stmt.name, t)
454
+ }
455
+ } else {
456
+ const valOp = lowerExpr(stmt.init, ctx, scope)
457
+ const t = ctx.freshTemp()
458
+ ctx.emit({ kind: 'copy', dst: t, src: valOp })
459
+ scope.set(stmt.name, t)
460
+ }
461
+ break
462
+ }
463
+
464
+ case 'let_destruct': {
465
+ // Tuple destructuring: let (a, b, c) = expr
466
+ const n = stmt.names.length
467
+ if (stmt.init.kind === 'tuple_lit') {
468
+ // Direct tuple literal: evaluate each element into its own temp
469
+ const elemTemps: Temp[] = []
470
+ for (let i = 0; i < stmt.init.elements.length && i < n; i++) {
471
+ const val = lowerExpr(stmt.init.elements[i], ctx, scope)
472
+ const t = ctx.freshTemp()
473
+ ctx.emit({ kind: 'copy', dst: t, src: val })
474
+ elemTemps.push(t)
475
+ scope.set(stmt.names[i], t)
476
+ }
477
+ } else if (stmt.init.kind === 'ident') {
478
+ // Could be referencing a known tuple var
479
+ const tv = ctx.tupleVars.get(stmt.init.name)
480
+ if (tv) {
481
+ for (let i = 0; i < n && i < tv.length; i++) {
482
+ scope.set(stmt.names[i], tv[i])
483
+ }
484
+ break
485
+ }
486
+ // Otherwise treat as a call result stored in __rf_ slots
487
+ lowerExpr(stmt.init, ctx, scope)
488
+ const elemTemps: Temp[] = []
489
+ for (let i = 0; i < n; i++) {
490
+ const t = ctx.freshTemp()
491
+ ctx.emit({ kind: 'copy', dst: t, src: { kind: 'temp', name: `__rf_${i}` } })
492
+ elemTemps.push(t)
493
+ scope.set(stmt.names[i], t)
494
+ }
495
+ // Register as tuple var so it can be passed around
496
+ const varName = stmt.names.join('_') + '_tup'
497
+ ctx.tupleVars.set(varName, elemTemps)
498
+ } else {
499
+ // General expression (e.g. function call) — evaluate, read __rf_ slots
500
+ lowerExpr(stmt.init, ctx, scope)
501
+ const elemTemps: Temp[] = []
502
+ for (let i = 0; i < n; i++) {
503
+ const t = ctx.freshTemp()
504
+ ctx.emit({ kind: 'copy', dst: t, src: { kind: 'temp', name: `__rf_${i}` } })
505
+ elemTemps.push(t)
506
+ scope.set(stmt.names[i], t)
507
+ }
508
+ }
509
+ break
510
+ }
511
+
512
+ case 'expr': {
513
+ lowerExpr(stmt.expr, ctx, scope)
514
+ break
515
+ }
516
+
517
+ case 'return': {
518
+ if (stmt.value?.kind === 'some_lit') {
519
+ // Option return: Some(expr) → set __rf_has=1, __rf_val=expr
520
+ const valOp = lowerExpr(stmt.value.value, ctx, scope)
521
+ ctx.emit({ kind: 'copy', dst: '__rf_has', src: { kind: 'const', value: 1 } })
522
+ ctx.emit({ kind: 'copy', dst: '__rf_val', src: valOp })
523
+ ctx.terminate({ kind: 'return', value: null })
524
+ } else if (stmt.value?.kind === 'none_lit') {
525
+ // Option return: None → set __rf_has=0, __rf_val=0
526
+ ctx.emit({ kind: 'copy', dst: '__rf_has', src: { kind: 'const', value: 0 } })
527
+ ctx.emit({ kind: 'copy', dst: '__rf_val', src: { kind: 'const', value: 0 } })
528
+ ctx.terminate({ kind: 'return', value: null })
529
+ } else if (stmt.value?.kind === 'struct_lit') {
530
+ // Struct return — copy each field to return field slots
531
+ for (const field of stmt.value.fields) {
532
+ const val = lowerExpr(field.value, ctx, scope)
533
+ ctx.emit({ kind: 'copy', dst: `__rf_${field.name}`, src: val })
534
+ }
535
+ ctx.terminate({ kind: 'return', value: null })
536
+ } else if (stmt.value?.kind === 'tuple_lit') {
537
+ // Tuple return — copy each element to __rf_0, __rf_1, ...
538
+ for (let i = 0; i < stmt.value.elements.length; i++) {
539
+ const val = lowerExpr(stmt.value.elements[i], ctx, scope)
540
+ ctx.emit({ kind: 'copy', dst: `__rf_${i}`, src: val })
541
+ }
542
+ ctx.terminate({ kind: 'return', value: null })
543
+ } else if (stmt.value?.kind === 'ident') {
544
+ // Check if returning an option struct var
545
+ const sv = ctx.structVars.get(stmt.value.name)
546
+ if (sv && sv.typeName === '__option') {
547
+ const hasT = sv.fields.get('has')!
548
+ const valT = sv.fields.get('val')!
549
+ ctx.emit({ kind: 'copy', dst: '__rf_has', src: { kind: 'temp', name: hasT } })
550
+ ctx.emit({ kind: 'copy', dst: '__rf_val', src: { kind: 'temp', name: valT } })
551
+ ctx.terminate({ kind: 'return', value: null })
552
+ } else {
553
+ const val = lowerExpr(stmt.value, ctx, scope)
554
+ ctx.terminate({ kind: 'return', value: val })
555
+ }
556
+ } else {
557
+ const val = stmt.value ? lowerExpr(stmt.value, ctx, scope) : null
558
+ ctx.terminate({ kind: 'return', value: val })
559
+ }
560
+ // Create a dead block for any subsequent statements
561
+ const dead = ctx.newBlock('post_ret')
562
+ ctx.switchTo(dead)
563
+ break
564
+ }
565
+
566
+ case 'break': {
567
+ const loop = ctx.currentLoop()
568
+ if (!loop) throw new Error('break outside loop')
569
+ ctx.terminate({ kind: 'jump', target: loop.exit })
570
+ const dead = ctx.newBlock('post_break')
571
+ ctx.switchTo(dead)
572
+ break
573
+ }
574
+
575
+ case 'continue': {
576
+ const loop = ctx.currentLoop()
577
+ if (!loop) throw new Error('continue outside loop')
578
+ ctx.terminate({ kind: 'jump', target: loop.continueTo })
579
+ const dead = ctx.newBlock('post_continue')
580
+ ctx.switchTo(dead)
581
+ break
582
+ }
583
+
584
+ case 'if': {
585
+ const condOp = lowerExpr(stmt.cond, ctx, scope)
586
+ const thenBlock = ctx.newBlock('then')
587
+ const mergeBlock = ctx.newBlock('merge')
588
+ const elseBlock = stmt.else_ ? ctx.newBlock('else') : mergeBlock
589
+
590
+ ctx.terminate({ kind: 'branch', cond: condOp, then: thenBlock.id, else: elseBlock.id })
591
+
592
+ // Then branch
593
+ ctx.switchTo(thenBlock)
594
+ lowerBlock(stmt.then, ctx, new Map(scope))
595
+ if (isPlaceholderTerm(ctx.current().term)) {
596
+ ctx.terminate({ kind: 'jump', target: mergeBlock.id })
597
+ }
598
+
599
+ // Else branch
600
+ if (stmt.else_) {
601
+ ctx.switchTo(elseBlock)
602
+ lowerBlock(stmt.else_, ctx, new Map(scope))
603
+ if (isPlaceholderTerm(ctx.current().term)) {
604
+ ctx.terminate({ kind: 'jump', target: mergeBlock.id })
605
+ }
606
+ }
607
+
608
+ ctx.switchTo(mergeBlock)
609
+ break
610
+ }
611
+
612
+ case 'while': {
613
+ const headerBlock = ctx.newBlock('loop_header')
614
+ const bodyBlock = ctx.newBlock('loop_body')
615
+ const exitBlock = ctx.newBlock('loop_exit')
616
+
617
+ // If there's a step block (for/for_range), create a latch block that
618
+ // executes the step and then jumps to the header. Continue targets the
619
+ // latch so the increment always runs.
620
+ let latchBlock: MIRBlock | null = null
621
+ if (stmt.step && stmt.step.length > 0) {
622
+ latchBlock = ctx.newBlock('loop_latch')
623
+ }
624
+ const continueTarget = latchBlock ? latchBlock.id : headerBlock.id
625
+
626
+ // Jump from current block to header
627
+ ctx.terminate({ kind: 'jump', target: headerBlock.id })
628
+
629
+ // Header: evaluate condition
630
+ ctx.switchTo(headerBlock)
631
+ const condOp = lowerExpr(stmt.cond, ctx, scope)
632
+ ctx.terminate({ kind: 'branch', cond: condOp, then: bodyBlock.id, else: exitBlock.id })
633
+
634
+ // Body
635
+ ctx.switchTo(bodyBlock)
636
+ ctx.pushLoop(headerBlock.id, exitBlock.id, continueTarget)
637
+ lowerBlock(stmt.body, ctx, new Map(scope))
638
+ ctx.popLoop()
639
+ if (isPlaceholderTerm(ctx.current().term)) {
640
+ ctx.terminate({ kind: 'jump', target: continueTarget })
641
+ }
642
+
643
+ // Latch block (step): execute increment, then jump to header
644
+ if (latchBlock && stmt.step) {
645
+ ctx.switchTo(latchBlock)
646
+ lowerBlock(stmt.step, ctx, new Map(scope))
647
+ if (isPlaceholderTerm(ctx.current().term)) {
648
+ ctx.terminate({ kind: 'jump', target: headerBlock.id })
649
+ }
650
+ }
651
+
652
+ ctx.switchTo(exitBlock)
653
+ break
654
+ }
655
+
656
+ case 'foreach': {
657
+ // foreach is MC-specific entity iteration — lower to call_context
658
+ // For now, extract body into a helper and emit call_context
659
+ const helperName = `${ctx.getFnName()}__foreach_${ctx.freshTemp()}`
660
+ const subcommands: ExecuteSubcmd[] = []
661
+
662
+ // The iterable should be a selector expression
663
+ if (stmt.iterable.kind === 'selector') {
664
+ subcommands.push({ kind: 'as', selector: stmt.iterable.raw })
665
+ }
666
+ if (stmt.executeContext === '@s') {
667
+ subcommands.push({ kind: 'at_self' })
668
+ }
669
+
670
+ // Build helper function body as MIR
671
+ const helperCtx = new FnContext(ctx.getNamespace(), helperName, ctx.structDefs, ctx.implMethods)
672
+ const helperScope = new Map(scope)
673
+ lowerBlock(stmt.body, helperCtx, helperScope)
674
+ if (isPlaceholderTerm(helperCtx.current().term)) {
675
+ helperCtx.terminate({ kind: 'return', value: null })
676
+ }
677
+ const helperReachable = computeReachable(helperCtx.blocks, 'entry')
678
+ const helperBlocks = helperCtx.blocks.filter(b => helperReachable.has(b.id))
679
+ computePreds(helperBlocks)
680
+
681
+ ctx.helperFunctions.push({
682
+ name: helperName,
683
+ params: [],
684
+ blocks: helperBlocks,
685
+ entry: 'entry',
686
+ isMacro: false,
687
+ })
688
+
689
+ ctx.emit({ kind: 'call_context', fn: helperName, subcommands })
690
+ break
691
+ }
692
+
693
+ case 'execute': {
694
+ // Extract body into a helper function, emit call_context
695
+ const helperName = `${ctx.getFnName()}__exec_${ctx.freshTemp()}`
696
+ const subcommands = stmt.subcommands.map(lowerExecuteSubcmd)
697
+
698
+ const helperCtx = new FnContext(ctx.getNamespace(), helperName, ctx.structDefs, ctx.implMethods)
699
+ const helperScope = new Map(scope)
700
+ lowerBlock(stmt.body, helperCtx, helperScope)
701
+ if (isPlaceholderTerm(helperCtx.current().term)) {
702
+ helperCtx.terminate({ kind: 'return', value: null })
703
+ }
704
+ const execReachable = computeReachable(helperCtx.blocks, 'entry')
705
+ const execBlocks = helperCtx.blocks.filter(b => execReachable.has(b.id))
706
+ computePreds(execBlocks)
707
+
708
+ ctx.helperFunctions.push({
709
+ name: helperName,
710
+ params: [],
711
+ blocks: execBlocks,
712
+ entry: 'entry',
713
+ isMacro: false,
714
+ })
715
+
716
+ ctx.emit({ kind: 'call_context', fn: helperName, subcommands })
717
+ break
718
+ }
719
+
720
+ case 'match': {
721
+ // Lower match as chained if/else
722
+ const matchVal = lowerExpr(stmt.expr, ctx, scope)
723
+ const mergeBlock = ctx.newBlock('match_merge')
724
+
725
+ for (let i = 0; i < stmt.arms.length; i++) {
726
+ const arm = stmt.arms[i]
727
+ if (arm.pattern === null) {
728
+ // Default arm — just emit the body
729
+ lowerBlock(arm.body, ctx, new Map(scope))
730
+ if (isPlaceholderTerm(ctx.current().term)) {
731
+ ctx.terminate({ kind: 'jump', target: mergeBlock.id })
732
+ }
733
+ } else {
734
+ const patOp = lowerExpr(arm.pattern, ctx, scope)
735
+ const cmpTemp = ctx.freshTemp()
736
+ ctx.emit({ kind: 'cmp', dst: cmpTemp, op: 'eq', a: matchVal, b: patOp })
737
+
738
+ const armBody = ctx.newBlock('match_arm')
739
+ const nextArm = ctx.newBlock('match_next')
740
+ ctx.terminate({ kind: 'branch', cond: { kind: 'temp', name: cmpTemp }, then: armBody.id, else: nextArm.id })
741
+
742
+ ctx.switchTo(armBody)
743
+ lowerBlock(arm.body, ctx, new Map(scope))
744
+ if (isPlaceholderTerm(ctx.current().term)) {
745
+ ctx.terminate({ kind: 'jump', target: mergeBlock.id })
746
+ }
747
+
748
+ ctx.switchTo(nextArm)
749
+ }
750
+ }
751
+
752
+ // If no default arm matched, jump to merge
753
+ if (isPlaceholderTerm(ctx.current().term)) {
754
+ ctx.terminate({ kind: 'jump', target: mergeBlock.id })
755
+ }
756
+
757
+ ctx.switchTo(mergeBlock)
758
+ break
759
+ }
760
+
761
+ case 'raw': {
762
+ // Raw commands are opaque at MIR level — emit as a call to a synthetic raw function
763
+ // For now, pass through as a call with no args (will be handled in LIR)
764
+ ctx.emit({ kind: 'call', dst: null, fn: `__raw:${stmt.cmd}`, args: [] })
765
+ break
766
+ }
767
+
768
+ case 'if_let_some': {
769
+ // if let Some(x) = opt { ... }
770
+ // Lower: check opt.has, if 1 then bind x = opt.val and run then-block
771
+ const sv = (() => {
772
+ if (stmt.init.kind === 'ident') return ctx.structVars.get(stmt.init.name)
773
+ return undefined
774
+ })()
775
+
776
+ let hasOp: Operand
777
+ let valTemp: Temp | undefined
778
+
779
+ if (sv && sv.typeName === '__option') {
780
+ const hasT = sv.fields.get('has')!
781
+ const valT = sv.fields.get('val')!
782
+ hasOp = { kind: 'temp', name: hasT }
783
+ valTemp = valT
784
+ } else {
785
+ // General: evaluate init (e.g. function call returning option via __rf_has/__rf_val)
786
+ lowerExpr(stmt.init, ctx, scope)
787
+ const hasT = ctx.freshTemp()
788
+ const valT = ctx.freshTemp()
789
+ ctx.emit({ kind: 'copy', dst: hasT, src: { kind: 'temp', name: '__rf_has' } })
790
+ ctx.emit({ kind: 'copy', dst: valT, src: { kind: 'temp', name: '__rf_val' } })
791
+ hasOp = { kind: 'temp', name: hasT }
792
+ valTemp = valT
793
+ }
794
+
795
+ const thenBlock = ctx.newBlock('ifl_then')
796
+ const mergeBlock = ctx.newBlock('ifl_merge')
797
+ const elseBlock = stmt.else_ ? ctx.newBlock('ifl_else') : mergeBlock
798
+
799
+ ctx.terminate({ kind: 'branch', cond: hasOp, then: thenBlock.id, else: elseBlock.id })
800
+
801
+ // Then branch: bind x = val temp
802
+ ctx.switchTo(thenBlock)
803
+ const thenScope = new Map(scope)
804
+ if (valTemp) thenScope.set(stmt.binding, valTemp)
805
+ lowerBlock(stmt.then, ctx, thenScope)
806
+ if (isPlaceholderTerm(ctx.current().term)) {
807
+ ctx.terminate({ kind: 'jump', target: mergeBlock.id })
808
+ }
809
+
810
+ // Else branch
811
+ if (stmt.else_) {
812
+ ctx.switchTo(elseBlock)
813
+ lowerBlock(stmt.else_, ctx, new Map(scope))
814
+ if (isPlaceholderTerm(ctx.current().term)) {
815
+ ctx.terminate({ kind: 'jump', target: mergeBlock.id })
816
+ }
817
+ }
818
+
819
+ ctx.switchTo(mergeBlock)
820
+ break
821
+ }
822
+
823
+ default: {
824
+ const _exhaustive: never = stmt
825
+ throw new Error(`Unknown HIR statement kind: ${(_exhaustive as any).kind}`)
826
+ }
827
+ }
828
+ }
829
+
830
+ // ---------------------------------------------------------------------------
831
+ // Expression lowering → produces an Operand (temp or const)
832
+ // ---------------------------------------------------------------------------
833
+
834
+ function lowerExpr(
835
+ expr: HIRExpr,
836
+ ctx: FnContext,
837
+ scope: Map<string, Temp>,
838
+ ): Operand {
839
+ switch (expr.kind) {
840
+ case 'int_lit':
841
+ return { kind: 'const', value: expr.value }
842
+
843
+ case 'float_lit':
844
+ // float is ×1000 fixed-point in RedScript
845
+ return { kind: 'const', value: expr.value }
846
+
847
+ case 'byte_lit':
848
+ case 'short_lit':
849
+ case 'long_lit':
850
+ case 'double_lit':
851
+ return { kind: 'const', value: expr.value }
852
+
853
+ case 'bool_lit': {
854
+ return { kind: 'const', value: expr.value ? 1 : 0 }
855
+ }
856
+
857
+ case 'struct_lit': {
858
+ // Struct literal in expression context (not let/return — those handle it directly).
859
+ // Lower each field value but return a placeholder since the struct
860
+ // is tracked via structVars at the statement level.
861
+ for (const field of expr.fields) {
862
+ lowerExpr(field.value, ctx, scope)
863
+ }
864
+ const t = ctx.freshTemp()
865
+ ctx.emit({ kind: 'const', dst: t, value: 0 })
866
+ return { kind: 'temp', name: t }
867
+ }
868
+
869
+ case 'str_lit':
870
+ case 'range_lit':
871
+ case 'array_lit':
872
+ case 'rel_coord':
873
+ case 'local_coord':
874
+ case 'mc_name':
875
+ case 'blockpos':
876
+ case 'selector':
877
+ case 'str_interp':
878
+ case 'f_string':
879
+ case 'is_check':
880
+ case 'lambda': {
881
+ // MC-specific / complex types — opaque at MIR level
882
+ // Emit as const 0 placeholder; these are handled in LIR lowering
883
+ const t = ctx.freshTemp()
884
+ ctx.emit({ kind: 'const', dst: t, value: 0 })
885
+ return { kind: 'temp', name: t }
886
+ }
887
+
888
+ case 'ident': {
889
+ const temp = scope.get(expr.name)
890
+ if (temp) return { kind: 'temp', name: temp }
891
+ // Unresolved ident — could be a global or external reference
892
+ const t = ctx.freshTemp()
893
+ ctx.emit({ kind: 'copy', dst: t, src: { kind: 'const', value: 0 } })
894
+ scope.set(expr.name, t)
895
+ return { kind: 'temp', name: t }
896
+ }
897
+
898
+ case 'binary': {
899
+ // Handle short-circuit && and ||
900
+ if (expr.op === '&&') {
901
+ return lowerShortCircuitAnd(expr, ctx, scope)
902
+ }
903
+ if (expr.op === '||') {
904
+ return lowerShortCircuitOr(expr, ctx, scope)
905
+ }
906
+
907
+ const left = lowerExpr(expr.left, ctx, scope)
908
+ const right = lowerExpr(expr.right, ctx, scope)
909
+ const t = ctx.freshTemp()
910
+
911
+ // Map HIR binary ops to MIR instructions
912
+ const arithmeticOps: Record<string, MIRInstr['kind']> = {
913
+ '+': 'add', '-': 'sub', '*': 'mul', '/': 'div', '%': 'mod',
914
+ }
915
+ const cmpOps: Record<string, CmpOp> = {
916
+ '==': 'eq', '!=': 'ne', '<': 'lt', '<=': 'le', '>': 'gt', '>=': 'ge',
917
+ }
918
+
919
+ if (expr.op in arithmeticOps) {
920
+ ctx.emit({ kind: arithmeticOps[expr.op] as any, dst: t, a: left, b: right })
921
+ } else if (expr.op in cmpOps) {
922
+ ctx.emit({ kind: 'cmp', dst: t, op: cmpOps[expr.op], a: left, b: right })
923
+ } else {
924
+ throw new Error(`Unknown binary op: ${expr.op}`)
925
+ }
926
+ return { kind: 'temp', name: t }
927
+ }
928
+
929
+ case 'unary': {
930
+ const operand = lowerExpr(expr.operand, ctx, scope)
931
+ const t = ctx.freshTemp()
932
+ if (expr.op === '-') {
933
+ ctx.emit({ kind: 'neg', dst: t, src: operand })
934
+ } else if (expr.op === '!') {
935
+ ctx.emit({ kind: 'not', dst: t, src: operand })
936
+ }
937
+ return { kind: 'temp', name: t }
938
+ }
939
+
940
+ case 'assign': {
941
+ const val = lowerExpr(expr.value, ctx, scope)
942
+ // Reuse the existing temp for this variable so that updates inside
943
+ // if/while bodies are visible to outer code (we target mutable
944
+ // scoreboard slots, not true SSA registers).
945
+ const existing = scope.get(expr.target)
946
+ const t = existing ?? ctx.freshTemp()
947
+ ctx.emit({ kind: 'copy', dst: t, src: val })
948
+ scope.set(expr.target, t)
949
+ return val
950
+ }
951
+
952
+ case 'member_assign': {
953
+ const val = lowerExpr(expr.value, ctx, scope)
954
+ // Struct field assignment: v.x = val → copy val to v's x temp
955
+ if (expr.obj.kind === 'ident') {
956
+ const sv = ctx.structVars.get(expr.obj.name)
957
+ if (sv) {
958
+ const fieldTemp = sv.fields.get(expr.field)
959
+ if (fieldTemp) {
960
+ ctx.emit({ kind: 'copy', dst: fieldTemp, src: val })
961
+ return val
962
+ }
963
+ }
964
+ }
965
+ return val
966
+ }
967
+
968
+ case 'path_expr': {
969
+ // Enum variant access: Phase::Idle → integer constant
970
+ const variants = ctx.enumDefs.get(expr.enumName)
971
+ const value = variants?.get(expr.variant) ?? 0
972
+ return { kind: 'const', value }
973
+ }
974
+
975
+ case 'member': {
976
+ // Enum variant access via dot syntax: Phase.Idle → integer constant
977
+ if (expr.obj.kind === 'ident') {
978
+ const enumVariants = ctx.enumDefs.get(expr.obj.name)
979
+ if (enumVariants && enumVariants.has(expr.field)) {
980
+ return { kind: 'const', value: enumVariants.get(expr.field)! }
981
+ }
982
+ }
983
+ // Struct field access: v.x → return v's x temp
984
+ if (expr.obj.kind === 'ident') {
985
+ const sv = ctx.structVars.get(expr.obj.name)
986
+ if (sv) {
987
+ const fieldTemp = sv.fields.get(expr.field)
988
+ if (fieldTemp) return { kind: 'temp', name: fieldTemp }
989
+ }
990
+ }
991
+ // Fallback: opaque
992
+ const obj = lowerExpr(expr.obj, ctx, scope)
993
+ const t = ctx.freshTemp()
994
+ ctx.emit({ kind: 'copy', dst: t, src: obj })
995
+ return { kind: 'temp', name: t }
996
+ }
997
+
998
+ case 'index': {
999
+ const obj = lowerExpr(expr.obj, ctx, scope)
1000
+ const idx = lowerExpr(expr.index, ctx, scope)
1001
+ const t = ctx.freshTemp()
1002
+ ctx.emit({ kind: 'copy', dst: t, src: obj })
1003
+ return { kind: 'temp', name: t }
1004
+ }
1005
+
1006
+ case 'call': {
1007
+ // Handle builtin calls → raw MC commands
1008
+ if (BUILTIN_SET.has(expr.fn)) {
1009
+ const cmd = formatBuiltinCall(expr.fn, expr.args, ctx.currentMacroParams)
1010
+ ctx.emit({ kind: 'call', dst: null, fn: `__raw:${cmd}`, args: [] })
1011
+ const t = ctx.freshTemp()
1012
+ ctx.emit({ kind: 'const', dst: t, value: 0 })
1013
+ return { kind: 'temp', name: t }
1014
+ }
1015
+
1016
+ // Check for struct instance method call: parser desugars v.method() → call('method', [v, ...])
1017
+ if (expr.args.length > 0 && expr.args[0].kind === 'ident') {
1018
+ const sv = ctx.structVars.get(expr.args[0].name)
1019
+ if (sv) {
1020
+ const methodInfo = ctx.implMethods.get(sv.typeName)?.get(expr.fn)
1021
+ if (methodInfo?.hasSelf) {
1022
+ // Build args: self fields first, then remaining explicit args
1023
+ const fields = ctx.structDefs.get(sv.typeName) ?? []
1024
+ const selfArgs: Operand[] = fields.map(f => {
1025
+ const temp = sv.fields.get(f)
1026
+ return temp ? { kind: 'temp' as const, name: temp } : { kind: 'const' as const, value: 0 }
1027
+ })
1028
+ const explicitArgs = expr.args.slice(1).map(a => lowerExpr(a, ctx, scope))
1029
+ const allArgs = [...selfArgs, ...explicitArgs]
1030
+ const t = ctx.freshTemp()
1031
+ ctx.emit({ kind: 'call', dst: t, fn: `${sv.typeName}::${expr.fn}`, args: allArgs })
1032
+ return { kind: 'temp', name: t }
1033
+ }
1034
+ }
1035
+ }
1036
+
1037
+ // Check if calling a macro function → emit call_macro
1038
+ const targetMacro = ctx.macroInfo.get(expr.fn)
1039
+ if (targetMacro) {
1040
+ const args = expr.args.map(a => lowerExpr(a, ctx, scope))
1041
+ const targetParams = ctx.fnParamInfo.get(expr.fn) ?? []
1042
+ const macroArgs: { name: string; value: Operand; type: NBTType; scale: number }[] = []
1043
+ for (let i = 0; i < targetParams.length && i < args.length; i++) {
1044
+ const paramName = targetParams[i].name
1045
+ if (targetMacro.macroParams.has(paramName)) {
1046
+ const paramTypeName = targetMacro.paramTypes.get(paramName) ?? 'int'
1047
+ const isFloat = paramTypeName === 'float'
1048
+ macroArgs.push({
1049
+ name: paramName,
1050
+ value: args[i],
1051
+ type: isFloat ? 'double' : 'int',
1052
+ scale: isFloat ? 0.01 : 1,
1053
+ })
1054
+ }
1055
+ }
1056
+ const t = ctx.freshTemp()
1057
+ ctx.emit({ kind: 'call_macro', dst: t, fn: expr.fn, args: macroArgs })
1058
+ return { kind: 'temp', name: t }
1059
+ }
1060
+
1061
+ const args = expr.args.map(a => lowerExpr(a, ctx, scope))
1062
+ const t = ctx.freshTemp()
1063
+ ctx.emit({ kind: 'call', dst: t, fn: expr.fn, args })
1064
+ return { kind: 'temp', name: t }
1065
+ }
1066
+
1067
+ case 'invoke': {
1068
+ // Check for struct method call: v.method(args)
1069
+ if (expr.callee.kind === 'member' && expr.callee.obj.kind === 'ident') {
1070
+ const sv = ctx.structVars.get(expr.callee.obj.name)
1071
+ if (sv) {
1072
+ const methodInfo = ctx.implMethods.get(sv.typeName)?.get(expr.callee.field)
1073
+ if (methodInfo?.hasSelf) {
1074
+ // Build args: self fields first, then explicit args
1075
+ const fields = ctx.structDefs.get(sv.typeName) ?? []
1076
+ const selfArgs: Operand[] = fields.map(f => {
1077
+ const temp = sv.fields.get(f)
1078
+ return temp ? { kind: 'temp' as const, name: temp } : { kind: 'const' as const, value: 0 }
1079
+ })
1080
+ const explicitArgs = expr.args.map(a => lowerExpr(a, ctx, scope))
1081
+ const allArgs = [...selfArgs, ...explicitArgs]
1082
+ const t = ctx.freshTemp()
1083
+ ctx.emit({ kind: 'call', dst: t, fn: `${sv.typeName}::${expr.callee.field}`, args: allArgs })
1084
+ return { kind: 'temp', name: t }
1085
+ }
1086
+ }
1087
+ }
1088
+ // Fallback: generic invoke
1089
+ const calleeOp = lowerExpr(expr.callee, ctx, scope)
1090
+ const args = expr.args.map(a => lowerExpr(a, ctx, scope))
1091
+ const t = ctx.freshTemp()
1092
+ ctx.emit({ kind: 'call', dst: t, fn: '__invoke', args: [calleeOp, ...args] })
1093
+ return { kind: 'temp', name: t }
1094
+ }
1095
+
1096
+ case 'static_call': {
1097
+ const args = expr.args.map(a => lowerExpr(a, ctx, scope))
1098
+ const t = ctx.freshTemp()
1099
+ ctx.emit({ kind: 'call', dst: t, fn: `${expr.type}::${expr.method}`, args })
1100
+ return { kind: 'temp', name: t }
1101
+ }
1102
+
1103
+ case 'tuple_lit': {
1104
+ // Inline tuple literal as expression: store elements into __rf_ slots and return a dummy temp
1105
+ // This happens when a tuple literal appears as an expression (e.g., passed to a function)
1106
+ for (let i = 0; i < expr.elements.length; i++) {
1107
+ const val = lowerExpr(expr.elements[i], ctx, scope)
1108
+ ctx.emit({ kind: 'copy', dst: `__rf_${i}`, src: val })
1109
+ }
1110
+ const t = ctx.freshTemp()
1111
+ ctx.emit({ kind: 'const', dst: t, value: 0 })
1112
+ return { kind: 'temp', name: t }
1113
+ }
1114
+
1115
+ case 'some_lit': {
1116
+ // Some(expr) in expression context: store has=1,val into __rf_has/__rf_val slots
1117
+ const valOp = lowerExpr(expr.value, ctx, scope)
1118
+ ctx.emit({ kind: 'copy', dst: '__rf_has', src: { kind: 'const', value: 1 } })
1119
+ ctx.emit({ kind: 'copy', dst: '__rf_val', src: valOp })
1120
+ const t = ctx.freshTemp()
1121
+ ctx.emit({ kind: 'const', dst: t, value: 1 })
1122
+ return { kind: 'temp', name: t }
1123
+ }
1124
+
1125
+ case 'none_lit': {
1126
+ // None in expression context: store has=0,val=0 into __rf_has/__rf_val slots
1127
+ ctx.emit({ kind: 'copy', dst: '__rf_has', src: { kind: 'const', value: 0 } })
1128
+ ctx.emit({ kind: 'copy', dst: '__rf_val', src: { kind: 'const', value: 0 } })
1129
+ const t = ctx.freshTemp()
1130
+ ctx.emit({ kind: 'const', dst: t, value: 0 })
1131
+ return { kind: 'temp', name: t }
1132
+ }
1133
+
1134
+ default: {
1135
+ const _exhaustive: never = expr
1136
+ throw new Error(`Unknown HIR expression kind: ${(_exhaustive as any).kind}`)
1137
+ }
1138
+ }
1139
+ }
1140
+
1141
+ // ---------------------------------------------------------------------------
1142
+ // Short-circuit lowering
1143
+ // ---------------------------------------------------------------------------
1144
+
1145
+ function lowerShortCircuitAnd(
1146
+ expr: Extract<HIRExpr, { kind: 'binary' }>,
1147
+ ctx: FnContext,
1148
+ scope: Map<string, Temp>,
1149
+ ): Operand {
1150
+ // a && b → if(a) { b } else { 0 }
1151
+ const left = lowerExpr(expr.left, ctx, scope)
1152
+ const result = ctx.freshTemp()
1153
+
1154
+ const evalRight = ctx.newBlock('and_right')
1155
+ const merge = ctx.newBlock('and_merge')
1156
+ const falseBlock = ctx.newBlock('and_false')
1157
+
1158
+ ctx.terminate({ kind: 'branch', cond: left, then: evalRight.id, else: falseBlock.id })
1159
+
1160
+ ctx.switchTo(evalRight)
1161
+ const right = lowerExpr(expr.right, ctx, scope)
1162
+ ctx.emit({ kind: 'copy', dst: result, src: right })
1163
+ ctx.terminate({ kind: 'jump', target: merge.id })
1164
+
1165
+ ctx.switchTo(falseBlock)
1166
+ ctx.emit({ kind: 'const', dst: result, value: 0 })
1167
+ ctx.terminate({ kind: 'jump', target: merge.id })
1168
+
1169
+ ctx.switchTo(merge)
1170
+ return { kind: 'temp', name: result }
1171
+ }
1172
+
1173
+ function lowerShortCircuitOr(
1174
+ expr: Extract<HIRExpr, { kind: 'binary' }>,
1175
+ ctx: FnContext,
1176
+ scope: Map<string, Temp>,
1177
+ ): Operand {
1178
+ // a || b → if(a) { 1 } else { b }
1179
+ const left = lowerExpr(expr.left, ctx, scope)
1180
+ const result = ctx.freshTemp()
1181
+
1182
+ const trueBlock = ctx.newBlock('or_true')
1183
+ const evalRight = ctx.newBlock('or_right')
1184
+ const merge = ctx.newBlock('or_merge')
1185
+
1186
+ ctx.terminate({ kind: 'branch', cond: left, then: trueBlock.id, else: evalRight.id })
1187
+
1188
+ ctx.switchTo(trueBlock)
1189
+ ctx.emit({ kind: 'const', dst: result, value: 1 })
1190
+ ctx.terminate({ kind: 'jump', target: merge.id })
1191
+
1192
+ ctx.switchTo(evalRight)
1193
+ const right = lowerExpr(expr.right, ctx, scope)
1194
+ ctx.emit({ kind: 'copy', dst: result, src: right })
1195
+ ctx.terminate({ kind: 'jump', target: merge.id })
1196
+
1197
+ ctx.switchTo(merge)
1198
+ return { kind: 'temp', name: result }
1199
+ }
1200
+
1201
+ // ---------------------------------------------------------------------------
1202
+ // Execute subcommand lowering
1203
+ // ---------------------------------------------------------------------------
1204
+
1205
+ function lowerExecuteSubcmd(sub: HIRExecuteSubcommand): ExecuteSubcmd {
1206
+ switch (sub.kind) {
1207
+ case 'as':
1208
+ return { kind: 'as', selector: selectorToString(sub.selector) }
1209
+ case 'at':
1210
+ return { kind: 'at', selector: selectorToString(sub.selector) }
1211
+ case 'positioned':
1212
+ return { kind: 'positioned', x: sub.x, y: sub.y, z: sub.z }
1213
+ case 'rotated':
1214
+ return { kind: 'rotated', yaw: sub.yaw, pitch: sub.pitch }
1215
+ case 'in':
1216
+ return { kind: 'in', dimension: sub.dimension }
1217
+ case 'anchored':
1218
+ return { kind: 'anchored', anchor: sub.anchor }
1219
+ case 'positioned_as':
1220
+ return { kind: 'at', selector: selectorToString(sub.selector) }
1221
+ case 'rotated_as':
1222
+ return { kind: 'rotated', yaw: '0', pitch: '0' }
1223
+ case 'facing':
1224
+ return { kind: 'positioned', x: sub.x, y: sub.y, z: sub.z }
1225
+ case 'facing_entity':
1226
+ return { kind: 'at', selector: selectorToString(sub.selector) }
1227
+ case 'align':
1228
+ return { kind: 'positioned', x: '0', y: '0', z: '0' }
1229
+ case 'on':
1230
+ return { kind: 'at_self' }
1231
+ case 'summon':
1232
+ return { kind: 'at_self' }
1233
+ case 'if_entity':
1234
+ case 'unless_entity':
1235
+ case 'if_block':
1236
+ case 'unless_block':
1237
+ case 'if_score':
1238
+ case 'unless_score':
1239
+ case 'if_score_range':
1240
+ case 'unless_score_range':
1241
+ case 'store_result':
1242
+ case 'store_success':
1243
+ // These are condition subcommands — pass through as-is for now
1244
+ return { kind: 'at_self' }
1245
+ default: {
1246
+ const _exhaustive: never = sub
1247
+ throw new Error(`Unknown execute subcommand kind: ${(_exhaustive as any).kind}`)
1248
+ }
1249
+ }
1250
+ }
1251
+
1252
+ function selectorToString(sel: { kind: string; filters?: any }): string {
1253
+ // EntitySelector has kind like '@a', '@e', '@s', etc.
1254
+ // Filters are key=value pairs that become [key=value,key=value]
1255
+ if (!sel.filters || Object.keys(sel.filters).length === 0) {
1256
+ return sel.kind
1257
+ }
1258
+ const parts: string[] = []
1259
+ for (const [key, value] of Object.entries(sel.filters)) {
1260
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
1261
+ // Range filter: { min, max } → key=min..max
1262
+ const rangeObj = value as { min?: number; max?: number }
1263
+ if (rangeObj.min !== undefined && rangeObj.max !== undefined) {
1264
+ parts.push(`${key}=${rangeObj.min}..${rangeObj.max}`)
1265
+ } else if (rangeObj.max !== undefined) {
1266
+ parts.push(`${key}=..${rangeObj.max}`)
1267
+ } else if (rangeObj.min !== undefined) {
1268
+ parts.push(`${key}=${rangeObj.min}..`)
1269
+ }
1270
+ } else {
1271
+ parts.push(`${key}=${value}`)
1272
+ }
1273
+ }
1274
+ return `${sel.kind}[${parts.join(',')}]`
1275
+ }
1276
+
1277
+ // ---------------------------------------------------------------------------
1278
+ // Builtin call formatting → raw MC command strings
1279
+ // ---------------------------------------------------------------------------
1280
+
1281
+ const MACRO_SENTINEL = '\x01'
1282
+
1283
+ /**
1284
+ * Format a builtin call as a raw MC command string.
1285
+ * If any argument uses a macro param, the command is prefixed with \x01
1286
+ * (converted to $ in LIR emission).
1287
+ */
1288
+ function formatBuiltinCall(
1289
+ fn: string,
1290
+ args: HIRExpr[],
1291
+ macroParams: Set<string>,
1292
+ ): string {
1293
+ const fmtArgs = args.map(a => exprToCommandArg(a, macroParams))
1294
+ const strs = fmtArgs.map(a => a.str)
1295
+ const hasMacro = fmtArgs.some(a => a.isMacro)
1296
+
1297
+ let cmd: string
1298
+ switch (fn) {
1299
+ case 'summon': {
1300
+ const [type, x, y, z, nbt] = strs
1301
+ const pos = [x ?? '~', y ?? '~', z ?? '~'].join(' ')
1302
+ cmd = nbt ? `summon ${type} ${pos} ${nbt}` : `summon ${type} ${pos}`
1303
+ break
1304
+ }
1305
+ case 'particle': {
1306
+ const [name, x, y, z, ...rest] = strs
1307
+ const pos = [x ?? '~', y ?? '~', z ?? '~'].join(' ')
1308
+ const extra = rest.filter(v => v !== undefined)
1309
+ cmd = extra.length > 0 ? `particle ${name} ${pos} ${extra.join(' ')}` : `particle ${name} ${pos}`
1310
+ break
1311
+ }
1312
+ case 'setblock': {
1313
+ const [x, y, z, block] = strs
1314
+ cmd = `setblock ${x} ${y} ${z} ${block}`
1315
+ break
1316
+ }
1317
+ case 'fill': {
1318
+ const [x1, y1, z1, x2, y2, z2, block] = strs
1319
+ cmd = `fill ${x1} ${y1} ${z1} ${x2} ${y2} ${z2} ${block}`
1320
+ break
1321
+ }
1322
+ case 'say': cmd = `say ${strs[0] ?? ''}`; break
1323
+ case 'tell':
1324
+ case 'tellraw': cmd = `tellraw ${strs[0]} {"text":"${strs[1]}"}`; break
1325
+ case 'title': cmd = `title ${strs[0]} title {"text":"${strs[1]}"}`; break
1326
+ case 'actionbar': cmd = `title ${strs[0]} actionbar {"text":"${strs[1]}"}`; break
1327
+ case 'subtitle': cmd = `title ${strs[0]} subtitle {"text":"${strs[1]}"}`; break
1328
+ case 'title_times': cmd = `title ${strs[0]} times ${strs[1]} ${strs[2]} ${strs[3]}`; break
1329
+ case 'announce': cmd = `tellraw @a {"text":"${strs[0]}"}`; break
1330
+ case 'give': {
1331
+ const nbt = strs[3] ? strs[3] : ''
1332
+ cmd = `give ${strs[0]} ${strs[1]}${nbt} ${strs[2] ?? '1'}`
1333
+ break
1334
+ }
1335
+ case 'kill': cmd = `kill ${strs[0] ?? '@s'}`; break
1336
+ case 'effect': cmd = `effect give ${strs[0]} ${strs[1]} ${strs[2] ?? '30'} ${strs[3] ?? '0'}`; break
1337
+ case 'effect_clear': cmd = strs[1] ? `effect clear ${strs[0]} ${strs[1]}` : `effect clear ${strs[0]}`; break
1338
+ case 'playsound': cmd = ['playsound', ...strs].filter(Boolean).join(' '); break
1339
+ case 'clear': cmd = `clear ${strs[0]} ${strs[1] ?? ''}`.trim(); break
1340
+ case 'weather': cmd = `weather ${strs[0]}`; break
1341
+ case 'time_set': cmd = `time set ${strs[0]}`; break
1342
+ case 'time_add': cmd = `time add ${strs[0]}`; break
1343
+ case 'gamerule': cmd = `gamerule ${strs[0]} ${strs[1]}`; break
1344
+ case 'tag_add': cmd = `tag ${strs[0]} add ${strs[1]}`; break
1345
+ case 'tag_remove': cmd = `tag ${strs[0]} remove ${strs[1]}`; break
1346
+ case 'kick': cmd = `kick ${strs[0]} ${strs[1] ?? ''}`.trim(); break
1347
+ case 'clone': cmd = `clone ${strs.join(' ')}`; break
1348
+ case 'difficulty': cmd = `difficulty ${strs[0]}`; break
1349
+ case 'xp_add': cmd = `xp add ${strs[0]} ${strs[1]} ${strs[2] ?? 'points'}`; break
1350
+ case 'xp_set': cmd = `xp set ${strs[0]} ${strs[1]} ${strs[2] ?? 'points'}`; break
1351
+ default: cmd = `${fn} ${strs.join(' ')}`
1352
+ }
1353
+
1354
+ return hasMacro ? `${MACRO_SENTINEL}${cmd}` : cmd
1355
+ }
1356
+
1357
+ /** Convert an HIR expression to its MC command string representation */
1358
+ function exprToCommandArg(
1359
+ expr: HIRExpr,
1360
+ macroParams: Set<string>,
1361
+ ): { str: string; isMacro: boolean } {
1362
+ switch (expr.kind) {
1363
+ case 'int_lit': return { str: String(expr.value), isMacro: false }
1364
+ case 'float_lit': return { str: String(expr.value), isMacro: false }
1365
+ case 'byte_lit': return { str: String(expr.value), isMacro: false }
1366
+ case 'short_lit': return { str: String(expr.value), isMacro: false }
1367
+ case 'long_lit': return { str: String(expr.value), isMacro: false }
1368
+ case 'double_lit': return { str: String(expr.value), isMacro: false }
1369
+ case 'bool_lit': return { str: expr.value ? 'true' : 'false', isMacro: false }
1370
+ case 'str_lit': return { str: expr.value, isMacro: false }
1371
+ case 'mc_name': return { str: expr.value, isMacro: false }
1372
+ case 'selector': return { str: expr.raw, isMacro: false }
1373
+ case 'ident':
1374
+ if (macroParams.has(expr.name)) return { str: `$(${expr.name})`, isMacro: true }
1375
+ return { str: expr.name, isMacro: false }
1376
+ case 'local_coord': {
1377
+ const prefix = expr.value[0] // ^
1378
+ const rest = expr.value.slice(1)
1379
+ if (rest && /^[a-zA-Z_]\w*$/.test(rest) && macroParams.has(rest)) {
1380
+ return { str: `${prefix}$(${rest})`, isMacro: true }
1381
+ }
1382
+ return { str: expr.value, isMacro: false }
1383
+ }
1384
+ case 'rel_coord': {
1385
+ const prefix = expr.value[0] // ~
1386
+ const rest = expr.value.slice(1)
1387
+ if (rest && /^[a-zA-Z_]\w*$/.test(rest) && macroParams.has(rest)) {
1388
+ return { str: `${prefix}$(${rest})`, isMacro: true }
1389
+ }
1390
+ return { str: expr.value, isMacro: false }
1391
+ }
1392
+ case 'unary':
1393
+ if (expr.op === '-' && expr.operand.kind === 'float_lit') {
1394
+ return { str: String(-expr.operand.value), isMacro: false }
1395
+ }
1396
+ if (expr.op === '-' && expr.operand.kind === 'int_lit') {
1397
+ return { str: String(-expr.operand.value), isMacro: false }
1398
+ }
1399
+ return { str: '~', isMacro: false }
1400
+ default:
1401
+ return { str: '~', isMacro: false }
1402
+ }
1403
+ }