redscript-mc 1.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 (272) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +31 -0
  3. package/.github/ISSUE_TEMPLATE/wrong_output.md +33 -0
  4. package/.github/PULL_REQUEST_TEMPLATE.md +34 -0
  5. package/.github/workflows/ci.yml +29 -0
  6. package/.github/workflows/publish-extension.yml +35 -0
  7. package/LICENSE +21 -0
  8. package/README.md +261 -0
  9. package/README.zh.md +261 -0
  10. package/dist/__tests__/cli.test.d.ts +1 -0
  11. package/dist/__tests__/cli.test.js +140 -0
  12. package/dist/__tests__/codegen.test.d.ts +1 -0
  13. package/dist/__tests__/codegen.test.js +121 -0
  14. package/dist/__tests__/diagnostics.test.d.ts +4 -0
  15. package/dist/__tests__/diagnostics.test.js +149 -0
  16. package/dist/__tests__/e2e.test.d.ts +6 -0
  17. package/dist/__tests__/e2e.test.js +1528 -0
  18. package/dist/__tests__/lexer.test.d.ts +1 -0
  19. package/dist/__tests__/lexer.test.js +316 -0
  20. package/dist/__tests__/lowering.test.d.ts +1 -0
  21. package/dist/__tests__/lowering.test.js +819 -0
  22. package/dist/__tests__/mc-integration.test.d.ts +12 -0
  23. package/dist/__tests__/mc-integration.test.js +395 -0
  24. package/dist/__tests__/mc-syntax.test.d.ts +1 -0
  25. package/dist/__tests__/mc-syntax.test.js +112 -0
  26. package/dist/__tests__/nbt.test.d.ts +1 -0
  27. package/dist/__tests__/nbt.test.js +82 -0
  28. package/dist/__tests__/optimizer-advanced.test.d.ts +1 -0
  29. package/dist/__tests__/optimizer-advanced.test.js +124 -0
  30. package/dist/__tests__/optimizer.test.d.ts +1 -0
  31. package/dist/__tests__/optimizer.test.js +118 -0
  32. package/dist/__tests__/parser.test.d.ts +1 -0
  33. package/dist/__tests__/parser.test.js +717 -0
  34. package/dist/__tests__/repl.test.d.ts +1 -0
  35. package/dist/__tests__/repl.test.js +27 -0
  36. package/dist/__tests__/runtime.test.d.ts +1 -0
  37. package/dist/__tests__/runtime.test.js +276 -0
  38. package/dist/__tests__/structure-optimizer.test.d.ts +1 -0
  39. package/dist/__tests__/structure-optimizer.test.js +33 -0
  40. package/dist/__tests__/typechecker.test.d.ts +1 -0
  41. package/dist/__tests__/typechecker.test.js +364 -0
  42. package/dist/ast/types.d.ts +357 -0
  43. package/dist/ast/types.js +9 -0
  44. package/dist/cli.d.ts +11 -0
  45. package/dist/cli.js +407 -0
  46. package/dist/codegen/cmdblock/index.d.ts +26 -0
  47. package/dist/codegen/cmdblock/index.js +45 -0
  48. package/dist/codegen/mcfunction/index.d.ts +34 -0
  49. package/dist/codegen/mcfunction/index.js +413 -0
  50. package/dist/codegen/structure/index.d.ts +18 -0
  51. package/dist/codegen/structure/index.js +249 -0
  52. package/dist/compile.d.ts +30 -0
  53. package/dist/compile.js +152 -0
  54. package/dist/data/arena/function/__load.mcfunction +6 -0
  55. package/dist/data/arena/function/__tick.mcfunction +2 -0
  56. package/dist/data/arena/function/announce_leaders/else_1.mcfunction +3 -0
  57. package/dist/data/arena/function/announce_leaders/foreach_0/merge_2.mcfunction +1 -0
  58. package/dist/data/arena/function/announce_leaders/foreach_0/then_0.mcfunction +3 -0
  59. package/dist/data/arena/function/announce_leaders/foreach_0.mcfunction +7 -0
  60. package/dist/data/arena/function/announce_leaders/foreach_1/merge_2.mcfunction +1 -0
  61. package/dist/data/arena/function/announce_leaders/foreach_1/then_0.mcfunction +4 -0
  62. package/dist/data/arena/function/announce_leaders/foreach_1.mcfunction +6 -0
  63. package/dist/data/arena/function/announce_leaders/merge_2.mcfunction +1 -0
  64. package/dist/data/arena/function/announce_leaders/then_0.mcfunction +4 -0
  65. package/dist/data/arena/function/announce_leaders.mcfunction +6 -0
  66. package/dist/data/arena/function/arena_tick/merge_2.mcfunction +1 -0
  67. package/dist/data/arena/function/arena_tick/then_0.mcfunction +4 -0
  68. package/dist/data/arena/function/arena_tick.mcfunction +11 -0
  69. package/dist/data/counter/function/__load.mcfunction +5 -0
  70. package/dist/data/counter/function/__tick.mcfunction +2 -0
  71. package/dist/data/counter/function/counter_tick/merge_2.mcfunction +1 -0
  72. package/dist/data/counter/function/counter_tick/then_0.mcfunction +3 -0
  73. package/dist/data/counter/function/counter_tick.mcfunction +11 -0
  74. package/dist/data/minecraft/tags/function/load.json +5 -0
  75. package/dist/data/minecraft/tags/function/tick.json +5 -0
  76. package/dist/data/quiz/function/__load.mcfunction +16 -0
  77. package/dist/data/quiz/function/__tick.mcfunction +6 -0
  78. package/dist/data/quiz/function/__trigger_quiz_a_dispatch.mcfunction +4 -0
  79. package/dist/data/quiz/function/__trigger_quiz_b_dispatch.mcfunction +4 -0
  80. package/dist/data/quiz/function/__trigger_quiz_c_dispatch.mcfunction +4 -0
  81. package/dist/data/quiz/function/__trigger_quiz_start_dispatch.mcfunction +4 -0
  82. package/dist/data/quiz/function/answer_a.mcfunction +4 -0
  83. package/dist/data/quiz/function/answer_b.mcfunction +4 -0
  84. package/dist/data/quiz/function/answer_c.mcfunction +4 -0
  85. package/dist/data/quiz/function/ask_question/else_1.mcfunction +5 -0
  86. package/dist/data/quiz/function/ask_question/else_4.mcfunction +5 -0
  87. package/dist/data/quiz/function/ask_question/else_7.mcfunction +4 -0
  88. package/dist/data/quiz/function/ask_question/merge_2.mcfunction +1 -0
  89. package/dist/data/quiz/function/ask_question/merge_5.mcfunction +2 -0
  90. package/dist/data/quiz/function/ask_question/merge_8.mcfunction +2 -0
  91. package/dist/data/quiz/function/ask_question/then_0.mcfunction +4 -0
  92. package/dist/data/quiz/function/ask_question/then_3.mcfunction +4 -0
  93. package/dist/data/quiz/function/ask_question/then_6.mcfunction +4 -0
  94. package/dist/data/quiz/function/ask_question.mcfunction +7 -0
  95. package/dist/data/quiz/function/finish_quiz.mcfunction +6 -0
  96. package/dist/data/quiz/function/handle_answer/else_1.mcfunction +5 -0
  97. package/dist/data/quiz/function/handle_answer/else_10.mcfunction +3 -0
  98. package/dist/data/quiz/function/handle_answer/else_16.mcfunction +3 -0
  99. package/dist/data/quiz/function/handle_answer/else_4.mcfunction +3 -0
  100. package/dist/data/quiz/function/handle_answer/else_7.mcfunction +5 -0
  101. package/dist/data/quiz/function/handle_answer/merge_11.mcfunction +2 -0
  102. package/dist/data/quiz/function/handle_answer/merge_14.mcfunction +2 -0
  103. package/dist/data/quiz/function/handle_answer/merge_17.mcfunction +2 -0
  104. package/dist/data/quiz/function/handle_answer/merge_2.mcfunction +8 -0
  105. package/dist/data/quiz/function/handle_answer/merge_5.mcfunction +2 -0
  106. package/dist/data/quiz/function/handle_answer/merge_8.mcfunction +2 -0
  107. package/dist/data/quiz/function/handle_answer/then_0.mcfunction +5 -0
  108. package/dist/data/quiz/function/handle_answer/then_12.mcfunction +5 -0
  109. package/dist/data/quiz/function/handle_answer/then_15.mcfunction +6 -0
  110. package/dist/data/quiz/function/handle_answer/then_3.mcfunction +6 -0
  111. package/dist/data/quiz/function/handle_answer/then_6.mcfunction +5 -0
  112. package/dist/data/quiz/function/handle_answer/then_9.mcfunction +6 -0
  113. package/dist/data/quiz/function/handle_answer.mcfunction +11 -0
  114. package/dist/data/quiz/function/start_quiz.mcfunction +5 -0
  115. package/dist/data/shop/function/__load.mcfunction +7 -0
  116. package/dist/data/shop/function/__tick.mcfunction +3 -0
  117. package/dist/data/shop/function/__trigger_shop_buy_dispatch.mcfunction +4 -0
  118. package/dist/data/shop/function/complete_purchase/else_1.mcfunction +5 -0
  119. package/dist/data/shop/function/complete_purchase/else_4.mcfunction +5 -0
  120. package/dist/data/shop/function/complete_purchase/else_7.mcfunction +3 -0
  121. package/dist/data/shop/function/complete_purchase/merge_2.mcfunction +2 -0
  122. package/dist/data/shop/function/complete_purchase/merge_5.mcfunction +2 -0
  123. package/dist/data/shop/function/complete_purchase/merge_8.mcfunction +2 -0
  124. package/dist/data/shop/function/complete_purchase/then_0.mcfunction +4 -0
  125. package/dist/data/shop/function/complete_purchase/then_3.mcfunction +4 -0
  126. package/dist/data/shop/function/complete_purchase/then_6.mcfunction +4 -0
  127. package/dist/data/shop/function/complete_purchase.mcfunction +7 -0
  128. package/dist/data/shop/function/handle_shop_trigger.mcfunction +3 -0
  129. package/dist/data/turret/function/__load.mcfunction +5 -0
  130. package/dist/data/turret/function/__tick.mcfunction +4 -0
  131. package/dist/data/turret/function/__trigger_deploy_turret_dispatch.mcfunction +4 -0
  132. package/dist/data/turret/function/deploy_turret.mcfunction +8 -0
  133. package/dist/data/turret/function/turret_tick/at_1.mcfunction +2 -0
  134. package/dist/data/turret/function/turret_tick/foreach_0.mcfunction +2 -0
  135. package/dist/data/turret/function/turret_tick/foreach_2.mcfunction +2 -0
  136. package/dist/data/turret/function/turret_tick/tick_body.mcfunction +3 -0
  137. package/dist/data/turret/function/turret_tick/tick_skip.mcfunction +1 -0
  138. package/dist/data/turret/function/turret_tick.mcfunction +5 -0
  139. package/dist/diagnostics/index.d.ts +44 -0
  140. package/dist/diagnostics/index.js +140 -0
  141. package/dist/index.d.ts +53 -0
  142. package/dist/index.js +126 -0
  143. package/dist/ir/builder.d.ts +32 -0
  144. package/dist/ir/builder.js +99 -0
  145. package/dist/ir/types.d.ts +117 -0
  146. package/dist/ir/types.js +15 -0
  147. package/dist/lexer/index.d.ts +36 -0
  148. package/dist/lexer/index.js +458 -0
  149. package/dist/lowering/index.d.ts +106 -0
  150. package/dist/lowering/index.js +2041 -0
  151. package/dist/mc-test/client.d.ts +128 -0
  152. package/dist/mc-test/client.js +174 -0
  153. package/dist/mc-test/runner.d.ts +28 -0
  154. package/dist/mc-test/runner.js +150 -0
  155. package/dist/mc-test/setup.d.ts +11 -0
  156. package/dist/mc-test/setup.js +98 -0
  157. package/dist/mc-validator/index.d.ts +17 -0
  158. package/dist/mc-validator/index.js +322 -0
  159. package/dist/nbt/index.d.ts +86 -0
  160. package/dist/nbt/index.js +250 -0
  161. package/dist/optimizer/commands.d.ts +36 -0
  162. package/dist/optimizer/commands.js +349 -0
  163. package/dist/optimizer/passes.d.ts +34 -0
  164. package/dist/optimizer/passes.js +227 -0
  165. package/dist/optimizer/structure.d.ts +8 -0
  166. package/dist/optimizer/structure.js +344 -0
  167. package/dist/pack.mcmeta +6 -0
  168. package/dist/parser/index.d.ts +76 -0
  169. package/dist/parser/index.js +1193 -0
  170. package/dist/repl.d.ts +16 -0
  171. package/dist/repl.js +165 -0
  172. package/dist/runtime/index.d.ts +101 -0
  173. package/dist/runtime/index.js +1288 -0
  174. package/dist/typechecker/index.d.ts +42 -0
  175. package/dist/typechecker/index.js +629 -0
  176. package/docs/COMPILATION_STATS.md +142 -0
  177. package/docs/IMPLEMENTATION_GUIDE.md +512 -0
  178. package/docs/LANGUAGE_REFERENCE.md +415 -0
  179. package/docs/MC_MAPPING.md +280 -0
  180. package/docs/STRUCTURE_TARGET.md +80 -0
  181. package/docs/mc-reference/commands.md +259 -0
  182. package/editors/vscode/.vscodeignore +10 -0
  183. package/editors/vscode/LICENSE +21 -0
  184. package/editors/vscode/README.md +78 -0
  185. package/editors/vscode/build.mjs +28 -0
  186. package/editors/vscode/icon.png +0 -0
  187. package/editors/vscode/mcfunction-language-configuration.json +28 -0
  188. package/editors/vscode/out/extension.js +7236 -0
  189. package/editors/vscode/package-lock.json +566 -0
  190. package/editors/vscode/package.json +137 -0
  191. package/editors/vscode/redscript-language-configuration.json +28 -0
  192. package/editors/vscode/snippets/redscript.json +114 -0
  193. package/editors/vscode/src/codeactions.ts +89 -0
  194. package/editors/vscode/src/completion.ts +130 -0
  195. package/editors/vscode/src/extension.ts +239 -0
  196. package/editors/vscode/src/hover.ts +1120 -0
  197. package/editors/vscode/src/symbols.ts +207 -0
  198. package/editors/vscode/syntaxes/mcfunction.tmLanguage.json +740 -0
  199. package/editors/vscode/syntaxes/redscript.tmLanguage.json +357 -0
  200. package/editors/vscode/tsconfig.json +13 -0
  201. package/jest.config.js +5 -0
  202. package/package.json +38 -0
  203. package/src/__tests__/cli.test.ts +130 -0
  204. package/src/__tests__/codegen.test.ts +128 -0
  205. package/src/__tests__/diagnostics.test.ts +195 -0
  206. package/src/__tests__/e2e.test.ts +1721 -0
  207. package/src/__tests__/fixtures/mc-commands-1.21.4.json +18734 -0
  208. package/src/__tests__/formatter.test.ts +46 -0
  209. package/src/__tests__/lexer.test.ts +356 -0
  210. package/src/__tests__/lowering.test.ts +962 -0
  211. package/src/__tests__/mc-integration.test.ts +409 -0
  212. package/src/__tests__/mc-syntax.test.ts +96 -0
  213. package/src/__tests__/nbt.test.ts +58 -0
  214. package/src/__tests__/optimizer-advanced.test.ts +144 -0
  215. package/src/__tests__/optimizer.test.ts +129 -0
  216. package/src/__tests__/parser.test.ts +800 -0
  217. package/src/__tests__/repl.test.ts +33 -0
  218. package/src/__tests__/runtime.test.ts +289 -0
  219. package/src/__tests__/structure-optimizer.test.ts +38 -0
  220. package/src/__tests__/typechecker.test.ts +395 -0
  221. package/src/ast/types.ts +248 -0
  222. package/src/cli.ts +445 -0
  223. package/src/codegen/cmdblock/index.ts +63 -0
  224. package/src/codegen/mcfunction/index.ts +471 -0
  225. package/src/codegen/structure/index.ts +305 -0
  226. package/src/compile.ts +188 -0
  227. package/src/diagnostics/index.ts +186 -0
  228. package/src/examples/README.md +77 -0
  229. package/src/examples/SHOWCASE_GAME.md +43 -0
  230. package/src/examples/arena.rs +44 -0
  231. package/src/examples/counter.rs +12 -0
  232. package/src/examples/pvp_arena.rs +131 -0
  233. package/src/examples/quiz.rs +90 -0
  234. package/src/examples/rpg.rs +13 -0
  235. package/src/examples/shop.rs +30 -0
  236. package/src/examples/showcase_game.rs +552 -0
  237. package/src/examples/stdlib_demo.rs +181 -0
  238. package/src/examples/turret.rs +27 -0
  239. package/src/examples/world_manager.rs +23 -0
  240. package/src/formatter/index.ts +22 -0
  241. package/src/index.ts +161 -0
  242. package/src/ir/builder.ts +114 -0
  243. package/src/ir/types.ts +119 -0
  244. package/src/lexer/index.ts +555 -0
  245. package/src/lowering/index.ts +2406 -0
  246. package/src/mc-test/client.ts +259 -0
  247. package/src/mc-test/runner.ts +140 -0
  248. package/src/mc-test/setup.ts +70 -0
  249. package/src/mc-validator/index.ts +367 -0
  250. package/src/nbt/index.ts +321 -0
  251. package/src/optimizer/commands.ts +416 -0
  252. package/src/optimizer/passes.ts +233 -0
  253. package/src/optimizer/structure.ts +441 -0
  254. package/src/parser/index.ts +1437 -0
  255. package/src/repl.ts +165 -0
  256. package/src/runtime/index.ts +1403 -0
  257. package/src/stdlib/README.md +156 -0
  258. package/src/stdlib/combat.rs +20 -0
  259. package/src/stdlib/cooldown.rs +45 -0
  260. package/src/stdlib/math.rs +49 -0
  261. package/src/stdlib/mobs.rs +99 -0
  262. package/src/stdlib/player.rs +29 -0
  263. package/src/stdlib/strings.rs +7 -0
  264. package/src/stdlib/timer.rs +51 -0
  265. package/src/templates/README.md +126 -0
  266. package/src/templates/combat.rs +96 -0
  267. package/src/templates/economy.rs +40 -0
  268. package/src/templates/mini-game-framework.rs +117 -0
  269. package/src/templates/quest.rs +78 -0
  270. package/src/test_programs/zombie_game.rs +25 -0
  271. package/src/typechecker/index.ts +737 -0
  272. package/tsconfig.json +16 -0
