redscript-mc 2.6.2 → 3.0.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 (636) hide show
  1. package/.github/workflows/ci.yml +11 -0
  2. package/CHANGELOG.md +18 -9
  3. package/README-benchmarks.md +48 -0
  4. package/README-vscode-test.md +251 -0
  5. package/RELEASE_NOTES.md +74 -0
  6. package/ROADMAP.md +131 -167
  7. package/benchmarks/_shared.ts +468 -0
  8. package/benchmarks/baseline.json +2816 -0
  9. package/benchmarks/baseline.md +13 -0
  10. package/benchmarks/compiler-perf.report.json +207 -0
  11. package/benchmarks/compiler-perf.ts +76 -0
  12. package/benchmarks/results.md +13 -0
  13. package/benchmarks/stdlib-complexity.report.json +2606 -0
  14. package/benchmarks/stdlib-complexity.ts +54 -0
  15. package/benchmarks/stdlib-size.md +57 -0
  16. package/benchmarks/stdlib-size.ts +91 -0
  17. package/coverage-report.md +177 -0
  18. package/dist/src/__tests__/budget.test.js +4 -0
  19. package/dist/src/__tests__/cache/cache-behavior.test.d.ts +10 -0
  20. package/dist/src/__tests__/cache/cache-behavior.test.js +425 -0
  21. package/dist/src/__tests__/cache-extra.test.d.ts +5 -0
  22. package/dist/src/__tests__/cache-extra.test.js +211 -0
  23. package/dist/src/__tests__/cli-init.test.d.ts +1 -0
  24. package/dist/src/__tests__/cli-init.test.js +97 -0
  25. package/dist/src/__tests__/cli-publish.test.d.ts +9 -0
  26. package/dist/src/__tests__/cli-publish.test.js +189 -0
  27. package/dist/src/__tests__/cli.test.js +76 -0
  28. package/dist/src/__tests__/compile-preprocess.test.d.ts +11 -0
  29. package/dist/src/__tests__/compile-preprocess.test.js +328 -0
  30. package/dist/src/__tests__/compiler/break-stmt.test.d.ts +1 -0
  31. package/dist/src/__tests__/compiler/break-stmt.test.js +58 -0
  32. package/dist/src/__tests__/compiler/const-decl.test.d.ts +1 -0
  33. package/dist/src/__tests__/compiler/const-decl.test.js +123 -0
  34. package/dist/src/__tests__/compiler/continue-stmt.test.d.ts +1 -0
  35. package/dist/src/__tests__/compiler/continue-stmt.test.js +67 -0
  36. package/dist/src/__tests__/compiler/coroutine-extended.test.d.ts +17 -0
  37. package/dist/src/__tests__/compiler/coroutine-extended.test.js +565 -0
  38. package/dist/src/__tests__/compiler/deprecated.test.d.ts +4 -0
  39. package/dist/src/__tests__/compiler/deprecated.test.js +285 -0
  40. package/dist/src/__tests__/compiler/do-while.test.d.ts +1 -0
  41. package/dist/src/__tests__/compiler/do-while.test.js +120 -0
  42. package/dist/src/__tests__/compiler/enum-payload.test.d.ts +9 -0
  43. package/dist/src/__tests__/compiler/enum-payload.test.js +272 -0
  44. package/dist/src/__tests__/compiler/interface.test.d.ts +10 -0
  45. package/dist/src/__tests__/compiler/interface.test.js +258 -0
  46. package/dist/src/__tests__/compiler/labeled-loops.test.d.ts +1 -0
  47. package/dist/src/__tests__/compiler/labeled-loops.test.js +263 -0
  48. package/dist/src/__tests__/compiler/match-string.test.d.ts +1 -0
  49. package/dist/src/__tests__/compiler/match-string.test.js +43 -0
  50. package/dist/src/__tests__/compiler/memoize.test.d.ts +1 -0
  51. package/dist/src/__tests__/compiler/memoize.test.js +113 -0
  52. package/dist/src/__tests__/compiler/method-chain.test.d.ts +5 -0
  53. package/dist/src/__tests__/compiler/method-chain.test.js +115 -0
  54. package/dist/src/__tests__/compiler/module-import.test.d.ts +12 -0
  55. package/dist/src/__tests__/compiler/module-import.test.js +261 -0
  56. package/dist/src/__tests__/compiler/option-extensions.test.d.ts +6 -0
  57. package/dist/src/__tests__/compiler/option-extensions.test.js +191 -0
  58. package/dist/src/__tests__/compiler/profile-decorator.test.d.ts +1 -0
  59. package/dist/src/__tests__/compiler/profile-decorator.test.js +69 -0
  60. package/dist/src/__tests__/compiler/string-advanced.test.d.ts +7 -0
  61. package/dist/src/__tests__/compiler/string-advanced.test.js +281 -0
  62. package/dist/src/__tests__/compiler/struct-extends.test.d.ts +1 -0
  63. package/dist/src/__tests__/compiler/struct-extends.test.js +95 -0
  64. package/dist/src/__tests__/compiler/throttle-retry.test.d.ts +1 -0
  65. package/dist/src/__tests__/compiler/throttle-retry.test.js +166 -0
  66. package/dist/src/__tests__/compiler/tuple-type.test.d.ts +10 -0
  67. package/dist/src/__tests__/compiler/tuple-type.test.js +229 -0
  68. package/dist/src/__tests__/compiler/watch-decorator.test.d.ts +1 -0
  69. package/dist/src/__tests__/compiler/watch-decorator.test.js +65 -0
  70. package/dist/src/__tests__/config/project-config.test.d.ts +1 -0
  71. package/dist/src/__tests__/config/project-config.test.js +199 -0
  72. package/dist/src/__tests__/config-decorator.test.d.ts +8 -0
  73. package/dist/src/__tests__/config-decorator.test.js +142 -0
  74. package/dist/src/__tests__/diagnostics-extra.test.d.ts +6 -0
  75. package/dist/src/__tests__/diagnostics-extra.test.js +132 -0
  76. package/dist/src/__tests__/emit/compile-branches.test.d.ts +1 -0
  77. package/dist/src/__tests__/emit/compile-branches.test.js +123 -0
  78. package/dist/src/__tests__/emit/compile-coverage.test.d.ts +25 -0
  79. package/dist/src/__tests__/emit/compile-coverage.test.js +617 -0
  80. package/dist/src/__tests__/emit/compile-extra-branches.test.d.ts +12 -0
  81. package/dist/src/__tests__/emit/compile-extra-branches.test.js +225 -0
  82. package/dist/src/__tests__/emit/compile-mocked-branches.test.d.ts +0 -0
  83. package/dist/src/__tests__/emit/compile-mocked-branches.test.js +238 -0
  84. package/dist/src/__tests__/emit/execute-chain.test.d.ts +10 -0
  85. package/dist/src/__tests__/emit/execute-chain.test.js +94 -0
  86. package/dist/src/__tests__/emit/index.test.js +2 -1
  87. package/dist/src/__tests__/emit/modules-branches.test.d.ts +1 -0
  88. package/dist/src/__tests__/emit/modules-branches.test.js +88 -0
  89. package/dist/src/__tests__/emit/modules-coverage.test.d.ts +15 -0
  90. package/dist/src/__tests__/emit/modules-coverage.test.js +221 -0
  91. package/dist/src/__tests__/emit/modules-errors.test.d.ts +12 -0
  92. package/dist/src/__tests__/emit/modules-errors.test.js +169 -0
  93. package/dist/src/__tests__/emit/modules-rewrite.test.d.ts +17 -0
  94. package/dist/src/__tests__/emit/modules-rewrite.test.js +204 -0
  95. package/dist/src/__tests__/emit/source-map.test.d.ts +1 -0
  96. package/dist/src/__tests__/emit/source-map.test.js +167 -0
  97. package/dist/src/__tests__/enum.test.js +9 -4
  98. package/dist/src/__tests__/error-recovery.test.d.ts +7 -0
  99. package/dist/src/__tests__/error-recovery.test.js +217 -0
  100. package/dist/src/__tests__/events-types-extra.test.d.ts +10 -0
  101. package/dist/src/__tests__/events-types-extra.test.js +91 -0
  102. package/dist/src/__tests__/events-types.test.d.ts +4 -0
  103. package/dist/src/__tests__/events-types.test.js +56 -0
  104. package/dist/src/__tests__/formatter.test.js +13 -5
  105. package/dist/src/__tests__/hir/lower-extra.test.d.ts +9 -0
  106. package/dist/src/__tests__/hir/lower-extra.test.js +140 -0
  107. package/dist/src/__tests__/hir/monomorphize-extra.test.d.ts +15 -0
  108. package/dist/src/__tests__/hir/monomorphize-extra.test.js +200 -0
  109. package/dist/src/__tests__/hir/monomorphize-extra2.test.d.ts +16 -0
  110. package/dist/src/__tests__/hir/monomorphize-extra2.test.js +316 -0
  111. package/dist/src/__tests__/incremental.test.js +10 -2
  112. package/dist/src/__tests__/index-extra.test.d.ts +10 -0
  113. package/dist/src/__tests__/index-extra.test.js +71 -0
  114. package/dist/src/__tests__/lexer.test.js +2 -2
  115. package/dist/src/__tests__/lint/rules.test.d.ts +5 -0
  116. package/dist/src/__tests__/lint/rules.test.js +208 -0
  117. package/dist/src/__tests__/lir/lower.test.js +29 -0
  118. package/dist/src/__tests__/lir/verify.test.js +30 -0
  119. package/dist/src/__tests__/lsp/completion.test.d.ts +7 -0
  120. package/dist/src/__tests__/lsp/completion.test.js +583 -0
  121. package/dist/src/__tests__/lsp/definition.test.d.ts +7 -0
  122. package/dist/src/__tests__/lsp/definition.test.js +454 -0
  123. package/dist/src/__tests__/lsp/diagnostics.test.d.ts +10 -0
  124. package/dist/src/__tests__/lsp/diagnostics.test.js +98 -0
  125. package/dist/src/__tests__/lsp/hover-docs.test.d.ts +10 -0
  126. package/dist/src/__tests__/lsp/hover-docs.test.js +210 -0
  127. package/dist/src/__tests__/lsp.test.js +4 -1
  128. package/dist/src/__tests__/mc-integration/item-entity-events.test.js +4 -0
  129. package/dist/src/__tests__/mc-integration/stdlib-coverage-2.test.js +4 -0
  130. package/dist/src/__tests__/mc-integration/stdlib-coverage-3.test.d.ts +13 -0
  131. package/dist/src/__tests__/mc-integration/stdlib-coverage-3.test.js +1227 -0
  132. package/dist/src/__tests__/mc-integration/stdlib-coverage-4.test.d.ts +13 -0
  133. package/dist/src/__tests__/mc-integration/stdlib-coverage-4.test.js +1509 -0
  134. package/dist/src/__tests__/mc-integration/stdlib-coverage-5.test.d.ts +14 -0
  135. package/dist/src/__tests__/mc-integration/stdlib-coverage-5.test.js +1374 -0
  136. package/dist/src/__tests__/mc-integration/stdlib-coverage-6.test.d.ts +10 -0
  137. package/dist/src/__tests__/mc-integration/stdlib-coverage-6.test.js +759 -0
  138. package/dist/src/__tests__/mc-integration/stdlib-coverage-7.test.d.ts +13 -0
  139. package/dist/src/__tests__/mc-integration/stdlib-coverage-7.test.js +855 -0
  140. package/dist/src/__tests__/mc-integration/stdlib-coverage.test.js +4 -0
  141. package/dist/src/__tests__/mc-integration/syntax-coverage.test.js +4 -0
  142. package/dist/src/__tests__/mc-validator-coverage.test.d.ts +13 -0
  143. package/dist/src/__tests__/mc-validator-coverage.test.js +296 -0
  144. package/dist/src/__tests__/mc-validator-extra.test.d.ts +13 -0
  145. package/dist/src/__tests__/mc-validator-extra.test.js +245 -0
  146. package/dist/src/__tests__/mir/lower-extra.test.d.ts +20 -0
  147. package/dist/src/__tests__/mir/lower-extra.test.js +361 -0
  148. package/dist/src/__tests__/mir/lower-extra2.test.d.ts +17 -0
  149. package/dist/src/__tests__/mir/lower-extra2.test.js +317 -0
  150. package/dist/src/__tests__/mir/lower-extra3.test.d.ts +19 -0
  151. package/dist/src/__tests__/mir/lower-extra3.test.js +249 -0
  152. package/dist/src/__tests__/mir/lower-extra4.test.d.ts +23 -0
  153. package/dist/src/__tests__/mir/lower-extra4.test.js +606 -0
  154. package/dist/src/__tests__/mir/lower-extra5.test.d.ts +25 -0
  155. package/dist/src/__tests__/mir/lower-extra5.test.js +543 -0
  156. package/dist/src/__tests__/mir/lower-extra6.test.d.ts +16 -0
  157. package/dist/src/__tests__/mir/lower-extra6.test.js +471 -0
  158. package/dist/src/__tests__/mir/lower-extra7.test.d.ts +35 -0
  159. package/dist/src/__tests__/mir/lower-extra7.test.js +921 -0
  160. package/dist/src/__tests__/mir/lower-extra8.test.d.ts +19 -0
  161. package/dist/src/__tests__/mir/lower-extra8.test.js +626 -0
  162. package/dist/src/__tests__/mir/lower-extra9.test.d.ts +14 -0
  163. package/dist/src/__tests__/mir/lower-extra9.test.js +717 -0
  164. package/dist/src/__tests__/optimizer/auto-inline.test.d.ts +1 -0
  165. package/dist/src/__tests__/optimizer/auto-inline.test.js +176 -0
  166. package/dist/src/__tests__/optimizer/cse.test.d.ts +4 -0
  167. package/dist/src/__tests__/optimizer/cse.test.js +178 -0
  168. package/dist/src/__tests__/optimizer/inline_fn.test.d.ts +1 -0
  169. package/dist/src/__tests__/optimizer/inline_fn.test.js +221 -0
  170. package/dist/src/__tests__/optimizer/licm.test.d.ts +1 -0
  171. package/dist/src/__tests__/optimizer/licm.test.js +244 -0
  172. package/dist/src/__tests__/optimizer/optimizer-extended.test.d.ts +12 -0
  173. package/dist/src/__tests__/optimizer/optimizer-extended.test.js +993 -0
  174. package/dist/src/__tests__/optimizer/strength-reduction.test.d.ts +1 -0
  175. package/dist/src/__tests__/optimizer/strength-reduction.test.js +86 -0
  176. package/dist/src/__tests__/optimizer/tco.test.d.ts +14 -0
  177. package/dist/src/__tests__/optimizer/tco.test.js +203 -0
  178. package/dist/src/__tests__/parser-coverage.test.d.ts +25 -0
  179. package/dist/src/__tests__/parser-coverage.test.js +491 -0
  180. package/dist/src/__tests__/parser-extra.test.d.ts +6 -0
  181. package/dist/src/__tests__/parser-extra.test.js +451 -0
  182. package/dist/src/__tests__/parser.test.js +12 -0
  183. package/dist/src/__tests__/repl-extra.test.d.ts +13 -0
  184. package/dist/src/__tests__/repl-extra.test.js +174 -0
  185. package/dist/src/__tests__/repl-server-extra.test.d.ts +10 -0
  186. package/dist/src/__tests__/repl-server-extra.test.js +161 -0
  187. package/dist/src/__tests__/repl-server.test.d.ts +6 -0
  188. package/dist/src/__tests__/repl-server.test.js +146 -0
  189. package/dist/src/__tests__/runtime-extra.test.d.ts +15 -0
  190. package/dist/src/__tests__/runtime-extra.test.js +732 -0
  191. package/dist/src/__tests__/singleton-decorator.test.d.ts +11 -0
  192. package/dist/src/__tests__/singleton-decorator.test.js +260 -0
  193. package/dist/src/__tests__/sourcemap.test.js +1 -1
  194. package/dist/src/__tests__/stdlib/advanced.test.d.ts +5 -0
  195. package/dist/src/__tests__/stdlib/advanced.test.js +301 -0
  196. package/dist/src/__tests__/stdlib/bigint.test.d.ts +4 -0
  197. package/dist/src/__tests__/stdlib/bigint.test.js +83 -0
  198. package/dist/src/__tests__/stdlib/bits.test.d.ts +4 -0
  199. package/dist/src/__tests__/stdlib/bits.test.js +96 -0
  200. package/dist/src/__tests__/stdlib/bossbar.test.d.ts +4 -0
  201. package/dist/src/__tests__/stdlib/bossbar.test.js +72 -0
  202. package/dist/src/__tests__/stdlib/color.test.d.ts +4 -0
  203. package/dist/src/__tests__/stdlib/color.test.js +84 -0
  204. package/dist/src/__tests__/stdlib/combat.test.d.ts +4 -0
  205. package/dist/src/__tests__/stdlib/combat.test.js +64 -0
  206. package/dist/src/__tests__/stdlib/cooldown.test.d.ts +4 -0
  207. package/dist/src/__tests__/stdlib/cooldown.test.js +64 -0
  208. package/dist/src/__tests__/stdlib/dialog.test.js +15 -7
  209. package/dist/src/__tests__/stdlib/ecs.test.d.ts +4 -0
  210. package/dist/src/__tests__/stdlib/ecs.test.js +81 -0
  211. package/dist/src/__tests__/stdlib/effects.test.d.ts +4 -0
  212. package/dist/src/__tests__/stdlib/effects.test.js +72 -0
  213. package/dist/src/__tests__/stdlib/events.test.d.ts +4 -0
  214. package/dist/src/__tests__/stdlib/events.test.js +55 -0
  215. package/dist/src/__tests__/stdlib/expr.test.d.ts +4 -0
  216. package/dist/src/__tests__/stdlib/expr.test.js +77 -0
  217. package/dist/src/__tests__/stdlib/fft.test.d.ts +4 -0
  218. package/dist/src/__tests__/stdlib/fft.test.js +82 -0
  219. package/dist/src/__tests__/stdlib/graph.test.d.ts +4 -0
  220. package/dist/src/__tests__/stdlib/graph.test.js +102 -0
  221. package/dist/src/__tests__/stdlib/interactions.test.d.ts +4 -0
  222. package/dist/src/__tests__/stdlib/interactions.test.js +60 -0
  223. package/dist/src/__tests__/stdlib/inventory.test.d.ts +4 -0
  224. package/dist/src/__tests__/stdlib/inventory.test.js +68 -0
  225. package/dist/src/__tests__/stdlib/linalg.test.d.ts +5 -0
  226. package/dist/src/__tests__/stdlib/linalg.test.js +78 -0
  227. package/dist/src/__tests__/stdlib/map.test.d.ts +1 -0
  228. package/dist/src/__tests__/stdlib/map.test.js +84 -0
  229. package/dist/src/__tests__/stdlib/math.test.js +19 -6
  230. package/dist/src/__tests__/stdlib/math_hp.test.d.ts +4 -0
  231. package/dist/src/__tests__/stdlib/math_hp.test.js +80 -0
  232. package/dist/src/__tests__/stdlib/mobs.test.d.ts +4 -0
  233. package/dist/src/__tests__/stdlib/mobs.test.js +61 -0
  234. package/dist/src/__tests__/stdlib/noise.test.d.ts +4 -0
  235. package/dist/src/__tests__/stdlib/noise.test.js +73 -0
  236. package/dist/src/__tests__/stdlib/ode.test.d.ts +4 -0
  237. package/dist/src/__tests__/stdlib/ode.test.js +68 -0
  238. package/dist/src/__tests__/stdlib/parabola.test.d.ts +4 -0
  239. package/dist/src/__tests__/stdlib/parabola.test.js +77 -0
  240. package/dist/src/__tests__/stdlib/particles.test.d.ts +4 -0
  241. package/dist/src/__tests__/stdlib/particles.test.js +68 -0
  242. package/dist/src/__tests__/stdlib/physics.test.d.ts +4 -0
  243. package/dist/src/__tests__/stdlib/physics.test.js +76 -0
  244. package/dist/src/__tests__/stdlib/player.test.d.ts +4 -0
  245. package/dist/src/__tests__/stdlib/player.test.js +64 -0
  246. package/dist/src/__tests__/stdlib/quaternion.test.d.ts +4 -0
  247. package/dist/src/__tests__/stdlib/quaternion.test.js +73 -0
  248. package/dist/src/__tests__/stdlib/queue.test.d.ts +1 -0
  249. package/dist/src/__tests__/stdlib/queue.test.js +97 -0
  250. package/dist/src/__tests__/stdlib/random.test.d.ts +4 -0
  251. package/dist/src/__tests__/stdlib/random.test.js +76 -0
  252. package/dist/src/__tests__/stdlib/result.test.d.ts +12 -0
  253. package/dist/src/__tests__/stdlib/result.test.js +329 -0
  254. package/dist/src/__tests__/stdlib/scheduler.test.js +19 -8
  255. package/dist/src/__tests__/stdlib/set_int.test.d.ts +1 -0
  256. package/dist/src/__tests__/stdlib/set_int.test.js +88 -0
  257. package/dist/src/__tests__/stdlib/sets.test.d.ts +6 -0
  258. package/dist/src/__tests__/stdlib/sets.test.js +60 -0
  259. package/dist/src/__tests__/stdlib/signal.test.d.ts +4 -0
  260. package/dist/src/__tests__/stdlib/signal.test.js +84 -0
  261. package/dist/src/__tests__/stdlib/spawn.test.d.ts +4 -0
  262. package/dist/src/__tests__/stdlib/spawn.test.js +68 -0
  263. package/dist/src/__tests__/stdlib/string.test.d.ts +12 -0
  264. package/dist/src/__tests__/stdlib/string.test.js +231 -0
  265. package/dist/src/__tests__/stdlib/strings.test.d.ts +4 -0
  266. package/dist/src/__tests__/stdlib/strings.test.js +83 -0
  267. package/dist/src/__tests__/stdlib/tags.test.d.ts +4 -0
  268. package/dist/src/__tests__/stdlib/tags.test.js +57 -0
  269. package/dist/src/__tests__/stdlib/teams.test.d.ts +4 -0
  270. package/dist/src/__tests__/stdlib/teams.test.js +72 -0
  271. package/dist/src/__tests__/stdlib/timer.test.d.ts +4 -0
  272. package/dist/src/__tests__/stdlib/timer.test.js +79 -0
  273. package/dist/src/__tests__/stdlib/vec.test.d.ts +5 -0
  274. package/dist/src/__tests__/stdlib/vec.test.js +94 -0
  275. package/dist/src/__tests__/stdlib/world.test.d.ts +4 -0
  276. package/dist/src/__tests__/stdlib/world.test.js +72 -0
  277. package/dist/src/__tests__/struct-display.test.d.ts +1 -0
  278. package/dist/src/__tests__/struct-display.test.js +64 -0
  279. package/dist/src/__tests__/test-framework/runner.test.d.ts +10 -0
  280. package/dist/src/__tests__/test-framework/runner.test.js +193 -0
  281. package/dist/src/__tests__/tuner/adapters.test.d.ts +14 -0
  282. package/dist/src/__tests__/tuner/adapters.test.js +194 -0
  283. package/dist/src/__tests__/tuner/simulator-extra.test.d.ts +4 -0
  284. package/dist/src/__tests__/tuner/simulator-extra.test.js +193 -0
  285. package/dist/src/__tests__/typechecker-coverage.test.d.ts +30 -0
  286. package/dist/src/__tests__/typechecker-coverage.test.js +627 -0
  287. package/dist/src/__tests__/typechecker.test.js +3 -3
  288. package/dist/src/__tests__/watch-decorator.test.d.ts +1 -0
  289. package/dist/src/__tests__/watch-decorator.test.js +54 -0
  290. package/dist/src/ast/types.d.ts +102 -3
  291. package/dist/src/cache/incremental.d.ts +13 -14
  292. package/dist/src/cache/incremental.js +106 -89
  293. package/dist/src/cache/index.d.ts +8 -2
  294. package/dist/src/cache/index.js +18 -6
  295. package/dist/src/cli.d.ts +1 -0
  296. package/dist/src/cli.js +466 -17
  297. package/dist/src/config/project-config.d.ts +29 -0
  298. package/dist/src/config/project-config.js +180 -0
  299. package/dist/src/diagnostics/index.d.ts +9 -0
  300. package/dist/src/diagnostics/index.js +18 -1
  301. package/dist/src/emit/compile.d.ts +10 -0
  302. package/dist/src/emit/compile.js +395 -50
  303. package/dist/src/emit/index.d.ts +40 -0
  304. package/dist/src/emit/index.js +307 -14
  305. package/dist/src/emit/modules.js +21 -3
  306. package/dist/src/emit/sourcemap.d.ts +23 -27
  307. package/dist/src/emit/sourcemap.js +52 -30
  308. package/dist/src/formatter/index.js +33 -8
  309. package/dist/src/hir/deprecated.d.ts +13 -0
  310. package/dist/src/hir/deprecated.js +218 -0
  311. package/dist/src/hir/lower.js +114 -8
  312. package/dist/src/hir/monomorphize.js +22 -2
  313. package/dist/src/hir/types.d.ts +65 -1
  314. package/dist/src/index.d.ts +6 -0
  315. package/dist/src/index.js +18 -3
  316. package/dist/src/lexer/index.d.ts +2 -1
  317. package/dist/src/lexer/index.js +39 -3
  318. package/dist/src/lint/index.d.ts +45 -0
  319. package/dist/src/lint/index.js +930 -0
  320. package/dist/src/lir/lower.js +29 -2
  321. package/dist/src/lir/types.d.ts +2 -0
  322. package/dist/src/lsp/server.js +92 -5
  323. package/dist/src/mir/lower.js +775 -34
  324. package/dist/src/mir/macro.js +36 -2
  325. package/dist/src/mir/types.d.ts +12 -0
  326. package/dist/src/mir/verify.js +9 -0
  327. package/dist/src/optimizer/auto-inline.d.ts +2 -0
  328. package/dist/src/optimizer/auto-inline.js +67 -0
  329. package/dist/src/optimizer/cse.d.ts +20 -0
  330. package/dist/src/optimizer/cse.js +234 -0
  331. package/dist/src/optimizer/inline.d.ts +26 -0
  332. package/dist/src/optimizer/inline.js +286 -0
  333. package/dist/src/optimizer/interprocedural.js +4 -0
  334. package/dist/src/optimizer/licm.d.ts +32 -0
  335. package/dist/src/optimizer/licm.js +371 -0
  336. package/dist/src/optimizer/pipeline.js +12 -2
  337. package/dist/src/optimizer/strength_reduction.d.ts +15 -0
  338. package/dist/src/optimizer/strength_reduction.js +90 -0
  339. package/dist/src/optimizer/tco.d.ts +53 -0
  340. package/dist/src/optimizer/tco.js +238 -0
  341. package/dist/src/parser/index.d.ts +32 -0
  342. package/dist/src/parser/index.js +421 -59
  343. package/dist/src/repl-server.d.ts +13 -0
  344. package/dist/src/repl-server.js +127 -0
  345. package/dist/src/structs/expand.d.ts +15 -0
  346. package/dist/src/structs/expand.js +46 -0
  347. package/dist/src/testing/runner.d.ts +40 -0
  348. package/dist/src/testing/runner.js +237 -0
  349. package/dist/src/typechecker/index.d.ts +3 -0
  350. package/dist/src/typechecker/index.js +254 -9
  351. package/dist/tsconfig.tsbuildinfo +1 -1
  352. package/doc-drafts/redscript-docs/docs/en/stdlib/graph.md +104 -0
  353. package/doc-drafts/redscript-docs/docs/en/stdlib/parabola.md +113 -0
  354. package/doc-drafts/redscript-docs/docs/en/stdlib/pathfind.md +104 -0
  355. package/doc-drafts/redscript-docs/docs/en/stdlib/physics.md +134 -0
  356. package/doc-drafts/redscript-docs/docs/en/stdlib/quaternion.md +135 -0
  357. package/doc-drafts/redscript-docs/docs/zh/stdlib/graph.md +104 -0
  358. package/doc-drafts/redscript-docs/docs/zh/stdlib/parabola.md +113 -0
  359. package/doc-drafts/redscript-docs/docs/zh/stdlib/pathfind.md +104 -0
  360. package/doc-drafts/redscript-docs/docs/zh/stdlib/physics.md +134 -0
  361. package/doc-drafts/redscript-docs/docs/zh/stdlib/quaternion.md +135 -0
  362. package/docs/stdlib/result.md +156 -0
  363. package/docs/stdlib/result.zh.md +156 -0
  364. package/editors/vscode/fixtures/test.mcrs +7 -0
  365. package/editors/vscode/out/extension.js +2095 -225
  366. package/editors/vscode/out/lsp-server.js +519 -51
  367. package/editors/vscode/package-lock.json +9 -4
  368. package/editors/vscode/package.json +1 -1
  369. package/examples/display-demo.mcrs +64 -0
  370. package/examples/game/racing.mcrs +301 -0
  371. package/examples/game/tower_defense.mcrs +311 -0
  372. package/examples/math/physics_sim.mcrs +322 -0
  373. package/examples/rpg/boss_fight.mcrs +313 -0
  374. package/examples/rpg/health_system.mcrs +237 -0
  375. package/examples/rpg/inventory.mcrs +265 -0
  376. package/examples/util/debug_hud.mcrs +279 -0
  377. package/jest.config.js +10 -0
  378. package/package.json +12 -3
  379. package/playground/index.html +823 -0
  380. package/scripts/gen-docs.ts +533 -0
  381. package/scripts/update-redscript-docs-stdlib.sh +770 -0
  382. package/src/__tests__/budget.test.ts +5 -0
  383. package/src/__tests__/cache/cache-behavior.test.ts +480 -0
  384. package/src/__tests__/cache-extra.test.ts +199 -0
  385. package/src/__tests__/cli-docs.test.ts +77 -0
  386. package/src/__tests__/cli-init.test.ts +91 -0
  387. package/src/__tests__/cli-publish.test.ts +190 -0
  388. package/src/__tests__/cli.test.ts +117 -1
  389. package/src/__tests__/compile-preprocess.test.ts +366 -0
  390. package/src/__tests__/compiler/break-stmt.test.ts +66 -0
  391. package/src/__tests__/compiler/const-decl.test.ts +141 -0
  392. package/src/__tests__/compiler/continue-stmt.test.ts +81 -0
  393. package/src/__tests__/compiler/coroutine-extended.test.ts +723 -0
  394. package/src/__tests__/compiler/deprecated.test.ts +305 -0
  395. package/src/__tests__/compiler/do-while.test.ts +130 -0
  396. package/src/__tests__/compiler/enum-payload.test.ts +299 -0
  397. package/src/__tests__/compiler/interface.test.ts +287 -0
  398. package/src/__tests__/compiler/labeled-loops.test.ts +279 -0
  399. package/src/__tests__/compiler/match-string.test.ts +45 -0
  400. package/src/__tests__/compiler/memoize.test.ts +126 -0
  401. package/src/__tests__/compiler/method-chain.test.ts +121 -0
  402. package/src/__tests__/compiler/module-import.test.ts +240 -0
  403. package/src/__tests__/compiler/option-extensions.test.ts +207 -0
  404. package/src/__tests__/compiler/profile-decorator.test.ts +79 -0
  405. package/src/__tests__/compiler/string-advanced.test.ts +310 -0
  406. package/src/__tests__/compiler/struct-extends.test.ts +109 -0
  407. package/src/__tests__/compiler/throttle-retry.test.ts +191 -0
  408. package/src/__tests__/compiler/tuple-type.test.ts +263 -0
  409. package/src/__tests__/compiler/watch-decorator.test.ts +72 -0
  410. package/src/__tests__/config/project-config.test.ts +181 -0
  411. package/src/__tests__/config-decorator.test.ts +157 -0
  412. package/src/__tests__/diagnostics-extra.test.ts +155 -0
  413. package/src/__tests__/emit/compile-branches.test.ts +135 -0
  414. package/src/__tests__/emit/compile-coverage.test.ts +696 -0
  415. package/src/__tests__/emit/compile-extra-branches.test.ts +228 -0
  416. package/src/__tests__/emit/compile-mocked-branches.test.ts +249 -0
  417. package/src/__tests__/emit/compile.test.ts +6 -1
  418. package/src/__tests__/emit/execute-chain.test.ts +114 -0
  419. package/src/__tests__/emit/index.test.ts +2 -1
  420. package/src/__tests__/emit/modules-branches.test.ts +90 -0
  421. package/src/__tests__/emit/modules-coverage.test.ts +241 -0
  422. package/src/__tests__/emit/modules-errors.test.ts +192 -0
  423. package/src/__tests__/emit/modules-rewrite.test.ts +232 -0
  424. package/src/__tests__/emit/source-map.test.ts +152 -0
  425. package/src/__tests__/enum.test.ts +9 -4
  426. package/src/__tests__/error-recovery.test.ts +226 -0
  427. package/src/__tests__/events-types-extra.test.ts +110 -0
  428. package/src/__tests__/events-types.test.ts +66 -0
  429. package/src/__tests__/formatter.test.ts +15 -5
  430. package/src/__tests__/generics.test.ts +16 -9
  431. package/src/__tests__/hir/lower-extra.test.ts +151 -0
  432. package/src/__tests__/hir/monomorphize-coverage.test.ts +432 -0
  433. package/src/__tests__/hir/monomorphize-extra.test.ts +220 -0
  434. package/src/__tests__/hir/monomorphize-extra2.test.ts +350 -0
  435. package/src/__tests__/impl.test.ts +12 -8
  436. package/src/__tests__/incremental.test.ts +10 -2
  437. package/src/__tests__/index-extra.test.ts +79 -0
  438. package/src/__tests__/lexer.test.ts +2 -2
  439. package/src/__tests__/lint/hir-coverage.test.ts +1716 -0
  440. package/src/__tests__/lint/rules-coverage.test.ts +598 -0
  441. package/src/__tests__/lint/rules.test.ts +230 -0
  442. package/src/__tests__/lir/lower.test.ts +33 -0
  443. package/src/__tests__/lir/verify.test.ts +33 -0
  444. package/src/__tests__/lsp/completion.test.ts +687 -0
  445. package/src/__tests__/lsp/definition.test.ts +499 -0
  446. package/src/__tests__/lsp/diagnostics.test.ts +108 -0
  447. package/src/__tests__/lsp/hover-docs.test.ts +222 -0
  448. package/src/__tests__/lsp.test.ts +4 -1
  449. package/src/__tests__/mc-integration/item-entity-events.test.ts +5 -0
  450. package/src/__tests__/mc-integration/stdlib-coverage-2.test.ts +5 -0
  451. package/src/__tests__/mc-integration/stdlib-coverage-3.test.ts +1105 -0
  452. package/src/__tests__/mc-integration/stdlib-coverage-4.test.ts +1366 -0
  453. package/src/__tests__/mc-integration/stdlib-coverage-5.test.ts +1245 -0
  454. package/src/__tests__/mc-integration/stdlib-coverage-6.test.ts +755 -0
  455. package/src/__tests__/mc-integration/stdlib-coverage-7.test.ts +771 -0
  456. package/src/__tests__/mc-integration/stdlib-coverage.test.ts +5 -0
  457. package/src/__tests__/mc-integration/syntax-coverage.test.ts +5 -0
  458. package/src/__tests__/mc-validator-coverage.test.ts +325 -0
  459. package/src/__tests__/mc-validator-extra.test.ts +252 -0
  460. package/src/__tests__/mir/lower-extra.test.ts +402 -0
  461. package/src/__tests__/mir/lower-extra2.test.ts +348 -0
  462. package/src/__tests__/mir/lower-extra3.test.ts +277 -0
  463. package/src/__tests__/mir/lower-extra4.test.ts +636 -0
  464. package/src/__tests__/mir/lower-extra5.test.ts +612 -0
  465. package/src/__tests__/mir/lower-extra6.test.ts +520 -0
  466. package/src/__tests__/mir/lower-extra7.test.ts +1045 -0
  467. package/src/__tests__/mir/lower-extra8.test.ts +704 -0
  468. package/src/__tests__/mir/lower-extra9.test.ts +821 -0
  469. package/src/__tests__/optimizer/auto-inline.test.ts +206 -0
  470. package/src/__tests__/optimizer/cse.test.ts +195 -0
  471. package/src/__tests__/optimizer/inline_fn.test.ts +263 -0
  472. package/src/__tests__/optimizer/licm.test.ts +358 -0
  473. package/src/__tests__/optimizer/nbt-coalesce.test.ts +147 -0
  474. package/src/__tests__/optimizer/optimizer-extended.test.ts +1081 -0
  475. package/src/__tests__/optimizer/scoreboard-batch.test.ts +141 -0
  476. package/src/__tests__/optimizer/strength-reduction.test.ts +111 -0
  477. package/src/__tests__/optimizer/tco-coverage.test.ts +309 -0
  478. package/src/__tests__/optimizer/tco.test.ts +238 -0
  479. package/src/__tests__/option.test.ts +14 -7
  480. package/src/__tests__/parser-coverage.test.ts +576 -0
  481. package/src/__tests__/parser-extra.test.ts +531 -0
  482. package/src/__tests__/parser.test.ts +14 -0
  483. package/src/__tests__/repl-extra.test.ts +195 -0
  484. package/src/__tests__/repl-server-extra.test.ts +150 -0
  485. package/src/__tests__/repl-server.test.ts +122 -0
  486. package/src/__tests__/runtime-extra.test.ts +862 -0
  487. package/src/__tests__/singleton-decorator.test.ts +285 -0
  488. package/src/__tests__/sourcemap.test.ts +1 -1
  489. package/src/__tests__/stdlib/advanced.test.ts +312 -0
  490. package/src/__tests__/stdlib/bigint.test.ts +57 -0
  491. package/src/__tests__/stdlib/bits.test.ts +75 -0
  492. package/src/__tests__/stdlib/bossbar.test.ts +45 -0
  493. package/src/__tests__/stdlib/color.test.ts +60 -0
  494. package/src/__tests__/stdlib/combat.test.ts +35 -0
  495. package/src/__tests__/stdlib/cooldown.test.ts +35 -0
  496. package/src/__tests__/stdlib/dialog.test.ts +14 -6
  497. package/src/__tests__/stdlib/ecs.test.ts +54 -0
  498. package/src/__tests__/stdlib/effects.test.ts +45 -0
  499. package/src/__tests__/stdlib/events.test.ts +23 -0
  500. package/src/__tests__/stdlib/expr.test.ts +48 -0
  501. package/src/__tests__/stdlib/fft.test.ts +54 -0
  502. package/src/__tests__/stdlib/graph.test.ts +77 -0
  503. package/src/__tests__/stdlib/interactions.test.ts +30 -0
  504. package/src/__tests__/stdlib/inventory.test.ts +40 -0
  505. package/src/__tests__/stdlib/linalg.test.ts +52 -0
  506. package/src/__tests__/stdlib/map.test.ts +55 -0
  507. package/src/__tests__/stdlib/math.test.ts +19 -5
  508. package/src/__tests__/stdlib/math_hp.test.ts +55 -0
  509. package/src/__tests__/stdlib/mobs.test.ts +40 -0
  510. package/src/__tests__/stdlib/noise.test.ts +46 -0
  511. package/src/__tests__/stdlib/ode.test.ts +40 -0
  512. package/src/__tests__/stdlib/parabola.test.ts +51 -0
  513. package/src/__tests__/stdlib/particles.test.ts +40 -0
  514. package/src/__tests__/stdlib/physics.test.ts +50 -0
  515. package/src/__tests__/stdlib/player.test.ts +35 -0
  516. package/src/__tests__/stdlib/quaternion.test.ts +46 -0
  517. package/src/__tests__/stdlib/queue.test.ts +73 -0
  518. package/src/__tests__/stdlib/random.test.ts +50 -0
  519. package/src/__tests__/stdlib/result.test.ts +326 -0
  520. package/src/__tests__/stdlib/scheduler.test.ts +18 -7
  521. package/src/__tests__/stdlib/set_int.test.ts +62 -0
  522. package/src/__tests__/stdlib/sets.test.ts +28 -0
  523. package/src/__tests__/stdlib/signal.test.ts +60 -0
  524. package/src/__tests__/stdlib/spawn.test.ts +40 -0
  525. package/src/__tests__/stdlib/string.test.ts +224 -0
  526. package/src/__tests__/stdlib/strings.test.ts +55 -0
  527. package/src/__tests__/stdlib/tags.test.ts +32 -0
  528. package/src/__tests__/stdlib/teams.test.ts +45 -0
  529. package/src/__tests__/stdlib/timer.test.ts +53 -0
  530. package/src/__tests__/stdlib/vec.test.ts +72 -0
  531. package/src/__tests__/stdlib/world.test.ts +45 -0
  532. package/src/__tests__/struct-display.test.ts +69 -0
  533. package/src/__tests__/test-framework/runner.test.ts +208 -0
  534. package/src/__tests__/tuner/adapters.test.ts +232 -0
  535. package/src/__tests__/tuner/simulator-extra.test.ts +222 -0
  536. package/src/__tests__/tuple.test.ts +11 -4
  537. package/src/__tests__/typechecker-coverage.test.ts +671 -0
  538. package/src/__tests__/typechecker.test.ts +4 -3
  539. package/src/__tests__/watch-decorator.test.ts +59 -0
  540. package/src/ast/types.ts +65 -3
  541. package/src/cache/incremental.ts +128 -99
  542. package/src/cache/index.ts +35 -8
  543. package/src/cli.ts +538 -29
  544. package/src/config/project-config.ts +176 -0
  545. package/src/diagnostics/index.ts +22 -0
  546. package/src/docs.ts +98 -0
  547. package/src/emit/compile.ts +408 -51
  548. package/src/emit/index.ts +366 -18
  549. package/src/emit/modules.ts +19 -3
  550. package/src/emit/sourcemap.ts +64 -43
  551. package/src/formatter/index.ts +35 -8
  552. package/src/hir/deprecated.ts +212 -0
  553. package/src/hir/lower.ts +128 -8
  554. package/src/hir/monomorphize.ts +24 -2
  555. package/src/hir/types.ts +26 -1
  556. package/src/index.ts +23 -3
  557. package/src/lexer/index.ts +45 -6
  558. package/src/lint/index.ts +922 -0
  559. package/src/lir/lower.ts +30 -2
  560. package/src/lir/types.ts +4 -0
  561. package/src/lsp/server.ts +100 -1
  562. package/src/mir/lower.ts +785 -40
  563. package/src/mir/macro.ts +30 -2
  564. package/src/mir/types.ts +13 -0
  565. package/src/mir/verify.ts +10 -2
  566. package/src/optimizer/auto-inline.ts +86 -0
  567. package/src/optimizer/copy_prop.ts +2 -2
  568. package/src/optimizer/coroutine.ts +3 -3
  569. package/src/optimizer/cse.ts +205 -0
  570. package/src/optimizer/dce.ts +2 -2
  571. package/src/optimizer/inline.ts +335 -0
  572. package/src/optimizer/interprocedural.ts +5 -1
  573. package/src/optimizer/licm.ts +454 -0
  574. package/src/optimizer/nbt-coalesce.ts +109 -0
  575. package/src/optimizer/pipeline.ts +16 -2
  576. package/src/optimizer/scoreboard-batch.ts +52 -0
  577. package/src/optimizer/strength_reduction.ts +95 -0
  578. package/src/optimizer/tco.ts +267 -0
  579. package/src/optimizer/unroll.ts +2 -2
  580. package/src/parser/index.ts +426 -53
  581. package/src/repl-server.ts +102 -0
  582. package/src/stdlib/advanced.mcrs +271 -101
  583. package/src/stdlib/bigint.mcrs +97 -11
  584. package/src/stdlib/bits.mcrs +75 -12
  585. package/src/stdlib/bossbar.mcrs +37 -8
  586. package/src/stdlib/calculus.mcrs +82 -26
  587. package/src/stdlib/color.mcrs +98 -16
  588. package/src/stdlib/combat.mcrs +23 -5
  589. package/src/stdlib/cooldown.mcrs +19 -0
  590. package/src/stdlib/dialog.mcrs +45 -7
  591. package/src/stdlib/easing.mcrs +132 -12
  592. package/src/stdlib/ecs.mcrs +142 -25
  593. package/src/stdlib/effects.mcrs +88 -12
  594. package/src/stdlib/events.mcrs +21 -2
  595. package/src/stdlib/expr.mcrs +18 -3
  596. package/src/stdlib/fft.mcrs +66 -56
  597. package/src/stdlib/geometry.mcrs +137 -39
  598. package/src/stdlib/graph.mcrs +73 -0
  599. package/src/stdlib/heap.mcrs +49 -8
  600. package/src/stdlib/i18n/zh.yaml +2891 -0
  601. package/src/stdlib/interactions.mcrs +43 -20
  602. package/src/stdlib/inventory.mcrs +14 -3
  603. package/src/stdlib/linalg.mcrs +185 -30
  604. package/src/stdlib/list.mcrs +168 -18
  605. package/src/stdlib/map.mcrs +112 -0
  606. package/src/stdlib/math.mcrs +68 -18
  607. package/src/stdlib/math_hp.mcrs +124 -33
  608. package/src/stdlib/matrix.mcrs +133 -20
  609. package/src/stdlib/mobs.mcrs +87 -0
  610. package/src/stdlib/noise.mcrs +65 -21
  611. package/src/stdlib/ode.mcrs +96 -0
  612. package/src/stdlib/parabola.mcrs +104 -29
  613. package/src/stdlib/particles.mcrs +78 -21
  614. package/src/stdlib/pathfind.mcrs +89 -35
  615. package/src/stdlib/physics.mcrs +134 -26
  616. package/src/stdlib/player.mcrs +18 -0
  617. package/src/stdlib/quaternion.mcrs +213 -9
  618. package/src/stdlib/queue.mcrs +123 -0
  619. package/src/stdlib/random.mcrs +63 -18
  620. package/src/stdlib/result.mcrs +111 -0
  621. package/src/stdlib/scheduler.mcrs +59 -10
  622. package/src/stdlib/set_int.mcrs +240 -0
  623. package/src/stdlib/sets.mcrs +49 -19
  624. package/src/stdlib/signal.mcrs +151 -79
  625. package/src/stdlib/sort.mcrs +44 -24
  626. package/src/stdlib/spawn.mcrs +30 -7
  627. package/src/stdlib/state.mcrs +40 -5
  628. package/src/stdlib/strings.mcrs +131 -3
  629. package/src/stdlib/tags.mcrs +2 -2
  630. package/src/stdlib/teams.mcrs +22 -10
  631. package/src/stdlib/timer.mcrs +36 -6
  632. package/src/stdlib/vec.mcrs +44 -9
  633. package/src/stdlib/world.mcrs +57 -25
  634. package/src/structs/expand.ts +64 -0
  635. package/src/testing/runner.ts +271 -0
  636. package/src/typechecker/index.ts +273 -9
