redscript-mc 1.2.29 → 2.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 (274) hide show
  1. package/.claude/commands/build-test.md +10 -0
  2. package/.claude/commands/deploy-demo.md +12 -0
  3. package/.claude/commands/stage-status.md +13 -0
  4. package/.claude/settings.json +12 -0
  5. package/.github/workflows/ci.yml +1 -0
  6. package/CLAUDE.md +231 -0
  7. package/README.md +29 -28
  8. package/README.zh.md +28 -28
  9. package/demo.gif +0 -0
  10. package/dist/cli.js +2 -554
  11. package/dist/compile.js +2 -266
  12. package/dist/index.js +2 -159
  13. package/dist/lexer/index.js +9 -1
  14. package/dist/lowering/index.js +22 -5
  15. package/dist/src/__tests__/cli.test.d.ts +1 -0
  16. package/dist/src/__tests__/cli.test.js +104 -0
  17. package/dist/src/__tests__/codegen.test.d.ts +1 -0
  18. package/dist/src/__tests__/codegen.test.js +152 -0
  19. package/dist/src/__tests__/compile-all.test.d.ts +10 -0
  20. package/dist/src/__tests__/compile-all.test.js +108 -0
  21. package/dist/src/__tests__/dce.test.d.ts +1 -0
  22. package/dist/src/__tests__/dce.test.js +102 -0
  23. package/dist/src/__tests__/diagnostics.test.d.ts +4 -0
  24. package/dist/src/__tests__/diagnostics.test.js +177 -0
  25. package/dist/src/__tests__/e2e.test.d.ts +6 -0
  26. package/dist/src/__tests__/e2e.test.js +1789 -0
  27. package/dist/src/__tests__/entity-types.test.d.ts +1 -0
  28. package/dist/src/__tests__/entity-types.test.js +203 -0
  29. package/dist/src/__tests__/formatter.test.d.ts +1 -0
  30. package/dist/src/__tests__/formatter.test.js +40 -0
  31. package/dist/src/__tests__/lexer.test.d.ts +1 -0
  32. package/dist/src/__tests__/lexer.test.js +343 -0
  33. package/dist/src/__tests__/lowering.test.d.ts +1 -0
  34. package/dist/src/__tests__/lowering.test.js +1015 -0
  35. package/dist/src/__tests__/macro.test.d.ts +8 -0
  36. package/dist/src/__tests__/macro.test.js +306 -0
  37. package/dist/src/__tests__/mc-integration.test.d.ts +12 -0
  38. package/dist/src/__tests__/mc-integration.test.js +817 -0
  39. package/dist/src/__tests__/mc-syntax.test.d.ts +1 -0
  40. package/dist/src/__tests__/mc-syntax.test.js +124 -0
  41. package/dist/src/__tests__/nbt.test.d.ts +1 -0
  42. package/dist/src/__tests__/nbt.test.js +82 -0
  43. package/dist/src/__tests__/optimizer-advanced.test.d.ts +1 -0
  44. package/dist/src/__tests__/optimizer-advanced.test.js +124 -0
  45. package/dist/src/__tests__/optimizer.test.d.ts +1 -0
  46. package/dist/src/__tests__/optimizer.test.js +149 -0
  47. package/dist/src/__tests__/parser.test.d.ts +1 -0
  48. package/dist/src/__tests__/parser.test.js +807 -0
  49. package/dist/src/__tests__/repl.test.d.ts +1 -0
  50. package/dist/src/__tests__/repl.test.js +27 -0
  51. package/dist/src/__tests__/runtime.test.d.ts +1 -0
  52. package/dist/src/__tests__/runtime.test.js +289 -0
  53. package/dist/src/__tests__/stdlib-advanced.test.d.ts +4 -0
  54. package/dist/src/__tests__/stdlib-advanced.test.js +374 -0
  55. package/dist/src/__tests__/stdlib-bigint.test.d.ts +7 -0
  56. package/dist/src/__tests__/stdlib-bigint.test.js +426 -0
  57. package/dist/src/__tests__/stdlib-math.test.d.ts +7 -0
  58. package/dist/src/__tests__/stdlib-math.test.js +351 -0
  59. package/dist/src/__tests__/stdlib-vec.test.d.ts +4 -0
  60. package/dist/src/__tests__/stdlib-vec.test.js +263 -0
  61. package/dist/src/__tests__/structure-optimizer.test.d.ts +1 -0
  62. package/dist/src/__tests__/structure-optimizer.test.js +33 -0
  63. package/dist/src/__tests__/typechecker.test.d.ts +1 -0
  64. package/dist/src/__tests__/typechecker.test.js +552 -0
  65. package/dist/src/__tests__/var-allocator.test.d.ts +1 -0
  66. package/dist/src/__tests__/var-allocator.test.js +69 -0
  67. package/dist/src/ast/types.d.ts +515 -0
  68. package/dist/src/ast/types.js +9 -0
  69. package/dist/src/builtins/metadata.d.ts +36 -0
  70. package/dist/src/builtins/metadata.js +1014 -0
  71. package/dist/src/cli.d.ts +11 -0
  72. package/dist/src/cli.js +443 -0
  73. package/dist/src/codegen/cmdblock/index.d.ts +26 -0
  74. package/dist/src/codegen/cmdblock/index.js +45 -0
  75. package/dist/src/codegen/mcfunction/index.d.ts +40 -0
  76. package/dist/src/codegen/mcfunction/index.js +606 -0
  77. package/dist/src/codegen/structure/index.d.ts +24 -0
  78. package/dist/src/codegen/structure/index.js +279 -0
  79. package/dist/src/codegen/var-allocator.d.ts +45 -0
  80. package/dist/src/codegen/var-allocator.js +104 -0
  81. package/dist/src/compile.d.ts +37 -0
  82. package/dist/src/compile.js +165 -0
  83. package/dist/src/diagnostics/index.d.ts +44 -0
  84. package/dist/src/diagnostics/index.js +140 -0
  85. package/dist/src/events/types.d.ts +35 -0
  86. package/dist/src/events/types.js +59 -0
  87. package/dist/src/formatter/index.d.ts +1 -0
  88. package/dist/src/formatter/index.js +26 -0
  89. package/dist/src/index.d.ts +22 -0
  90. package/dist/src/index.js +45 -0
  91. package/dist/src/ir/builder.d.ts +33 -0
  92. package/dist/src/ir/builder.js +99 -0
  93. package/dist/src/ir/types.d.ts +132 -0
  94. package/dist/src/ir/types.js +15 -0
  95. package/dist/src/lexer/index.d.ts +37 -0
  96. package/dist/src/lexer/index.js +569 -0
  97. package/dist/src/lowering/index.d.ts +188 -0
  98. package/dist/src/lowering/index.js +3405 -0
  99. package/dist/src/mc-test/client.d.ts +128 -0
  100. package/dist/src/mc-test/client.js +174 -0
  101. package/dist/src/mc-test/runner.d.ts +28 -0
  102. package/dist/src/mc-test/runner.js +151 -0
  103. package/dist/src/mc-test/setup.d.ts +11 -0
  104. package/dist/src/mc-test/setup.js +98 -0
  105. package/dist/src/mc-validator/index.d.ts +17 -0
  106. package/dist/src/mc-validator/index.js +322 -0
  107. package/dist/src/nbt/index.d.ts +86 -0
  108. package/dist/src/nbt/index.js +250 -0
  109. package/dist/src/optimizer/commands.d.ts +38 -0
  110. package/dist/src/optimizer/commands.js +451 -0
  111. package/dist/src/optimizer/dce.d.ts +34 -0
  112. package/dist/src/optimizer/dce.js +639 -0
  113. package/dist/src/optimizer/passes.d.ts +34 -0
  114. package/dist/src/optimizer/passes.js +243 -0
  115. package/dist/src/optimizer/structure.d.ts +9 -0
  116. package/dist/src/optimizer/structure.js +356 -0
  117. package/dist/src/parser/index.d.ts +93 -0
  118. package/dist/src/parser/index.js +1687 -0
  119. package/dist/src/repl.d.ts +16 -0
  120. package/dist/src/repl.js +165 -0
  121. package/dist/src/runtime/index.d.ts +107 -0
  122. package/dist/src/runtime/index.js +1409 -0
  123. package/dist/src/typechecker/index.d.ts +61 -0
  124. package/dist/src/typechecker/index.js +1034 -0
  125. package/dist/src/types/entity-hierarchy.d.ts +29 -0
  126. package/dist/src/types/entity-hierarchy.js +107 -0
  127. package/dist/src2/__tests__/e2e/basic.test.d.ts +8 -0
  128. package/dist/src2/__tests__/e2e/basic.test.js +140 -0
  129. package/dist/src2/__tests__/e2e/macros.test.d.ts +9 -0
  130. package/dist/src2/__tests__/e2e/macros.test.js +182 -0
  131. package/dist/src2/__tests__/e2e/migrate.test.d.ts +13 -0
  132. package/dist/src2/__tests__/e2e/migrate.test.js +2739 -0
  133. package/dist/src2/__tests__/hir/desugar.test.d.ts +1 -0
  134. package/dist/src2/__tests__/hir/desugar.test.js +234 -0
  135. package/dist/src2/__tests__/lir/lower.test.d.ts +1 -0
  136. package/dist/src2/__tests__/lir/lower.test.js +559 -0
  137. package/dist/src2/__tests__/lir/types.test.d.ts +1 -0
  138. package/dist/src2/__tests__/lir/types.test.js +185 -0
  139. package/dist/src2/__tests__/lir/verify.test.d.ts +1 -0
  140. package/dist/src2/__tests__/lir/verify.test.js +221 -0
  141. package/dist/src2/__tests__/mir/arithmetic.test.d.ts +1 -0
  142. package/dist/src2/__tests__/mir/arithmetic.test.js +130 -0
  143. package/dist/src2/__tests__/mir/control-flow.test.d.ts +1 -0
  144. package/dist/src2/__tests__/mir/control-flow.test.js +205 -0
  145. package/dist/src2/__tests__/mir/verify.test.d.ts +1 -0
  146. package/dist/src2/__tests__/mir/verify.test.js +223 -0
  147. package/dist/src2/__tests__/optimizer/block_merge.test.d.ts +1 -0
  148. package/dist/src2/__tests__/optimizer/block_merge.test.js +78 -0
  149. package/dist/src2/__tests__/optimizer/branch_simplify.test.d.ts +1 -0
  150. package/dist/src2/__tests__/optimizer/branch_simplify.test.js +58 -0
  151. package/dist/src2/__tests__/optimizer/constant_fold.test.d.ts +1 -0
  152. package/dist/src2/__tests__/optimizer/constant_fold.test.js +131 -0
  153. package/dist/src2/__tests__/optimizer/copy_prop.test.d.ts +1 -0
  154. package/dist/src2/__tests__/optimizer/copy_prop.test.js +91 -0
  155. package/dist/src2/__tests__/optimizer/dce.test.d.ts +1 -0
  156. package/dist/src2/__tests__/optimizer/dce.test.js +76 -0
  157. package/dist/src2/__tests__/optimizer/pipeline.test.d.ts +1 -0
  158. package/dist/src2/__tests__/optimizer/pipeline.test.js +102 -0
  159. package/dist/src2/emit/compile.d.ts +19 -0
  160. package/dist/src2/emit/compile.js +80 -0
  161. package/dist/src2/emit/index.d.ts +17 -0
  162. package/dist/src2/emit/index.js +172 -0
  163. package/dist/src2/hir/lower.d.ts +15 -0
  164. package/dist/src2/hir/lower.js +378 -0
  165. package/dist/src2/hir/types.d.ts +373 -0
  166. package/dist/src2/hir/types.js +16 -0
  167. package/dist/src2/lir/lower.d.ts +15 -0
  168. package/dist/src2/lir/lower.js +453 -0
  169. package/dist/src2/lir/types.d.ts +136 -0
  170. package/dist/src2/lir/types.js +11 -0
  171. package/dist/src2/lir/verify.d.ts +14 -0
  172. package/dist/src2/lir/verify.js +113 -0
  173. package/dist/src2/mir/lower.d.ts +9 -0
  174. package/dist/src2/mir/lower.js +1030 -0
  175. package/dist/src2/mir/macro.d.ts +22 -0
  176. package/dist/src2/mir/macro.js +168 -0
  177. package/dist/src2/mir/types.d.ts +183 -0
  178. package/dist/src2/mir/types.js +11 -0
  179. package/dist/src2/mir/verify.d.ts +16 -0
  180. package/dist/src2/mir/verify.js +216 -0
  181. package/dist/src2/optimizer/block_merge.d.ts +12 -0
  182. package/dist/src2/optimizer/block_merge.js +84 -0
  183. package/dist/src2/optimizer/branch_simplify.d.ts +9 -0
  184. package/dist/src2/optimizer/branch_simplify.js +28 -0
  185. package/dist/src2/optimizer/constant_fold.d.ts +10 -0
  186. package/dist/src2/optimizer/constant_fold.js +85 -0
  187. package/dist/src2/optimizer/copy_prop.d.ts +9 -0
  188. package/dist/src2/optimizer/copy_prop.js +113 -0
  189. package/dist/src2/optimizer/dce.d.ts +8 -0
  190. package/dist/src2/optimizer/dce.js +155 -0
  191. package/dist/src2/optimizer/pipeline.d.ts +10 -0
  192. package/dist/src2/optimizer/pipeline.js +42 -0
  193. package/dist/tsconfig.tsbuildinfo +1 -0
  194. package/docs/compiler-pipeline-redesign.md +2243 -0
  195. package/docs/optimization-ideas.md +1076 -0
  196. package/editors/vscode/package-lock.json +3 -3
  197. package/editors/vscode/package.json +1 -1
  198. package/examples/readme-demo.mcrs +44 -66
  199. package/jest.config.js +1 -1
  200. package/package.json +6 -5
  201. package/scripts/postbuild.js +15 -0
  202. package/src/__tests__/cli.test.ts +8 -220
  203. package/src/__tests__/dce.test.ts +11 -56
  204. package/src/__tests__/diagnostics.test.ts +59 -38
  205. package/src/__tests__/mc-integration.test.ts +1 -2
  206. package/src/ast/types.ts +6 -1
  207. package/src/cli.ts +29 -156
  208. package/src/compile.ts +6 -162
  209. package/src/index.ts +14 -178
  210. package/src/lexer/index.ts +9 -1
  211. package/src/mc-test/runner.ts +4 -3
  212. package/src/parser/index.ts +1 -1
  213. package/src/repl.ts +1 -1
  214. package/src/runtime/index.ts +1 -1
  215. package/src2/__tests__/e2e/basic.test.ts +154 -0
  216. package/src2/__tests__/e2e/macros.test.ts +199 -0
  217. package/src2/__tests__/e2e/migrate.test.ts +3008 -0
  218. package/src2/__tests__/hir/desugar.test.ts +263 -0
  219. package/src2/__tests__/lir/lower.test.ts +619 -0
  220. package/src2/__tests__/lir/types.test.ts +207 -0
  221. package/src2/__tests__/lir/verify.test.ts +249 -0
  222. package/src2/__tests__/mir/arithmetic.test.ts +156 -0
  223. package/src2/__tests__/mir/control-flow.test.ts +242 -0
  224. package/src2/__tests__/mir/verify.test.ts +254 -0
  225. package/src2/__tests__/optimizer/block_merge.test.ts +84 -0
  226. package/src2/__tests__/optimizer/branch_simplify.test.ts +64 -0
  227. package/src2/__tests__/optimizer/constant_fold.test.ts +145 -0
  228. package/src2/__tests__/optimizer/copy_prop.test.ts +99 -0
  229. package/src2/__tests__/optimizer/dce.test.ts +83 -0
  230. package/src2/__tests__/optimizer/pipeline.test.ts +116 -0
  231. package/src2/emit/compile.ts +99 -0
  232. package/src2/emit/index.ts +222 -0
  233. package/src2/hir/lower.ts +428 -0
  234. package/src2/hir/types.ts +216 -0
  235. package/src2/lir/lower.ts +556 -0
  236. package/src2/lir/types.ts +109 -0
  237. package/src2/lir/verify.ts +129 -0
  238. package/src2/mir/lower.ts +1160 -0
  239. package/src2/mir/macro.ts +167 -0
  240. package/src2/mir/types.ts +106 -0
  241. package/src2/mir/verify.ts +218 -0
  242. package/src2/optimizer/block_merge.ts +93 -0
  243. package/src2/optimizer/branch_simplify.ts +27 -0
  244. package/src2/optimizer/constant_fold.ts +88 -0
  245. package/src2/optimizer/copy_prop.ts +106 -0
  246. package/src2/optimizer/dce.ts +133 -0
  247. package/src2/optimizer/pipeline.ts +44 -0
  248. package/tsconfig.json +2 -2
  249. package/src/__tests__/codegen.test.ts +0 -161
  250. package/src/__tests__/e2e.test.ts +0 -2039
  251. package/src/__tests__/entity-types.test.ts +0 -236
  252. package/src/__tests__/lowering.test.ts +0 -1185
  253. package/src/__tests__/macro.test.ts +0 -343
  254. package/src/__tests__/nbt.test.ts +0 -58
  255. package/src/__tests__/optimizer-advanced.test.ts +0 -144
  256. package/src/__tests__/optimizer.test.ts +0 -162
  257. package/src/__tests__/runtime.test.ts +0 -305
  258. package/src/__tests__/stdlib-advanced.test.ts +0 -379
  259. package/src/__tests__/stdlib-bigint.test.ts +0 -427
  260. package/src/__tests__/stdlib-math.test.ts +0 -374
  261. package/src/__tests__/stdlib-vec.test.ts +0 -259
  262. package/src/__tests__/structure-optimizer.test.ts +0 -38
  263. package/src/__tests__/var-allocator.test.ts +0 -75
  264. package/src/codegen/cmdblock/index.ts +0 -63
  265. package/src/codegen/mcfunction/index.ts +0 -662
  266. package/src/codegen/structure/index.ts +0 -346
  267. package/src/codegen/var-allocator.ts +0 -104
  268. package/src/ir/builder.ts +0 -116
  269. package/src/ir/types.ts +0 -134
  270. package/src/lowering/index.ts +0 -3860
  271. package/src/optimizer/commands.ts +0 -534
  272. package/src/optimizer/dce.ts +0 -679
  273. package/src/optimizer/passes.ts +0 -250
  274. package/src/optimizer/structure.ts +0 -450
