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,556 @@
1
+ /**
2
+ * MIR → LIR Lowering — Stage 5 of the RedScript compiler pipeline.
3
+ *
4
+ * Converts 3-address MIR (CFG with basic blocks) to 2-address LIR
5
+ * (flat instruction lists with MC scoreboard semantics).
6
+ *
7
+ * Key transformations:
8
+ * - Each MIR Temp → a Slot (player = $tempname, obj = module.objective)
9
+ * - 3-address arithmetic → score_copy dst←a, then score_op dst←b
10
+ * - CFG control flow → call_if_matches / call_unless_matches to extracted functions
11
+ * - MIR calls → parameter slot setup + call instruction
12
+ */
13
+
14
+ import type {
15
+ MIRModule, MIRFunction, MIRBlock, MIRInstr, Operand, Temp, BlockId,
16
+ } from '../mir/types'
17
+ import type {
18
+ LIRModule, LIRFunction, LIRInstr, Slot,
19
+ } from './types'
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Public API
23
+ // ---------------------------------------------------------------------------
24
+
25
+ export function lowerToLIR(mir: MIRModule): LIRModule {
26
+ const ctx = new LoweringContext(mir.namespace, mir.objective)
27
+ for (const fn of mir.functions) {
28
+ lowerFunction(fn, ctx)
29
+ }
30
+ return {
31
+ functions: ctx.functions,
32
+ namespace: mir.namespace,
33
+ objective: mir.objective,
34
+ }
35
+ }
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // Lowering context
39
+ // ---------------------------------------------------------------------------
40
+
41
+ class LoweringContext {
42
+ readonly functions: LIRFunction[] = []
43
+ readonly namespace: string
44
+ readonly objective: string
45
+ /** Track which blocks have multiple predecessors (need their own function) */
46
+ private multiPredBlocks = new Set<BlockId>()
47
+ /** Map block id → generated LIR function name for multi-pred blocks */
48
+ private blockFnNames = new Map<BlockId, string>()
49
+ /** Current MIR function being lowered */
50
+ private currentMIRFn: MIRFunction | null = null
51
+ /** Block map for quick lookup */
52
+ private blockMap = new Map<BlockId, MIRBlock>()
53
+
54
+ constructor(namespace: string, objective: string) {
55
+ this.namespace = namespace
56
+ this.objective = objective
57
+ }
58
+
59
+ slot(temp: Temp): Slot {
60
+ // Return field temps are global (shared between caller/callee)
61
+ if (temp.startsWith('__rf_')) {
62
+ return { player: `$ret_${temp.slice(5)}`, obj: this.objective }
63
+ }
64
+ // Prefix temp names with function name to avoid caller/callee collision
65
+ const fn = this.currentMIRFn
66
+ const prefix = fn ? fn.name : ''
67
+ return { player: `$${prefix}_${temp}`, obj: this.objective }
68
+ }
69
+
70
+ qualifiedName(fnName: string): string {
71
+ // Convert :: to / and lowercase for MC function paths
72
+ const mcName = fnName.replace(/::/g, '/').toLowerCase()
73
+ return `${this.namespace}:${mcName}`
74
+ }
75
+
76
+ addFunction(fn: LIRFunction): void {
77
+ this.functions.push(fn)
78
+ }
79
+
80
+ analyzeBlocks(fn: MIRFunction): void {
81
+ this.currentMIRFn = fn
82
+ this.multiPredBlocks.clear()
83
+ this.blockFnNames.clear()
84
+ this.blockMap.clear()
85
+
86
+ for (const block of fn.blocks) {
87
+ this.blockMap.set(block.id, block)
88
+ }
89
+
90
+ // Count predecessors for each block
91
+ const predCount = new Map<BlockId, number>()
92
+ for (const block of fn.blocks) {
93
+ const targets = getTermTargets(block.term)
94
+ for (const target of targets) {
95
+ predCount.set(target, (predCount.get(target) || 0) + 1)
96
+ }
97
+ }
98
+
99
+ // Blocks with >1 predecessors or that are branch targets need their own function
100
+ for (const [blockId, count] of predCount) {
101
+ if (count > 1 && blockId !== fn.entry) {
102
+ this.multiPredBlocks.add(blockId)
103
+ this.blockFnNames.set(blockId, `${fn.name}__${blockId}`)
104
+ }
105
+ }
106
+ }
107
+
108
+ isMultiPred(blockId: BlockId): boolean {
109
+ return this.multiPredBlocks.has(blockId)
110
+ }
111
+
112
+ getBlockFnName(blockId: BlockId): string {
113
+ const name = this.blockFnNames.get(blockId)
114
+ if (name) return name
115
+ // Generate one on demand
116
+ const generated = `${this.currentMIRFn!.name}__${blockId}`
117
+ this.blockFnNames.set(blockId, generated)
118
+ return generated
119
+ }
120
+
121
+ getBlock(id: BlockId): MIRBlock | undefined {
122
+ return this.blockMap.get(id)
123
+ }
124
+ }
125
+
126
+ // ---------------------------------------------------------------------------
127
+ // Function lowering
128
+ // ---------------------------------------------------------------------------
129
+
130
+ function lowerFunction(fn: MIRFunction, ctx: LoweringContext): void {
131
+ ctx.analyzeBlocks(fn)
132
+
133
+ // Lower the entry block as the main function body
134
+ const instrs: LIRInstr[] = []
135
+ const visited = new Set<BlockId>()
136
+
137
+ // Copy parameter slots ($p0, $p1, ...) into the callee's temp slots
138
+ for (let i = 0; i < fn.params.length; i++) {
139
+ const paramSlot: Slot = { player: `$p${i}`, obj: ctx.objective }
140
+ const tempSlot = ctx.slot(fn.params[i].name)
141
+ instrs.push({ kind: 'score_copy', dst: tempSlot, src: paramSlot })
142
+ }
143
+
144
+ lowerBlock(fn.entry, fn, ctx, instrs, visited)
145
+
146
+ ctx.addFunction({
147
+ name: fn.name,
148
+ instructions: instrs,
149
+ isMacro: fn.isMacro,
150
+ macroParams: fn.params.filter(p => p.isMacroParam).map(p => p.name),
151
+ })
152
+
153
+ // Emit separate functions for multi-pred blocks
154
+ for (const blockId of ctx['multiPredBlocks']) {
155
+ if (!visited.has(blockId)) {
156
+ const blockInstrs: LIRInstr[] = []
157
+ const blockVisited = new Set<BlockId>()
158
+ lowerBlock(blockId, fn, ctx, blockInstrs, blockVisited)
159
+ ctx.addFunction({
160
+ name: ctx.getBlockFnName(blockId),
161
+ instructions: blockInstrs,
162
+ isMacro: false,
163
+ macroParams: [],
164
+ })
165
+ }
166
+ }
167
+ }
168
+
169
+ function lowerBlock(
170
+ blockId: BlockId,
171
+ fn: MIRFunction,
172
+ ctx: LoweringContext,
173
+ instrs: LIRInstr[],
174
+ visited: Set<BlockId>,
175
+ ): void {
176
+ if (visited.has(blockId)) return
177
+ visited.add(blockId)
178
+
179
+ const block = ctx.getBlock(blockId)
180
+ if (!block) return
181
+
182
+ // Lower all non-terminator instructions
183
+ for (const instr of block.instrs) {
184
+ lowerInstr(instr, fn, ctx, instrs)
185
+ }
186
+
187
+ // Lower the terminator
188
+ lowerTerminator(block.term, fn, ctx, instrs, visited)
189
+ }
190
+
191
+ // ---------------------------------------------------------------------------
192
+ // Instruction lowering
193
+ // ---------------------------------------------------------------------------
194
+
195
+ function lowerInstr(
196
+ instr: MIRInstr,
197
+ fn: MIRFunction,
198
+ ctx: LoweringContext,
199
+ instrs: LIRInstr[],
200
+ ): void {
201
+ switch (instr.kind) {
202
+ case 'const': {
203
+ instrs.push({ kind: 'score_set', dst: ctx.slot(instr.dst), value: instr.value })
204
+ break
205
+ }
206
+
207
+ case 'copy': {
208
+ lowerOperandToSlot(instr.dst, instr.src, ctx, instrs)
209
+ break
210
+ }
211
+
212
+ case 'add':
213
+ case 'sub':
214
+ case 'mul':
215
+ case 'div':
216
+ case 'mod': {
217
+ // 3-address → 2-address: copy a to dst, then op dst with b
218
+ lowerOperandToSlot(instr.dst, instr.a, ctx, instrs)
219
+ const scoreOp = {
220
+ add: 'score_add',
221
+ sub: 'score_sub',
222
+ mul: 'score_mul',
223
+ div: 'score_div',
224
+ mod: 'score_mod',
225
+ } as const
226
+ lowerBinOp(instr.dst, instr.b, scoreOp[instr.kind], ctx, instrs)
227
+ break
228
+ }
229
+
230
+ case 'neg': {
231
+ // 0 - src: set tmp to 0, then subtract src
232
+ const dst = ctx.slot(instr.dst)
233
+ instrs.push({ kind: 'score_set', dst, value: 0 })
234
+ const srcSlot = operandToSlot(instr.src, ctx, instrs)
235
+ instrs.push({ kind: 'score_sub', dst, src: srcSlot })
236
+ break
237
+ }
238
+
239
+ case 'cmp': {
240
+ // Strategy: set dst=0, then conditionally set to 1
241
+ // MC pattern: execute if score $a <op> $b run scoreboard players set $dst 1
242
+ const dst = ctx.slot(instr.dst)
243
+ const aSlot = operandToSlot(instr.a, ctx, instrs)
244
+ const bSlot = operandToSlot(instr.b, ctx, instrs)
245
+
246
+ instrs.push({ kind: 'score_set', dst, value: 0 })
247
+
248
+ const cmpOps: Record<string, string> = {
249
+ eq: '=', ne: '=', lt: '<', le: '<=', gt: '>', ge: '>=',
250
+ }
251
+ const op = cmpOps[instr.op]
252
+ const guard = instr.op === 'ne' ? 'unless' : 'if'
253
+ const dstStr = `${dst.player} ${dst.obj}`
254
+ const aStr = `${aSlot.player} ${aSlot.obj}`
255
+ const bStr = `${bSlot.player} ${bSlot.obj}`
256
+ instrs.push({
257
+ kind: 'raw',
258
+ cmd: `execute ${guard} score ${aStr} ${op} ${bStr} run scoreboard players set ${dstStr} 1`,
259
+ })
260
+ break
261
+ }
262
+
263
+ case 'and': {
264
+ // Bitwise/logical AND: both are 0/1, so multiply works
265
+ // But more accurately: dst = (a != 0) && (b != 0)
266
+ // Simple approach: copy a, then score_mul with b (since both are 0/1)
267
+ lowerOperandToSlot(instr.dst, instr.a, ctx, instrs)
268
+ lowerBinOp(instr.dst, instr.b, 'score_mul', ctx, instrs)
269
+ break
270
+ }
271
+
272
+ case 'or': {
273
+ // OR for 0/1 values: add then clamp to 1
274
+ // dst = a + b; if dst > 1, dst = 1
275
+ const dst = ctx.slot(instr.dst)
276
+ lowerOperandToSlot(instr.dst, instr.a, ctx, instrs)
277
+ lowerBinOp(instr.dst, instr.b, 'score_add', ctx, instrs)
278
+ // Clamp: use score_min with a const slot set to 1
279
+ const oneSlot = constSlot(1, ctx, instrs)
280
+ instrs.push({ kind: 'score_min', dst, src: oneSlot })
281
+ break
282
+ }
283
+
284
+ case 'not': {
285
+ // NOT for 0/1: dst = 1 - src
286
+ const dst = ctx.slot(instr.dst)
287
+ instrs.push({ kind: 'score_set', dst, value: 1 })
288
+ const srcSlot = operandToSlot(instr.src, ctx, instrs)
289
+ instrs.push({ kind: 'score_sub', dst, src: srcSlot })
290
+ break
291
+ }
292
+
293
+ case 'nbt_read': {
294
+ const dst = ctx.slot(instr.dst)
295
+ instrs.push({
296
+ kind: 'store_nbt_to_score',
297
+ dst,
298
+ ns: instr.ns,
299
+ path: instr.path,
300
+ scale: instr.scale,
301
+ })
302
+ break
303
+ }
304
+
305
+ case 'nbt_write': {
306
+ const srcSlot = operandToSlot(instr.src, ctx, instrs)
307
+ instrs.push({
308
+ kind: 'store_score_to_nbt',
309
+ ns: instr.ns,
310
+ path: instr.path,
311
+ type: instr.type,
312
+ scale: instr.scale,
313
+ src: srcSlot,
314
+ })
315
+ break
316
+ }
317
+
318
+ case 'call': {
319
+ // Set parameter slots $p0, $p1, ...
320
+ for (let i = 0; i < instr.args.length; i++) {
321
+ const paramSlot: Slot = { player: `$p${i}`, obj: ctx.objective }
322
+ lowerOperandToSlotDirect(paramSlot, instr.args[i], ctx, instrs)
323
+ }
324
+
325
+ // Handle raw commands embedded in call
326
+ if (instr.fn.startsWith('__raw:')) {
327
+ const cmd = instr.fn.slice(6)
328
+ if (cmd.startsWith('\x01')) {
329
+ // Macro sentinel → emit as macro_line ($ prefix added by emit)
330
+ instrs.push({ kind: 'macro_line', template: cmd.slice(1) })
331
+ } else {
332
+ instrs.push({ kind: 'raw', cmd })
333
+ }
334
+ } else {
335
+ instrs.push({ kind: 'call', fn: ctx.qualifiedName(instr.fn) })
336
+ }
337
+
338
+ // Copy return value to dst if needed
339
+ if (instr.dst) {
340
+ const retSlot: Slot = { player: '$ret', obj: ctx.objective }
341
+ instrs.push({ kind: 'score_copy', dst: ctx.slot(instr.dst), src: retSlot })
342
+ }
343
+ break
344
+ }
345
+
346
+ case 'call_macro': {
347
+ const macroStorage = `rs:macro_args`
348
+ // Store each arg to NBT
349
+ for (const arg of instr.args) {
350
+ const srcSlot = operandToSlot(arg.value, ctx, instrs)
351
+ instrs.push({
352
+ kind: 'store_score_to_nbt',
353
+ ns: 'rs:macro_args',
354
+ path: arg.name,
355
+ type: arg.type,
356
+ scale: arg.scale,
357
+ src: srcSlot,
358
+ })
359
+ }
360
+ instrs.push({ kind: 'call_macro', fn: ctx.qualifiedName(instr.fn), storage: macroStorage })
361
+
362
+ // Copy return value to dst if needed
363
+ if (instr.dst) {
364
+ const retSlot: Slot = { player: '$ret', obj: ctx.objective }
365
+ instrs.push({ kind: 'score_copy', dst: ctx.slot(instr.dst), src: retSlot })
366
+ }
367
+ break
368
+ }
369
+
370
+ case 'call_context': {
371
+ instrs.push({
372
+ kind: 'call_context',
373
+ fn: ctx.qualifiedName(instr.fn),
374
+ subcommands: instr.subcommands,
375
+ })
376
+ break
377
+ }
378
+
379
+ default:
380
+ break
381
+ }
382
+ }
383
+
384
+ // ---------------------------------------------------------------------------
385
+ // Terminator lowering
386
+ // ---------------------------------------------------------------------------
387
+
388
+ function lowerTerminator(
389
+ term: MIRInstr,
390
+ fn: MIRFunction,
391
+ ctx: LoweringContext,
392
+ instrs: LIRInstr[],
393
+ visited: Set<BlockId>,
394
+ ): void {
395
+ switch (term.kind) {
396
+ case 'return': {
397
+ if (term.value) {
398
+ const retSlot: Slot = { player: '$ret', obj: ctx.objective }
399
+ const srcSlot = operandToSlot(term.value, ctx, instrs)
400
+ instrs.push({ kind: 'return_value', slot: srcSlot })
401
+ }
402
+ break
403
+ }
404
+
405
+ case 'jump': {
406
+ if (ctx.isMultiPred(term.target)) {
407
+ // Target has multiple predecessors — call the extracted function
408
+ instrs.push({ kind: 'call', fn: ctx.qualifiedName(ctx.getBlockFnName(term.target)) })
409
+ } else {
410
+ // Inline the target block's instructions
411
+ lowerBlock(term.target, fn, ctx, instrs, visited)
412
+ }
413
+ break
414
+ }
415
+
416
+ case 'branch': {
417
+ const condSlot = operandToSlot(term.cond, ctx, instrs)
418
+
419
+ // Then branch: use `return run function` to atomically call and exit.
420
+ // This prevents fallthrough to the else-branch even when recursive calls
421
+ // (e.g. continue → loop header → loop body) clobber the condition slot.
422
+ const thenFnName = emitBranchTarget(term.then, fn, ctx, visited)
423
+ instrs.push({
424
+ kind: 'raw',
425
+ cmd: `execute if score ${condSlot.player} ${condSlot.obj} matches 1 run return run function ${ctx.qualifiedName(thenFnName)}`,
426
+ })
427
+
428
+ // Else branch: if we reach here, cond was not 1 (the then-path returned).
429
+ const elseFnName = emitBranchTarget(term.else, fn, ctx, visited)
430
+ instrs.push({ kind: 'call', fn: ctx.qualifiedName(elseFnName) })
431
+ break
432
+ }
433
+ }
434
+ }
435
+
436
+ /**
437
+ * Emit a branch target as a separate LIR function and return its name.
438
+ * If the target is already a multi-pred block with a function, reuse it.
439
+ */
440
+ function emitBranchTarget(
441
+ blockId: BlockId,
442
+ fn: MIRFunction,
443
+ ctx: LoweringContext,
444
+ parentVisited: Set<BlockId>,
445
+ ): string {
446
+ // If already has a function (multi-pred), return its name
447
+ if (ctx.isMultiPred(blockId)) {
448
+ // Make sure the block gets emitted
449
+ if (!parentVisited.has(blockId)) {
450
+ const blockInstrs: LIRInstr[] = []
451
+ const blockVisited = new Set<BlockId>()
452
+ lowerBlock(blockId, fn, ctx, blockInstrs, blockVisited)
453
+ ctx.addFunction({
454
+ name: ctx.getBlockFnName(blockId),
455
+ instructions: blockInstrs,
456
+ isMacro: false,
457
+ macroParams: [],
458
+ })
459
+ parentVisited.add(blockId)
460
+ }
461
+ return ctx.getBlockFnName(blockId)
462
+ }
463
+
464
+ // Create a new function for this branch target
465
+ const branchFnName = ctx.getBlockFnName(blockId)
466
+ const blockInstrs: LIRInstr[] = []
467
+ const blockVisited = new Set<BlockId>()
468
+ lowerBlock(blockId, fn, ctx, blockInstrs, blockVisited)
469
+
470
+ ctx.addFunction({
471
+ name: branchFnName,
472
+ instructions: blockInstrs,
473
+ isMacro: false,
474
+ macroParams: [],
475
+ })
476
+
477
+ // Mark visited so parent doesn't re-inline
478
+ parentVisited.add(blockId)
479
+
480
+ return branchFnName
481
+ }
482
+
483
+ // ---------------------------------------------------------------------------
484
+ // Helpers
485
+ // ---------------------------------------------------------------------------
486
+
487
+ /** Lower an operand into a named temp slot (copy const or score_copy) */
488
+ function lowerOperandToSlot(
489
+ dstTemp: Temp,
490
+ src: Operand,
491
+ ctx: LoweringContext,
492
+ instrs: LIRInstr[],
493
+ ): void {
494
+ const dst = ctx.slot(dstTemp)
495
+ if (src.kind === 'const') {
496
+ instrs.push({ kind: 'score_set', dst, value: src.value })
497
+ } else {
498
+ instrs.push({ kind: 'score_copy', dst, src: ctx.slot(src.name) })
499
+ }
500
+ }
501
+
502
+ /** Lower an operand into a specific slot (not by temp name) */
503
+ function lowerOperandToSlotDirect(
504
+ dst: Slot,
505
+ src: Operand,
506
+ ctx: LoweringContext,
507
+ instrs: LIRInstr[],
508
+ ): void {
509
+ if (src.kind === 'const') {
510
+ instrs.push({ kind: 'score_set', dst, value: src.value })
511
+ } else {
512
+ instrs.push({ kind: 'score_copy', dst, src: ctx.slot(src.name) })
513
+ }
514
+ }
515
+
516
+ /** Get a slot for an operand, emitting a score_set for constants into a temp */
517
+ function operandToSlot(
518
+ op: Operand,
519
+ ctx: LoweringContext,
520
+ instrs: LIRInstr[],
521
+ ): Slot {
522
+ if (op.kind === 'temp') {
523
+ return ctx.slot(op.name)
524
+ }
525
+ // Constant → need a temporary slot
526
+ return constSlot(op.value, ctx, instrs)
527
+ }
528
+
529
+ /** Create a constant slot with a given value */
530
+ function constSlot(value: number, ctx: LoweringContext, instrs: LIRInstr[]): Slot {
531
+ const slot: Slot = { player: `$__const_${value}`, obj: ctx.objective }
532
+ instrs.push({ kind: 'score_set', dst: slot, value })
533
+ return slot
534
+ }
535
+
536
+ /** Apply a binary score operation: dst op= src */
537
+ function lowerBinOp(
538
+ dstTemp: Temp,
539
+ b: Operand,
540
+ scoreKind: 'score_add' | 'score_sub' | 'score_mul' | 'score_div' | 'score_mod',
541
+ ctx: LoweringContext,
542
+ instrs: LIRInstr[],
543
+ ): void {
544
+ const dst = ctx.slot(dstTemp)
545
+ const srcSlot = operandToSlot(b, ctx, instrs)
546
+ instrs.push({ kind: scoreKind, dst, src: srcSlot })
547
+ }
548
+
549
+ function getTermTargets(term: MIRInstr): BlockId[] {
550
+ switch (term.kind) {
551
+ case 'jump': return [term.target]
552
+ case 'branch': return [term.then, term.else]
553
+ case 'return': return []
554
+ default: return []
555
+ }
556
+ }
@@ -0,0 +1,109 @@
1
+ /**
2
+ * LIR (Low-level IR) Types — Stage 5 of the RedScript compiler pipeline.
3
+ *
4
+ * LIR is 2-address, MC-specific, typed nodes — no raw strings.
5
+ * Each LIR instruction maps 1:1 (or near) to one MC command.
6
+ *
7
+ * Spec: docs/compiler-pipeline-redesign.md § "LIR Instruction Set"
8
+ */
9
+
10
+ import type { CmpOp, NBTType, ExecuteSubcmd } from '../mir/types'
11
+
12
+ // A scoreboard slot: fake-player name + objective
13
+ export interface Slot {
14
+ player: string
15
+ obj: string
16
+ }
17
+
18
+ // Re-export types used in LIR from MIR
19
+ export type { CmpOp, NBTType, ExecuteSubcmd }
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // LIR Instructions
23
+ // ---------------------------------------------------------------------------
24
+
25
+ export type LIRInstr =
26
+ // ── Scoreboard ───────────────────────────────────────────────────────────
27
+ | { kind: 'score_set'; dst: Slot; value: number }
28
+ // scoreboard players set <dst.player> <dst.obj> value
29
+
30
+ | { kind: 'score_copy'; dst: Slot; src: Slot }
31
+ // scoreboard players operation <dst> = <src>
32
+
33
+ | { kind: 'score_add'; dst: Slot; src: Slot } // +=
34
+ | { kind: 'score_sub'; dst: Slot; src: Slot } // -=
35
+ | { kind: 'score_mul'; dst: Slot; src: Slot } // *=
36
+ | { kind: 'score_div'; dst: Slot; src: Slot } // /=
37
+ | { kind: 'score_mod'; dst: Slot; src: Slot } // %=
38
+ | { kind: 'score_min'; dst: Slot; src: Slot } // < (min)
39
+ | { kind: 'score_max'; dst: Slot; src: Slot } // > (max)
40
+ | { kind: 'score_swap'; a: Slot; b: Slot } // ><
41
+
42
+ // ── Execute store ────────────────────────────────────────────────────────
43
+ | { kind: 'store_cmd_to_score'; dst: Slot; cmd: LIRInstr }
44
+ // execute store result score <dst> run <cmd>
45
+
46
+ | { kind: 'store_score_to_nbt';
47
+ ns: string; path: string; type: NBTType; scale: number;
48
+ src: Slot }
49
+ // execute store result storage <ns> <path> <type> <scale> run scoreboard players get <src>
50
+
51
+ | { kind: 'store_nbt_to_score';
52
+ dst: Slot; ns: string; path: string; scale: number }
53
+ // execute store result score <dst> run data get storage <ns> <path> <scale>
54
+
55
+ // ── NBT ──────────────────────────────────────────────────────────────────
56
+ | { kind: 'nbt_set_literal'; ns: string; path: string; value: string }
57
+ // data modify storage <ns> <path> set value <value>
58
+
59
+ | { kind: 'nbt_copy'; srcNs: string; srcPath: string; dstNs: string; dstPath: string }
60
+ // data modify storage <dstNs> <dstPath> set from storage <srcNs> <srcPath>
61
+
62
+ // ── Control flow ─────────────────────────────────────────────────────────
63
+ | { kind: 'call'; fn: string }
64
+ // function <fn>
65
+
66
+ | { kind: 'call_macro'; fn: string; storage: string }
67
+ // function <fn> with storage <storage>
68
+
69
+ | { kind: 'call_if_matches'; fn: string; slot: Slot; range: string }
70
+ // execute if score <slot> matches <range> run function <fn>
71
+
72
+ | { kind: 'call_unless_matches'; fn: string; slot: Slot; range: string }
73
+
74
+ | { kind: 'call_if_score'; fn: string; a: Slot; op: CmpOp; b: Slot }
75
+ // execute if score <a> <op> <b> run function <fn>
76
+
77
+ | { kind: 'call_unless_score'; fn: string; a: Slot; op: CmpOp; b: Slot }
78
+
79
+ | { kind: 'call_context'; fn: string; subcommands: ExecuteSubcmd[] }
80
+ // execute [subcommands] run function <fn>
81
+
82
+ | { kind: 'return_value'; slot: Slot }
83
+ // scoreboard players operation $ret <obj> = <slot> (then implicit return)
84
+
85
+ // ── Macro line ────────────────────────────────────────────────────────────
86
+ | { kind: 'macro_line'; template: string }
87
+ // A line starting with $ in a macro function.
88
+ // template uses $(param) substitutions
89
+
90
+ // ── Arbitrary MC command ─────────────────────────────────────────────────
91
+ | { kind: 'raw'; cmd: string }
92
+ // Emitted verbatim. Use sparingly — prefer typed instructions.
93
+
94
+ // ---------------------------------------------------------------------------
95
+ // LIR function and module structure
96
+ // ---------------------------------------------------------------------------
97
+
98
+ export interface LIRFunction {
99
+ name: string
100
+ instructions: LIRInstr[] // flat list (no blocks; control flow is via call_if_*)
101
+ isMacro: boolean
102
+ macroParams: string[] // names of $(param) substitution keys
103
+ }
104
+
105
+ export interface LIRModule {
106
+ functions: LIRFunction[]
107
+ namespace: string
108
+ objective: string
109
+ }