package/src/mir/lower.ts CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  import type {
9
9
  HIRModule, HIRFunction, HIRStmt, HIRBlock, HIRExpr,
10
- HIRExecuteSubcommand, HIRParam,
10
+ HIRExecuteSubcommand, HIRParam, TypeNode, HIRFStringPart,
11
11
  } from '../hir/types'
12
12
  import type {
13
13
  MIRModule, MIRFunction, MIRBlock, MIRInstr, BlockId,
@@ -15,6 +15,35 @@ import type {
15
15
  } from './types'
16
16
  import { detectMacroFunctions, BUILTIN_SET, type MacroFunctionInfo } from './macro'
17
17
 
18
+ function formatTypeNode(type: TypeNode): string {
19
+ switch (type.kind) {
20
+ case 'named':
21
+ return type.name
22
+ case 'array':
23
+ return `${formatTypeNode(type.elem)}[]`
24
+ case 'struct':
25
+ case 'enum':
26
+ return type.name
27
+ case 'function_type':
28
+ return `fn(${type.params.map(formatTypeNode).join(', ')}) -> ${formatTypeNode(type.return)}`
29
+ case 'entity':
30
+ return type.entityType
31
+ case 'selector':
32
+ return type.entityType ? `selector<${type.entityType}>` : 'selector'
33
+ case 'tuple':
34
+ return `(${type.elements.map(formatTypeNode).join(', ')})`
35
+ case 'option':
36
+ return `Option<${formatTypeNode(type.inner)}>`
37
+ }
38
+ }
39
+
40
+ function formatFunctionSignature(fn: HIRFunction): string {
41
+ const params = fn.params
42
+ .map(param => `${param.name}: ${formatTypeNode(param.type)}`)
43
+ .join(', ')
44
+ return `fn ${fn.name}(${params}) -> ${formatTypeNode(fn.returnType)}`
45
+ }
46
+
18
47
  // ---------------------------------------------------------------------------
19
48
  // Public API
20
49
  // ---------------------------------------------------------------------------
@@ -22,31 +51,59 @@ import { detectMacroFunctions, BUILTIN_SET, type MacroFunctionInfo } from './mac
22
51
  export function lowerToMIR(hir: HIRModule, sourceFile?: string): MIRModule {
23
52
  // Build struct definitions: name → field names
24
53
  const structDefs = new Map<string, string[]>()
54
+ // Track @singleton struct names for special expansion of GameState::set(gs) calls
55
+ const singletonStructs = new Set<string>()
25
56
  for (const s of hir.structs) {
26
57
  structDefs.set(s.name, s.fields.map(f => f.name))
58
+ if (s.isSingleton) singletonStructs.add(s.name)
27
59
  }
28
60
 
29
61
  // Build enum definitions: enumName → variantName → integer value
30
62
  const enumDefs = new Map<string, Map<string, number>>()
63
+ // Build enum payload info: enumName → variantName → field list
64
+ const enumPayloads = new Map<string, Map<string, { name: string; type: TypeNode }[]>>()
31
65
  for (const e of hir.enums) {
32
66
  const variants = new Map<string, number>()
67
+ const payloads = new Map<string, { name: string; type: TypeNode }[]>()
33
68
  for (const v of e.variants) {
34
69
  variants.set(v.name, v.value ?? 0)
70
+ if (v.fields && v.fields.length > 0) {
71
+ payloads.set(v.name, v.fields)
72
+ }
35
73
  }
36
74
  enumDefs.set(e.name, variants)
75
+ if (payloads.size > 0) {
76
+ enumPayloads.set(e.name, payloads)
77
+ }
37
78
  }
38
79
 
39
- // Build impl method info: typeName → methodName → { hasSelf }
40
- const implMethods = new Map<string, Map<string, { hasSelf: boolean }>>()
80
+ // Build impl method info: typeName → methodName → { hasSelf, returnStructName? }
81
+ const implMethods = new Map<string, Map<string, { hasSelf: boolean; returnStructName?: string }>>()
41
82
  for (const ib of hir.implBlocks) {
42
- const methods = new Map<string, { hasSelf: boolean }>()
83
+ const methods = new Map<string, { hasSelf: boolean; returnStructName?: string }>()
43
84
  for (const m of ib.methods) {
44
85
  const hasSelf = m.params.length > 0 && m.params[0].name === 'self'
45
- methods.set(m.name, { hasSelf })
86
+ const returnStructName = m.returnType.kind === 'struct' ? m.returnType.name : undefined
87
+ methods.set(m.name, { hasSelf, returnStructName })
46
88
  }
47
89
  implMethods.set(ib.typeName, methods)
48
90
  }
49
91
 
92
+ // Build Display impl registry: typeName → f-string parts from to_string body
93
+ // Used to inline Display::to_string() calls at call sites instead of generating a function.
94
+ const displayImpls = new Map<string, HIRFStringPart[]>()
95
+ for (const ib of hir.implBlocks) {
96
+ if (ib.traitName === 'Display') {
97
+ const toStringMethod = ib.methods.find(m => m.name === 'to_string')
98
+ if (toStringMethod && toStringMethod.body.length > 0) {
99
+ const firstStmt = toStringMethod.body[0]
100
+ if (firstStmt.kind === 'return' && firstStmt.value && firstStmt.value.kind === 'f_string') {
101
+ displayImpls.set(ib.typeName, firstStmt.value.parts)
102
+ }
103
+ }
104
+ }
105
+ }
106
+
50
107
  // Pre-scan for macro functions
51
108
  const macroInfo = detectMacroFunctions(hir)
52
109
 
@@ -71,16 +128,25 @@ export function lowerToMIR(hir: HIRModule, sourceFile?: string): MIRModule {
71
128
  // Shared registry: specializedName → [mirFn, ...helpers]
72
129
  const specializedFnsRegistry = new Map<string, MIRFunction[]>()
73
130
 
131
+ // Build module-level const map: name → integer value (for inlining at use sites)
132
+ const constValues = new Map<string, number>()
133
+ for (const c of hir.consts) {
134
+ if (c.value.kind === 'int_lit') constValues.set(c.name, c.value.value)
135
+ else if (c.value.kind === 'bool_lit') constValues.set(c.name, c.value.value ? 1 : 0)
136
+ else if (c.value.kind === 'float_lit') constValues.set(c.name, Math.round(c.value.value * 10000))
137
+ }
138
+
74
139
  const allFunctions: MIRFunction[] = []
75
140
  for (const f of hir.functions) {
76
- const { fn, helpers } = lowerFunction(f, hir.namespace, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, sourceFile, timerCounter, undefined, hirFnMap, specializedFnsRegistry)
141
+ const { fn, helpers } = lowerFunction(f, hir.namespace, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, sourceFile, timerCounter, undefined, hirFnMap, specializedFnsRegistry, undefined, enumPayloads, constValues, singletonStructs, displayImpls)
77
142
  allFunctions.push(fn, ...helpers)
78
143
  }
79
144
 
80
- // Lower impl block methods
145
+ // Lower impl block methods (skip Display::to_string — inlined at call sites instead)
81
146
  for (const ib of hir.implBlocks) {
147
+ if (ib.traitName === 'Display') continue // Display impls are inlined, not emitted as functions
82
148
  for (const m of ib.methods) {
83
- const { fn, helpers } = lowerImplMethod(m, ib.typeName, hir.namespace, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, sourceFile, timerCounter)
149
+ const { fn, helpers } = lowerImplMethod(m, ib.typeName, hir.namespace, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, sourceFile, timerCounter, enumPayloads, constValues)
84
150
  allFunctions.push(fn, ...helpers)
85
151
  }
86
152
  }
@@ -106,22 +172,26 @@ class FnContext {
106
172
  private blockCounter = 0
107
173
  readonly blocks: MIRBlock[] = []
108
174
  private currentBlock: MIRBlock
109
- /** Stack of (loopHeader, loopExit, continueTo) for break/continue */
110
- private loopStack: { header: BlockId; exit: BlockId; continueTo: BlockId }[] = []
175
+ /** Stack of (loopHeader, loopExit, continueTo, label?) for break/continue */
176
+ private loopStack: { header: BlockId; exit: BlockId; continueTo: BlockId; label?: string }[] = []
177
+ /** Pending label to attach to the next pushLoop call (set by labeled_loop lowering) */
178
+ private pendingLoopLabel: string | undefined = undefined
111
179
  /** Extracted helper functions for execute blocks */
112
180
  readonly helperFunctions: MIRFunction[] = []
113
181
  private readonly namespace: string
114
182
  private readonly fnName: string
115
183
  /** Struct definitions: struct name → field names */
116
184
  readonly structDefs: Map<string, string[]>
117
- /** Impl method info: typeName → methodName → { hasSelf } */
118
- readonly implMethods: Map<string, Map<string, { hasSelf: boolean }>>
185
+ /** Impl method info: typeName → methodName → { hasSelf, returnStructName? } */
186
+ readonly implMethods: Map<string, Map<string, { hasSelf: boolean; returnStructName?: string }>>
119
187
  /** Struct variable tracking: varName → { typeName, fields: fieldName → temp } */
120
188
  readonly structVars = new Map<string, { typeName: string; fields: Map<string, Temp> }>()
121
189
  /** Tuple variable tracking: varName → array of element temps (index = slot) */
122
190
  readonly tupleVars = new Map<string, Temp[]>()
123
191
  /** Array variable tracking: varName → { ns, pathPrefix } for NBT-backed int[] */
124
192
  readonly arrayVars = new Map<string, { ns: string; pathPrefix: string; knownLen?: number }>()
193
+ /** String variable tracking: varName → storage path within rs:strings */
194
+ readonly stringVars = new Map<string, string>()
125
195
  /** Macro function info for all functions in the module */
126
196
  readonly macroInfo: Map<string, MacroFunctionInfo>
127
197
  /** Function parameter info for call_macro generation */
@@ -130,6 +200,8 @@ class FnContext {
130
200
  readonly currentMacroParams: Set<string>
131
201
  /** Enum definitions: enumName → variantName → integer value */
132
202
  readonly enumDefs: Map<string, Map<string, number>>
203
+ /** Enum payload fields: enumName → variantName → field list */
204
+ readonly enumPayloads: Map<string, Map<string, { name: string; type: TypeNode }[]>>
133
205
  /** Current source location (set during statement lowering) */
134
206
  currentSourceLoc: SourceLoc | undefined = undefined
135
207
  /** Source file path for the module being compiled */
@@ -143,20 +215,28 @@ class FnContext {
143
215
  /** Tracks double variables: varName → NBT storage path (without "rs:d " prefix) */
144
216
  readonly doubleVars = new Map<string, string>()
145
217
  private doubleVarCount = 0
218
+ private stringVarCount = 0
146
219
  /** HIR function definitions for array-arg monomorphization */
147
220
  hirFunctions: Map<string, HIRFunction> = new Map()
148
221
  /** Shared registry of already-generated specialized (monomorphized) MIR functions */
149
222
  specializedFnsRegistry: Map<string, MIRFunction[]> = new Map()
223
+ /** Module-level const values: name → integer value (inlined at use sites) */
224
+ constValues: Map<string, number> = new Map()
225
+ /** @singleton struct names — static_call GameState::set(gs) expands struct fields */
226
+ singletonStructs: Set<string> = new Set()
227
+ /** Display trait impls: typeName → f-string parts from to_string body (inlined at call sites) */
228
+ displayImpls: Map<string, HIRFStringPart[]> = new Map()
150
229
 
151
230
  constructor(
152
231
  namespace: string,
153
232
  fnName: string,
154
233
  structDefs: Map<string, string[]> = new Map(),
155
- implMethods: Map<string, Map<string, { hasSelf: boolean }>> = new Map(),
234
+ implMethods: Map<string, Map<string, { hasSelf: boolean; returnStructName?: string }>> = new Map(),
156
235
  macroInfo: Map<string, MacroFunctionInfo> = new Map(),
157
236
  fnParamInfo: Map<string, HIRParam[]> = new Map(),
158
237
  enumDefs: Map<string, Map<string, number>> = new Map(),
159
238
  timerCounter: { count: number; timerId: number } = { count: 0, timerId: 0 },
239
+ enumPayloads: Map<string, Map<string, { name: string; type: TypeNode }[]>> = new Map(),
160
240
  ) {
161
241
  this.namespace = namespace
162
242
  this.fnName = fnName
@@ -166,6 +246,7 @@ class FnContext {
166
246
  this.fnParamInfo = fnParamInfo
167
247
  this.currentMacroParams = macroInfo.get(fnName)?.macroParams ?? new Set()
168
248
  this.enumDefs = enumDefs
249
+ this.enumPayloads = enumPayloads
169
250
  this.timerCounter = timerCounter
170
251
  const entry = this.makeBlock('entry')
171
252
  this.currentBlock = entry
@@ -212,18 +293,32 @@ class FnContext {
212
293
  return this.currentBlock
213
294
  }
214
295
 
215
- pushLoop(header: BlockId, exit: BlockId, continueTo?: BlockId): void {
216
- this.loopStack.push({ header, exit, continueTo: continueTo ?? header })
296
+ setPendingLoopLabel(label: string): void {
297
+ this.pendingLoopLabel = label
298
+ }
299
+
300
+ pushLoop(header: BlockId, exit: BlockId, continueTo?: BlockId, label?: string): void {
301
+ const effectiveLabel = label ?? this.pendingLoopLabel
302
+ this.pendingLoopLabel = undefined
303
+ this.loopStack.push({ header, exit, continueTo: continueTo ?? header, label: effectiveLabel })
217
304
  }
218
305
 
219
306
  popLoop(): void {
220
307
  this.loopStack.pop()
221
308
  }
222
309
 
223
- currentLoop(): { header: BlockId; exit: BlockId; continueTo: BlockId } | undefined {
310
+ currentLoop(): { header: BlockId; exit: BlockId; continueTo: BlockId; label?: string } | undefined {
224
311
  return this.loopStack[this.loopStack.length - 1]
225
312
  }
226
313
 
314
+ /** Find loop by label — searches from innermost to outermost */
315
+ findLoopByLabel(label: string): { header: BlockId; exit: BlockId; continueTo: BlockId } | undefined {
316
+ for (let i = this.loopStack.length - 1; i >= 0; i--) {
317
+ if (this.loopStack[i].label === label) return this.loopStack[i]
318
+ }
319
+ return undefined
320
+ }
321
+
227
322
  getNamespace(): string {
228
323
  return this.namespace
229
324
  }
@@ -238,6 +333,11 @@ class FnContext {
238
333
  this.doubleVars.set(varName, path)
239
334
  return path
240
335
  }
336
+
337
+ /** Allocate a unique NBT storage path for a string value */
338
+ freshStringVar(varName: string): string {
339
+ return `${this.namespace}_${this.fnName}_${varName}_${this.stringVarCount++}`
340
+ }
241
341
  }
242
342
 
243
343
  // ---------------------------------------------------------------------------
@@ -248,7 +348,7 @@ function lowerFunction(
248
348
  fn: HIRFunction,
249
349
  namespace: string,
250
350
  structDefs: Map<string, string[]> = new Map(),
251
- implMethods: Map<string, Map<string, { hasSelf: boolean }>> = new Map(),
351
+ implMethods: Map<string, Map<string, { hasSelf: boolean; returnStructName?: string }>> = new Map(),
252
352
  macroInfo: Map<string, MacroFunctionInfo> = new Map(),
253
353
  fnParamInfo: Map<string, HIRParam[]> = new Map(),
254
354
  enumDefs: Map<string, Map<string, number>> = new Map(),
@@ -262,10 +362,17 @@ function lowerFunction(
262
362
  specializedFnsRegistry?: Map<string, MIRFunction[]>,
263
363
  /** Override the MIR function name (used when generating specialized versions) */
264
364
  overrideName?: string,
365
+ enumPayloads: Map<string, Map<string, { name: string; type: TypeNode }[]>> = new Map(),
366
+ constValues: Map<string, number> = new Map(),
367
+ singletonStructs: Set<string> = new Set(),
368
+ displayImpls: Map<string, HIRFStringPart[]> = new Map(),
265
369
  ): { fn: MIRFunction; helpers: MIRFunction[] } {
266
370
  const mirFnName = overrideName ?? fn.name
267
- const ctx = new FnContext(namespace, mirFnName, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, timerCounter)
268
- ctx.sourceFile = sourceFile
371
+ const ctx = new FnContext(namespace, mirFnName, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, timerCounter, enumPayloads)
372
+ ctx.sourceFile = fn.sourceFile ?? sourceFile
373
+ ctx.constValues = constValues
374
+ ctx.singletonStructs = singletonStructs
375
+ ctx.displayImpls = displayImpls
269
376
  if (hirFnMap) ctx.hirFunctions = hirFnMap
270
377
  if (specializedFnsRegistry) ctx.specializedFnsRegistry = specializedFnsRegistry
271
378
  const fnMacroInfo = macroInfo.get(fn.name)
@@ -282,6 +389,7 @@ function lowerFunction(
282
389
  const params: { name: Temp; isMacroParam: boolean }[] = []
283
390
  const scope = new Map<string, Temp>()
284
391
  let doubleParamSlot = 0
392
+ let stringParamSlot = 0
285
393
  fn.params.forEach((p) => {
286
394
  if (p.type.kind === 'array' && arrayArgBindings?.has(p.name)) {
287
395
  // Array param already bound via arrayVars; no scoreboard slot needed
@@ -294,6 +402,10 @@ function lowerFunction(
294
402
  // No scoreboard param slot; callee reads from rs:d __dp<i> via doubleVars
295
403
  return
296
404
  }
405
+ if (p.type.kind === 'named' && (p.type.name === 'string' || p.type.name === 'format_string')) {
406
+ ctx.stringVars.set(p.name, `__sp${stringParamSlot++}`)
407
+ return
408
+ }
297
409
  const t = ctx.freshTemp()
298
410
  params.push({ name: t, isMacroParam: fnMacroInfo?.macroParams.has(p.name) ?? false })
299
411
  scope.set(p.name, t)
@@ -320,6 +432,8 @@ function lowerFunction(
320
432
  blocks: liveBlocks,
321
433
  entry: 'entry',
322
434
  isMacro: fnMacroInfo != null,
435
+ sourceLoc: fn.span && (fn.sourceFile ?? sourceFile) ? { file: fn.sourceFile ?? sourceFile!, line: fn.span.line, col: fn.span.col } : undefined,
436
+ sourceSnippet: formatFunctionSignature(fn),
323
437
  }
324
438
 
325
439
  return { fn: result, helpers: ctx.helperFunctions }
@@ -330,16 +444,19 @@ function lowerImplMethod(
330
444
  typeName: string,
331
445
  namespace: string,
332
446
  structDefs: Map<string, string[]>,
333
- implMethods: Map<string, Map<string, { hasSelf: boolean }>>,
447
+ implMethods: Map<string, Map<string, { hasSelf: boolean; returnStructName?: string }>>,
334
448
  macroInfo: Map<string, MacroFunctionInfo> = new Map(),
335
449
  fnParamInfo: Map<string, HIRParam[]> = new Map(),
336
450
  enumDefs: Map<string, Map<string, number>> = new Map(),
337
451
  sourceFile?: string,
338
452
  timerCounter: { count: number; timerId: number } = { count: 0, timerId: 0 },
453
+ enumPayloads: Map<string, Map<string, { name: string; type: TypeNode }[]>> = new Map(),
454
+ constValues: Map<string, number> = new Map(),
339
455
  ): { fn: MIRFunction; helpers: MIRFunction[] } {
340
456
  const fnName = `${typeName}::${method.name}`
341
- const ctx = new FnContext(namespace, fnName, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, timerCounter)
342
- ctx.sourceFile = sourceFile
457
+ const ctx = new FnContext(namespace, fnName, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, timerCounter, enumPayloads)
458
+ ctx.sourceFile = method.sourceFile ?? sourceFile
459
+ ctx.constValues = constValues
343
460
  const fields = structDefs.get(typeName) ?? []
344
461
  const hasSelf = method.params.length > 0 && method.params[0].name === 'self'
345
462
 
@@ -415,6 +532,8 @@ function lowerImplMethod(
415
532
  blocks: liveBlocks,
416
533
  entry: 'entry',
417
534
  isMacro: macroInfo.has(fnName),
535
+ sourceLoc: method.span && (method.sourceFile ?? sourceFile) ? { file: method.sourceFile ?? sourceFile!, line: method.span.line, col: method.span.col } : undefined,
536
+ sourceSnippet: formatFunctionSignature(method),
418
537
  }
419
538
 
420
539
  return { fn: result, helpers: ctx.helperFunctions }
@@ -492,7 +611,13 @@ function lowerStmt(
492
611
 
493
612
  switch (stmt.kind) {
494
613
  case 'let': {
495
- if (stmt.init.kind === 'some_lit') {
614
+ if (stmt.type?.kind === 'named' && (stmt.type.name === 'string' || stmt.type.name === 'format_string')) {
615
+ const path = lowerStringExprToPath(stmt.init, ctx, scope, stmt.name) ?? ctx.freshStringVar(stmt.name)
616
+ ctx.stringVars.set(stmt.name, path)
617
+ const t = ctx.freshTemp()
618
+ ctx.emit({ kind: 'const', dst: t, value: 0 })
619
+ scope.set(stmt.name, t)
620
+ } else if (stmt.init.kind === 'some_lit') {
496
621
  // Some(expr) — create option struct vars: has=1, val=expr
497
622
  // Use __opt_ prefix so DCE treats these as side-effectful scoreboard writes
498
623
  const hasTemp: Temp = `__opt_${stmt.name}_has`
@@ -549,9 +674,18 @@ function lowerStmt(
549
674
  ctx.emit({ kind: 'copy', dst: valTemp, src: { kind: 'temp', name: '__rf_val' } })
550
675
  const fieldTemps = new Map<string, Temp>([['has', hasTemp], ['val', valTemp]])
551
676
  ctx.structVars.set(stmt.name, { typeName: '__option', fields: fieldTemps })
552
- } else if (stmt.type?.kind === 'struct') {
677
+ } else if (
678
+ // Struct-typed let: explicit annotation OR inferred from @singleton::get() return
679
+ stmt.type?.kind === 'struct' ||
680
+ (stmt.init.kind === 'static_call' &&
681
+ ctx.singletonStructs.has((stmt.init as { kind: 'static_call'; type: string; method: string }).type) &&
682
+ (stmt.init as { kind: 'static_call'; method: string }).method === 'get')
683
+ ) {
553
684
  // Struct-typed let with non-literal init (e.g., call returning struct)
554
- const fields = ctx.structDefs.get(stmt.type.name)
685
+ const inferredStructName = stmt.type?.kind === 'struct'
686
+ ? stmt.type.name
687
+ : (stmt.init as { kind: 'static_call'; type: string }).type
688
+ const fields = ctx.structDefs.get(inferredStructName)
555
689
  if (fields) {
556
690
  lowerExpr(stmt.init, ctx, scope)
557
691
  // Copy from return field slots into struct variable temps
@@ -567,7 +701,7 @@ function lowerStmt(
567
701
  ctx.constTemps.set(t, constVal)
568
702
  }
569
703
  }
570
- ctx.structVars.set(stmt.name, { typeName: stmt.type.name, fields: fieldTemps })
704
+ ctx.structVars.set(stmt.name, { typeName: inferredStructName, fields: fieldTemps })
571
705
  } else {
572
706
  const valOp = lowerExpr(stmt.init, ctx, scope)
573
707
  const t = ctx.freshTemp()
@@ -701,6 +835,14 @@ function lowerStmt(
701
835
  break
702
836
  }
703
837
 
838
+ case 'const_decl': {
839
+ // Evaluate the literal at compile time and store in constValues for inlining at use sites
840
+ const op = lowerExpr(stmt.value, ctx, scope)
841
+ const numericValue = op.kind === 'const' ? op.value : 0
842
+ ctx.constValues.set(stmt.name, numericValue)
843
+ break
844
+ }
845
+
704
846
  case 'expr': {
705
847
  lowerExpr(stmt.expr, ctx, scope)
706
848
  break
@@ -773,6 +915,33 @@ function lowerStmt(
773
915
  break
774
916
  }
775
917
 
918
+ case 'break_label': {
919
+ const loop = ctx.findLoopByLabel(stmt.label)
920
+ if (!loop) throw new Error(`break: label '${stmt.label}' not found`)
921
+ ctx.terminate({ kind: 'jump', target: loop.exit })
922
+ const dead = ctx.newBlock('post_break_label')
923
+ ctx.switchTo(dead)
924
+ break
925
+ }
926
+
927
+ case 'continue_label': {
928
+ const loop = ctx.findLoopByLabel(stmt.label)
929
+ if (!loop) throw new Error(`continue: label '${stmt.label}' not found`)
930
+ ctx.terminate({ kind: 'jump', target: loop.continueTo })
931
+ const dead = ctx.newBlock('post_continue_label')
932
+ ctx.switchTo(dead)
933
+ break
934
+ }
935
+
936
+ case 'labeled_loop': {
937
+ // The body is a while/foreach stmt; we need to push the label into the loop stack.
938
+ // We do this by injecting the label into the next pushLoop call by temporarily
939
+ // storing the pending label in ctx, then letting the inner loop case handle it.
940
+ ctx.setPendingLoopLabel(stmt.label)
941
+ lowerStmt(stmt.body, ctx, scope)
942
+ break
943
+ }
944
+
776
945
  case 'if': {
777
946
  const condOp = lowerExpr(stmt.cond, ctx, scope)
778
947
  const thenBlock = ctx.newBlock('then')
@@ -876,6 +1045,8 @@ function lowerStmt(
876
1045
  blocks: helperBlocks,
877
1046
  entry: 'entry',
878
1047
  isMacro: false,
1048
+ sourceLoc: stmt.span && ctx.sourceFile ? { file: ctx.sourceFile, line: stmt.span.line, col: stmt.span.col } : undefined,
1049
+ sourceSnippet: 'foreach helper',
879
1050
  })
880
1051
 
881
1052
  ctx.emit({ kind: 'call_context', fn: helperName, subcommands })
@@ -903,6 +1074,8 @@ function lowerStmt(
903
1074
  blocks: execBlocks,
904
1075
  entry: 'entry',
905
1076
  isMacro: false,
1077
+ sourceLoc: stmt.span && ctx.sourceFile ? { file: ctx.sourceFile, line: stmt.span.line, col: stmt.span.col } : undefined,
1078
+ sourceSnippet: 'execute helper',
906
1079
  })
907
1080
 
908
1081
  ctx.emit({ kind: 'call_context', fn: helperName, subcommands })
@@ -910,6 +1083,53 @@ function lowerStmt(
910
1083
  }
911
1084
 
912
1085
  case 'match': {
1086
+ const hasStringPats = stmt.arms.some(a =>
1087
+ a.pattern.kind === 'PatExpr' && a.pattern.expr.kind === 'str_lit'
1088
+ )
1089
+ if (hasStringPats) {
1090
+ const matchPath = lowerStringExprToPath(stmt.expr, ctx, scope, 'match')
1091
+ if (!matchPath) {
1092
+ throw new Error('String match requires a string literal or tracked string variable')
1093
+ }
1094
+
1095
+ const mergeBlock = ctx.newBlock('match_merge')
1096
+ for (let i = 0; i < stmt.arms.length; i++) {
1097
+ const arm = stmt.arms[i]
1098
+ const pat = arm.pattern
1099
+
1100
+ if (pat.kind === 'PatWild') {
1101
+ lowerBlock(arm.body, ctx, new Map(scope))
1102
+ if (isPlaceholderTerm(ctx.current().term)) {
1103
+ ctx.terminate({ kind: 'jump', target: mergeBlock.id })
1104
+ }
1105
+ continue
1106
+ }
1107
+
1108
+ if (pat.kind === 'PatExpr' && pat.expr.kind === 'str_lit') {
1109
+ const cmpTemp = ctx.freshTemp()
1110
+ ctx.emit({ kind: 'string_match', dst: cmpTemp, ns: 'rs:strings', path: matchPath, value: pat.expr.value })
1111
+ const armBody = ctx.newBlock('match_arm')
1112
+ const nextArm = ctx.newBlock('match_next')
1113
+ ctx.terminate({ kind: 'branch', cond: { kind: 'temp', name: cmpTemp }, then: armBody.id, else: nextArm.id })
1114
+ ctx.switchTo(armBody)
1115
+ lowerBlock(arm.body, ctx, new Map(scope))
1116
+ if (isPlaceholderTerm(ctx.current().term)) {
1117
+ ctx.terminate({ kind: 'jump', target: mergeBlock.id })
1118
+ }
1119
+ ctx.switchTo(nextArm)
1120
+ continue
1121
+ }
1122
+
1123
+ throw new Error(`Unsupported string match pattern: ${pat.kind}`)
1124
+ }
1125
+
1126
+ if (isPlaceholderTerm(ctx.current().term)) {
1127
+ ctx.terminate({ kind: 'jump', target: mergeBlock.id })
1128
+ }
1129
+ ctx.switchTo(mergeBlock)
1130
+ break
1131
+ }
1132
+
913
1133
  // Lower match as chained if/else
914
1134
  const mergeBlock = ctx.newBlock('match_merge')
915
1135
 
@@ -1005,6 +1225,37 @@ function lowerStmt(
1005
1225
  ctx.terminate({ kind: 'jump', target: mergeBlock.id })
1006
1226
  }
1007
1227
  ctx.switchTo(nextArm)
1228
+ } else if (pat.kind === 'PatEnum') {
1229
+ // Enum pattern: check tag value matches, then bind payload fields via NBT reads
1230
+ const tagValue = ctx.enumDefs.get(pat.enumName)?.get(pat.variant) ?? 0
1231
+ const cmpTemp = ctx.freshTemp()
1232
+ ctx.emit({ kind: 'cmp', dst: cmpTemp, op: 'eq', a: matchVal, b: { kind: 'const', value: tagValue } })
1233
+ const armBody = ctx.newBlock('match_arm')
1234
+ const nextArm = ctx.newBlock('match_next')
1235
+ ctx.terminate({ kind: 'branch', cond: { kind: 'temp', name: cmpTemp }, then: armBody.id, else: nextArm.id })
1236
+ ctx.switchTo(armBody)
1237
+ const armScope = new Map(scope)
1238
+ // Bind each pattern variable by reading the corresponding NBT payload field
1239
+ const payloadFields = ctx.enumPayloads.get(pat.enumName)?.get(pat.variant) ?? []
1240
+ for (let bi = 0; bi < pat.bindings.length; bi++) {
1241
+ const binding = pat.bindings[bi]
1242
+ const fieldDef = payloadFields[bi]
1243
+ const fieldName = fieldDef ? fieldDef.name : binding
1244
+ const bindTemp = ctx.freshTemp()
1245
+ ctx.emit({
1246
+ kind: 'nbt_read',
1247
+ dst: bindTemp,
1248
+ ns: 'rs:enums',
1249
+ path: `${pat.enumName}_${fieldName}`,
1250
+ scale: 1,
1251
+ })
1252
+ armScope.set(binding, bindTemp)
1253
+ }
1254
+ lowerBlock(arm.body, ctx, armScope)
1255
+ if (isPlaceholderTerm(ctx.current().term)) {
1256
+ ctx.terminate({ kind: 'jump', target: mergeBlock.id })
1257
+ }
1258
+ ctx.switchTo(nextArm)
1008
1259
  } else if (pat.kind === 'PatExpr') {
1009
1260
  // Legacy: range_lit or other expression
1010
1261
  const expr = pat.expr
@@ -1126,6 +1377,58 @@ function lowerStmt(
1126
1377
  break
1127
1378
  }
1128
1379
 
1380
+ case 'while_let_some': {
1381
+ // while let Some(x) = init { body }
1382
+ // Compiles to: loop { check opt.has; if 0 break; bind x = opt.val; body }
1383
+ const headerBlock = ctx.newBlock('whl_header')
1384
+ const bodyBlock = ctx.newBlock('whl_body')
1385
+ const exitBlock = ctx.newBlock('whl_exit')
1386
+
1387
+ ctx.terminate({ kind: 'jump', target: headerBlock.id })
1388
+
1389
+ // Header: re-evaluate init and check has
1390
+ ctx.switchTo(headerBlock)
1391
+
1392
+ let hasOp: Operand
1393
+ let valTemp: Temp | undefined
1394
+
1395
+ const sv = (() => {
1396
+ if (stmt.init.kind === 'ident') return ctx.structVars.get(stmt.init.name)
1397
+ return undefined
1398
+ })()
1399
+
1400
+ if (sv && sv.typeName === '__option') {
1401
+ const hasT = sv.fields.get('has')!
1402
+ const valT = sv.fields.get('val')!
1403
+ hasOp = { kind: 'temp', name: hasT }
1404
+ valTemp = valT
1405
+ } else {
1406
+ lowerExpr(stmt.init, ctx, scope)
1407
+ const hasT = ctx.freshTemp()
1408
+ const valT = ctx.freshTemp()
1409
+ ctx.emit({ kind: 'copy', dst: hasT, src: { kind: 'temp', name: '__rf_has' } })
1410
+ ctx.emit({ kind: 'copy', dst: valT, src: { kind: 'temp', name: '__rf_val' } })
1411
+ hasOp = { kind: 'temp', name: hasT }
1412
+ valTemp = valT
1413
+ }
1414
+
1415
+ ctx.terminate({ kind: 'branch', cond: hasOp, then: bodyBlock.id, else: exitBlock.id })
1416
+
1417
+ // Body: bind x = val, run body
1418
+ ctx.switchTo(bodyBlock)
1419
+ const bodyScope = new Map(scope)
1420
+ if (valTemp) bodyScope.set(stmt.binding, valTemp)
1421
+ ctx.pushLoop(headerBlock.id, exitBlock.id, headerBlock.id)
1422
+ lowerBlock(stmt.body, ctx, bodyScope)
1423
+ ctx.popLoop()
1424
+ if (isPlaceholderTerm(ctx.current().term)) {
1425
+ ctx.terminate({ kind: 'jump', target: headerBlock.id })
1426
+ }
1427
+
1428
+ ctx.switchTo(exitBlock)
1429
+ break
1430
+ }
1431
+
1129
1432
  default: {
1130
1433
  const _exhaustive: never = stmt
1131
1434
  throw new Error(`Unknown HIR statement kind: ${(_exhaustive as any).kind}`)
@@ -1213,6 +1516,10 @@ function lowerExpr(
1213
1516
  }
1214
1517
  const temp = scope.get(expr.name)
1215
1518
  if (temp) return { kind: 'temp', name: temp }
1519
+ // Module-level const: inline the literal value directly (no scoreboard slot)
1520
+ if (ctx.constValues.has(expr.name)) {
1521
+ return { kind: 'const', value: ctx.constValues.get(expr.name)! }
1522
+ }
1216
1523
  // Unresolved ident — could be a global or external reference
1217
1524
  const t = ctx.freshTemp()
1218
1525
  ctx.emit({ kind: 'copy', dst: t, src: { kind: 'const', value: 0 } })
@@ -1346,6 +1653,36 @@ function lowerExpr(
1346
1653
  return { kind: 'const', value }
1347
1654
  }
1348
1655
 
1656
+ case 'enum_construct': {
1657
+ // Enum variant construction with payload:
1658
+ // Color::RGB(r: 10, g: 20, b: 30)
1659
+ // → scoreboard set tag = variant int value
1660
+ // → nbt_write rs:enums Color_r = 10, Color_g = 20, Color_b = 30
1661
+ const variants = ctx.enumDefs.get(expr.enumName)
1662
+ const tagValue = variants?.get(expr.variant) ?? 0
1663
+ // Write tag to a temp (the result of the expression is the integer tag)
1664
+ const tagTemp = ctx.freshTemp()
1665
+ ctx.emit({ kind: 'const', dst: tagTemp, value: tagValue })
1666
+ // Write payload fields to NBT storage rs:enums
1667
+ const payloadFields = ctx.enumPayloads.get(expr.enumName)?.get(expr.variant) ?? []
1668
+ for (const arg of expr.args) {
1669
+ const fieldDef = payloadFields.find(f => f.name === arg.name)
1670
+ const argOp = lowerExpr(arg.value, ctx, scope)
1671
+ // Determine NBT type from field definition
1672
+ const nbtType: NBTType = fieldDef && (fieldDef.type.kind === 'named') &&
1673
+ (fieldDef.type.name === 'float' || fieldDef.type.name === 'fixed') ? 'float' : 'int'
1674
+ ctx.emit({
1675
+ kind: 'nbt_write',
1676
+ ns: 'rs:enums',
1677
+ path: `${expr.enumName}_${arg.name}`,
1678
+ type: nbtType,
1679
+ scale: 1,
1680
+ src: argOp,
1681
+ })
1682
+ }
1683
+ return { kind: 'temp', name: tagTemp }
1684
+ }
1685
+
1349
1686
  case 'member': {
1350
1687
  // Enum variant access via dot syntax: Phase.Idle → integer constant
1351
1688
  if (expr.obj.kind === 'ident') {
@@ -1695,9 +2032,57 @@ function lowerExpr(
1695
2032
  return { kind: 'temp', name: t }
1696
2033
  }
1697
2034
 
2035
+ // Handle int_to_str / bool_to_str — identity functions for scoreboard int/bool → string
2036
+ // In f-string context these are handled by precomputeFStringParts; outside that, just
2037
+ // evaluate the argument and return it (integer stays as integer for scoreboard purposes).
2038
+ if (expr.fn === 'int_to_str' || expr.fn === 'bool_to_str') {
2039
+ if (expr.args.length === 1) {
2040
+ return lowerExpr(expr.args[0], ctx, scope)
2041
+ }
2042
+ const t = ctx.freshTemp()
2043
+ ctx.emit({ kind: 'const', dst: t, value: 0 })
2044
+ return { kind: 'temp', name: t }
2045
+ }
2046
+
2047
+ // Handle assert(cond[, message]) — test framework builtin
2048
+ if (expr.fn === 'assert') {
2049
+ const condArg = expr.args[0]
2050
+ const msgArg = expr.args[1]
2051
+ const condOp = condArg ? lowerExpr(condArg, ctx, scope) : { kind: 'const' as const, value: 0 }
2052
+ // Evaluate condition to a temp
2053
+ let condTemp: string
2054
+ if (condOp.kind === 'temp') {
2055
+ condTemp = condOp.name
2056
+ } else {
2057
+ condTemp = ctx.freshTemp()
2058
+ ctx.emit({ kind: 'const', dst: condTemp, value: condOp.value })
2059
+ }
2060
+ // Get message string
2061
+ let msgStr = 'assert failed'
2062
+ if (msgArg && msgArg.kind === 'str_lit') {
2063
+ msgStr = msgArg.value
2064
+ }
2065
+ const obj = `__${ctx.getNamespace()}`
2066
+ // emit: execute unless score $condTemp <obj> matches 1 run tellraw @a "FAIL: <msg>"
2067
+ const failMsg = JSON.stringify({ text: `FAIL: ${msgStr}`, color: 'red' })
2068
+ ctx.emit({ kind: 'call', dst: null, fn: `__raw:execute unless score $${ctx.getFnName()}_${condTemp} ${obj} matches 1 run tellraw @a ${failMsg}`, args: [] })
2069
+ ctx.emit({ kind: 'call', dst: null, fn: `__raw:execute unless score $${ctx.getFnName()}_${condTemp} ${obj} matches 1 run scoreboard players add rs.test_failed rs.meta 1`, args: [] })
2070
+ const t = ctx.freshTemp()
2071
+ ctx.emit({ kind: 'const', dst: t, value: 0 })
2072
+ return { kind: 'temp', name: t }
2073
+ }
2074
+
1698
2075
  // Handle builtin calls → raw MC commands
1699
2076
  if (BUILTIN_SET.has(expr.fn)) {
1700
- const cmd = formatBuiltinCall(expr.fn, expr.args, ctx.currentMacroParams, ctx.getNamespace())
2077
+ // For text builtins with f-string args, precompute complex expressions to temp vars
2078
+ const TEXT_BUILTINS_SET = new Set(['tell', 'tellraw', 'title', 'subtitle', 'actionbar', 'announce'])
2079
+ let resolvedArgs = expr.args
2080
+ if (TEXT_BUILTINS_SET.has(expr.fn)) {
2081
+ resolvedArgs = expr.args.map(arg =>
2082
+ arg.kind === 'f_string' ? precomputeFStringParts(arg, ctx, scope) : arg
2083
+ )
2084
+ }
2085
+ const cmd = formatBuiltinCall(expr.fn, resolvedArgs, ctx.currentMacroParams, ctx.getNamespace())
1701
2086
  ctx.emit({ kind: 'call', dst: null, fn: `__raw:${cmd}`, args: [] })
1702
2087
  const t = ctx.freshTemp()
1703
2088
  ctx.emit({ kind: 'const', dst: t, value: 0 })
@@ -1705,9 +2090,24 @@ function lowerExpr(
1705
2090
  }
1706
2091
 
1707
2092
  // Check for struct instance method call: parser desugars v.method() → call('method', [v, ...])
2093
+ // Some method names are remapped by the parser (e.g. add→set_add for set builtins).
2094
+ // We reverse-map them here to support impl methods with those names.
2095
+ const PARSER_METHOD_REMAP: Record<string, string> = {
2096
+ 'set_add': 'add', 'set_contains': 'contains', 'set_remove': 'remove', 'set_clear': 'clear',
2097
+ '__array_push': 'push', '__array_pop': 'pop',
2098
+ '__entity_tag': 'tag', '__entity_untag': 'untag', '__entity_has_tag': 'has_tag',
2099
+ }
1708
2100
  if (expr.args.length > 0 && expr.args[0].kind === 'ident') {
1709
2101
  const sv = ctx.structVars.get(expr.args[0].name)
1710
2102
  if (sv) {
2103
+ // Intercept Display::to_string() calls — inlined at f-string call sites; return 0 otherwise
2104
+ if (expr.fn === 'to_string' && ctx.displayImpls.has(sv.typeName)) {
2105
+ // Display::to_string() is expanded inline in f-string context (precomputeFStringParts).
2106
+ // Outside of that context, return a dummy 0 (the call is not valid standalone).
2107
+ const t = ctx.freshTemp()
2108
+ ctx.emit({ kind: 'const', dst: t, value: 0 })
2109
+ return { kind: 'temp', name: t }
2110
+ }
1711
2111
  // Intercept Timer method calls when _id is a known compile-time constant
1712
2112
  if (sv.typeName === 'Timer') {
1713
2113
  const idTemp = sv.fields.get('_id')
@@ -1716,7 +2116,10 @@ function lowerExpr(
1716
2116
  return lowerTimerMethod(expr.fn, timerId, sv, ctx, scope, expr.args.slice(1))
1717
2117
  }
1718
2118
  }
2119
+ // Try direct name, then try reverse-mapped name (for parser-remapped builtins)
2120
+ const originalMethodName = PARSER_METHOD_REMAP[expr.fn] ?? expr.fn
1719
2121
  const methodInfo = ctx.implMethods.get(sv.typeName)?.get(expr.fn)
2122
+ ?? ctx.implMethods.get(sv.typeName)?.get(originalMethodName)
1720
2123
  if (methodInfo?.hasSelf) {
1721
2124
  // Build args: self fields first, then remaining explicit args
1722
2125
  const fields = ctx.structDefs.get(sv.typeName) ?? []
@@ -1745,7 +2148,7 @@ function lowerExpr(
1745
2148
  }
1746
2149
  const allArgs = [...selfArgs, ...explicitArgs]
1747
2150
  const t = ctx.freshTemp()
1748
- ctx.emit({ kind: 'call', dst: t, fn: `${sv.typeName}::${expr.fn}`, args: allArgs })
2151
+ ctx.emit({ kind: 'call', dst: t, fn: `${sv.typeName}::${originalMethodName}`, args: allArgs })
1749
2152
  return { kind: 'temp', name: t }
1750
2153
  }
1751
2154
  }
@@ -1754,21 +2157,45 @@ function lowerExpr(
1754
2157
  // Check if calling a macro function → emit call_macro
1755
2158
  const targetMacro = ctx.macroInfo.get(expr.fn)
1756
2159
  if (targetMacro) {
1757
- const args = expr.args.map(a => lowerExpr(a, ctx, scope))
1758
2160
  const targetParams = ctx.fnParamInfo.get(expr.fn) ?? []
1759
2161
  const macroArgs: { name: string; value: Operand; type: NBTType; scale: number }[] = []
1760
- for (let i = 0; i < targetParams.length && i < args.length; i++) {
2162
+ for (let i = 0; i < targetParams.length && i < expr.args.length; i++) {
1761
2163
  const paramName = targetParams[i].name
1762
2164
  if (targetMacro.macroParams.has(paramName)) {
1763
2165
  const paramTypeName = targetMacro.paramTypes.get(paramName) ?? 'int'
1764
- const isFloat = paramTypeName === 'float'
1765
- const isFixed = paramTypeName === 'fixed'
1766
- macroArgs.push({
1767
- name: paramName,
1768
- value: args[i],
1769
- type: (isFloat || isFixed) ? 'double' : 'int',
1770
- scale: isFloat ? 0.01 : isFixed ? 0.0001 : 1,
1771
- })
2166
+ const isString = paramTypeName === 'string' || paramTypeName === 'format_string'
2167
+ const isSelector = paramTypeName === 'selector'
2168
+ if (isString) {
2169
+ // String macro params: store directly to rs:macro_args as NBT string
2170
+ const srcPath = lowerStringExprToPath(expr.args[i], ctx, scope, paramName)
2171
+ if (srcPath) {
2172
+ ctx.emit({
2173
+ kind: 'call',
2174
+ dst: null,
2175
+ fn: `__raw:data modify storage rs:macro_args ${paramName} set from storage rs:strings ${srcPath}`,
2176
+ args: [],
2177
+ })
2178
+ }
2179
+ } else if (isSelector) {
2180
+ // Selector macro params: store the selector string as an NBT string in rs:macro_args
2181
+ const arg = expr.args[i]
2182
+ const selStr = arg.kind === 'selector' ? arg.raw : '@s'
2183
+ ctx.emit({
2184
+ kind: 'call',
2185
+ dst: null,
2186
+ fn: `__raw:data modify storage rs:macro_args ${paramName} set value ${JSON.stringify(selStr)}`,
2187
+ args: [],
2188
+ })
2189
+ } else {
2190
+ const isFloat = paramTypeName === 'float'
2191
+ const isFixed = paramTypeName === 'fixed'
2192
+ macroArgs.push({
2193
+ name: paramName,
2194
+ value: lowerExpr(expr.args[i], ctx, scope),
2195
+ type: (isFloat || isFixed) ? 'double' : 'int',
2196
+ scale: isFloat ? 0.01 : isFixed ? 0.0001 : 1,
2197
+ })
2198
+ }
1772
2199
  }
1773
2200
  }
1774
2201
  const t = ctx.freshTemp()
@@ -1818,6 +2245,10 @@ function lowerExpr(
1818
2245
  ctx.hirFunctions,
1819
2246
  ctx.specializedFnsRegistry,
1820
2247
  specializedName,
2248
+ ctx.enumPayloads,
2249
+ ctx.constValues,
2250
+ ctx.singletonStructs,
2251
+ ctx.displayImpls,
1821
2252
  )
1822
2253
  ctx.specializedFnsRegistry.set(specializedName, [specFn, ...specHelpers])
1823
2254
  }
@@ -1842,6 +2273,36 @@ function lowerExpr(
1842
2273
  {
1843
2274
  const targetParams = ctx.fnParamInfo.get(expr.fn)
1844
2275
  if (targetParams) {
2276
+ const hasStringParam = targetParams.some(
2277
+ p => p.type.kind === 'named' && (p.type.name === 'string' || p.type.name === 'format_string')
2278
+ )
2279
+ if (hasStringParam) {
2280
+ const nonStringArgs: Operand[] = []
2281
+ let stringSlot = 0
2282
+ for (let i = 0; i < targetParams.length && i < expr.args.length; i++) {
2283
+ const p = targetParams[i]
2284
+ if (p.type.kind === 'named' && (p.type.name === 'string' || p.type.name === 'format_string')) {
2285
+ const srcPath = lowerStringExprToPath(expr.args[i], ctx, scope, `arg${stringSlot}`)
2286
+ if (srcPath) {
2287
+ ctx.emit({
2288
+ kind: 'call',
2289
+ dst: null,
2290
+ fn: `__raw:data modify storage rs:strings __sp${stringSlot} set from storage rs:strings ${srcPath}`,
2291
+ args: [],
2292
+ })
2293
+ }
2294
+ stringSlot++
2295
+ } else {
2296
+ nonStringArgs.push(lowerExpr(expr.args[i], ctx, scope))
2297
+ }
2298
+ }
2299
+ for (let i = targetParams.length; i < expr.args.length; i++) {
2300
+ nonStringArgs.push(lowerExpr(expr.args[i], ctx, scope))
2301
+ }
2302
+ const t = ctx.freshTemp()
2303
+ ctx.emit({ kind: 'call', dst: t, fn: expr.fn, args: nonStringArgs })
2304
+ return { kind: 'temp', name: t }
2305
+ }
1845
2306
  const hasDoubleParam = targetParams.some(
1846
2307
  p => p.type.kind === 'named' && p.type.name === 'double'
1847
2308
  )
@@ -1914,6 +2375,12 @@ function lowerExpr(
1914
2375
  if (expr.callee.kind === 'member' && expr.callee.obj.kind === 'ident') {
1915
2376
  const sv = ctx.structVars.get(expr.callee.obj.name)
1916
2377
  if (sv) {
2378
+ // Intercept Display::to_string() — inlined in f-string context; return 0 otherwise
2379
+ if (expr.callee.field === 'to_string' && ctx.displayImpls.has(sv.typeName)) {
2380
+ const t = ctx.freshTemp()
2381
+ ctx.emit({ kind: 'const', dst: t, value: 0 })
2382
+ return { kind: 'temp', name: t }
2383
+ }
1917
2384
  // Intercept Timer method calls when _id is a known compile-time constant
1918
2385
  if (sv.typeName === 'Timer') {
1919
2386
  const idTemp = sv.fields.get('_id')
@@ -1938,6 +2405,35 @@ function lowerExpr(
1938
2405
  }
1939
2406
  }
1940
2407
  }
2408
+ // Method chaining: callee obj is not a simple ident (e.g. v.scale(2).add(...))
2409
+ // Determine if the callee obj expression returns a struct via __rf_ slots
2410
+ if (expr.callee.kind === 'member' && expr.callee.obj.kind !== 'ident') {
2411
+ const returnedStructType = inferInvokeReturnStructType(expr.callee.obj, ctx)
2412
+ if (returnedStructType) {
2413
+ // Lower the inner call — result goes into __rf_ slots
2414
+ lowerExpr(expr.callee.obj, ctx, scope)
2415
+ // Read __rf_ slots into temps for this chained call
2416
+ const chainFields = ctx.structDefs.get(returnedStructType) ?? []
2417
+ const chainFieldTemps = new Map<string, Temp>()
2418
+ for (const fieldName of chainFields) {
2419
+ const ft = ctx.freshTemp()
2420
+ ctx.emit({ kind: 'copy', dst: ft, src: { kind: 'temp', name: `__rf_${fieldName}` } })
2421
+ chainFieldTemps.set(fieldName, ft)
2422
+ }
2423
+ const methodInfo = ctx.implMethods.get(returnedStructType)?.get(expr.callee.field)
2424
+ if (methodInfo?.hasSelf) {
2425
+ const selfArgs: Operand[] = chainFields.map(f => {
2426
+ const temp = chainFieldTemps.get(f)
2427
+ return temp ? { kind: 'temp' as const, name: temp } : { kind: 'const' as const, value: 0 }
2428
+ })
2429
+ const explicitArgs = expr.args.map(a => lowerExpr(a, ctx, scope))
2430
+ const allArgs = [...selfArgs, ...explicitArgs]
2431
+ const ct = ctx.freshTemp()
2432
+ ctx.emit({ kind: 'call', dst: ct, fn: `${returnedStructType}::${expr.callee.field}`, args: allArgs })
2433
+ return { kind: 'temp', name: ct }
2434
+ }
2435
+ }
2436
+ }
1941
2437
  // Fallback: generic invoke
1942
2438
  const calleeOp = lowerExpr(expr.callee, ctx, scope)
1943
2439
  const args = expr.args.map(a => lowerExpr(a, ctx, scope))
@@ -1965,6 +2461,28 @@ function lowerExpr(
1965
2461
  ctx.emit({ kind: 'const', dst: t, value: 0 })
1966
2462
  return { kind: 'temp', name: t }
1967
2463
  }
2464
+ // @singleton struct static calls: expand struct arg field-by-field for ::set
2465
+ if (ctx.singletonStructs.has(expr.type)) {
2466
+ if (expr.method === 'get') {
2467
+ // GameState::get() — no struct args, our synthetic LIR fn writes to $__rf_<field> slots
2468
+ const t = ctx.freshTemp()
2469
+ ctx.emit({ kind: 'call', dst: t, fn: `${expr.type}::${expr.method}`, args: [] })
2470
+ return { kind: 'temp', name: t }
2471
+ } else if (expr.method === 'set' && expr.args.length === 1 && expr.args[0].kind === 'ident') {
2472
+ // GameState::set(gs) — flatten struct arg into individual field args ($p0, $p1, ...)
2473
+ const sv = ctx.structVars.get(expr.args[0].name)
2474
+ if (sv) {
2475
+ const fields = ctx.structDefs.get(sv.typeName) ?? []
2476
+ const fieldArgs: Operand[] = fields.map(f => {
2477
+ const temp = sv.fields.get(f)
2478
+ return temp ? { kind: 'temp' as const, name: temp } : { kind: 'const' as const, value: 0 }
2479
+ })
2480
+ const t = ctx.freshTemp()
2481
+ ctx.emit({ kind: 'call', dst: t, fn: `${expr.type}::${expr.method}`, args: fieldArgs })
2482
+ return { kind: 'temp', name: t }
2483
+ }
2484
+ }
2485
+ }
1968
2486
  const args = expr.args.map(a => lowerExpr(a, ctx, scope))
1969
2487
  const t = ctx.freshTemp()
1970
2488
  ctx.emit({ kind: 'call', dst: t, fn: `${expr.type}::${expr.method}`, args })
@@ -2002,6 +2520,47 @@ function lowerExpr(
2002
2520
  return { kind: 'temp', name: t }
2003
2521
  }
2004
2522
 
2523
+ case 'unwrap_or': {
2524
+ // opt.unwrap_or(default) → evaluate opt, if has=1 return val else return default
2525
+ const resultTemp = ctx.freshTemp()
2526
+ const defaultOp = lowerExpr(expr.default_, ctx, scope)
2527
+ ctx.emit({ kind: 'copy', dst: resultTemp, src: defaultOp })
2528
+
2529
+ const sv = (() => {
2530
+ if (expr.opt.kind === 'ident') return ctx.structVars.get(expr.opt.name)
2531
+ return undefined
2532
+ })()
2533
+
2534
+ let hasOp: Operand
2535
+ let valTemp: Temp | undefined
2536
+
2537
+ if (sv && sv.typeName === '__option') {
2538
+ const hasT = sv.fields.get('has')!
2539
+ const valT = sv.fields.get('val')!
2540
+ hasOp = { kind: 'temp', name: hasT }
2541
+ valTemp = valT
2542
+ } else {
2543
+ lowerExpr(expr.opt, ctx, scope)
2544
+ const hasT = ctx.freshTemp()
2545
+ const valT = ctx.freshTemp()
2546
+ ctx.emit({ kind: 'copy', dst: hasT, src: { kind: 'temp', name: '__rf_has' } })
2547
+ ctx.emit({ kind: 'copy', dst: valT, src: { kind: 'temp', name: '__rf_val' } })
2548
+ hasOp = { kind: 'temp', name: hasT }
2549
+ valTemp = valT
2550
+ }
2551
+
2552
+ const someBlock = ctx.newBlock('unwrap_some')
2553
+ const mergeBlock = ctx.newBlock('unwrap_merge')
2554
+ ctx.terminate({ kind: 'branch', cond: hasOp, then: someBlock.id, else: mergeBlock.id })
2555
+
2556
+ ctx.switchTo(someBlock)
2557
+ if (valTemp) ctx.emit({ kind: 'copy', dst: resultTemp, src: { kind: 'temp', name: valTemp } })
2558
+ ctx.terminate({ kind: 'jump', target: mergeBlock.id })
2559
+
2560
+ ctx.switchTo(mergeBlock)
2561
+ return { kind: 'temp', name: resultTemp }
2562
+ }
2563
+
2005
2564
  case 'type_cast': {
2006
2565
  const ns = ctx.getNamespace()
2007
2566
  const targetName = expr.targetType.kind === 'named' ? expr.targetType.name : null
@@ -2157,6 +2716,34 @@ function lowerShortCircuitOr(
2157
2716
  // Timer method inlining
2158
2717
  // ---------------------------------------------------------------------------
2159
2718
 
2719
+ /**
2720
+ * Infer the struct type name returned by a chained invoke/call expression.
2721
+ * Used to support method chaining: v.scale(2).add(...) where scale() returns Vec2.
2722
+ * Returns the struct type name if determinable, otherwise undefined.
2723
+ */
2724
+ function inferInvokeReturnStructType(
2725
+ expr: HIRExpr,
2726
+ ctx: FnContext,
2727
+ ): string | undefined {
2728
+ if (expr.kind === 'invoke' && expr.callee.kind === 'member') {
2729
+ // Find the receiver type via structVars
2730
+ let receiverTypeName: string | undefined
2731
+ if (expr.callee.obj.kind === 'ident') {
2732
+ receiverTypeName = ctx.structVars.get(expr.callee.obj.name)?.typeName
2733
+ } else {
2734
+ // Recursively infer the type for deeper chains
2735
+ receiverTypeName = inferInvokeReturnStructType(expr.callee.obj, ctx)
2736
+ }
2737
+ if (receiverTypeName) {
2738
+ const methodInfo = ctx.implMethods.get(receiverTypeName)?.get(expr.callee.field)
2739
+ if (methodInfo?.returnStructName) {
2740
+ return methodInfo.returnStructName
2741
+ }
2742
+ }
2743
+ }
2744
+ return undefined
2745
+ }
2746
+
2160
2747
  /**
2161
2748
  * Inline a Timer instance method call using the statically-assigned timer ID.
2162
2749
  * Emits scoreboard operations directly, bypassing the Timer::* function calls.
@@ -2296,6 +2883,43 @@ function lowerExecuteSubcmd(sub: HIRExecuteSubcommand): ExecuteSubcmd {
2296
2883
  }
2297
2884
  }
2298
2885
 
2886
+ function lowerStringExprToPath(
2887
+ expr: HIRExpr,
2888
+ ctx: FnContext,
2889
+ scope: Map<string, Temp>,
2890
+ hint = 'str',
2891
+ ): string | null {
2892
+ switch (expr.kind) {
2893
+ case 'str_lit': {
2894
+ const path = ctx.freshStringVar(hint)
2895
+ ctx.emit({
2896
+ kind: 'call',
2897
+ dst: null,
2898
+ fn: `__raw:data modify storage rs:strings ${path} set value ${JSON.stringify(expr.value)}`,
2899
+ args: [],
2900
+ })
2901
+ return path
2902
+ }
2903
+ case 'ident':
2904
+ return ctx.stringVars.get(expr.name) ?? null
2905
+ case 'assign': {
2906
+ if (!ctx.stringVars.has(expr.target)) return null
2907
+ const dstPath = ctx.stringVars.get(expr.target)!
2908
+ const srcPath = lowerStringExprToPath(expr.value, ctx, scope, expr.target)
2909
+ if (!srcPath || srcPath === dstPath) return dstPath
2910
+ ctx.emit({
2911
+ kind: 'call',
2912
+ dst: null,
2913
+ fn: `__raw:data modify storage rs:strings ${dstPath} set from storage rs:strings ${srcPath}`,
2914
+ args: [],
2915
+ })
2916
+ return dstPath
2917
+ }
2918
+ default:
2919
+ return null
2920
+ }
2921
+ }
2922
+
2299
2923
  function selectorToString(sel: { kind: string; filters?: any }): string {
2300
2924
  // EntitySelector has kind like '@a', '@e', '@s', etc.
2301
2925
  // Filters are key=value pairs that become [key=value,key=value]
@@ -2336,6 +2960,105 @@ const MACRO_SENTINEL = '\x01'
2336
2960
  * Convert an f_string HIRExpr to a Minecraft JSON text component string.
2337
2961
  * Each interpolated variable becomes a {"score":{"name":"$var","objective":"__ns"}} component.
2338
2962
  */
2963
+ /**
2964
+ * Pre-evaluate complex f-string expression parts to MIR temp vars,
2965
+ * returning a rewritten HIRExpr where each complex part is replaced by a simple ident.
2966
+ */
2967
+ function precomputeFStringParts(
2968
+ expr: HIRExpr,
2969
+ ctx: FnContext,
2970
+ scope: Map<string, Temp>,
2971
+ ): HIRExpr {
2972
+ if (expr.kind !== 'f_string') return expr
2973
+
2974
+ const newParts: HIRFStringPart[] = []
2975
+
2976
+ for (const part of expr.parts) {
2977
+ if (part.kind === 'text') {
2978
+ newParts.push(part)
2979
+ continue
2980
+ }
2981
+
2982
+ const inner: HIRExpr = part.expr
2983
+ // Simple cases that fStringToJsonText already handles
2984
+ if (inner.kind === 'ident' || inner.kind === 'int_lit' || inner.kind === 'bool_lit') {
2985
+ newParts.push({ kind: 'expr', expr: inner })
2986
+ continue
2987
+ }
2988
+
2989
+ // Display::to_string() inline expansion in f-string context:
2990
+ // v.to_string() (desugared as call{fn:'to_string', args:[v]}) → inline the Display impl's f-string
2991
+ if (inner.kind === 'call' && inner.fn === 'to_string' && inner.args.length === 1 && inner.args[0].kind === 'ident') {
2992
+ const argName = (inner.args[0] as { kind: 'ident'; name: string }).name
2993
+ const sv = ctx.structVars.get(argName)
2994
+ if (sv && ctx.displayImpls.has(sv.typeName)) {
2995
+ const displayParts = ctx.displayImpls.get(sv.typeName)!
2996
+ // Expand Display impl parts, substituting self.<field> with the actual struct field temps
2997
+ for (const dp of displayParts) {
2998
+ if (dp.kind === 'text') {
2999
+ newParts.push(dp)
3000
+ } else {
3001
+ const dpExpr = dp.expr
3002
+ // member(ident('self'), field) → lookup struct field temp
3003
+ if (dpExpr.kind === 'member' && dpExpr.obj.kind === 'ident' && dpExpr.obj.name === 'self') {
3004
+ const fieldTemp = sv.fields.get(dpExpr.field)
3005
+ if (fieldTemp) {
3006
+ newParts.push({ kind: 'expr', expr: { kind: 'ident', name: fieldTemp } })
3007
+ } else {
3008
+ newParts.push({ kind: 'expr', expr: { kind: 'int_lit', value: 0 } })
3009
+ }
3010
+ } else {
3011
+ // Other expressions: lower them with self fields in scope
3012
+ const selfScope = new Map(scope)
3013
+ for (const [fieldName, fieldTemp] of sv.fields) {
3014
+ selfScope.set(`self.${fieldName}`, fieldTemp)
3015
+ }
3016
+ const tempOp = lowerExpr(dpExpr, ctx, selfScope)
3017
+ if (tempOp.kind === 'temp') {
3018
+ newParts.push({ kind: 'expr', expr: { kind: 'ident', name: tempOp.name } })
3019
+ } else if (tempOp.kind === 'const') {
3020
+ newParts.push({ kind: 'expr', expr: { kind: 'int_lit', value: tempOp.value } })
3021
+ }
3022
+ }
3023
+ }
3024
+ }
3025
+ continue
3026
+ }
3027
+ }
3028
+
3029
+ // int_to_str(x) / bool_to_str(x) in f-string: pass through the inner arg as a scoreboard score ref
3030
+ if (inner.kind === 'call' && (inner.fn === 'int_to_str' || inner.fn === 'bool_to_str') && inner.args.length === 1) {
3031
+ const arg: HIRExpr = inner.args[0]
3032
+ if (arg.kind === 'ident') {
3033
+ newParts.push({ kind: 'expr', expr: arg })
3034
+ continue
3035
+ }
3036
+ // If arg is complex, lower it first
3037
+ const argOp = lowerExpr(arg, ctx, scope)
3038
+ if (argOp.kind === 'temp') {
3039
+ newParts.push({ kind: 'expr', expr: { kind: 'ident', name: argOp.name } })
3040
+ } else if (argOp.kind === 'const') {
3041
+ newParts.push({ kind: 'expr', expr: { kind: 'int_lit', value: argOp.value } })
3042
+ } else {
3043
+ newParts.push(part)
3044
+ }
3045
+ continue
3046
+ }
3047
+
3048
+ // Complex expression: lower to a temp var, then reference it as ident
3049
+ const tempOp = lowerExpr(inner, ctx, scope)
3050
+ if (tempOp.kind === 'temp') {
3051
+ newParts.push({ kind: 'expr', expr: { kind: 'ident', name: tempOp.name } })
3052
+ } else if (tempOp.kind === 'const') {
3053
+ newParts.push({ kind: 'expr', expr: { kind: 'int_lit', value: tempOp.value } })
3054
+ } else {
3055
+ newParts.push(part)
3056
+ }
3057
+ }
3058
+
3059
+ return { kind: 'f_string', parts: newParts }
3060
+ }
3061
+
2339
3062
  function fStringToJsonText(expr: HIRExpr, namespace: string): string {
2340
3063
  if (expr.kind !== 'f_string') return JSON.stringify(expr.kind === 'str_lit' ? { text: expr.value } : { text: '~' })
2341
3064
  const objective = `__${namespace}`
@@ -2344,12 +3067,14 @@ function fStringToJsonText(expr: HIRExpr, namespace: string): string {
2344
3067
  if (part.kind === 'text') {
2345
3068
  if (part.value) extra.push({ text: part.value })
2346
3069
  } else {
2347
- // expr part — must be a scoreboard variable (ident)
2348
- const inner = part.expr
3070
+ // expr part — must be a scoreboard variable (ident) or literal
3071
+ const inner = part.expr as HIRExpr
2349
3072
  if (inner.kind === 'ident') {
2350
3073
  extra.push({ score: { name: `$${inner.name}`, objective } })
2351
3074
  } else if (inner.kind === 'int_lit') {
2352
3075
  extra.push({ text: String(inner.value) })
3076
+ } else if (inner.kind === 'bool_lit') {
3077
+ extra.push({ text: inner.value ? 'true' : 'false' })
2353
3078
  } else {
2354
3079
  extra.push({ text: '?' })
2355
3080
  }
@@ -2473,6 +3198,26 @@ function formatBuiltinCall(
2473
3198
  case 'difficulty': cmd = `difficulty ${strs[0]}`; break
2474
3199
  case 'xp_add': cmd = `xp add ${strs[0]} ${strs[1]} ${strs[2] ?? 'points'}`; break
2475
3200
  case 'xp_set': cmd = `xp set ${strs[0]} ${strs[1]} ${strs[2] ?? 'points'}`; break
3201
+ case 'scoreboard_add_objective': cmd = strs[2] ? `scoreboard objectives add ${strs[0]} ${strs[1]} ${strs[2]}` : `scoreboard objectives add ${strs[0]} ${strs[1]}`; break
3202
+ case 'scoreboard_remove_objective': cmd = `scoreboard objectives remove ${strs[0]}`; break
3203
+ case 'scoreboard_display': cmd = `scoreboard objectives setdisplay ${strs[0]} ${strs[1] ?? ''}`; break
3204
+ case 'scoreboard_hide': cmd = `scoreboard objectives setdisplay ${strs[0]}`; break
3205
+ case 'team_add': cmd = strs[1] ? `team add ${strs[0]} ${strs[1]}` : `team add ${strs[0]}`; break
3206
+ case 'team_remove': cmd = `team remove ${strs[0]}`; break
3207
+ case 'team_join': cmd = `team join ${strs[0]} ${strs[1]}`; break
3208
+ case 'team_leave': cmd = `team leave ${strs[0]}`; break
3209
+ case 'team_option': cmd = `team modify ${strs[0]} ${strs[1]} ${strs[2]}`; break
3210
+ case 'bossbar_add': cmd = `bossbar add ${strs[0]} ${strs[1] ? JSON.stringify({ text: strs[1] }) : '""'}`; break
3211
+ case 'bossbar_remove': cmd = `bossbar remove ${strs[0]}`; break
3212
+ case 'bossbar_set_value': cmd = `bossbar set ${strs[0]} value ${strs[1]}`; break
3213
+ case 'bossbar_get_value': cmd = `bossbar get ${strs[0]} value`; break
3214
+ case 'bossbar_set_max': cmd = `bossbar set ${strs[0]} max ${strs[1]}`; break
3215
+ case 'bossbar_set_color': cmd = `bossbar set ${strs[0]} color ${strs[1]}`; break
3216
+ case 'bossbar_set_style': cmd = `bossbar set ${strs[0]} style ${strs[1]}`; break
3217
+ case 'bossbar_set_visible': cmd = `bossbar set ${strs[0]} visible ${strs[1]}`; break
3218
+ case 'bossbar_set_players': cmd = `bossbar set ${strs[0]} players ${strs[1]}`; break
3219
+ case 'data_get': cmd = `data get ${strs[0]} ${strs[1]} ${strs[2] ?? ''}`.trimEnd(); break
3220
+ case 'data_merge': cmd = `data merge ${strs[0]} ${strs[1]} ${strs[2]}`; break
2476
3221
  default: cmd = `${fn} ${strs.join(' ')}`
2477
3222
  }
2478
3223