@@ -0,0 +1,2041 @@
1
+ "use strict";
2
+ /**
3
+ * RedScript Lowering
4
+ *
5
+ * Transforms AST into IR (Three-Address Code).
6
+ * Handles control flow, function extraction for foreach, and builtin calls.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.Lowering = void 0;
10
+ const builder_1 = require("../ir/builder");
11
+ const diagnostics_1 = require("../diagnostics");
12
+ // ---------------------------------------------------------------------------
13
+ // Builtin Functions
14
+ // ---------------------------------------------------------------------------
15
+ const BUILTINS = {
16
+ say: ([msg]) => `say ${msg}`,
17
+ tell: ([sel, msg]) => `tellraw ${sel} {"text":"${msg}"}`,
18
+ title: ([sel, msg]) => `title ${sel} title {"text":"${msg}"}`,
19
+ actionbar: ([sel, msg]) => `title ${sel} actionbar {"text":"${msg}"}`,
20
+ subtitle: ([sel, msg]) => `title ${sel} subtitle {"text":"${msg}"}`,
21
+ title_times: ([sel, fadeIn, stay, fadeOut]) => `title ${sel} times ${fadeIn} ${stay} ${fadeOut}`,
22
+ announce: ([msg]) => `tellraw @a {"text":"${msg}"}`,
23
+ give: ([sel, item, count]) => `give ${sel} ${item} ${count ?? '1'}`,
24
+ kill: ([sel]) => `kill ${sel ?? '@s'}`,
25
+ effect: ([sel, eff, dur, amp]) => `effect give ${sel} ${eff} ${dur ?? '30'} ${amp ?? '0'}`,
26
+ summon: ([type, x, y, z, nbt]) => {
27
+ const pos = [x ?? '~', y ?? '~', z ?? '~'].join(' ');
28
+ return nbt ? `summon ${type} ${pos} ${nbt}` : `summon ${type} ${pos}`;
29
+ },
30
+ particle: ([name, x, y, z]) => {
31
+ const pos = [x ?? '~', y ?? '~', z ?? '~'].join(' ');
32
+ return `particle ${name} ${pos}`;
33
+ },
34
+ playsound: ([sound, source, sel, x, y, z, volume, pitch, minVolume]) => ['playsound', sound, source, sel, x, y, z, volume, pitch, minVolume].filter(Boolean).join(' '),
35
+ tp: () => null, // Special handling
36
+ tp_to: () => null, // Special handling (deprecated alias)
37
+ clear: ([sel, item]) => `clear ${sel} ${item ?? ''}`.trim(),
38
+ weather: ([type]) => `weather ${type}`,
39
+ time_set: ([val]) => `time set ${val}`,
40
+ time_add: ([val]) => `time add ${val}`,
41
+ gamerule: ([rule, val]) => `gamerule ${rule} ${val}`,
42
+ tag_add: ([sel, tag]) => `tag ${sel} add ${tag}`,
43
+ tag_remove: ([sel, tag]) => `tag ${sel} remove ${tag}`,
44
+ kick: ([player, reason]) => `kick ${player} ${reason ?? ''}`.trim(),
45
+ setblock: ([x, y, z, block]) => `setblock ${x} ${y} ${z} ${block}`,
46
+ fill: ([x1, y1, z1, x2, y2, z2, block]) => `fill ${x1} ${y1} ${z1} ${x2} ${y2} ${z2} ${block}`,
47
+ clone: ([x1, y1, z1, x2, y2, z2, dx, dy, dz]) => `clone ${x1} ${y1} ${z1} ${x2} ${y2} ${z2} ${dx} ${dy} ${dz}`,
48
+ difficulty: ([level]) => `difficulty ${level}`,
49
+ xp_add: ([sel, amount, type]) => `xp add ${sel} ${amount} ${type ?? 'points'}`,
50
+ xp_set: ([sel, amount, type]) => `xp set ${sel} ${amount} ${type ?? 'points'}`,
51
+ random: () => null, // Special handling
52
+ random_native: () => null, // Special handling
53
+ random_sequence: () => null, // Special handling
54
+ scoreboard_get: () => null, // Special handling (returns value)
55
+ scoreboard_set: () => null, // Special handling
56
+ score: () => null, // Special handling (same as scoreboard_get)
57
+ scoreboard_display: () => null, // Special handling
58
+ scoreboard_hide: () => null, // Special handling
59
+ scoreboard_add_objective: () => null, // Special handling
60
+ scoreboard_remove_objective: () => null, // Special handling
61
+ bossbar_add: () => null, // Special handling
62
+ bossbar_set_value: () => null, // Special handling
63
+ bossbar_set_max: () => null, // Special handling
64
+ bossbar_set_color: () => null, // Special handling
65
+ bossbar_set_style: () => null, // Special handling
66
+ bossbar_set_visible: () => null, // Special handling
67
+ bossbar_set_players: () => null, // Special handling
68
+ bossbar_remove: () => null, // Special handling
69
+ bossbar_get_value: () => null, // Special handling
70
+ team_add: () => null, // Special handling
71
+ team_remove: () => null, // Special handling
72
+ team_join: () => null, // Special handling
73
+ team_leave: () => null, // Special handling
74
+ team_option: () => null, // Special handling
75
+ data_get: () => null, // Special handling (returns value from NBT)
76
+ };
77
+ function getSpan(node) {
78
+ return node?.span;
79
+ }
80
+ const NAMESPACED_ENTITY_TYPE_RE = /^[a-z0-9_.-]+:[a-z0-9_./-]+$/;
81
+ const BARE_ENTITY_TYPE_RE = /^[a-z0-9_./-]+$/;
82
+ function normalizeSelector(selector, warnings) {
83
+ return selector.replace(/type=([^,\]]+)/g, (match, entityType) => {
84
+ const trimmed = entityType.trim();
85
+ if (trimmed.includes(':')) {
86
+ if (!NAMESPACED_ENTITY_TYPE_RE.test(trimmed)) {
87
+ throw new diagnostics_1.DiagnosticError('LoweringError', `Invalid entity type format: "${trimmed}" (must be namespace:name)`, { line: 1, col: 1 });
88
+ }
89
+ return match;
90
+ }
91
+ if (!BARE_ENTITY_TYPE_RE.test(trimmed)) {
92
+ throw new diagnostics_1.DiagnosticError('LoweringError', `Invalid entity type format: "${trimmed}" (must be namespace:name or bare_name)`, { line: 1, col: 1 });
93
+ }
94
+ warnings.push({
95
+ message: `Unnamespaced entity type "${trimmed}", auto-qualifying to "minecraft:${trimmed}"`,
96
+ code: 'W_UNNAMESPACED_TYPE',
97
+ });
98
+ return `type=minecraft:${trimmed}`;
99
+ });
100
+ }
101
+ function emitCoord(component) {
102
+ switch (component.kind) {
103
+ case 'absolute':
104
+ return String(component.value);
105
+ case 'relative':
106
+ return component.offset === 0 ? '~' : `~${component.offset}`;
107
+ case 'local':
108
+ return component.offset === 0 ? '^' : `^${component.offset}`;
109
+ }
110
+ }
111
+ function emitBlockPos(pos) {
112
+ return `${emitCoord(pos.x)} ${emitCoord(pos.y)} ${emitCoord(pos.z)}`;
113
+ }
114
+ // ---------------------------------------------------------------------------
115
+ // Lowering Class
116
+ // ---------------------------------------------------------------------------
117
+ class Lowering {
118
+ constructor(namespace) {
119
+ this.functions = [];
120
+ this.globals = [];
121
+ this.fnDecls = new Map();
122
+ this.specializedFunctions = new Map();
123
+ this.currentFn = '';
124
+ this.foreachCounter = 0;
125
+ this.lambdaCounter = 0;
126
+ this.warnings = [];
127
+ this.varMap = new Map();
128
+ this.lambdaBindings = new Map();
129
+ this.currentCallbackBindings = new Map();
130
+ this.currentContext = {};
131
+ this.blockPosVars = new Map();
132
+ // Struct definitions: name → { fieldName: TypeNode }
133
+ this.structDefs = new Map();
134
+ this.enumDefs = new Map();
135
+ this.functionDefaults = new Map();
136
+ this.constValues = new Map();
137
+ this.stringValues = new Map();
138
+ // Variable types: varName → TypeNode
139
+ this.varTypes = new Map();
140
+ // Float variables (stored as fixed-point × 1000)
141
+ this.floatVars = new Set();
142
+ // World object counter for unique tags
143
+ this.worldObjCounter = 0;
144
+ this.namespace = namespace;
145
+ }
146
+ lower(program) {
147
+ this.namespace = program.namespace;
148
+ // Load struct definitions
149
+ for (const struct of program.structs ?? []) {
150
+ const fields = new Map();
151
+ for (const field of struct.fields) {
152
+ fields.set(field.name, field.type);
153
+ }
154
+ this.structDefs.set(struct.name, fields);
155
+ }
156
+ for (const enumDecl of program.enums ?? []) {
157
+ const variants = new Map();
158
+ for (const variant of enumDecl.variants) {
159
+ variants.set(variant.name, variant.value ?? 0);
160
+ }
161
+ this.enumDefs.set(enumDecl.name, variants);
162
+ }
163
+ for (const constDecl of program.consts ?? []) {
164
+ this.constValues.set(constDecl.name, constDecl.value);
165
+ this.varTypes.set(constDecl.name, this.normalizeType(constDecl.type));
166
+ }
167
+ for (const fn of program.declarations) {
168
+ this.fnDecls.set(fn.name, fn);
169
+ this.functionDefaults.set(fn.name, fn.params.map(param => param.default));
170
+ }
171
+ for (const fn of program.declarations) {
172
+ this.lowerFn(fn);
173
+ }
174
+ return (0, builder_1.buildModule)(this.namespace, this.functions, this.globals);
175
+ }
176
+ // -------------------------------------------------------------------------
177
+ // Function Lowering
178
+ // -------------------------------------------------------------------------
179
+ lowerFn(fn, options = {}) {
180
+ const loweredName = options.name ?? fn.name;
181
+ const callbackBindings = options.callbackBindings ?? new Map();
182
+ const runtimeParams = fn.params.filter(param => !callbackBindings.has(param.name));
183
+ this.currentFn = loweredName;
184
+ this.foreachCounter = 0;
185
+ this.varMap = new Map();
186
+ this.lambdaBindings = new Map();
187
+ this.currentCallbackBindings = new Map(callbackBindings);
188
+ this.currentContext = {};
189
+ this.blockPosVars = new Map();
190
+ this.stringValues = new Map();
191
+ this.builder = new LoweringBuilder();
192
+ // Map parameters
193
+ for (const param of runtimeParams) {
194
+ const paramName = param.name;
195
+ this.varMap.set(paramName, `$${paramName}`);
196
+ this.varTypes.set(paramName, this.normalizeType(param.type));
197
+ }
198
+ for (const param of fn.params) {
199
+ if (callbackBindings.has(param.name)) {
200
+ this.varTypes.set(param.name, this.normalizeType(param.type));
201
+ }
202
+ }
203
+ // Start entry block
204
+ this.builder.startBlock('entry');
205
+ // Copy params from $p0, $p1, ... to named variables
206
+ for (let i = 0; i < runtimeParams.length; i++) {
207
+ const paramName = runtimeParams[i].name;
208
+ const varName = `$${paramName}`;
209
+ this.builder.emitAssign(varName, { kind: 'var', name: `$p${i}` });
210
+ }
211
+ // Lower body
212
+ this.lowerBlock(fn.body);
213
+ // If no explicit return, add void return
214
+ if (!this.builder.isBlockSealed()) {
215
+ this.builder.emitReturn();
216
+ }
217
+ // Build function
218
+ const isTickLoop = fn.decorators.some(d => d.name === 'tick');
219
+ const tickRate = this.getTickRate(fn.decorators);
220
+ // Check for trigger handler
221
+ const triggerDec = fn.decorators.find(d => d.name === 'on_trigger');
222
+ const isTriggerHandler = !!triggerDec;
223
+ const triggerName = triggerDec?.args?.trigger;
224
+ const irFn = this.builder.build(loweredName, runtimeParams.map(p => `$${p.name}`), isTickLoop);
225
+ // Add trigger metadata if applicable
226
+ if (isTriggerHandler && triggerName) {
227
+ irFn.isTriggerHandler = true;
228
+ irFn.triggerName = triggerName;
229
+ }
230
+ const eventDec = fn.decorators.find(d => d.name === 'on_advancement' ||
231
+ d.name === 'on_craft' ||
232
+ d.name === 'on_death' ||
233
+ d.name === 'on_login' ||
234
+ d.name === 'on_join_team');
235
+ if (eventDec) {
236
+ switch (eventDec.name) {
237
+ case 'on_advancement':
238
+ irFn.eventTrigger = { kind: 'advancement', value: eventDec.args?.advancement };
239
+ break;
240
+ case 'on_craft':
241
+ irFn.eventTrigger = { kind: 'craft', value: eventDec.args?.item };
242
+ break;
243
+ case 'on_death':
244
+ irFn.eventTrigger = { kind: 'death' };
245
+ break;
246
+ case 'on_login':
247
+ irFn.eventTrigger = { kind: 'login' };
248
+ break;
249
+ case 'on_join_team':
250
+ irFn.eventTrigger = { kind: 'join_team', value: eventDec.args?.team };
251
+ break;
252
+ }
253
+ }
254
+ // Handle tick rate counter if needed
255
+ if (tickRate && tickRate > 1) {
256
+ this.wrapWithTickRate(irFn, tickRate);
257
+ }
258
+ this.functions.push(irFn);
259
+ }
260
+ getTickRate(decorators) {
261
+ const tickDec = decorators.find(d => d.name === 'tick');
262
+ return tickDec?.args?.rate;
263
+ }
264
+ wrapWithTickRate(fn, rate) {
265
+ // Add tick counter logic to entry block
266
+ const counterVar = `$__tick_${fn.name}`;
267
+ this.globals.push(counterVar);
268
+ // Prepend counter logic to entry block
269
+ const entry = fn.blocks[0];
270
+ const originalInstrs = [...entry.instrs];
271
+ const originalTerm = entry.term;
272
+ entry.instrs = [
273
+ { op: 'raw', cmd: `scoreboard players add ${counterVar} rs 1` },
274
+ ];
275
+ // Create conditional jump
276
+ const bodyLabel = 'tick_body';
277
+ const skipLabel = 'tick_skip';
278
+ entry.term = {
279
+ op: 'jump_if',
280
+ cond: `${counterVar}_check`,
281
+ then: bodyLabel,
282
+ else_: skipLabel,
283
+ };
284
+ // Add check instruction
285
+ entry.instrs.push({
286
+ op: 'raw',
287
+ cmd: `execute store success score ${counterVar}_check rs if score ${counterVar} rs matches ${rate}..`,
288
+ });
289
+ // Body block (original logic + counter reset)
290
+ fn.blocks.push({
291
+ label: bodyLabel,
292
+ instrs: [
293
+ { op: 'raw', cmd: `scoreboard players set ${counterVar} rs 0` },
294
+ ...originalInstrs,
295
+ ],
296
+ term: originalTerm,
297
+ });
298
+ // Skip block (just return)
299
+ fn.blocks.push({
300
+ label: skipLabel,
301
+ instrs: [],
302
+ term: { op: 'return' },
303
+ });
304
+ }
305
+ // -------------------------------------------------------------------------
306
+ // Statement Lowering
307
+ // -------------------------------------------------------------------------
308
+ lowerBlock(stmts) {
309
+ for (const stmt of stmts) {
310
+ this.lowerStmt(stmt);
311
+ }
312
+ }
313
+ lowerStmt(stmt) {
314
+ switch (stmt.kind) {
315
+ case 'let':
316
+ this.lowerLetStmt(stmt);
317
+ break;
318
+ case 'expr':
319
+ this.lowerExpr(stmt.expr);
320
+ break;
321
+ case 'return':
322
+ this.lowerReturnStmt(stmt);
323
+ break;
324
+ case 'if':
325
+ this.lowerIfStmt(stmt);
326
+ break;
327
+ case 'while':
328
+ this.lowerWhileStmt(stmt);
329
+ break;
330
+ case 'for':
331
+ this.lowerForStmt(stmt);
332
+ break;
333
+ case 'foreach':
334
+ this.lowerForeachStmt(stmt);
335
+ break;
336
+ case 'for_range':
337
+ this.lowerForRangeStmt(stmt);
338
+ break;
339
+ case 'match':
340
+ this.lowerMatchStmt(stmt);
341
+ break;
342
+ case 'as_block':
343
+ this.lowerAsBlockStmt(stmt);
344
+ break;
345
+ case 'at_block':
346
+ this.lowerAtBlockStmt(stmt);
347
+ break;
348
+ case 'as_at':
349
+ this.lowerAsAtStmt(stmt);
350
+ break;
351
+ case 'execute':
352
+ this.lowerExecuteStmt(stmt);
353
+ break;
354
+ case 'raw':
355
+ this.builder.emitRaw(stmt.cmd);
356
+ break;
357
+ }
358
+ }
359
+ lowerLetStmt(stmt) {
360
+ const varName = `$${stmt.name}`;
361
+ this.varMap.set(stmt.name, varName);
362
+ // Track variable type
363
+ const declaredType = stmt.type ? this.normalizeType(stmt.type) : this.inferExprType(stmt.init);
364
+ if (declaredType) {
365
+ this.varTypes.set(stmt.name, declaredType);
366
+ // Track float variables for fixed-point arithmetic
367
+ if (declaredType.kind === 'named' && declaredType.name === 'float') {
368
+ this.floatVars.add(stmt.name);
369
+ }
370
+ }
371
+ if (stmt.init.kind === 'lambda') {
372
+ const lambdaName = this.lowerLambdaExpr(stmt.init);
373
+ this.lambdaBindings.set(stmt.name, lambdaName);
374
+ return;
375
+ }
376
+ // Handle struct literal initialization
377
+ if (stmt.init.kind === 'struct_lit' && stmt.type?.kind === 'struct') {
378
+ const structName = stmt.type.name.toLowerCase();
379
+ for (const field of stmt.init.fields) {
380
+ const path = `rs:heap ${structName}_${stmt.name}.${field.name}`;
381
+ const fieldValue = this.lowerExpr(field.value);
382
+ if (fieldValue.kind === 'const') {
383
+ this.builder.emitRaw(`data modify storage ${path} set value ${fieldValue.value}`);
384
+ }
385
+ else if (fieldValue.kind === 'var') {
386
+ // Copy from scoreboard to NBT
387
+ this.builder.emitRaw(`execute store result storage ${path} int 1 run scoreboard players get ${fieldValue.name} rs`);
388
+ }
389
+ }
390
+ return;
391
+ }
392
+ // Handle array literal initialization
393
+ if (stmt.init.kind === 'array_lit') {
394
+ // Initialize empty NBT array
395
+ this.builder.emitRaw(`data modify storage rs:heap ${stmt.name} set value []`);
396
+ // Add each element
397
+ for (const elem of stmt.init.elements) {
398
+ const elemValue = this.lowerExpr(elem);
399
+ if (elemValue.kind === 'const') {
400
+ this.builder.emitRaw(`data modify storage rs:heap ${stmt.name} append value ${elemValue.value}`);
401
+ }
402
+ else if (elemValue.kind === 'var') {
403
+ this.builder.emitRaw(`data modify storage rs:heap ${stmt.name} append value 0`);
404
+ this.builder.emitRaw(`execute store result storage rs:heap ${stmt.name}[-1] int 1 run scoreboard players get ${elemValue.name} rs`);
405
+ }
406
+ }
407
+ return;
408
+ }
409
+ // Handle spawn_object returning entity handle
410
+ if (stmt.init.kind === 'call' && stmt.init.fn === 'spawn_object') {
411
+ const value = this.lowerExpr(stmt.init);
412
+ // value is the selector like @e[tag=__rs_obj_0,limit=1]
413
+ if (value.kind === 'var' && value.name.startsWith('@e[tag=__rs_obj_')) {
414
+ this.varMap.set(stmt.name, value.name);
415
+ // Mark as entity type for later member access
416
+ this.varTypes.set(stmt.name, { kind: 'named', name: 'void' }); // Marker
417
+ }
418
+ return;
419
+ }
420
+ const blockPosValue = this.resolveBlockPosExpr(stmt.init);
421
+ if (blockPosValue) {
422
+ this.blockPosVars.set(stmt.name, blockPosValue);
423
+ return;
424
+ }
425
+ const stmtType = stmt.type ? this.normalizeType(stmt.type) : this.inferExprType(stmt.init);
426
+ if (stmtType?.kind === 'named' && stmtType.name === 'string' && this.storeStringValue(stmt.name, stmt.init)) {
427
+ return;
428
+ }
429
+ const value = this.lowerExpr(stmt.init);
430
+ this.builder.emitAssign(varName, value);
431
+ }
432
+ lowerReturnStmt(stmt) {
433
+ if (stmt.value) {
434
+ const value = this.lowerExpr(stmt.value);
435
+ this.builder.emitReturn(value);
436
+ }
437
+ else {
438
+ this.builder.emitReturn();
439
+ }
440
+ }
441
+ lowerIfStmt(stmt) {
442
+ const condVar = this.lowerExpr(stmt.cond);
443
+ const condName = this.operandToVar(condVar);
444
+ const thenLabel = this.builder.freshLabel('then');
445
+ const elseLabel = this.builder.freshLabel('else');
446
+ const mergeLabel = this.builder.freshLabel('merge');
447
+ this.builder.emitJumpIf(condName, thenLabel, stmt.else_ ? elseLabel : mergeLabel);
448
+ // Then block
449
+ this.builder.startBlock(thenLabel);
450
+ this.lowerBlock(stmt.then);
451
+ if (!this.builder.isBlockSealed()) {
452
+ this.builder.emitJump(mergeLabel);
453
+ }
454
+ // Else block (if present)
455
+ if (stmt.else_) {
456
+ this.builder.startBlock(elseLabel);
457
+ this.lowerBlock(stmt.else_);
458
+ if (!this.builder.isBlockSealed()) {
459
+ this.builder.emitJump(mergeLabel);
460
+ }
461
+ }
462
+ // Merge block
463
+ this.builder.startBlock(mergeLabel);
464
+ }
465
+ lowerWhileStmt(stmt) {
466
+ const checkLabel = this.builder.freshLabel('loop_check');
467
+ const bodyLabel = this.builder.freshLabel('loop_body');
468
+ const exitLabel = this.builder.freshLabel('loop_exit');
469
+ this.builder.emitJump(checkLabel);
470
+ // Check block
471
+ this.builder.startBlock(checkLabel);
472
+ const condVar = this.lowerExpr(stmt.cond);
473
+ const condName = this.operandToVar(condVar);
474
+ this.builder.emitJumpIf(condName, bodyLabel, exitLabel);
475
+ // Body block
476
+ this.builder.startBlock(bodyLabel);
477
+ this.lowerBlock(stmt.body);
478
+ if (!this.builder.isBlockSealed()) {
479
+ this.builder.emitJump(checkLabel);
480
+ }
481
+ // Exit block
482
+ this.builder.startBlock(exitLabel);
483
+ }
484
+ lowerForStmt(stmt) {
485
+ // For loop is lowered to: init; while(cond) { body; step; }
486
+ // Init statement (if present)
487
+ if (stmt.init) {
488
+ this.lowerStmt(stmt.init);
489
+ }
490
+ const checkLabel = this.builder.freshLabel('for_check');
491
+ const bodyLabel = this.builder.freshLabel('for_body');
492
+ const exitLabel = this.builder.freshLabel('for_exit');
493
+ this.builder.emitJump(checkLabel);
494
+ // Check block
495
+ this.builder.startBlock(checkLabel);
496
+ const condVar = this.lowerExpr(stmt.cond);
497
+ const condName = this.operandToVar(condVar);
498
+ this.builder.emitJumpIf(condName, bodyLabel, exitLabel);
499
+ // Body block
500
+ this.builder.startBlock(bodyLabel);
501
+ this.lowerBlock(stmt.body);
502
+ // Step expression
503
+ this.lowerExpr(stmt.step);
504
+ if (!this.builder.isBlockSealed()) {
505
+ this.builder.emitJump(checkLabel);
506
+ }
507
+ // Exit block
508
+ this.builder.startBlock(exitLabel);
509
+ }
510
+ lowerForRangeStmt(stmt) {
511
+ const loopVar = `$${stmt.varName}`;
512
+ const subFnName = `${this.currentFn}/__for_${this.foreachCounter++}`;
513
+ // Initialize loop variable
514
+ this.varMap.set(stmt.varName, loopVar);
515
+ const startVal = this.lowerExpr(stmt.start);
516
+ if (startVal.kind === 'const') {
517
+ this.builder.emitRaw(`scoreboard players set ${loopVar} rs ${startVal.value}`);
518
+ }
519
+ else if (startVal.kind === 'var') {
520
+ this.builder.emitRaw(`scoreboard players operation ${loopVar} rs = ${startVal.name} rs`);
521
+ }
522
+ // Call loop function
523
+ this.builder.emitRaw(`function ${this.namespace}:${subFnName}`);
524
+ // Generate loop sub-function
525
+ const savedBuilder = this.builder;
526
+ const savedVarMap = new Map(this.varMap);
527
+ const savedContext = this.currentContext;
528
+ const savedBlockPosVars = new Map(this.blockPosVars);
529
+ this.builder = new LoweringBuilder();
530
+ this.varMap = new Map(savedVarMap);
531
+ this.currentContext = savedContext;
532
+ this.blockPosVars = new Map(savedBlockPosVars);
533
+ this.builder.startBlock('entry');
534
+ // Body
535
+ this.lowerBlock(stmt.body);
536
+ // Increment
537
+ this.builder.emitRaw(`scoreboard players add ${loopVar} rs 1`);
538
+ // Loop condition: execute if score matches ..<end-1> run function
539
+ const endVal = this.lowerExpr(stmt.end);
540
+ const endNum = endVal.kind === 'const' ? endVal.value - 1 : '?';
541
+ this.builder.emitRaw(`execute if score ${loopVar} rs matches ..${endNum} run function ${this.namespace}:${subFnName}`);
542
+ if (!this.builder.isBlockSealed()) {
543
+ this.builder.emitReturn();
544
+ }
545
+ const subFn = this.builder.build(subFnName, [], false);
546
+ this.functions.push(subFn);
547
+ // Restore
548
+ this.builder = savedBuilder;
549
+ this.varMap = savedVarMap;
550
+ this.currentContext = savedContext;
551
+ this.blockPosVars = savedBlockPosVars;
552
+ }
553
+ lowerForeachStmt(stmt) {
554
+ if (stmt.iterable.kind !== 'selector') {
555
+ this.lowerArrayForeachStmt(stmt);
556
+ return;
557
+ }
558
+ // Extract body into a separate function
559
+ const subFnName = `${this.currentFn}/foreach_${this.foreachCounter++}`;
560
+ const selector = this.exprToString(stmt.iterable);
561
+ // Emit execute as ... run function ...
562
+ this.builder.emitRaw(`execute as ${selector} run function ${this.namespace}:${subFnName}`);
563
+ // Create the sub-function
564
+ const savedBuilder = this.builder;
565
+ const savedVarMap = new Map(this.varMap);
566
+ const savedContext = this.currentContext;
567
+ const savedBlockPosVars = new Map(this.blockPosVars);
568
+ this.builder = new LoweringBuilder();
569
+ this.varMap = new Map(savedVarMap);
570
+ this.currentContext = { binding: stmt.binding };
571
+ this.blockPosVars = new Map(savedBlockPosVars);
572
+ // In foreach body, the binding maps to @s
573
+ this.varMap.set(stmt.binding, '@s');
574
+ this.builder.startBlock('entry');
575
+ this.lowerBlock(stmt.body);
576
+ if (!this.builder.isBlockSealed()) {
577
+ this.builder.emitReturn();
578
+ }
579
+ const subFn = this.builder.build(subFnName, [], false);
580
+ this.functions.push(subFn);
581
+ // Restore
582
+ this.builder = savedBuilder;
583
+ this.varMap = savedVarMap;
584
+ this.currentContext = savedContext;
585
+ this.blockPosVars = savedBlockPosVars;
586
+ }
587
+ lowerMatchStmt(stmt) {
588
+ const subject = this.operandToVar(this.lowerExpr(stmt.expr));
589
+ const matchedVar = this.builder.freshTemp();
590
+ this.builder.emitAssign(matchedVar, { kind: 'const', value: 0 });
591
+ let defaultArm = null;
592
+ for (const arm of stmt.arms) {
593
+ if (arm.pattern === null) {
594
+ defaultArm = arm;
595
+ continue;
596
+ }
597
+ const patternValue = this.lowerExpr(arm.pattern);
598
+ if (patternValue.kind !== 'const') {
599
+ throw new Error('Match patterns must lower to compile-time constants');
600
+ }
601
+ const subFnName = `${this.currentFn}/match_${this.foreachCounter++}`;
602
+ this.builder.emitRaw(`execute if score ${matchedVar} rs matches ..0 if score ${subject} rs matches ${patternValue.value} run function ${this.namespace}:${subFnName}`);
603
+ this.emitMatchArmSubFunction(subFnName, matchedVar, arm.body, true);
604
+ }
605
+ if (defaultArm) {
606
+ const subFnName = `${this.currentFn}/match_${this.foreachCounter++}`;
607
+ this.builder.emitRaw(`execute if score ${matchedVar} rs matches ..0 run function ${this.namespace}:${subFnName}`);
608
+ this.emitMatchArmSubFunction(subFnName, matchedVar, defaultArm.body, false);
609
+ }
610
+ }
611
+ emitMatchArmSubFunction(name, matchedVar, body, setMatched) {
612
+ const savedBuilder = this.builder;
613
+ const savedVarMap = new Map(this.varMap);
614
+ const savedContext = this.currentContext;
615
+ const savedBlockPosVars = new Map(this.blockPosVars);
616
+ this.builder = new LoweringBuilder();
617
+ this.varMap = new Map(savedVarMap);
618
+ this.currentContext = savedContext;
619
+ this.blockPosVars = new Map(savedBlockPosVars);
620
+ this.builder.startBlock('entry');
621
+ if (setMatched) {
622
+ this.builder.emitRaw(`scoreboard players set ${matchedVar} rs 1`);
623
+ }
624
+ this.lowerBlock(body);
625
+ if (!this.builder.isBlockSealed()) {
626
+ this.builder.emitReturn();
627
+ }
628
+ this.functions.push(this.builder.build(name, [], false));
629
+ this.builder = savedBuilder;
630
+ this.varMap = savedVarMap;
631
+ this.currentContext = savedContext;
632
+ this.blockPosVars = savedBlockPosVars;
633
+ }
634
+ lowerArrayForeachStmt(stmt) {
635
+ const arrayName = this.getArrayStorageName(stmt.iterable);
636
+ if (!arrayName) {
637
+ this.builder.emitRaw('# Unsupported foreach iterable');
638
+ return;
639
+ }
640
+ const arrayType = this.inferExprType(stmt.iterable);
641
+ const bindingVar = `$${stmt.binding}`;
642
+ const indexVar = this.builder.freshTemp();
643
+ const lengthVar = this.builder.freshTemp();
644
+ const condVar = this.builder.freshTemp();
645
+ const oneVar = this.builder.freshTemp();
646
+ const savedBinding = this.varMap.get(stmt.binding);
647
+ const savedType = this.varTypes.get(stmt.binding);
648
+ this.varMap.set(stmt.binding, bindingVar);
649
+ if (arrayType?.kind === 'array') {
650
+ this.varTypes.set(stmt.binding, arrayType.elem);
651
+ }
652
+ this.builder.emitAssign(indexVar, { kind: 'const', value: 0 });
653
+ this.builder.emitAssign(oneVar, { kind: 'const', value: 1 });
654
+ this.builder.emitRaw(`execute store result score ${lengthVar} rs run data get storage rs:heap ${arrayName}`);
655
+ const checkLabel = this.builder.freshLabel('foreach_array_check');
656
+ const bodyLabel = this.builder.freshLabel('foreach_array_body');
657
+ const exitLabel = this.builder.freshLabel('foreach_array_exit');
658
+ this.builder.emitJump(checkLabel);
659
+ this.builder.startBlock(checkLabel);
660
+ this.builder.emitCmp(condVar, { kind: 'var', name: indexVar }, '<', { kind: 'var', name: lengthVar });
661
+ this.builder.emitJumpIf(condVar, bodyLabel, exitLabel);
662
+ this.builder.startBlock(bodyLabel);
663
+ const element = this.readArrayElement(arrayName, { kind: 'var', name: indexVar });
664
+ this.builder.emitAssign(bindingVar, element);
665
+ this.lowerBlock(stmt.body);
666
+ if (!this.builder.isBlockSealed()) {
667
+ this.builder.emitRaw(`scoreboard players operation ${indexVar} rs += ${oneVar} rs`);
668
+ this.builder.emitJump(checkLabel);
669
+ }
670
+ this.builder.startBlock(exitLabel);
671
+ if (savedBinding) {
672
+ this.varMap.set(stmt.binding, savedBinding);
673
+ }
674
+ else {
675
+ this.varMap.delete(stmt.binding);
676
+ }
677
+ if (savedType) {
678
+ this.varTypes.set(stmt.binding, savedType);
679
+ }
680
+ else {
681
+ this.varTypes.delete(stmt.binding);
682
+ }
683
+ }
684
+ lowerAsBlockStmt(stmt) {
685
+ const selector = this.selectorToString(stmt.selector);
686
+ const subFnName = `${this.currentFn}/as_${this.foreachCounter++}`;
687
+ this.builder.emitRaw(`execute as ${selector} run function ${this.namespace}:${subFnName}`);
688
+ // Create sub-function
689
+ const savedBuilder = this.builder;
690
+ const savedVarMap = new Map(this.varMap);
691
+ const savedBlockPosVars = new Map(this.blockPosVars);
692
+ this.builder = new LoweringBuilder();
693
+ this.varMap = new Map(savedVarMap);
694
+ this.blockPosVars = new Map(savedBlockPosVars);
695
+ this.builder.startBlock('entry');
696
+ this.lowerBlock(stmt.body);
697
+ if (!this.builder.isBlockSealed()) {
698
+ this.builder.emitReturn();
699
+ }
700
+ const subFn = this.builder.build(subFnName, [], false);
701
+ this.functions.push(subFn);
702
+ this.builder = savedBuilder;
703
+ this.varMap = savedVarMap;
704
+ this.blockPosVars = savedBlockPosVars;
705
+ }
706
+ lowerAtBlockStmt(stmt) {
707
+ const selector = this.selectorToString(stmt.selector);
708
+ const subFnName = `${this.currentFn}/at_${this.foreachCounter++}`;
709
+ this.builder.emitRaw(`execute at ${selector} run function ${this.namespace}:${subFnName}`);
710
+ // Create sub-function
711
+ const savedBuilder = this.builder;
712
+ const savedVarMap = new Map(this.varMap);
713
+ const savedBlockPosVars = new Map(this.blockPosVars);
714
+ this.builder = new LoweringBuilder();
715
+ this.varMap = new Map(savedVarMap);
716
+ this.blockPosVars = new Map(savedBlockPosVars);
717
+ this.builder.startBlock('entry');
718
+ this.lowerBlock(stmt.body);
719
+ if (!this.builder.isBlockSealed()) {
720
+ this.builder.emitReturn();
721
+ }
722
+ const subFn = this.builder.build(subFnName, [], false);
723
+ this.functions.push(subFn);
724
+ this.builder = savedBuilder;
725
+ this.varMap = savedVarMap;
726
+ this.blockPosVars = savedBlockPosVars;
727
+ }
728
+ lowerAsAtStmt(stmt) {
729
+ const asSel = this.selectorToString(stmt.as_sel);
730
+ const atSel = this.selectorToString(stmt.at_sel);
731
+ const subFnName = `${this.currentFn}/as_at_${this.foreachCounter++}`;
732
+ this.builder.emitRaw(`execute as ${asSel} at ${atSel} run function ${this.namespace}:${subFnName}`);
733
+ // Create sub-function
734
+ const savedBuilder = this.builder;
735
+ const savedVarMap = new Map(this.varMap);
736
+ const savedBlockPosVars = new Map(this.blockPosVars);
737
+ this.builder = new LoweringBuilder();
738
+ this.varMap = new Map(savedVarMap);
739
+ this.blockPosVars = new Map(savedBlockPosVars);
740
+ this.builder.startBlock('entry');
741
+ this.lowerBlock(stmt.body);
742
+ if (!this.builder.isBlockSealed()) {
743
+ this.builder.emitReturn();
744
+ }
745
+ const subFn = this.builder.build(subFnName, [], false);
746
+ this.functions.push(subFn);
747
+ this.builder = savedBuilder;
748
+ this.varMap = savedVarMap;
749
+ this.blockPosVars = savedBlockPosVars;
750
+ }
751
+ lowerExecuteStmt(stmt) {
752
+ // Build the execute prefix from subcommands
753
+ const parts = ['execute'];
754
+ for (const sub of stmt.subcommands) {
755
+ switch (sub.kind) {
756
+ case 'as':
757
+ parts.push(`as ${this.selectorToString(sub.selector)}`);
758
+ break;
759
+ case 'at':
760
+ parts.push(`at ${this.selectorToString(sub.selector)}`);
761
+ break;
762
+ case 'if_entity':
763
+ parts.push(`if entity ${this.selectorToString(sub.selector)}`);
764
+ break;
765
+ case 'unless_entity':
766
+ parts.push(`unless entity ${this.selectorToString(sub.selector)}`);
767
+ break;
768
+ case 'in':
769
+ parts.push(`in ${sub.dimension}`);
770
+ break;
771
+ }
772
+ }
773
+ const subFnName = `${this.currentFn}/exec_${this.foreachCounter++}`;
774
+ this.builder.emitRaw(`${parts.join(' ')} run function ${this.namespace}:${subFnName}`);
775
+ // Create sub-function for the body
776
+ const savedBuilder = this.builder;
777
+ const savedVarMap = new Map(this.varMap);
778
+ const savedBlockPosVars = new Map(this.blockPosVars);
779
+ this.builder = new LoweringBuilder();
780
+ this.varMap = new Map(savedVarMap);
781
+ this.blockPosVars = new Map(savedBlockPosVars);
782
+ this.builder.startBlock('entry');
783
+ this.lowerBlock(stmt.body);
784
+ if (!this.builder.isBlockSealed()) {
785
+ this.builder.emitReturn();
786
+ }
787
+ const subFn = this.builder.build(subFnName, [], false);
788
+ this.functions.push(subFn);
789
+ this.builder = savedBuilder;
790
+ this.varMap = savedVarMap;
791
+ this.blockPosVars = savedBlockPosVars;
792
+ }
793
+ // -------------------------------------------------------------------------
794
+ // Expression Lowering
795
+ // -------------------------------------------------------------------------
796
+ lowerExpr(expr) {
797
+ switch (expr.kind) {
798
+ case 'int_lit':
799
+ return { kind: 'const', value: expr.value };
800
+ case 'float_lit':
801
+ // Float stored as fixed-point × 1000
802
+ return { kind: 'const', value: Math.round(expr.value * 1000) };
803
+ case 'byte_lit':
804
+ return { kind: 'const', value: expr.value };
805
+ case 'short_lit':
806
+ return { kind: 'const', value: expr.value };
807
+ case 'long_lit':
808
+ return { kind: 'const', value: expr.value };
809
+ case 'double_lit':
810
+ return { kind: 'const', value: Math.round(expr.value * 1000) };
811
+ case 'bool_lit':
812
+ return { kind: 'const', value: expr.value ? 1 : 0 };
813
+ case 'str_lit':
814
+ // Strings are handled inline in builtins
815
+ return { kind: 'const', value: 0 }; // Placeholder
816
+ case 'mc_name':
817
+ // MC names (#health, #red) treated as string constants
818
+ return { kind: 'const', value: 0 }; // Handled inline in exprToString
819
+ case 'str_interp':
820
+ // Interpolated strings are handled inline in message builtins.
821
+ return { kind: 'const', value: 0 };
822
+ case 'range_lit':
823
+ // Ranges are handled in context (selectors, etc.)
824
+ return { kind: 'const', value: 0 };
825
+ case 'blockpos':
826
+ return { kind: 'const', value: 0 };
827
+ case 'ident': {
828
+ const constValue = this.constValues.get(expr.name);
829
+ if (constValue) {
830
+ return this.lowerConstLiteral(constValue);
831
+ }
832
+ const mapped = this.varMap.get(expr.name);
833
+ if (mapped) {
834
+ // Check if it's a selector reference (like @s)
835
+ if (mapped.startsWith('@')) {
836
+ return { kind: 'var', name: mapped };
837
+ }
838
+ return { kind: 'var', name: mapped };
839
+ }
840
+ return { kind: 'var', name: `$${expr.name}` };
841
+ }
842
+ case 'member':
843
+ if (expr.obj.kind === 'ident' && this.enumDefs.has(expr.obj.name)) {
844
+ const variants = this.enumDefs.get(expr.obj.name);
845
+ const value = variants.get(expr.field);
846
+ if (value === undefined) {
847
+ throw new Error(`Unknown enum variant ${expr.obj.name}.${expr.field}`);
848
+ }
849
+ return { kind: 'const', value };
850
+ }
851
+ return this.lowerMemberExpr(expr);
852
+ case 'selector':
853
+ // Selectors are handled inline in builtins
854
+ return { kind: 'var', name: this.selectorToString(expr.sel) };
855
+ case 'binary':
856
+ return this.lowerBinaryExpr(expr);
857
+ case 'unary':
858
+ return this.lowerUnaryExpr(expr);
859
+ case 'assign':
860
+ return this.lowerAssignExpr(expr);
861
+ case 'call':
862
+ return this.lowerCallExpr(expr);
863
+ case 'invoke':
864
+ return this.lowerInvokeExpr(expr);
865
+ case 'member_assign':
866
+ return this.lowerMemberAssign(expr);
867
+ case 'index':
868
+ return this.lowerIndexExpr(expr);
869
+ case 'struct_lit':
870
+ // Struct literals should be handled in let statement
871
+ return { kind: 'const', value: 0 };
872
+ case 'array_lit':
873
+ // Array literals should be handled in let statement
874
+ return { kind: 'const', value: 0 };
875
+ case 'lambda':
876
+ throw new Error('Lambda expressions must be used in a function context');
877
+ }
878
+ throw new Error(`Unhandled expression kind: ${expr.kind}`);
879
+ }
880
+ lowerMemberExpr(expr) {
881
+ // Check if this is a struct field access
882
+ if (expr.obj.kind === 'ident') {
883
+ const varType = this.varTypes.get(expr.obj.name);
884
+ // Check for world object handle (entity selector)
885
+ const mapped = this.varMap.get(expr.obj.name);
886
+ if (mapped && mapped.startsWith('@e[tag=__rs_obj_')) {
887
+ // World object field access → scoreboard get
888
+ const dst = this.builder.freshTemp();
889
+ this.builder.emitRaw(`scoreboard players operation ${dst} rs = ${mapped} rs`);
890
+ return { kind: 'var', name: dst };
891
+ }
892
+ if (varType?.kind === 'struct') {
893
+ const structName = varType.name.toLowerCase();
894
+ const path = `rs:heap ${structName}_${expr.obj.name}.${expr.field}`;
895
+ const dst = this.builder.freshTemp();
896
+ // Read from NBT storage into scoreboard
897
+ this.builder.emitRaw(`execute store result score ${dst} rs run data get storage ${path}`);
898
+ return { kind: 'var', name: dst };
899
+ }
900
+ // Array length property
901
+ if (varType?.kind === 'array' && expr.field === 'len') {
902
+ const dst = this.builder.freshTemp();
903
+ this.builder.emitRaw(`execute store result score ${dst} rs run data get storage rs:heap ${expr.obj.name}`);
904
+ return { kind: 'var', name: dst };
905
+ }
906
+ }
907
+ // Default behavior: simple member access
908
+ return { kind: 'var', name: `$${expr.obj.name}_${expr.field}` };
909
+ }
910
+ lowerMemberAssign(expr) {
911
+ if (expr.obj.kind === 'ident') {
912
+ const varType = this.varTypes.get(expr.obj.name);
913
+ // Check for world object handle
914
+ const mapped = this.varMap.get(expr.obj.name);
915
+ if (mapped && mapped.startsWith('@e[tag=__rs_obj_')) {
916
+ const value = this.lowerExpr(expr.value);
917
+ if (expr.op === '=') {
918
+ if (value.kind === 'const') {
919
+ this.builder.emitRaw(`scoreboard players set ${mapped} rs ${value.value}`);
920
+ }
921
+ else if (value.kind === 'var') {
922
+ this.builder.emitRaw(`scoreboard players operation ${mapped} rs = ${value.name} rs`);
923
+ }
924
+ }
925
+ else {
926
+ // Compound assignment
927
+ const binOp = expr.op.slice(0, -1);
928
+ const opMap = { '+': '+=', '-': '-=', '*': '*=', '/': '/=', '%': '%=' };
929
+ if (value.kind === 'const') {
930
+ const constTemp = this.builder.freshTemp();
931
+ this.builder.emitAssign(constTemp, value);
932
+ this.builder.emitRaw(`scoreboard players operation ${mapped} rs ${opMap[binOp]} ${constTemp} rs`);
933
+ }
934
+ else if (value.kind === 'var') {
935
+ this.builder.emitRaw(`scoreboard players operation ${mapped} rs ${opMap[binOp]} ${value.name} rs`);
936
+ }
937
+ }
938
+ return { kind: 'const', value: 0 };
939
+ }
940
+ if (varType?.kind === 'struct') {
941
+ const structName = varType.name.toLowerCase();
942
+ const path = `rs:heap ${structName}_${expr.obj.name}.${expr.field}`;
943
+ const value = this.lowerExpr(expr.value);
944
+ if (expr.op === '=') {
945
+ if (value.kind === 'const') {
946
+ this.builder.emitRaw(`data modify storage ${path} set value ${value.value}`);
947
+ }
948
+ else if (value.kind === 'var') {
949
+ this.builder.emitRaw(`execute store result storage ${path} int 1 run scoreboard players get ${value.name} rs`);
950
+ }
951
+ }
952
+ else {
953
+ // Compound assignment: read, modify, write back
954
+ const dst = this.builder.freshTemp();
955
+ this.builder.emitRaw(`execute store result score ${dst} rs run data get storage ${path}`);
956
+ const binOp = expr.op.slice(0, -1);
957
+ this.builder.emitBinop(dst, { kind: 'var', name: dst }, binOp, value);
958
+ this.builder.emitRaw(`execute store result storage ${path} int 1 run scoreboard players get ${dst} rs`);
959
+ }
960
+ return { kind: 'const', value: 0 };
961
+ }
962
+ }
963
+ // Default: simple assignment
964
+ const varName = `$${expr.obj.name}_${expr.field}`;
965
+ const value = this.lowerExpr(expr.value);
966
+ this.builder.emitAssign(varName, value);
967
+ return { kind: 'var', name: varName };
968
+ }
969
+ lowerIndexExpr(expr) {
970
+ const arrayName = this.getArrayStorageName(expr.obj);
971
+ if (arrayName) {
972
+ return this.readArrayElement(arrayName, this.lowerExpr(expr.index));
973
+ }
974
+ return { kind: 'const', value: 0 };
975
+ }
976
+ lowerBinaryExpr(expr) {
977
+ const left = this.lowerExpr(expr.left);
978
+ const right = this.lowerExpr(expr.right);
979
+ const dst = this.builder.freshTemp();
980
+ if (['&&', '||'].includes(expr.op)) {
981
+ // Logical operators need special handling
982
+ if (expr.op === '&&') {
983
+ // Short-circuit AND
984
+ this.builder.emitAssign(dst, left);
985
+ const rightVar = this.operandToVar(right);
986
+ // dst = dst && right → if dst != 0 then dst = right
987
+ this.builder.emitRaw(`execute if score ${dst} rs matches 1.. run scoreboard players operation ${dst} rs = ${rightVar} rs`);
988
+ }
989
+ else {
990
+ // Short-circuit OR
991
+ this.builder.emitAssign(dst, left);
992
+ const rightVar = this.operandToVar(right);
993
+ // dst = dst || right → if dst == 0 then dst = right
994
+ this.builder.emitRaw(`execute if score ${dst} rs matches ..0 run scoreboard players operation ${dst} rs = ${rightVar} rs`);
995
+ }
996
+ return { kind: 'var', name: dst };
997
+ }
998
+ if (['==', '!=', '<', '<=', '>', '>='].includes(expr.op)) {
999
+ this.builder.emitCmp(dst, left, expr.op, right);
1000
+ }
1001
+ else {
1002
+ // Check if this is float arithmetic
1003
+ const isFloatOp = this.isFloatExpr(expr.left) || this.isFloatExpr(expr.right);
1004
+ if (isFloatOp && (expr.op === '*' || expr.op === '/')) {
1005
+ // Float multiplication: a * b / 1000
1006
+ // Float division: a * 1000 / b
1007
+ if (expr.op === '*') {
1008
+ this.builder.emitBinop(dst, left, '*', right);
1009
+ // Divide by 1000 to correct for double scaling
1010
+ const constDiv = this.builder.freshTemp();
1011
+ this.builder.emitAssign(constDiv, { kind: 'const', value: 1000 });
1012
+ this.builder.emitRaw(`scoreboard players operation ${dst} rs /= ${constDiv} rs`);
1013
+ }
1014
+ else {
1015
+ // Division: a * 1000 / b
1016
+ const constMul = this.builder.freshTemp();
1017
+ this.builder.emitAssign(constMul, { kind: 'const', value: 1000 });
1018
+ this.builder.emitAssign(dst, left);
1019
+ this.builder.emitRaw(`scoreboard players operation ${dst} rs *= ${constMul} rs`);
1020
+ const rightVar = this.operandToVar(right);
1021
+ this.builder.emitRaw(`scoreboard players operation ${dst} rs /= ${rightVar} rs`);
1022
+ }
1023
+ return { kind: 'var', name: dst };
1024
+ }
1025
+ this.builder.emitBinop(dst, left, expr.op, right);
1026
+ }
1027
+ return { kind: 'var', name: dst };
1028
+ }
1029
+ isFloatExpr(expr) {
1030
+ if (expr.kind === 'float_lit')
1031
+ return true;
1032
+ if (expr.kind === 'ident') {
1033
+ return this.floatVars.has(expr.name);
1034
+ }
1035
+ if (expr.kind === 'binary') {
1036
+ return this.isFloatExpr(expr.left) || this.isFloatExpr(expr.right);
1037
+ }
1038
+ return false;
1039
+ }
1040
+ lowerUnaryExpr(expr) {
1041
+ const operand = this.lowerExpr(expr.operand);
1042
+ const dst = this.builder.freshTemp();
1043
+ if (expr.op === '!') {
1044
+ // Logical NOT: dst = (operand == 0) ? 1 : 0
1045
+ this.builder.emitCmp(dst, operand, '==', { kind: 'const', value: 0 });
1046
+ }
1047
+ else if (expr.op === '-') {
1048
+ // Negation: dst = 0 - operand
1049
+ this.builder.emitBinop(dst, { kind: 'const', value: 0 }, '-', operand);
1050
+ }
1051
+ return { kind: 'var', name: dst };
1052
+ }
1053
+ lowerAssignExpr(expr) {
1054
+ const blockPosValue = this.resolveBlockPosExpr(expr.value);
1055
+ if (blockPosValue) {
1056
+ this.blockPosVars.set(expr.target, blockPosValue);
1057
+ return { kind: 'const', value: 0 };
1058
+ }
1059
+ this.blockPosVars.delete(expr.target);
1060
+ const targetType = this.varTypes.get(expr.target);
1061
+ if (targetType?.kind === 'named' && targetType.name === 'string' && this.storeStringValue(expr.target, expr.value)) {
1062
+ return { kind: 'const', value: 0 };
1063
+ }
1064
+ const varName = this.varMap.get(expr.target) ?? `$${expr.target}`;
1065
+ const value = this.lowerExpr(expr.value);
1066
+ if (expr.op === '=') {
1067
+ this.builder.emitAssign(varName, value);
1068
+ }
1069
+ else {
1070
+ // Compound assignment
1071
+ const binOp = expr.op.slice(0, -1); // Remove '='
1072
+ const dst = this.builder.freshTemp();
1073
+ this.builder.emitBinop(dst, { kind: 'var', name: varName }, binOp, value);
1074
+ this.builder.emitAssign(varName, { kind: 'var', name: dst });
1075
+ }
1076
+ return { kind: 'var', name: varName };
1077
+ }
1078
+ lowerCallExpr(expr) {
1079
+ if (expr.fn === 'str_len') {
1080
+ const storagePath = this.getStringStoragePath(expr.args[0]);
1081
+ if (storagePath) {
1082
+ const dst = this.builder.freshTemp();
1083
+ this.builder.emitRaw(`execute store result score ${dst} rs run data get storage ${storagePath}`);
1084
+ return { kind: 'var', name: dst };
1085
+ }
1086
+ const staticString = this.resolveStaticString(expr.args[0]);
1087
+ if (staticString !== null) {
1088
+ return { kind: 'const', value: Array.from(staticString).length };
1089
+ }
1090
+ else {
1091
+ const dst = this.builder.freshTemp();
1092
+ this.builder.emitAssign(dst, { kind: 'const', value: 0 });
1093
+ return { kind: 'var', name: dst };
1094
+ }
1095
+ }
1096
+ // Check for builtin
1097
+ if (expr.fn in BUILTINS) {
1098
+ return this.lowerBuiltinCall(expr.fn, expr.args, getSpan(expr));
1099
+ }
1100
+ // Handle entity methods: __entity_tag, __entity_untag, __entity_has_tag
1101
+ if (expr.fn === '__entity_tag') {
1102
+ const entity = this.exprToString(expr.args[0]);
1103
+ const tagName = this.exprToString(expr.args[1]);
1104
+ this.builder.emitRaw(`tag ${entity} add ${tagName}`);
1105
+ return { kind: 'const', value: 0 };
1106
+ }
1107
+ if (expr.fn === '__entity_untag') {
1108
+ const entity = this.exprToString(expr.args[0]);
1109
+ const tagName = this.exprToString(expr.args[1]);
1110
+ this.builder.emitRaw(`tag ${entity} remove ${tagName}`);
1111
+ return { kind: 'const', value: 0 };
1112
+ }
1113
+ if (expr.fn === '__entity_has_tag') {
1114
+ const entity = this.exprToString(expr.args[0]);
1115
+ const tagName = this.exprToString(expr.args[1]);
1116
+ const dst = this.builder.freshTemp();
1117
+ this.builder.emitRaw(`execute store result score ${dst} rs if entity ${entity}[tag=${tagName}]`);
1118
+ return { kind: 'var', name: dst };
1119
+ }
1120
+ // Handle array push
1121
+ if (expr.fn === '__array_push') {
1122
+ const arrExpr = expr.args[0];
1123
+ const valueExpr = expr.args[1];
1124
+ const arrName = this.getArrayStorageName(arrExpr);
1125
+ if (arrName) {
1126
+ const value = this.lowerExpr(valueExpr);
1127
+ if (value.kind === 'const') {
1128
+ this.builder.emitRaw(`data modify storage rs:heap ${arrName} append value ${value.value}`);
1129
+ }
1130
+ else if (value.kind === 'var') {
1131
+ this.builder.emitRaw(`data modify storage rs:heap ${arrName} append value 0`);
1132
+ this.builder.emitRaw(`execute store result storage rs:heap ${arrName}[-1] int 1 run scoreboard players get ${value.name} rs`);
1133
+ }
1134
+ }
1135
+ return { kind: 'const', value: 0 };
1136
+ }
1137
+ if (expr.fn === '__array_pop') {
1138
+ const arrName = this.getArrayStorageName(expr.args[0]);
1139
+ const dst = this.builder.freshTemp();
1140
+ if (arrName) {
1141
+ this.builder.emitRaw(`execute store result score ${dst} rs run data get storage rs:heap ${arrName}[-1]`);
1142
+ this.builder.emitRaw(`data remove storage rs:heap ${arrName}[-1]`);
1143
+ }
1144
+ else {
1145
+ this.builder.emitAssign(dst, { kind: 'const', value: 0 });
1146
+ }
1147
+ return { kind: 'var', name: dst };
1148
+ }
1149
+ // Handle spawn_object - creates world object (invisible armor stand)
1150
+ if (expr.fn === 'spawn_object') {
1151
+ const x = this.exprToString(expr.args[0]);
1152
+ const y = this.exprToString(expr.args[1]);
1153
+ const z = this.exprToString(expr.args[2]);
1154
+ const tag = `__rs_obj_${this.worldObjCounter++}`;
1155
+ this.builder.emitRaw(`summon minecraft:armor_stand ${x} ${y} ${z} {Invisible:1b,Marker:1b,NoGravity:1b,Tags:["${tag}"]}`);
1156
+ // Return a selector pointing to this entity
1157
+ const selector = `@e[tag=${tag},limit=1]`;
1158
+ return { kind: 'var', name: selector };
1159
+ }
1160
+ // Handle kill for world objects
1161
+ if (expr.fn === 'kill' && expr.args.length === 1 && expr.args[0].kind === 'ident') {
1162
+ const mapped = this.varMap.get(expr.args[0].name);
1163
+ if (mapped && mapped.startsWith('@e[tag=__rs_obj_')) {
1164
+ this.builder.emitRaw(`kill ${mapped}`);
1165
+ return { kind: 'const', value: 0 };
1166
+ }
1167
+ }
1168
+ const callbackTarget = this.resolveFunctionRefByName(expr.fn);
1169
+ if (callbackTarget) {
1170
+ return this.emitDirectFunctionCall(callbackTarget, expr.args);
1171
+ }
1172
+ // Regular function call
1173
+ const fnDecl = this.fnDecls.get(expr.fn);
1174
+ const defaultArgs = this.functionDefaults.get(expr.fn) ?? [];
1175
+ const fullArgs = [...expr.args];
1176
+ for (let i = fullArgs.length; i < defaultArgs.length; i++) {
1177
+ const defaultExpr = defaultArgs[i];
1178
+ if (!defaultExpr) {
1179
+ break;
1180
+ }
1181
+ fullArgs.push(defaultExpr);
1182
+ }
1183
+ if (fnDecl) {
1184
+ const callbackBindings = new Map();
1185
+ const runtimeArgs = [];
1186
+ for (let i = 0; i < fullArgs.length; i++) {
1187
+ const param = fnDecl.params[i];
1188
+ if (param && this.normalizeType(param.type).kind === 'function_type') {
1189
+ const functionRef = this.resolveFunctionRefExpr(fullArgs[i]);
1190
+ if (!functionRef) {
1191
+ throw new Error(`Cannot lower callback argument for parameter '${param.name}'`);
1192
+ }
1193
+ callbackBindings.set(param.name, functionRef);
1194
+ continue;
1195
+ }
1196
+ runtimeArgs.push(fullArgs[i]);
1197
+ }
1198
+ const targetFn = callbackBindings.size > 0
1199
+ ? this.ensureSpecializedFunction(fnDecl, callbackBindings)
1200
+ : expr.fn;
1201
+ return this.emitDirectFunctionCall(targetFn, runtimeArgs);
1202
+ }
1203
+ return this.emitDirectFunctionCall(expr.fn, fullArgs);
1204
+ }
1205
+ lowerInvokeExpr(expr) {
1206
+ if (expr.callee.kind === 'lambda') {
1207
+ if (!Array.isArray(expr.callee.body)) {
1208
+ return this.inlineLambdaInvoke(expr.callee, expr.args);
1209
+ }
1210
+ const lambdaName = this.lowerLambdaExpr(expr.callee);
1211
+ return this.emitDirectFunctionCall(lambdaName, expr.args);
1212
+ }
1213
+ const functionRef = this.resolveFunctionRefExpr(expr.callee);
1214
+ if (!functionRef) {
1215
+ throw new Error('Cannot invoke a non-function value');
1216
+ }
1217
+ return this.emitDirectFunctionCall(functionRef, expr.args);
1218
+ }
1219
+ inlineLambdaInvoke(expr, args) {
1220
+ const savedVarMap = new Map(this.varMap);
1221
+ const savedVarTypes = new Map(this.varTypes);
1222
+ const savedLambdaBindings = new Map(this.lambdaBindings);
1223
+ const savedBlockPosVars = new Map(this.blockPosVars);
1224
+ for (let i = 0; i < expr.params.length; i++) {
1225
+ const param = expr.params[i];
1226
+ const temp = this.builder.freshTemp();
1227
+ const arg = args[i];
1228
+ this.builder.emitAssign(temp, arg ? this.lowerExpr(arg) : { kind: 'const', value: 0 });
1229
+ this.varMap.set(param.name, temp);
1230
+ if (param.type) {
1231
+ this.varTypes.set(param.name, this.normalizeType(param.type));
1232
+ }
1233
+ this.lambdaBindings.delete(param.name);
1234
+ this.blockPosVars.delete(param.name);
1235
+ }
1236
+ const result = this.lowerExpr(expr.body);
1237
+ this.varMap = savedVarMap;
1238
+ this.varTypes = savedVarTypes;
1239
+ this.lambdaBindings = savedLambdaBindings;
1240
+ this.blockPosVars = savedBlockPosVars;
1241
+ return result;
1242
+ }
1243
+ emitDirectFunctionCall(fn, args) {
1244
+ const loweredArgs = args.map(arg => this.lowerExpr(arg));
1245
+ const dst = this.builder.freshTemp();
1246
+ this.builder.emitCall(fn, loweredArgs, dst);
1247
+ return { kind: 'var', name: dst };
1248
+ }
1249
+ resolveFunctionRefExpr(expr) {
1250
+ if (expr.kind === 'lambda') {
1251
+ return this.lowerLambdaExpr(expr);
1252
+ }
1253
+ if (expr.kind === 'ident') {
1254
+ return this.resolveFunctionRefByName(expr.name) ?? (this.fnDecls.has(expr.name) ? expr.name : null);
1255
+ }
1256
+ return null;
1257
+ }
1258
+ resolveFunctionRefByName(name) {
1259
+ return this.lambdaBindings.get(name) ?? this.currentCallbackBindings.get(name) ?? null;
1260
+ }
1261
+ ensureSpecializedFunction(fn, callbackBindings) {
1262
+ const parts = [...callbackBindings.entries()]
1263
+ .sort(([left], [right]) => left.localeCompare(right))
1264
+ .map(([param, target]) => `${param}_${target.replace(/[^a-zA-Z0-9_]/g, '_')}`);
1265
+ const key = `${fn.name}::${parts.join('::')}`;
1266
+ const cached = this.specializedFunctions.get(key);
1267
+ if (cached) {
1268
+ return cached;
1269
+ }
1270
+ const specializedName = `${fn.name}__${parts.join('__')}`;
1271
+ this.specializedFunctions.set(key, specializedName);
1272
+ this.withSavedFunctionState(() => {
1273
+ this.lowerFn(fn, { name: specializedName, callbackBindings });
1274
+ });
1275
+ return specializedName;
1276
+ }
1277
+ lowerLambdaExpr(expr) {
1278
+ const lambdaName = `__lambda_${this.lambdaCounter++}`;
1279
+ const lambdaFn = {
1280
+ name: lambdaName,
1281
+ params: expr.params.map(param => ({
1282
+ name: param.name,
1283
+ type: param.type ?? { kind: 'named', name: 'int' },
1284
+ })),
1285
+ returnType: expr.returnType ?? this.inferLambdaReturnType(expr),
1286
+ decorators: [],
1287
+ body: Array.isArray(expr.body) ? expr.body : [{ kind: 'return', value: expr.body }],
1288
+ };
1289
+ this.withSavedFunctionState(() => {
1290
+ this.lowerFn(lambdaFn);
1291
+ });
1292
+ return lambdaName;
1293
+ }
1294
+ withSavedFunctionState(callback) {
1295
+ const savedCurrentFn = this.currentFn;
1296
+ const savedForeachCounter = this.foreachCounter;
1297
+ const savedBuilder = this.builder;
1298
+ const savedVarMap = new Map(this.varMap);
1299
+ const savedLambdaBindings = new Map(this.lambdaBindings);
1300
+ const savedCallbackBindings = new Map(this.currentCallbackBindings);
1301
+ const savedContext = this.currentContext;
1302
+ const savedBlockPosVars = new Map(this.blockPosVars);
1303
+ const savedStringValues = new Map(this.stringValues);
1304
+ const savedVarTypes = new Map(this.varTypes);
1305
+ try {
1306
+ return callback();
1307
+ }
1308
+ finally {
1309
+ this.currentFn = savedCurrentFn;
1310
+ this.foreachCounter = savedForeachCounter;
1311
+ this.builder = savedBuilder;
1312
+ this.varMap = savedVarMap;
1313
+ this.lambdaBindings = savedLambdaBindings;
1314
+ this.currentCallbackBindings = savedCallbackBindings;
1315
+ this.currentContext = savedContext;
1316
+ this.blockPosVars = savedBlockPosVars;
1317
+ this.stringValues = savedStringValues;
1318
+ this.varTypes = savedVarTypes;
1319
+ }
1320
+ }
1321
+ lowerBuiltinCall(name, args, callSpan) {
1322
+ const richTextCommand = this.lowerRichTextBuiltin(name, args);
1323
+ if (richTextCommand) {
1324
+ this.builder.emitRaw(richTextCommand);
1325
+ return { kind: 'const', value: 0 };
1326
+ }
1327
+ // Special case: random - legacy scoreboard RNG for pre-1.20.3 compatibility
1328
+ if (name === 'random') {
1329
+ const dst = this.builder.freshTemp();
1330
+ const min = args[0] ? this.exprToLiteral(args[0]) : '0';
1331
+ const max = args[1] ? this.exprToLiteral(args[1]) : '100';
1332
+ this.builder.emitRaw(`scoreboard players random ${dst} rs ${min} ${max}`);
1333
+ return { kind: 'var', name: dst };
1334
+ }
1335
+ // Special case: random_native - /random value (MC 1.20.3+)
1336
+ if (name === 'random_native') {
1337
+ const dst = this.builder.freshTemp();
1338
+ const min = args[0] ? this.exprToLiteral(args[0]) : '0';
1339
+ const max = args[1] ? this.exprToLiteral(args[1]) : '100';
1340
+ this.builder.emitRaw(`execute store result score ${dst} rs run random value ${min} ${max}`);
1341
+ return { kind: 'var', name: dst };
1342
+ }
1343
+ // Special case: random_sequence - /random reset (MC 1.20.3+)
1344
+ if (name === 'random_sequence') {
1345
+ const sequence = this.exprToString(args[0]);
1346
+ const seed = args[1] ? this.exprToLiteral(args[1]) : '0';
1347
+ this.builder.emitRaw(`random reset ${sequence} ${seed}`);
1348
+ return { kind: 'const', value: 0 };
1349
+ }
1350
+ // Special case: scoreboard_get / score — read from vanilla MC scoreboard
1351
+ if (name === 'scoreboard_get' || name === 'score') {
1352
+ const dst = this.builder.freshTemp();
1353
+ const player = this.exprToTargetString(args[0]);
1354
+ const objective = this.exprToString(args[1]);
1355
+ this.builder.emitRaw(`execute store result score ${dst} rs run scoreboard players get ${player} ${objective}`);
1356
+ return { kind: 'var', name: dst };
1357
+ }
1358
+ // Special case: scoreboard_set — write to vanilla MC scoreboard
1359
+ if (name === 'scoreboard_set') {
1360
+ const player = this.exprToTargetString(args[0]);
1361
+ const objective = this.exprToString(args[1]);
1362
+ const value = this.lowerExpr(args[2]);
1363
+ if (value.kind === 'const') {
1364
+ this.builder.emitRaw(`scoreboard players set ${player} ${objective} ${value.value}`);
1365
+ }
1366
+ else if (value.kind === 'var') {
1367
+ // Read directly from the computed scoreboard temp. Routing through a fresh
1368
+ // temp here breaks once optimization removes the apparently-dead assign.
1369
+ this.builder.emitRaw(`execute store result score ${player} ${objective} run scoreboard players get ${value.name} rs`);
1370
+ }
1371
+ return { kind: 'const', value: 0 };
1372
+ }
1373
+ if (name === 'scoreboard_display') {
1374
+ const slot = this.exprToString(args[0]);
1375
+ const objective = this.exprToString(args[1]);
1376
+ this.builder.emitRaw(`scoreboard objectives setdisplay ${slot} ${objective}`);
1377
+ return { kind: 'const', value: 0 };
1378
+ }
1379
+ if (name === 'scoreboard_hide') {
1380
+ const slot = this.exprToString(args[0]);
1381
+ this.builder.emitRaw(`scoreboard objectives setdisplay ${slot}`);
1382
+ return { kind: 'const', value: 0 };
1383
+ }
1384
+ if (name === 'scoreboard_add_objective') {
1385
+ const objective = this.exprToString(args[0]);
1386
+ const criteria = this.exprToString(args[1]);
1387
+ const displayName = args[2] ? ` ${this.exprToQuotedString(args[2])}` : '';
1388
+ this.builder.emitRaw(`scoreboard objectives add ${objective} ${criteria}${displayName}`);
1389
+ return { kind: 'const', value: 0 };
1390
+ }
1391
+ if (name === 'scoreboard_remove_objective') {
1392
+ const objective = this.exprToString(args[0]);
1393
+ this.builder.emitRaw(`scoreboard objectives remove ${objective}`);
1394
+ return { kind: 'const', value: 0 };
1395
+ }
1396
+ if (name === 'bossbar_add') {
1397
+ const id = this.exprToString(args[0]);
1398
+ const title = this.exprToTextComponent(args[1]);
1399
+ this.builder.emitRaw(`bossbar add ${id} ${title}`);
1400
+ return { kind: 'const', value: 0 };
1401
+ }
1402
+ if (name === 'bossbar_set_value') {
1403
+ this.builder.emitRaw(`bossbar set ${this.exprToString(args[0])} value ${this.exprToString(args[1])}`);
1404
+ return { kind: 'const', value: 0 };
1405
+ }
1406
+ if (name === 'bossbar_set_max') {
1407
+ this.builder.emitRaw(`bossbar set ${this.exprToString(args[0])} max ${this.exprToString(args[1])}`);
1408
+ return { kind: 'const', value: 0 };
1409
+ }
1410
+ if (name === 'bossbar_set_color') {
1411
+ this.builder.emitRaw(`bossbar set ${this.exprToString(args[0])} color ${this.exprToString(args[1])}`);
1412
+ return { kind: 'const', value: 0 };
1413
+ }
1414
+ if (name === 'bossbar_set_style') {
1415
+ this.builder.emitRaw(`bossbar set ${this.exprToString(args[0])} style ${this.exprToString(args[1])}`);
1416
+ return { kind: 'const', value: 0 };
1417
+ }
1418
+ if (name === 'bossbar_set_visible') {
1419
+ this.builder.emitRaw(`bossbar set ${this.exprToString(args[0])} visible ${this.exprToBoolString(args[1])}`);
1420
+ return { kind: 'const', value: 0 };
1421
+ }
1422
+ if (name === 'bossbar_set_players') {
1423
+ this.builder.emitRaw(`bossbar set ${this.exprToString(args[0])} players ${this.exprToTargetString(args[1])}`);
1424
+ return { kind: 'const', value: 0 };
1425
+ }
1426
+ if (name === 'bossbar_remove') {
1427
+ this.builder.emitRaw(`bossbar remove ${this.exprToString(args[0])}`);
1428
+ return { kind: 'const', value: 0 };
1429
+ }
1430
+ if (name === 'bossbar_get_value') {
1431
+ const dst = this.builder.freshTemp();
1432
+ this.builder.emitRaw(`execute store result score ${dst} rs run bossbar get ${this.exprToString(args[0])} value`);
1433
+ return { kind: 'var', name: dst };
1434
+ }
1435
+ if (name === 'team_add') {
1436
+ const team = this.exprToString(args[0]);
1437
+ const displayName = args[1] ? ` ${this.exprToTextComponent(args[1])}` : '';
1438
+ this.builder.emitRaw(`team add ${team}${displayName}`);
1439
+ return { kind: 'const', value: 0 };
1440
+ }
1441
+ if (name === 'team_remove') {
1442
+ this.builder.emitRaw(`team remove ${this.exprToString(args[0])}`);
1443
+ return { kind: 'const', value: 0 };
1444
+ }
1445
+ if (name === 'team_join') {
1446
+ this.builder.emitRaw(`team join ${this.exprToString(args[0])} ${this.exprToTargetString(args[1])}`);
1447
+ return { kind: 'const', value: 0 };
1448
+ }
1449
+ if (name === 'team_leave') {
1450
+ this.builder.emitRaw(`team leave ${this.exprToTargetString(args[0])}`);
1451
+ return { kind: 'const', value: 0 };
1452
+ }
1453
+ if (name === 'team_option') {
1454
+ const team = this.exprToString(args[0]);
1455
+ const option = this.exprToString(args[1]);
1456
+ const value = this.isTeamTextOption(option)
1457
+ ? this.exprToTextComponent(args[2])
1458
+ : this.exprToString(args[2]);
1459
+ this.builder.emitRaw(`team modify ${team} ${option} ${value}`);
1460
+ return { kind: 'const', value: 0 };
1461
+ }
1462
+ // Special case: data_get — read NBT data into a variable
1463
+ // data_get(target_type, target, path, scale?)
1464
+ // target_type: "entity", "block", "storage"
1465
+ if (name === 'data_get') {
1466
+ const dst = this.builder.freshTemp();
1467
+ const targetType = this.exprToString(args[0]);
1468
+ const target = targetType === 'entity'
1469
+ ? this.exprToTargetString(args[1])
1470
+ : this.exprToString(args[1]);
1471
+ const path = this.exprToString(args[2]);
1472
+ const scale = args[3] ? this.exprToString(args[3]) : '1';
1473
+ this.builder.emitRaw(`execute store result score ${dst} rs run data get ${targetType} ${target} ${path} ${scale}`);
1474
+ return { kind: 'var', name: dst };
1475
+ }
1476
+ const coordCommand = this.lowerCoordinateBuiltin(name, args);
1477
+ if (coordCommand) {
1478
+ this.builder.emitRaw(coordCommand);
1479
+ return { kind: 'const', value: 0 };
1480
+ }
1481
+ if (name === 'tp_to') {
1482
+ this.warnings.push({
1483
+ message: 'tp_to is deprecated; use tp instead',
1484
+ code: 'W_DEPRECATED',
1485
+ ...(callSpan ? { line: callSpan.line, col: callSpan.col } : {}),
1486
+ });
1487
+ const tpCommand = this.lowerTpCommand(args);
1488
+ if (tpCommand) {
1489
+ this.builder.emitRaw(tpCommand);
1490
+ }
1491
+ return { kind: 'const', value: 0 };
1492
+ }
1493
+ if (name === 'tp') {
1494
+ const tpCommand = this.lowerTpCommand(args);
1495
+ if (tpCommand) {
1496
+ this.builder.emitRaw(tpCommand);
1497
+ }
1498
+ return { kind: 'const', value: 0 };
1499
+ }
1500
+ // Convert args to strings for builtin
1501
+ const strArgs = args.map(arg => this.exprToString(arg));
1502
+ const cmd = BUILTINS[name](strArgs);
1503
+ if (cmd) {
1504
+ this.builder.emitRaw(cmd);
1505
+ }
1506
+ return { kind: 'const', value: 0 };
1507
+ }
1508
+ lowerRichTextBuiltin(name, args) {
1509
+ const messageArgIndex = this.getRichTextArgIndex(name);
1510
+ if (messageArgIndex === null) {
1511
+ return null;
1512
+ }
1513
+ const messageExpr = args[messageArgIndex];
1514
+ if (!messageExpr || messageExpr.kind !== 'str_interp') {
1515
+ return null;
1516
+ }
1517
+ const json = this.buildRichTextJson(messageExpr);
1518
+ switch (name) {
1519
+ case 'say':
1520
+ case 'announce':
1521
+ return `tellraw @a ${json}`;
1522
+ case 'tell':
1523
+ return `tellraw ${this.exprToString(args[0])} ${json}`;
1524
+ case 'title':
1525
+ return `title ${this.exprToString(args[0])} title ${json}`;
1526
+ case 'actionbar':
1527
+ return `title ${this.exprToString(args[0])} actionbar ${json}`;
1528
+ case 'subtitle':
1529
+ return `title ${this.exprToString(args[0])} subtitle ${json}`;
1530
+ default:
1531
+ return null;
1532
+ }
1533
+ }
1534
+ getRichTextArgIndex(name) {
1535
+ switch (name) {
1536
+ case 'say':
1537
+ case 'announce':
1538
+ return 0;
1539
+ case 'tell':
1540
+ case 'title':
1541
+ case 'actionbar':
1542
+ case 'subtitle':
1543
+ return 1;
1544
+ default:
1545
+ return null;
1546
+ }
1547
+ }
1548
+ buildRichTextJson(expr) {
1549
+ const components = [''];
1550
+ for (const part of expr.parts) {
1551
+ if (typeof part === 'string') {
1552
+ if (part.length > 0) {
1553
+ components.push({ text: part });
1554
+ }
1555
+ continue;
1556
+ }
1557
+ this.appendRichTextExpr(components, part);
1558
+ }
1559
+ return JSON.stringify(components);
1560
+ }
1561
+ appendRichTextExpr(components, expr) {
1562
+ if (expr.kind === 'ident') {
1563
+ const constValue = this.constValues.get(expr.name);
1564
+ if (constValue) {
1565
+ this.appendRichTextExpr(components, constValue);
1566
+ return;
1567
+ }
1568
+ const stringValue = this.stringValues.get(expr.name);
1569
+ if (stringValue !== undefined) {
1570
+ components.push({ text: stringValue });
1571
+ return;
1572
+ }
1573
+ }
1574
+ if (expr.kind === 'str_lit') {
1575
+ if (expr.value.length > 0) {
1576
+ components.push({ text: expr.value });
1577
+ }
1578
+ return;
1579
+ }
1580
+ if (expr.kind === 'str_interp') {
1581
+ for (const part of expr.parts) {
1582
+ if (typeof part === 'string') {
1583
+ if (part.length > 0) {
1584
+ components.push({ text: part });
1585
+ }
1586
+ }
1587
+ else {
1588
+ this.appendRichTextExpr(components, part);
1589
+ }
1590
+ }
1591
+ return;
1592
+ }
1593
+ if (expr.kind === 'bool_lit') {
1594
+ components.push({ text: expr.value ? 'true' : 'false' });
1595
+ return;
1596
+ }
1597
+ if (expr.kind === 'int_lit') {
1598
+ components.push({ text: expr.value.toString() });
1599
+ return;
1600
+ }
1601
+ if (expr.kind === 'float_lit') {
1602
+ components.push({ text: expr.value.toString() });
1603
+ return;
1604
+ }
1605
+ const operand = this.lowerExpr(expr);
1606
+ if (operand.kind === 'const') {
1607
+ components.push({ text: operand.value.toString() });
1608
+ return;
1609
+ }
1610
+ components.push({ score: { name: this.operandToVar(operand), objective: 'rs' } });
1611
+ }
1612
+ exprToString(expr) {
1613
+ switch (expr.kind) {
1614
+ case 'int_lit':
1615
+ return expr.value.toString();
1616
+ case 'float_lit':
1617
+ return Math.trunc(expr.value).toString();
1618
+ case 'byte_lit':
1619
+ return `${expr.value}b`;
1620
+ case 'short_lit':
1621
+ return `${expr.value}s`;
1622
+ case 'long_lit':
1623
+ return `${expr.value}L`;
1624
+ case 'double_lit':
1625
+ return `${expr.value}d`;
1626
+ case 'bool_lit':
1627
+ return expr.value ? '1' : '0';
1628
+ case 'str_lit':
1629
+ return expr.value;
1630
+ case 'mc_name':
1631
+ return expr.value; // #health → "health" (no quotes, used as bare MC name)
1632
+ case 'str_interp':
1633
+ return this.buildRichTextJson(expr);
1634
+ case 'blockpos':
1635
+ return emitBlockPos(expr);
1636
+ case 'ident': {
1637
+ const constValue = this.constValues.get(expr.name);
1638
+ if (constValue) {
1639
+ return this.exprToString(constValue);
1640
+ }
1641
+ const stringValue = this.stringValues.get(expr.name);
1642
+ if (stringValue !== undefined) {
1643
+ return stringValue;
1644
+ }
1645
+ const mapped = this.varMap.get(expr.name);
1646
+ return mapped ?? `$${expr.name}`;
1647
+ }
1648
+ case 'selector':
1649
+ return this.selectorToString(expr.sel);
1650
+ default:
1651
+ // Complex expression - lower and return var name
1652
+ const op = this.lowerExpr(expr);
1653
+ return this.operandToVar(op);
1654
+ }
1655
+ }
1656
+ exprToTargetString(expr) {
1657
+ if (expr.kind === 'selector') {
1658
+ return this.selectorToString(expr.sel);
1659
+ }
1660
+ if (expr.kind === 'str_lit' && expr.value.startsWith('@')) {
1661
+ const span = getSpan(expr);
1662
+ this.warnings.push({
1663
+ message: `Quoted selector "${expr.value}" is deprecated; pass ${expr.value} without quotes`,
1664
+ code: 'W_QUOTED_SELECTOR',
1665
+ ...(span ? { line: span.line, col: span.col } : {}),
1666
+ });
1667
+ return expr.value;
1668
+ }
1669
+ return this.exprToString(expr);
1670
+ }
1671
+ exprToLiteral(expr) {
1672
+ if (expr.kind === 'int_lit')
1673
+ return expr.value.toString();
1674
+ if (expr.kind === 'float_lit')
1675
+ return Math.trunc(expr.value).toString();
1676
+ return '0';
1677
+ }
1678
+ exprToQuotedString(expr) {
1679
+ return JSON.stringify(this.exprToString(expr));
1680
+ }
1681
+ exprToTextComponent(expr) {
1682
+ return JSON.stringify({ text: this.exprToString(expr) });
1683
+ }
1684
+ exprToBoolString(expr) {
1685
+ if (expr.kind === 'bool_lit') {
1686
+ return expr.value ? 'true' : 'false';
1687
+ }
1688
+ return this.exprToString(expr);
1689
+ }
1690
+ isTeamTextOption(option) {
1691
+ return option === 'displayName' || option === 'prefix' || option === 'suffix';
1692
+ }
1693
+ lowerCoordinateBuiltin(name, args) {
1694
+ const pos0 = args[0] ? this.resolveBlockPosExpr(args[0]) : null;
1695
+ const pos1 = args[1] ? this.resolveBlockPosExpr(args[1]) : null;
1696
+ const pos2 = args[2] ? this.resolveBlockPosExpr(args[2]) : null;
1697
+ if (name === 'setblock') {
1698
+ if (args.length === 2 && pos0) {
1699
+ return `setblock ${emitBlockPos(pos0)} ${this.exprToString(args[1])}`;
1700
+ }
1701
+ return null;
1702
+ }
1703
+ if (name === 'fill') {
1704
+ if (args.length === 3 && pos0 && pos1) {
1705
+ return `fill ${emitBlockPos(pos0)} ${emitBlockPos(pos1)} ${this.exprToString(args[2])}`;
1706
+ }
1707
+ return null;
1708
+ }
1709
+ if (name === 'clone') {
1710
+ if (args.length === 3 && pos0 && pos1 && pos2) {
1711
+ return `clone ${emitBlockPos(pos0)} ${emitBlockPos(pos1)} ${emitBlockPos(pos2)}`;
1712
+ }
1713
+ return null;
1714
+ }
1715
+ return null;
1716
+ }
1717
+ lowerTpCommand(args) {
1718
+ const pos0 = args[0] ? this.resolveBlockPosExpr(args[0]) : null;
1719
+ const pos1 = args[1] ? this.resolveBlockPosExpr(args[1]) : null;
1720
+ if (args.length === 1 && pos0) {
1721
+ return `tp ${emitBlockPos(pos0)}`;
1722
+ }
1723
+ if (args.length === 2) {
1724
+ if (pos1) {
1725
+ return `tp ${this.exprToString(args[0])} ${emitBlockPos(pos1)}`;
1726
+ }
1727
+ return `tp ${this.exprToString(args[0])} ${this.exprToString(args[1])}`;
1728
+ }
1729
+ if (args.length === 4) {
1730
+ return `tp ${this.exprToString(args[0])} ${this.exprToString(args[1])} ${this.exprToString(args[2])} ${this.exprToString(args[3])}`;
1731
+ }
1732
+ return null;
1733
+ }
1734
+ resolveBlockPosExpr(expr) {
1735
+ if (expr.kind === 'blockpos') {
1736
+ return expr;
1737
+ }
1738
+ if (expr.kind === 'ident') {
1739
+ return this.blockPosVars.get(expr.name) ?? null;
1740
+ }
1741
+ return null;
1742
+ }
1743
+ getArrayStorageName(expr) {
1744
+ if (expr.kind === 'ident') {
1745
+ return expr.name;
1746
+ }
1747
+ return null;
1748
+ }
1749
+ inferLambdaReturnType(expr) {
1750
+ if (expr.returnType) {
1751
+ return this.normalizeType(expr.returnType);
1752
+ }
1753
+ if (Array.isArray(expr.body)) {
1754
+ return { kind: 'named', name: 'void' };
1755
+ }
1756
+ return this.inferExprType(expr.body) ?? { kind: 'named', name: 'void' };
1757
+ }
1758
+ inferExprType(expr) {
1759
+ if (expr.kind === 'int_lit')
1760
+ return { kind: 'named', name: 'int' };
1761
+ if (expr.kind === 'float_lit')
1762
+ return { kind: 'named', name: 'float' };
1763
+ if (expr.kind === 'bool_lit')
1764
+ return { kind: 'named', name: 'bool' };
1765
+ if (expr.kind === 'str_lit' || expr.kind === 'str_interp')
1766
+ return { kind: 'named', name: 'string' };
1767
+ if (expr.kind === 'blockpos')
1768
+ return { kind: 'named', name: 'BlockPos' };
1769
+ if (expr.kind === 'ident') {
1770
+ const constValue = this.constValues.get(expr.name);
1771
+ if (constValue) {
1772
+ switch (constValue.kind) {
1773
+ case 'int_lit':
1774
+ return { kind: 'named', name: 'int' };
1775
+ case 'float_lit':
1776
+ return { kind: 'named', name: 'float' };
1777
+ case 'bool_lit':
1778
+ return { kind: 'named', name: 'bool' };
1779
+ case 'str_lit':
1780
+ return { kind: 'named', name: 'string' };
1781
+ }
1782
+ }
1783
+ return this.varTypes.get(expr.name);
1784
+ }
1785
+ if (expr.kind === 'lambda') {
1786
+ return {
1787
+ kind: 'function_type',
1788
+ params: expr.params.map(param => this.normalizeType(param.type ?? { kind: 'named', name: 'int' })),
1789
+ return: this.inferLambdaReturnType(expr),
1790
+ };
1791
+ }
1792
+ if (expr.kind === 'call') {
1793
+ return this.fnDecls.get(this.resolveFunctionRefByName(expr.fn) ?? expr.fn)?.returnType;
1794
+ }
1795
+ if (expr.kind === 'invoke') {
1796
+ const calleeType = this.inferExprType(expr.callee);
1797
+ if (calleeType?.kind === 'function_type') {
1798
+ return calleeType.return;
1799
+ }
1800
+ }
1801
+ if (expr.kind === 'binary') {
1802
+ if (['==', '!=', '<', '<=', '>', '>=', '&&', '||'].includes(expr.op)) {
1803
+ return { kind: 'named', name: 'bool' };
1804
+ }
1805
+ return this.inferExprType(expr.left);
1806
+ }
1807
+ if (expr.kind === 'unary') {
1808
+ return expr.op === '!' ? { kind: 'named', name: 'bool' } : this.inferExprType(expr.operand);
1809
+ }
1810
+ if (expr.kind === 'array_lit') {
1811
+ return {
1812
+ kind: 'array',
1813
+ elem: expr.elements[0] ? (this.inferExprType(expr.elements[0]) ?? { kind: 'named', name: 'int' }) : { kind: 'named', name: 'int' },
1814
+ };
1815
+ }
1816
+ if (expr.kind === 'member' && expr.obj.kind === 'ident' && this.enumDefs.has(expr.obj.name)) {
1817
+ return { kind: 'enum', name: expr.obj.name };
1818
+ }
1819
+ return undefined;
1820
+ }
1821
+ normalizeType(type) {
1822
+ if (type.kind === 'array') {
1823
+ return { kind: 'array', elem: this.normalizeType(type.elem) };
1824
+ }
1825
+ if (type.kind === 'function_type') {
1826
+ return {
1827
+ kind: 'function_type',
1828
+ params: type.params.map(param => this.normalizeType(param)),
1829
+ return: this.normalizeType(type.return),
1830
+ };
1831
+ }
1832
+ if ((type.kind === 'struct' || type.kind === 'enum') && this.enumDefs.has(type.name)) {
1833
+ return { kind: 'enum', name: type.name };
1834
+ }
1835
+ return type;
1836
+ }
1837
+ readArrayElement(arrayName, index) {
1838
+ const dst = this.builder.freshTemp();
1839
+ if (index.kind === 'const') {
1840
+ this.builder.emitRaw(`execute store result score ${dst} rs run data get storage rs:heap ${arrayName}[${index.value}]`);
1841
+ return { kind: 'var', name: dst };
1842
+ }
1843
+ const macroKey = `__rs_index_${this.foreachCounter++}`;
1844
+ const subFnName = `${this.currentFn}/array_get_${this.foreachCounter++}`;
1845
+ const indexVar = index.kind === 'var' ? index.name : this.operandToVar(index);
1846
+ this.builder.emitRaw(`execute store result storage rs:heap ${macroKey} int 1 run scoreboard players get ${indexVar} rs`);
1847
+ this.builder.emitRaw(`function ${this.namespace}:${subFnName} with storage rs:heap`);
1848
+ this.emitRawSubFunction(subFnName, `$execute store result score ${dst} rs run data get storage rs:heap ${arrayName}[$(${macroKey})]`);
1849
+ return { kind: 'var', name: dst };
1850
+ }
1851
+ emitRawSubFunction(name, ...commands) {
1852
+ const builder = new LoweringBuilder();
1853
+ builder.startBlock('entry');
1854
+ for (const cmd of commands) {
1855
+ builder.emitRaw(cmd);
1856
+ }
1857
+ builder.emitReturn();
1858
+ this.functions.push(builder.build(name, [], false));
1859
+ }
1860
+ // -------------------------------------------------------------------------
1861
+ // Helpers
1862
+ // -------------------------------------------------------------------------
1863
+ storeStringValue(name, expr) {
1864
+ const value = this.resolveStaticString(expr);
1865
+ if (value === null) {
1866
+ this.stringValues.delete(name);
1867
+ return false;
1868
+ }
1869
+ this.stringValues.set(name, value);
1870
+ this.builder.emitRaw(`data modify storage rs:strings ${name} set value ${JSON.stringify(value)}`);
1871
+ return true;
1872
+ }
1873
+ resolveStaticString(expr) {
1874
+ if (!expr) {
1875
+ return null;
1876
+ }
1877
+ if (expr.kind === 'str_lit') {
1878
+ return expr.value;
1879
+ }
1880
+ if (expr.kind === 'ident') {
1881
+ const constValue = this.constValues.get(expr.name);
1882
+ if (constValue?.kind === 'str_lit') {
1883
+ return constValue.value;
1884
+ }
1885
+ return this.stringValues.get(expr.name) ?? null;
1886
+ }
1887
+ return null;
1888
+ }
1889
+ getStringStoragePath(expr) {
1890
+ if (!expr || expr.kind !== 'ident') {
1891
+ return null;
1892
+ }
1893
+ if (this.stringValues.has(expr.name)) {
1894
+ return `rs:strings ${expr.name}`;
1895
+ }
1896
+ return null;
1897
+ }
1898
+ lowerConstLiteral(expr) {
1899
+ switch (expr.kind) {
1900
+ case 'int_lit':
1901
+ return { kind: 'const', value: expr.value };
1902
+ case 'float_lit':
1903
+ return { kind: 'const', value: Math.round(expr.value * 1000) };
1904
+ case 'bool_lit':
1905
+ return { kind: 'const', value: expr.value ? 1 : 0 };
1906
+ case 'str_lit':
1907
+ return { kind: 'const', value: 0 };
1908
+ }
1909
+ }
1910
+ operandToVar(op) {
1911
+ if (op.kind === 'var')
1912
+ return op.name;
1913
+ // Constant needs to be stored in a temp
1914
+ const dst = this.builder.freshTemp();
1915
+ this.builder.emitAssign(dst, op);
1916
+ return dst;
1917
+ }
1918
+ selectorToString(sel) {
1919
+ const { kind, filters } = sel;
1920
+ if (!filters)
1921
+ return this.finalizeSelector(kind);
1922
+ const parts = [];
1923
+ if (filters.type)
1924
+ parts.push(`type=${filters.type}`);
1925
+ if (filters.distance)
1926
+ parts.push(`distance=${this.rangeToString(filters.distance)}`);
1927
+ if (filters.tag)
1928
+ filters.tag.forEach(t => parts.push(`tag=${t}`));
1929
+ if (filters.notTag)
1930
+ filters.notTag.forEach(t => parts.push(`tag=!${t}`));
1931
+ if (filters.limit !== undefined)
1932
+ parts.push(`limit=${filters.limit}`);
1933
+ if (filters.sort)
1934
+ parts.push(`sort=${filters.sort}`);
1935
+ if (filters.scores) {
1936
+ const scoreStr = Object.entries(filters.scores)
1937
+ .map(([k, v]) => `${k}=${this.rangeToString(v)}`).join(',');
1938
+ parts.push(`scores={${scoreStr}}`);
1939
+ }
1940
+ if (filters.nbt)
1941
+ parts.push(`nbt=${filters.nbt}`);
1942
+ if (filters.gamemode)
1943
+ parts.push(`gamemode=${filters.gamemode}`);
1944
+ return this.finalizeSelector(parts.length ? `${kind}[${parts.join(',')}]` : kind);
1945
+ }
1946
+ finalizeSelector(selector) {
1947
+ return normalizeSelector(selector, this.warnings);
1948
+ }
1949
+ rangeToString(r) {
1950
+ if (r.min !== undefined && r.max !== undefined) {
1951
+ if (r.min === r.max)
1952
+ return `${r.min}`;
1953
+ return `${r.min}..${r.max}`;
1954
+ }
1955
+ if (r.min !== undefined)
1956
+ return `${r.min}..`;
1957
+ if (r.max !== undefined)
1958
+ return `..${r.max}`;
1959
+ return '..';
1960
+ }
1961
+ }
1962
+ exports.Lowering = Lowering;
1963
+ // ---------------------------------------------------------------------------
1964
+ // LoweringBuilder - Wrapper around IR construction
1965
+ // ---------------------------------------------------------------------------
1966
+ class LoweringBuilder {
1967
+ constructor() {
1968
+ this.tempCount = 0;
1969
+ this.labelCount = 0;
1970
+ this.blocks = [];
1971
+ this.currentBlock = null;
1972
+ this.locals = new Set();
1973
+ }
1974
+ freshTemp() {
1975
+ const name = `$t${this.tempCount++}`;
1976
+ this.locals.add(name);
1977
+ return name;
1978
+ }
1979
+ freshLabel(hint = 'L') {
1980
+ return `${hint}_${this.labelCount++}`;
1981
+ }
1982
+ startBlock(label) {
1983
+ this.currentBlock = { label, instrs: [], term: null };
1984
+ }
1985
+ isBlockSealed() {
1986
+ return this.currentBlock === null || this.currentBlock.term !== null;
1987
+ }
1988
+ sealBlock(term) {
1989
+ if (this.currentBlock) {
1990
+ this.currentBlock.term = term;
1991
+ this.blocks.push(this.currentBlock);
1992
+ this.currentBlock = null;
1993
+ }
1994
+ }
1995
+ emitAssign(dst, src) {
1996
+ if (!dst.startsWith('$') && !dst.startsWith('@')) {
1997
+ dst = '$' + dst;
1998
+ }
1999
+ this.locals.add(dst);
2000
+ this.currentBlock?.instrs.push({ op: 'assign', dst, src });
2001
+ }
2002
+ emitBinop(dst, lhs, bop, rhs) {
2003
+ this.locals.add(dst);
2004
+ this.currentBlock?.instrs.push({ op: 'binop', dst, lhs, bop, rhs });
2005
+ }
2006
+ emitCmp(dst, lhs, cop, rhs) {
2007
+ this.locals.add(dst);
2008
+ this.currentBlock?.instrs.push({ op: 'cmp', dst, lhs, cop, rhs });
2009
+ }
2010
+ emitCall(fn, args, dst) {
2011
+ if (dst)
2012
+ this.locals.add(dst);
2013
+ this.currentBlock?.instrs.push({ op: 'call', fn, args, dst });
2014
+ }
2015
+ emitRaw(cmd) {
2016
+ this.currentBlock?.instrs.push({ op: 'raw', cmd });
2017
+ }
2018
+ emitJump(target) {
2019
+ this.sealBlock({ op: 'jump', target });
2020
+ }
2021
+ emitJumpIf(cond, then, else_) {
2022
+ this.sealBlock({ op: 'jump_if', cond, then, else_ });
2023
+ }
2024
+ emitReturn(value) {
2025
+ this.sealBlock({ op: 'return', value });
2026
+ }
2027
+ build(name, params, isTickLoop = false) {
2028
+ // Ensure current block is sealed
2029
+ if (this.currentBlock && !this.currentBlock.term) {
2030
+ this.sealBlock({ op: 'return' });
2031
+ }
2032
+ return {
2033
+ name,
2034
+ params,
2035
+ locals: Array.from(this.locals),
2036
+ blocks: this.blocks,
2037
+ isTickLoop,
2038
+ };
2039
+ }
2040
+ }
2041
+ //# sourceMappingURL=index.js.map