@@ -0,0 +1,1030 @@
1
+ "use strict";
2
+ /**
3
+ * HIR → MIR Lowering — Stage 3 of the RedScript compiler pipeline.
4
+ *
5
+ * Converts structured HIR (if/while/break/continue) into an explicit CFG
6
+ * with 3-address instructions and unlimited fresh temporaries.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.lowerToMIR = lowerToMIR;
10
+ const macro_1 = require("./macro");
11
+ // ---------------------------------------------------------------------------
12
+ // Public API
13
+ // ---------------------------------------------------------------------------
14
+ function lowerToMIR(hir) {
15
+ // Build struct definitions: name → field names
16
+ const structDefs = new Map();
17
+ for (const s of hir.structs) {
18
+ structDefs.set(s.name, s.fields.map(f => f.name));
19
+ }
20
+ // Build impl method info: typeName → methodName → { hasSelf }
21
+ const implMethods = new Map();
22
+ for (const ib of hir.implBlocks) {
23
+ const methods = new Map();
24
+ for (const m of ib.methods) {
25
+ const hasSelf = m.params.length > 0 && m.params[0].name === 'self';
26
+ methods.set(m.name, { hasSelf });
27
+ }
28
+ implMethods.set(ib.typeName, methods);
29
+ }
30
+ // Pre-scan for macro functions
31
+ const macroInfo = (0, macro_1.detectMacroFunctions)(hir);
32
+ // Build function param info for call_macro generation at call sites
33
+ const fnParamInfo = new Map();
34
+ for (const f of hir.functions) {
35
+ fnParamInfo.set(f.name, f.params);
36
+ }
37
+ for (const ib of hir.implBlocks) {
38
+ for (const m of ib.methods) {
39
+ fnParamInfo.set(`${ib.typeName}::${m.name}`, m.params);
40
+ }
41
+ }
42
+ const allFunctions = [];
43
+ for (const f of hir.functions) {
44
+ const { fn, helpers } = lowerFunction(f, hir.namespace, structDefs, implMethods, macroInfo, fnParamInfo);
45
+ allFunctions.push(fn, ...helpers);
46
+ }
47
+ // Lower impl block methods
48
+ for (const ib of hir.implBlocks) {
49
+ for (const m of ib.methods) {
50
+ const { fn, helpers } = lowerImplMethod(m, ib.typeName, hir.namespace, structDefs, implMethods, macroInfo, fnParamInfo);
51
+ allFunctions.push(fn, ...helpers);
52
+ }
53
+ }
54
+ return {
55
+ functions: allFunctions,
56
+ namespace: hir.namespace,
57
+ objective: `__${hir.namespace}`,
58
+ };
59
+ }
60
+ // ---------------------------------------------------------------------------
61
+ // Function lowering context
62
+ // ---------------------------------------------------------------------------
63
+ class FnContext {
64
+ constructor(namespace, fnName, structDefs = new Map(), implMethods = new Map(), macroInfo = new Map(), fnParamInfo = new Map()) {
65
+ this.tempCounter = 0;
66
+ this.blockCounter = 0;
67
+ this.blocks = [];
68
+ /** Stack of (loopHeader, loopExit, continueTo) for break/continue */
69
+ this.loopStack = [];
70
+ /** Extracted helper functions for execute blocks */
71
+ this.helperFunctions = [];
72
+ /** Struct variable tracking: varName → { typeName, fields: fieldName → temp } */
73
+ this.structVars = new Map();
74
+ this.namespace = namespace;
75
+ this.fnName = fnName;
76
+ this.structDefs = structDefs;
77
+ this.implMethods = implMethods;
78
+ this.macroInfo = macroInfo;
79
+ this.fnParamInfo = fnParamInfo;
80
+ this.currentMacroParams = macroInfo.get(fnName)?.macroParams ?? new Set();
81
+ const entry = this.makeBlock('entry');
82
+ this.currentBlock = entry;
83
+ }
84
+ freshTemp() {
85
+ return `t${this.tempCounter++}`;
86
+ }
87
+ makeBlock(id) {
88
+ const block = {
89
+ id: id ?? `bb${this.blockCounter++}`,
90
+ instrs: [],
91
+ term: { kind: 'return', value: null }, // placeholder
92
+ preds: [],
93
+ };
94
+ this.blocks.push(block);
95
+ return block;
96
+ }
97
+ newBlock(prefix) {
98
+ return this.makeBlock(prefix ? `${prefix}_${this.blockCounter++}` : undefined);
99
+ }
100
+ emit(instr) {
101
+ this.currentBlock.instrs.push(instr);
102
+ }
103
+ terminate(term) {
104
+ this.currentBlock.term = term;
105
+ }
106
+ switchTo(block) {
107
+ this.currentBlock = block;
108
+ }
109
+ current() {
110
+ return this.currentBlock;
111
+ }
112
+ pushLoop(header, exit, continueTo) {
113
+ this.loopStack.push({ header, exit, continueTo: continueTo ?? header });
114
+ }
115
+ popLoop() {
116
+ this.loopStack.pop();
117
+ }
118
+ currentLoop() {
119
+ return this.loopStack[this.loopStack.length - 1];
120
+ }
121
+ getNamespace() {
122
+ return this.namespace;
123
+ }
124
+ getFnName() {
125
+ return this.fnName;
126
+ }
127
+ }
128
+ // ---------------------------------------------------------------------------
129
+ // Function lowering
130
+ // ---------------------------------------------------------------------------
131
+ function lowerFunction(fn, namespace, structDefs = new Map(), implMethods = new Map(), macroInfo = new Map(), fnParamInfo = new Map()) {
132
+ const ctx = new FnContext(namespace, fn.name, structDefs, implMethods, macroInfo, fnParamInfo);
133
+ const fnMacroInfo = macroInfo.get(fn.name);
134
+ // Create temps for parameters
135
+ const params = fn.params.map(p => {
136
+ const t = ctx.freshTemp();
137
+ return { name: t, isMacroParam: fnMacroInfo?.macroParams.has(p.name) ?? false };
138
+ });
139
+ // Map parameter names to their temps
140
+ const scope = new Map();
141
+ fn.params.forEach((p, i) => {
142
+ scope.set(p.name, params[i].name);
143
+ });
144
+ lowerBlock(fn.body, ctx, scope);
145
+ // If the current block doesn't have a real terminator, add void return
146
+ const cur = ctx.current();
147
+ if (isPlaceholderTerm(cur.term)) {
148
+ ctx.terminate({ kind: 'return', value: null });
149
+ }
150
+ // Remove unreachable blocks (dead continuations after return/break/continue)
151
+ const reachable = computeReachable(ctx.blocks, 'entry');
152
+ const liveBlocks = ctx.blocks.filter(b => reachable.has(b.id));
153
+ // Fill predecessor lists
154
+ computePreds(liveBlocks);
155
+ const result = {
156
+ name: fn.name,
157
+ params,
158
+ blocks: liveBlocks,
159
+ entry: 'entry',
160
+ isMacro: fnMacroInfo != null,
161
+ };
162
+ return { fn: result, helpers: ctx.helperFunctions };
163
+ }
164
+ function lowerImplMethod(method, typeName, namespace, structDefs, implMethods, macroInfo = new Map(), fnParamInfo = new Map()) {
165
+ const fnName = `${typeName}::${method.name}`;
166
+ const ctx = new FnContext(namespace, fnName, structDefs, implMethods, macroInfo, fnParamInfo);
167
+ const fields = structDefs.get(typeName) ?? [];
168
+ const hasSelf = method.params.length > 0 && method.params[0].name === 'self';
169
+ const params = [];
170
+ const scope = new Map();
171
+ if (hasSelf) {
172
+ // Self fields become the first N params (one per struct field)
173
+ const selfFields = new Map();
174
+ for (const fieldName of fields) {
175
+ const t = ctx.freshTemp();
176
+ params.push({ name: t, isMacroParam: false });
177
+ selfFields.set(fieldName, t);
178
+ }
179
+ ctx.structVars.set('self', { typeName, fields: selfFields });
180
+ // Remaining params (after self)
181
+ for (let i = 1; i < method.params.length; i++) {
182
+ const t = ctx.freshTemp();
183
+ params.push({ name: t, isMacroParam: false });
184
+ scope.set(method.params[i].name, t);
185
+ }
186
+ }
187
+ else {
188
+ // Static method — regular params
189
+ for (const p of method.params) {
190
+ const t = ctx.freshTemp();
191
+ params.push({ name: t, isMacroParam: false });
192
+ scope.set(p.name, t);
193
+ }
194
+ }
195
+ lowerBlock(method.body, ctx, scope);
196
+ const cur = ctx.current();
197
+ if (isPlaceholderTerm(cur.term)) {
198
+ ctx.terminate({ kind: 'return', value: null });
199
+ }
200
+ const reachable = computeReachable(ctx.blocks, 'entry');
201
+ const liveBlocks = ctx.blocks.filter(b => reachable.has(b.id));
202
+ computePreds(liveBlocks);
203
+ const result = {
204
+ name: fnName,
205
+ params,
206
+ blocks: liveBlocks,
207
+ entry: 'entry',
208
+ isMacro: macroInfo.has(fnName),
209
+ };
210
+ return { fn: result, helpers: ctx.helperFunctions };
211
+ }
212
+ function isPlaceholderTerm(term) {
213
+ // Our placeholder is a return null that was set in makeBlock
214
+ return term.kind === 'return' && term.value === null;
215
+ }
216
+ function computeReachable(blocks, entry) {
217
+ const reachable = new Set();
218
+ const queue = [entry];
219
+ while (queue.length > 0) {
220
+ const id = queue.shift();
221
+ if (reachable.has(id))
222
+ continue;
223
+ reachable.add(id);
224
+ const block = blocks.find(b => b.id === id);
225
+ if (block) {
226
+ for (const t of getTermTargets(block.term)) {
227
+ if (!reachable.has(t))
228
+ queue.push(t);
229
+ }
230
+ }
231
+ }
232
+ return reachable;
233
+ }
234
+ function computePreds(blocks) {
235
+ // Clear all preds
236
+ for (const b of blocks)
237
+ b.preds = [];
238
+ for (const b of blocks) {
239
+ const targets = getTermTargets(b.term);
240
+ for (const t of targets) {
241
+ const target = blocks.find(bb => bb.id === t);
242
+ if (target && !target.preds.includes(b.id)) {
243
+ target.preds.push(b.id);
244
+ }
245
+ }
246
+ }
247
+ }
248
+ function getTermTargets(term) {
249
+ switch (term.kind) {
250
+ case 'jump': return [term.target];
251
+ case 'branch': return [term.then, term.else];
252
+ case 'return': return [];
253
+ default: return [];
254
+ }
255
+ }
256
+ // ---------------------------------------------------------------------------
257
+ // Block / statement lowering
258
+ // ---------------------------------------------------------------------------
259
+ function lowerBlock(stmts, ctx, scope) {
260
+ for (const stmt of stmts) {
261
+ lowerStmt(stmt, ctx, scope);
262
+ }
263
+ }
264
+ function lowerStmt(stmt, ctx, scope) {
265
+ switch (stmt.kind) {
266
+ case 'let': {
267
+ if (stmt.init.kind === 'struct_lit') {
268
+ // Struct literal: create per-field temps
269
+ const typeName = (stmt.type?.kind === 'struct') ? stmt.type.name : '__anon';
270
+ const fieldTemps = new Map();
271
+ for (const field of stmt.init.fields) {
272
+ const val = lowerExpr(field.value, ctx, scope);
273
+ const t = ctx.freshTemp();
274
+ ctx.emit({ kind: 'copy', dst: t, src: val });
275
+ fieldTemps.set(field.name, t);
276
+ }
277
+ ctx.structVars.set(stmt.name, { typeName, fields: fieldTemps });
278
+ }
279
+ else if (stmt.type?.kind === 'struct') {
280
+ // Struct-typed let with non-literal init (e.g., call returning struct)
281
+ const fields = ctx.structDefs.get(stmt.type.name);
282
+ if (fields) {
283
+ lowerExpr(stmt.init, ctx, scope);
284
+ // Copy from return field slots into struct variable temps
285
+ const fieldTemps = new Map();
286
+ for (const fieldName of fields) {
287
+ const t = ctx.freshTemp();
288
+ ctx.emit({ kind: 'copy', dst: t, src: { kind: 'temp', name: `__rf_${fieldName}` } });
289
+ fieldTemps.set(fieldName, t);
290
+ }
291
+ ctx.structVars.set(stmt.name, { typeName: stmt.type.name, fields: fieldTemps });
292
+ }
293
+ else {
294
+ const valOp = lowerExpr(stmt.init, ctx, scope);
295
+ const t = ctx.freshTemp();
296
+ ctx.emit({ kind: 'copy', dst: t, src: valOp });
297
+ scope.set(stmt.name, t);
298
+ }
299
+ }
300
+ else {
301
+ const valOp = lowerExpr(stmt.init, ctx, scope);
302
+ const t = ctx.freshTemp();
303
+ ctx.emit({ kind: 'copy', dst: t, src: valOp });
304
+ scope.set(stmt.name, t);
305
+ }
306
+ break;
307
+ }
308
+ case 'expr': {
309
+ lowerExpr(stmt.expr, ctx, scope);
310
+ break;
311
+ }
312
+ case 'return': {
313
+ if (stmt.value?.kind === 'struct_lit') {
314
+ // Struct return — copy each field to return field slots
315
+ for (const field of stmt.value.fields) {
316
+ const val = lowerExpr(field.value, ctx, scope);
317
+ ctx.emit({ kind: 'copy', dst: `__rf_${field.name}`, src: val });
318
+ }
319
+ ctx.terminate({ kind: 'return', value: null });
320
+ }
321
+ else {
322
+ const val = stmt.value ? lowerExpr(stmt.value, ctx, scope) : null;
323
+ ctx.terminate({ kind: 'return', value: val });
324
+ }
325
+ // Create a dead block for any subsequent statements
326
+ const dead = ctx.newBlock('post_ret');
327
+ ctx.switchTo(dead);
328
+ break;
329
+ }
330
+ case 'break': {
331
+ const loop = ctx.currentLoop();
332
+ if (!loop)
333
+ throw new Error('break outside loop');
334
+ ctx.terminate({ kind: 'jump', target: loop.exit });
335
+ const dead = ctx.newBlock('post_break');
336
+ ctx.switchTo(dead);
337
+ break;
338
+ }
339
+ case 'continue': {
340
+ const loop = ctx.currentLoop();
341
+ if (!loop)
342
+ throw new Error('continue outside loop');
343
+ ctx.terminate({ kind: 'jump', target: loop.continueTo });
344
+ const dead = ctx.newBlock('post_continue');
345
+ ctx.switchTo(dead);
346
+ break;
347
+ }
348
+ case 'if': {
349
+ const condOp = lowerExpr(stmt.cond, ctx, scope);
350
+ const thenBlock = ctx.newBlock('then');
351
+ const mergeBlock = ctx.newBlock('merge');
352
+ const elseBlock = stmt.else_ ? ctx.newBlock('else') : mergeBlock;
353
+ ctx.terminate({ kind: 'branch', cond: condOp, then: thenBlock.id, else: elseBlock.id });
354
+ // Then branch
355
+ ctx.switchTo(thenBlock);
356
+ lowerBlock(stmt.then, ctx, new Map(scope));
357
+ if (isPlaceholderTerm(ctx.current().term)) {
358
+ ctx.terminate({ kind: 'jump', target: mergeBlock.id });
359
+ }
360
+ // Else branch
361
+ if (stmt.else_) {
362
+ ctx.switchTo(elseBlock);
363
+ lowerBlock(stmt.else_, ctx, new Map(scope));
364
+ if (isPlaceholderTerm(ctx.current().term)) {
365
+ ctx.terminate({ kind: 'jump', target: mergeBlock.id });
366
+ }
367
+ }
368
+ ctx.switchTo(mergeBlock);
369
+ break;
370
+ }
371
+ case 'while': {
372
+ const headerBlock = ctx.newBlock('loop_header');
373
+ const bodyBlock = ctx.newBlock('loop_body');
374
+ const exitBlock = ctx.newBlock('loop_exit');
375
+ // If there's a step block (for/for_range), create a latch block that
376
+ // executes the step and then jumps to the header. Continue targets the
377
+ // latch so the increment always runs.
378
+ let latchBlock = null;
379
+ if (stmt.step && stmt.step.length > 0) {
380
+ latchBlock = ctx.newBlock('loop_latch');
381
+ }
382
+ const continueTarget = latchBlock ? latchBlock.id : headerBlock.id;
383
+ // Jump from current block to header
384
+ ctx.terminate({ kind: 'jump', target: headerBlock.id });
385
+ // Header: evaluate condition
386
+ ctx.switchTo(headerBlock);
387
+ const condOp = lowerExpr(stmt.cond, ctx, scope);
388
+ ctx.terminate({ kind: 'branch', cond: condOp, then: bodyBlock.id, else: exitBlock.id });
389
+ // Body
390
+ ctx.switchTo(bodyBlock);
391
+ ctx.pushLoop(headerBlock.id, exitBlock.id, continueTarget);
392
+ lowerBlock(stmt.body, ctx, new Map(scope));
393
+ ctx.popLoop();
394
+ if (isPlaceholderTerm(ctx.current().term)) {
395
+ ctx.terminate({ kind: 'jump', target: continueTarget });
396
+ }
397
+ // Latch block (step): execute increment, then jump to header
398
+ if (latchBlock && stmt.step) {
399
+ ctx.switchTo(latchBlock);
400
+ lowerBlock(stmt.step, ctx, new Map(scope));
401
+ if (isPlaceholderTerm(ctx.current().term)) {
402
+ ctx.terminate({ kind: 'jump', target: headerBlock.id });
403
+ }
404
+ }
405
+ ctx.switchTo(exitBlock);
406
+ break;
407
+ }
408
+ case 'foreach': {
409
+ // foreach is MC-specific entity iteration — lower to call_context
410
+ // For now, extract body into a helper and emit call_context
411
+ const helperName = `${ctx.getFnName()}__foreach_${ctx.freshTemp()}`;
412
+ const subcommands = [];
413
+ // The iterable should be a selector expression
414
+ if (stmt.iterable.kind === 'selector') {
415
+ subcommands.push({ kind: 'as', selector: stmt.iterable.raw });
416
+ }
417
+ if (stmt.executeContext === '@s') {
418
+ subcommands.push({ kind: 'at_self' });
419
+ }
420
+ // Build helper function body as MIR
421
+ const helperCtx = new FnContext(ctx.getNamespace(), helperName, ctx.structDefs, ctx.implMethods);
422
+ const helperScope = new Map(scope);
423
+ lowerBlock(stmt.body, helperCtx, helperScope);
424
+ if (isPlaceholderTerm(helperCtx.current().term)) {
425
+ helperCtx.terminate({ kind: 'return', value: null });
426
+ }
427
+ const helperReachable = computeReachable(helperCtx.blocks, 'entry');
428
+ const helperBlocks = helperCtx.blocks.filter(b => helperReachable.has(b.id));
429
+ computePreds(helperBlocks);
430
+ ctx.helperFunctions.push({
431
+ name: helperName,
432
+ params: [],
433
+ blocks: helperBlocks,
434
+ entry: 'entry',
435
+ isMacro: false,
436
+ });
437
+ ctx.emit({ kind: 'call_context', fn: helperName, subcommands });
438
+ break;
439
+ }
440
+ case 'execute': {
441
+ // Extract body into a helper function, emit call_context
442
+ const helperName = `${ctx.getFnName()}__exec_${ctx.freshTemp()}`;
443
+ const subcommands = stmt.subcommands.map(lowerExecuteSubcmd);
444
+ const helperCtx = new FnContext(ctx.getNamespace(), helperName, ctx.structDefs, ctx.implMethods);
445
+ const helperScope = new Map(scope);
446
+ lowerBlock(stmt.body, helperCtx, helperScope);
447
+ if (isPlaceholderTerm(helperCtx.current().term)) {
448
+ helperCtx.terminate({ kind: 'return', value: null });
449
+ }
450
+ const execReachable = computeReachable(helperCtx.blocks, 'entry');
451
+ const execBlocks = helperCtx.blocks.filter(b => execReachable.has(b.id));
452
+ computePreds(execBlocks);
453
+ ctx.helperFunctions.push({
454
+ name: helperName,
455
+ params: [],
456
+ blocks: execBlocks,
457
+ entry: 'entry',
458
+ isMacro: false,
459
+ });
460
+ ctx.emit({ kind: 'call_context', fn: helperName, subcommands });
461
+ break;
462
+ }
463
+ case 'match': {
464
+ // Lower match as chained if/else
465
+ const matchVal = lowerExpr(stmt.expr, ctx, scope);
466
+ const mergeBlock = ctx.newBlock('match_merge');
467
+ for (let i = 0; i < stmt.arms.length; i++) {
468
+ const arm = stmt.arms[i];
469
+ if (arm.pattern === null) {
470
+ // Default arm — just emit the body
471
+ lowerBlock(arm.body, ctx, new Map(scope));
472
+ if (isPlaceholderTerm(ctx.current().term)) {
473
+ ctx.terminate({ kind: 'jump', target: mergeBlock.id });
474
+ }
475
+ }
476
+ else {
477
+ const patOp = lowerExpr(arm.pattern, ctx, scope);
478
+ const cmpTemp = ctx.freshTemp();
479
+ ctx.emit({ kind: 'cmp', dst: cmpTemp, op: 'eq', a: matchVal, b: patOp });
480
+ const armBody = ctx.newBlock('match_arm');
481
+ const nextArm = ctx.newBlock('match_next');
482
+ ctx.terminate({ kind: 'branch', cond: { kind: 'temp', name: cmpTemp }, then: armBody.id, else: nextArm.id });
483
+ ctx.switchTo(armBody);
484
+ lowerBlock(arm.body, ctx, new Map(scope));
485
+ if (isPlaceholderTerm(ctx.current().term)) {
486
+ ctx.terminate({ kind: 'jump', target: mergeBlock.id });
487
+ }
488
+ ctx.switchTo(nextArm);
489
+ }
490
+ }
491
+ // If no default arm matched, jump to merge
492
+ if (isPlaceholderTerm(ctx.current().term)) {
493
+ ctx.terminate({ kind: 'jump', target: mergeBlock.id });
494
+ }
495
+ ctx.switchTo(mergeBlock);
496
+ break;
497
+ }
498
+ case 'raw': {
499
+ // Raw commands are opaque at MIR level — emit as a call to a synthetic raw function
500
+ // For now, pass through as a call with no args (will be handled in LIR)
501
+ ctx.emit({ kind: 'call', dst: null, fn: `__raw:${stmt.cmd}`, args: [] });
502
+ break;
503
+ }
504
+ default: {
505
+ const _exhaustive = stmt;
506
+ throw new Error(`Unknown HIR statement kind: ${_exhaustive.kind}`);
507
+ }
508
+ }
509
+ }
510
+ // ---------------------------------------------------------------------------
511
+ // Expression lowering → produces an Operand (temp or const)
512
+ // ---------------------------------------------------------------------------
513
+ function lowerExpr(expr, ctx, scope) {
514
+ switch (expr.kind) {
515
+ case 'int_lit':
516
+ return { kind: 'const', value: expr.value };
517
+ case 'float_lit':
518
+ // float is ×1000 fixed-point in RedScript
519
+ return { kind: 'const', value: expr.value };
520
+ case 'byte_lit':
521
+ case 'short_lit':
522
+ case 'long_lit':
523
+ case 'double_lit':
524
+ return { kind: 'const', value: expr.value };
525
+ case 'bool_lit': {
526
+ return { kind: 'const', value: expr.value ? 1 : 0 };
527
+ }
528
+ case 'struct_lit': {
529
+ // Struct literal in expression context (not let/return — those handle it directly).
530
+ // Lower each field value but return a placeholder since the struct
531
+ // is tracked via structVars at the statement level.
532
+ for (const field of expr.fields) {
533
+ lowerExpr(field.value, ctx, scope);
534
+ }
535
+ const t = ctx.freshTemp();
536
+ ctx.emit({ kind: 'const', dst: t, value: 0 });
537
+ return { kind: 'temp', name: t };
538
+ }
539
+ case 'str_lit':
540
+ case 'range_lit':
541
+ case 'array_lit':
542
+ case 'rel_coord':
543
+ case 'local_coord':
544
+ case 'mc_name':
545
+ case 'blockpos':
546
+ case 'selector':
547
+ case 'str_interp':
548
+ case 'f_string':
549
+ case 'is_check':
550
+ case 'lambda': {
551
+ // MC-specific / complex types — opaque at MIR level
552
+ // Emit as const 0 placeholder; these are handled in LIR lowering
553
+ const t = ctx.freshTemp();
554
+ ctx.emit({ kind: 'const', dst: t, value: 0 });
555
+ return { kind: 'temp', name: t };
556
+ }
557
+ case 'ident': {
558
+ const temp = scope.get(expr.name);
559
+ if (temp)
560
+ return { kind: 'temp', name: temp };
561
+ // Unresolved ident — could be a global or external reference
562
+ const t = ctx.freshTemp();
563
+ ctx.emit({ kind: 'copy', dst: t, src: { kind: 'const', value: 0 } });
564
+ scope.set(expr.name, t);
565
+ return { kind: 'temp', name: t };
566
+ }
567
+ case 'binary': {
568
+ // Handle short-circuit && and ||
569
+ if (expr.op === '&&') {
570
+ return lowerShortCircuitAnd(expr, ctx, scope);
571
+ }
572
+ if (expr.op === '||') {
573
+ return lowerShortCircuitOr(expr, ctx, scope);
574
+ }
575
+ const left = lowerExpr(expr.left, ctx, scope);
576
+ const right = lowerExpr(expr.right, ctx, scope);
577
+ const t = ctx.freshTemp();
578
+ // Map HIR binary ops to MIR instructions
579
+ const arithmeticOps = {
580
+ '+': 'add', '-': 'sub', '*': 'mul', '/': 'div', '%': 'mod',
581
+ };
582
+ const cmpOps = {
583
+ '==': 'eq', '!=': 'ne', '<': 'lt', '<=': 'le', '>': 'gt', '>=': 'ge',
584
+ };
585
+ if (expr.op in arithmeticOps) {
586
+ ctx.emit({ kind: arithmeticOps[expr.op], dst: t, a: left, b: right });
587
+ }
588
+ else if (expr.op in cmpOps) {
589
+ ctx.emit({ kind: 'cmp', dst: t, op: cmpOps[expr.op], a: left, b: right });
590
+ }
591
+ else {
592
+ throw new Error(`Unknown binary op: ${expr.op}`);
593
+ }
594
+ return { kind: 'temp', name: t };
595
+ }
596
+ case 'unary': {
597
+ const operand = lowerExpr(expr.operand, ctx, scope);
598
+ const t = ctx.freshTemp();
599
+ if (expr.op === '-') {
600
+ ctx.emit({ kind: 'neg', dst: t, src: operand });
601
+ }
602
+ else if (expr.op === '!') {
603
+ ctx.emit({ kind: 'not', dst: t, src: operand });
604
+ }
605
+ return { kind: 'temp', name: t };
606
+ }
607
+ case 'assign': {
608
+ const val = lowerExpr(expr.value, ctx, scope);
609
+ // Reuse the existing temp for this variable so that updates inside
610
+ // if/while bodies are visible to outer code (we target mutable
611
+ // scoreboard slots, not true SSA registers).
612
+ const existing = scope.get(expr.target);
613
+ const t = existing ?? ctx.freshTemp();
614
+ ctx.emit({ kind: 'copy', dst: t, src: val });
615
+ scope.set(expr.target, t);
616
+ return val;
617
+ }
618
+ case 'member_assign': {
619
+ const val = lowerExpr(expr.value, ctx, scope);
620
+ // Struct field assignment: v.x = val → copy val to v's x temp
621
+ if (expr.obj.kind === 'ident') {
622
+ const sv = ctx.structVars.get(expr.obj.name);
623
+ if (sv) {
624
+ const fieldTemp = sv.fields.get(expr.field);
625
+ if (fieldTemp) {
626
+ ctx.emit({ kind: 'copy', dst: fieldTemp, src: val });
627
+ return val;
628
+ }
629
+ }
630
+ }
631
+ return val;
632
+ }
633
+ case 'member': {
634
+ // Struct field access: v.x → return v's x temp
635
+ if (expr.obj.kind === 'ident') {
636
+ const sv = ctx.structVars.get(expr.obj.name);
637
+ if (sv) {
638
+ const fieldTemp = sv.fields.get(expr.field);
639
+ if (fieldTemp)
640
+ return { kind: 'temp', name: fieldTemp };
641
+ }
642
+ }
643
+ // Fallback: opaque
644
+ const obj = lowerExpr(expr.obj, ctx, scope);
645
+ const t = ctx.freshTemp();
646
+ ctx.emit({ kind: 'copy', dst: t, src: obj });
647
+ return { kind: 'temp', name: t };
648
+ }
649
+ case 'index': {
650
+ const obj = lowerExpr(expr.obj, ctx, scope);
651
+ const idx = lowerExpr(expr.index, ctx, scope);
652
+ const t = ctx.freshTemp();
653
+ ctx.emit({ kind: 'copy', dst: t, src: obj });
654
+ return { kind: 'temp', name: t };
655
+ }
656
+ case 'call': {
657
+ // Handle builtin calls → raw MC commands
658
+ if (macro_1.BUILTIN_SET.has(expr.fn)) {
659
+ const cmd = formatBuiltinCall(expr.fn, expr.args, ctx.currentMacroParams);
660
+ ctx.emit({ kind: 'call', dst: null, fn: `__raw:${cmd}`, args: [] });
661
+ const t = ctx.freshTemp();
662
+ ctx.emit({ kind: 'const', dst: t, value: 0 });
663
+ return { kind: 'temp', name: t };
664
+ }
665
+ // Check for struct instance method call: parser desugars v.method() → call('method', [v, ...])
666
+ if (expr.args.length > 0 && expr.args[0].kind === 'ident') {
667
+ const sv = ctx.structVars.get(expr.args[0].name);
668
+ if (sv) {
669
+ const methodInfo = ctx.implMethods.get(sv.typeName)?.get(expr.fn);
670
+ if (methodInfo?.hasSelf) {
671
+ // Build args: self fields first, then remaining explicit args
672
+ const fields = ctx.structDefs.get(sv.typeName) ?? [];
673
+ const selfArgs = fields.map(f => {
674
+ const temp = sv.fields.get(f);
675
+ return temp ? { kind: 'temp', name: temp } : { kind: 'const', value: 0 };
676
+ });
677
+ const explicitArgs = expr.args.slice(1).map(a => lowerExpr(a, ctx, scope));
678
+ const allArgs = [...selfArgs, ...explicitArgs];
679
+ const t = ctx.freshTemp();
680
+ ctx.emit({ kind: 'call', dst: t, fn: `${sv.typeName}::${expr.fn}`, args: allArgs });
681
+ return { kind: 'temp', name: t };
682
+ }
683
+ }
684
+ }
685
+ // Check if calling a macro function → emit call_macro
686
+ const targetMacro = ctx.macroInfo.get(expr.fn);
687
+ if (targetMacro) {
688
+ const args = expr.args.map(a => lowerExpr(a, ctx, scope));
689
+ const targetParams = ctx.fnParamInfo.get(expr.fn) ?? [];
690
+ const macroArgs = [];
691
+ for (let i = 0; i < targetParams.length && i < args.length; i++) {
692
+ const paramName = targetParams[i].name;
693
+ if (targetMacro.macroParams.has(paramName)) {
694
+ const paramTypeName = targetMacro.paramTypes.get(paramName) ?? 'int';
695
+ const isFloat = paramTypeName === 'float';
696
+ macroArgs.push({
697
+ name: paramName,
698
+ value: args[i],
699
+ type: isFloat ? 'double' : 'int',
700
+ scale: isFloat ? 0.01 : 1,
701
+ });
702
+ }
703
+ }
704
+ const t = ctx.freshTemp();
705
+ ctx.emit({ kind: 'call_macro', dst: t, fn: expr.fn, args: macroArgs });
706
+ return { kind: 'temp', name: t };
707
+ }
708
+ const args = expr.args.map(a => lowerExpr(a, ctx, scope));
709
+ const t = ctx.freshTemp();
710
+ ctx.emit({ kind: 'call', dst: t, fn: expr.fn, args });
711
+ return { kind: 'temp', name: t };
712
+ }
713
+ case 'invoke': {
714
+ // Check for struct method call: v.method(args)
715
+ if (expr.callee.kind === 'member' && expr.callee.obj.kind === 'ident') {
716
+ const sv = ctx.structVars.get(expr.callee.obj.name);
717
+ if (sv) {
718
+ const methodInfo = ctx.implMethods.get(sv.typeName)?.get(expr.callee.field);
719
+ if (methodInfo?.hasSelf) {
720
+ // Build args: self fields first, then explicit args
721
+ const fields = ctx.structDefs.get(sv.typeName) ?? [];
722
+ const selfArgs = fields.map(f => {
723
+ const temp = sv.fields.get(f);
724
+ return temp ? { kind: 'temp', name: temp } : { kind: 'const', value: 0 };
725
+ });
726
+ const explicitArgs = expr.args.map(a => lowerExpr(a, ctx, scope));
727
+ const allArgs = [...selfArgs, ...explicitArgs];
728
+ const t = ctx.freshTemp();
729
+ ctx.emit({ kind: 'call', dst: t, fn: `${sv.typeName}::${expr.callee.field}`, args: allArgs });
730
+ return { kind: 'temp', name: t };
731
+ }
732
+ }
733
+ }
734
+ // Fallback: generic invoke
735
+ const calleeOp = lowerExpr(expr.callee, ctx, scope);
736
+ const args = expr.args.map(a => lowerExpr(a, ctx, scope));
737
+ const t = ctx.freshTemp();
738
+ ctx.emit({ kind: 'call', dst: t, fn: '__invoke', args: [calleeOp, ...args] });
739
+ return { kind: 'temp', name: t };
740
+ }
741
+ case 'static_call': {
742
+ const args = expr.args.map(a => lowerExpr(a, ctx, scope));
743
+ const t = ctx.freshTemp();
744
+ ctx.emit({ kind: 'call', dst: t, fn: `${expr.type}::${expr.method}`, args });
745
+ return { kind: 'temp', name: t };
746
+ }
747
+ default: {
748
+ const _exhaustive = expr;
749
+ throw new Error(`Unknown HIR expression kind: ${_exhaustive.kind}`);
750
+ }
751
+ }
752
+ }
753
+ // ---------------------------------------------------------------------------
754
+ // Short-circuit lowering
755
+ // ---------------------------------------------------------------------------
756
+ function lowerShortCircuitAnd(expr, ctx, scope) {
757
+ // a && b → if(a) { b } else { 0 }
758
+ const left = lowerExpr(expr.left, ctx, scope);
759
+ const result = ctx.freshTemp();
760
+ const evalRight = ctx.newBlock('and_right');
761
+ const merge = ctx.newBlock('and_merge');
762
+ const falseBlock = ctx.newBlock('and_false');
763
+ ctx.terminate({ kind: 'branch', cond: left, then: evalRight.id, else: falseBlock.id });
764
+ ctx.switchTo(evalRight);
765
+ const right = lowerExpr(expr.right, ctx, scope);
766
+ ctx.emit({ kind: 'copy', dst: result, src: right });
767
+ ctx.terminate({ kind: 'jump', target: merge.id });
768
+ ctx.switchTo(falseBlock);
769
+ ctx.emit({ kind: 'const', dst: result, value: 0 });
770
+ ctx.terminate({ kind: 'jump', target: merge.id });
771
+ ctx.switchTo(merge);
772
+ return { kind: 'temp', name: result };
773
+ }
774
+ function lowerShortCircuitOr(expr, ctx, scope) {
775
+ // a || b → if(a) { 1 } else { b }
776
+ const left = lowerExpr(expr.left, ctx, scope);
777
+ const result = ctx.freshTemp();
778
+ const trueBlock = ctx.newBlock('or_true');
779
+ const evalRight = ctx.newBlock('or_right');
780
+ const merge = ctx.newBlock('or_merge');
781
+ ctx.terminate({ kind: 'branch', cond: left, then: trueBlock.id, else: evalRight.id });
782
+ ctx.switchTo(trueBlock);
783
+ ctx.emit({ kind: 'const', dst: result, value: 1 });
784
+ ctx.terminate({ kind: 'jump', target: merge.id });
785
+ ctx.switchTo(evalRight);
786
+ const right = lowerExpr(expr.right, ctx, scope);
787
+ ctx.emit({ kind: 'copy', dst: result, src: right });
788
+ ctx.terminate({ kind: 'jump', target: merge.id });
789
+ ctx.switchTo(merge);
790
+ return { kind: 'temp', name: result };
791
+ }
792
+ // ---------------------------------------------------------------------------
793
+ // Execute subcommand lowering
794
+ // ---------------------------------------------------------------------------
795
+ function lowerExecuteSubcmd(sub) {
796
+ switch (sub.kind) {
797
+ case 'as':
798
+ return { kind: 'as', selector: selectorToString(sub.selector) };
799
+ case 'at':
800
+ return { kind: 'at', selector: selectorToString(sub.selector) };
801
+ case 'positioned':
802
+ return { kind: 'positioned', x: sub.x, y: sub.y, z: sub.z };
803
+ case 'rotated':
804
+ return { kind: 'rotated', yaw: sub.yaw, pitch: sub.pitch };
805
+ case 'in':
806
+ return { kind: 'in', dimension: sub.dimension };
807
+ case 'anchored':
808
+ return { kind: 'anchored', anchor: sub.anchor };
809
+ case 'positioned_as':
810
+ return { kind: 'at', selector: selectorToString(sub.selector) };
811
+ case 'rotated_as':
812
+ return { kind: 'rotated', yaw: '0', pitch: '0' };
813
+ case 'facing':
814
+ return { kind: 'positioned', x: sub.x, y: sub.y, z: sub.z };
815
+ case 'facing_entity':
816
+ return { kind: 'at', selector: selectorToString(sub.selector) };
817
+ case 'align':
818
+ return { kind: 'positioned', x: '0', y: '0', z: '0' };
819
+ case 'on':
820
+ return { kind: 'at_self' };
821
+ case 'summon':
822
+ return { kind: 'at_self' };
823
+ case 'if_entity':
824
+ case 'unless_entity':
825
+ case 'if_block':
826
+ case 'unless_block':
827
+ case 'if_score':
828
+ case 'unless_score':
829
+ case 'if_score_range':
830
+ case 'unless_score_range':
831
+ case 'store_result':
832
+ case 'store_success':
833
+ // These are condition subcommands — pass through as-is for now
834
+ return { kind: 'at_self' };
835
+ default: {
836
+ const _exhaustive = sub;
837
+ throw new Error(`Unknown execute subcommand kind: ${_exhaustive.kind}`);
838
+ }
839
+ }
840
+ }
841
+ function selectorToString(sel) {
842
+ // EntitySelector has kind like '@a', '@e', '@s', etc.
843
+ // Filters are key=value pairs that become [key=value,key=value]
844
+ if (!sel.filters || Object.keys(sel.filters).length === 0) {
845
+ return sel.kind;
846
+ }
847
+ const parts = [];
848
+ for (const [key, value] of Object.entries(sel.filters)) {
849
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
850
+ // Range filter: { min, max } → key=min..max
851
+ const rangeObj = value;
852
+ if (rangeObj.min !== undefined && rangeObj.max !== undefined) {
853
+ parts.push(`${key}=${rangeObj.min}..${rangeObj.max}`);
854
+ }
855
+ else if (rangeObj.max !== undefined) {
856
+ parts.push(`${key}=..${rangeObj.max}`);
857
+ }
858
+ else if (rangeObj.min !== undefined) {
859
+ parts.push(`${key}=${rangeObj.min}..`);
860
+ }
861
+ }
862
+ else {
863
+ parts.push(`${key}=${value}`);
864
+ }
865
+ }
866
+ return `${sel.kind}[${parts.join(',')}]`;
867
+ }
868
+ // ---------------------------------------------------------------------------
869
+ // Builtin call formatting → raw MC command strings
870
+ // ---------------------------------------------------------------------------
871
+ const MACRO_SENTINEL = '\x01';
872
+ /**
873
+ * Format a builtin call as a raw MC command string.
874
+ * If any argument uses a macro param, the command is prefixed with \x01
875
+ * (converted to $ in LIR emission).
876
+ */
877
+ function formatBuiltinCall(fn, args, macroParams) {
878
+ const fmtArgs = args.map(a => exprToCommandArg(a, macroParams));
879
+ const strs = fmtArgs.map(a => a.str);
880
+ const hasMacro = fmtArgs.some(a => a.isMacro);
881
+ let cmd;
882
+ switch (fn) {
883
+ case 'summon': {
884
+ const [type, x, y, z, nbt] = strs;
885
+ const pos = [x ?? '~', y ?? '~', z ?? '~'].join(' ');
886
+ cmd = nbt ? `summon ${type} ${pos} ${nbt}` : `summon ${type} ${pos}`;
887
+ break;
888
+ }
889
+ case 'particle': {
890
+ const [name, x, y, z, ...rest] = strs;
891
+ const pos = [x ?? '~', y ?? '~', z ?? '~'].join(' ');
892
+ const extra = rest.filter(v => v !== undefined);
893
+ cmd = extra.length > 0 ? `particle ${name} ${pos} ${extra.join(' ')}` : `particle ${name} ${pos}`;
894
+ break;
895
+ }
896
+ case 'setblock': {
897
+ const [x, y, z, block] = strs;
898
+ cmd = `setblock ${x} ${y} ${z} ${block}`;
899
+ break;
900
+ }
901
+ case 'fill': {
902
+ const [x1, y1, z1, x2, y2, z2, block] = strs;
903
+ cmd = `fill ${x1} ${y1} ${z1} ${x2} ${y2} ${z2} ${block}`;
904
+ break;
905
+ }
906
+ case 'say':
907
+ cmd = `say ${strs[0] ?? ''}`;
908
+ break;
909
+ case 'tell':
910
+ case 'tellraw':
911
+ cmd = `tellraw ${strs[0]} {"text":"${strs[1]}"}`;
912
+ break;
913
+ case 'title':
914
+ cmd = `title ${strs[0]} title {"text":"${strs[1]}"}`;
915
+ break;
916
+ case 'actionbar':
917
+ cmd = `title ${strs[0]} actionbar {"text":"${strs[1]}"}`;
918
+ break;
919
+ case 'subtitle':
920
+ cmd = `title ${strs[0]} subtitle {"text":"${strs[1]}"}`;
921
+ break;
922
+ case 'title_times':
923
+ cmd = `title ${strs[0]} times ${strs[1]} ${strs[2]} ${strs[3]}`;
924
+ break;
925
+ case 'announce':
926
+ cmd = `tellraw @a {"text":"${strs[0]}"}`;
927
+ break;
928
+ case 'give': {
929
+ const nbt = strs[3] ? strs[3] : '';
930
+ cmd = `give ${strs[0]} ${strs[1]}${nbt} ${strs[2] ?? '1'}`;
931
+ break;
932
+ }
933
+ case 'kill':
934
+ cmd = `kill ${strs[0] ?? '@s'}`;
935
+ break;
936
+ case 'effect':
937
+ cmd = `effect give ${strs[0]} ${strs[1]} ${strs[2] ?? '30'} ${strs[3] ?? '0'}`;
938
+ break;
939
+ case 'effect_clear':
940
+ cmd = strs[1] ? `effect clear ${strs[0]} ${strs[1]}` : `effect clear ${strs[0]}`;
941
+ break;
942
+ case 'playsound':
943
+ cmd = ['playsound', ...strs].filter(Boolean).join(' ');
944
+ break;
945
+ case 'clear':
946
+ cmd = `clear ${strs[0]} ${strs[1] ?? ''}`.trim();
947
+ break;
948
+ case 'weather':
949
+ cmd = `weather ${strs[0]}`;
950
+ break;
951
+ case 'time_set':
952
+ cmd = `time set ${strs[0]}`;
953
+ break;
954
+ case 'time_add':
955
+ cmd = `time add ${strs[0]}`;
956
+ break;
957
+ case 'gamerule':
958
+ cmd = `gamerule ${strs[0]} ${strs[1]}`;
959
+ break;
960
+ case 'tag_add':
961
+ cmd = `tag ${strs[0]} add ${strs[1]}`;
962
+ break;
963
+ case 'tag_remove':
964
+ cmd = `tag ${strs[0]} remove ${strs[1]}`;
965
+ break;
966
+ case 'kick':
967
+ cmd = `kick ${strs[0]} ${strs[1] ?? ''}`.trim();
968
+ break;
969
+ case 'clone':
970
+ cmd = `clone ${strs.join(' ')}`;
971
+ break;
972
+ case 'difficulty':
973
+ cmd = `difficulty ${strs[0]}`;
974
+ break;
975
+ case 'xp_add':
976
+ cmd = `xp add ${strs[0]} ${strs[1]} ${strs[2] ?? 'points'}`;
977
+ break;
978
+ case 'xp_set':
979
+ cmd = `xp set ${strs[0]} ${strs[1]} ${strs[2] ?? 'points'}`;
980
+ break;
981
+ default: cmd = `${fn} ${strs.join(' ')}`;
982
+ }
983
+ return hasMacro ? `${MACRO_SENTINEL}${cmd}` : cmd;
984
+ }
985
+ /** Convert an HIR expression to its MC command string representation */
986
+ function exprToCommandArg(expr, macroParams) {
987
+ switch (expr.kind) {
988
+ case 'int_lit': return { str: String(expr.value), isMacro: false };
989
+ case 'float_lit': return { str: String(expr.value), isMacro: false };
990
+ case 'byte_lit': return { str: String(expr.value), isMacro: false };
991
+ case 'short_lit': return { str: String(expr.value), isMacro: false };
992
+ case 'long_lit': return { str: String(expr.value), isMacro: false };
993
+ case 'double_lit': return { str: String(expr.value), isMacro: false };
994
+ case 'bool_lit': return { str: expr.value ? 'true' : 'false', isMacro: false };
995
+ case 'str_lit': return { str: expr.value, isMacro: false };
996
+ case 'mc_name': return { str: expr.value, isMacro: false };
997
+ case 'selector': return { str: expr.raw, isMacro: false };
998
+ case 'ident':
999
+ if (macroParams.has(expr.name))
1000
+ return { str: `$(${expr.name})`, isMacro: true };
1001
+ return { str: expr.name, isMacro: false };
1002
+ case 'local_coord': {
1003
+ const prefix = expr.value[0]; // ^
1004
+ const rest = expr.value.slice(1);
1005
+ if (rest && /^[a-zA-Z_]\w*$/.test(rest) && macroParams.has(rest)) {
1006
+ return { str: `${prefix}$(${rest})`, isMacro: true };
1007
+ }
1008
+ return { str: expr.value, isMacro: false };
1009
+ }
1010
+ case 'rel_coord': {
1011
+ const prefix = expr.value[0]; // ~
1012
+ const rest = expr.value.slice(1);
1013
+ if (rest && /^[a-zA-Z_]\w*$/.test(rest) && macroParams.has(rest)) {
1014
+ return { str: `${prefix}$(${rest})`, isMacro: true };
1015
+ }
1016
+ return { str: expr.value, isMacro: false };
1017
+ }
1018
+ case 'unary':
1019
+ if (expr.op === '-' && expr.operand.kind === 'float_lit') {
1020
+ return { str: String(-expr.operand.value), isMacro: false };
1021
+ }
1022
+ if (expr.op === '-' && expr.operand.kind === 'int_lit') {
1023
+ return { str: String(-expr.operand.value), isMacro: false };
1024
+ }
1025
+ return { str: '~', isMacro: false };
1026
+ default:
1027
+ return { str: '~', isMacro: false };
1028
+ }
1029
+ }
1030
+ //# sourceMappingURL=lower.js.map