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,167 @@
1
+ /**
2
+ * Macro function detection — pre-scans HIR to find parameters used in
3
+ * builtin call positions (coordinates, entity types, etc.)
4
+ *
5
+ * A function becomes a "macro function" when one of its params appears
6
+ * in a position that requires literal substitution in the MC command
7
+ * (e.g. summon coords, particle coords, setblock coords, local/relative
8
+ * coords like ^px or ~height).
9
+ */
10
+
11
+ import type { HIRModule, HIRExpr, HIRStmt, HIRBlock } from '../hir/types'
12
+
13
+ // ---------------------------------------------------------------------------
14
+ // Known builtins that emit MC commands with inline arguments
15
+ // ---------------------------------------------------------------------------
16
+
17
+ /** Builtins whose arguments appear literally in MC commands */
18
+ export const BUILTIN_SET = new Set([
19
+ 'say', 'tell', 'tellraw', 'title', 'actionbar', 'subtitle', 'title_times',
20
+ 'announce', 'give', 'kill', 'effect', 'effect_clear',
21
+ 'summon', 'particle', 'playsound', 'clear', 'weather',
22
+ 'time_set', 'time_add', 'gamerule', 'tag_add', 'tag_remove',
23
+ 'kick', 'setblock', 'fill', 'clone', 'difficulty', 'xp_add', 'xp_set',
24
+ ])
25
+
26
+ // ---------------------------------------------------------------------------
27
+ // Public API
28
+ // ---------------------------------------------------------------------------
29
+
30
+ export interface MacroFunctionInfo {
31
+ macroParams: Set<string>
32
+ /** param name → param type name (for NBT scale inference) */
33
+ paramTypes: Map<string, string>
34
+ }
35
+
36
+ /**
37
+ * Pre-scan HIR functions to detect which params need macro treatment.
38
+ * Returns a map: function name → MacroFunctionInfo.
39
+ */
40
+ export function detectMacroFunctions(hir: HIRModule): Map<string, MacroFunctionInfo> {
41
+ const result = new Map<string, MacroFunctionInfo>()
42
+
43
+ for (const fn of hir.functions) {
44
+ const paramNames = new Set(fn.params.map(p => p.name))
45
+ const macroParams = new Set<string>()
46
+ scanBlock(fn.body, paramNames, macroParams)
47
+ if (macroParams.size > 0) {
48
+ const paramTypes = new Map<string, string>()
49
+ for (const p of fn.params) {
50
+ const typeName = p.type?.kind === 'named' ? (p.type as any).name : 'int'
51
+ paramTypes.set(p.name, typeName)
52
+ }
53
+ result.set(fn.name, { macroParams, paramTypes })
54
+ }
55
+ }
56
+
57
+ for (const ib of hir.implBlocks) {
58
+ for (const m of ib.methods) {
59
+ const paramNames = new Set(m.params.map(p => p.name))
60
+ const macroParams = new Set<string>()
61
+ scanBlock(m.body, paramNames, macroParams)
62
+ if (macroParams.size > 0) {
63
+ const paramTypes = new Map<string, string>()
64
+ for (const p of m.params) {
65
+ const typeName = p.type?.kind === 'named' ? (p.type as any).name : 'int'
66
+ paramTypes.set(p.name, typeName)
67
+ }
68
+ result.set(`${ib.typeName}::${m.name}`, { macroParams, paramTypes })
69
+ }
70
+ }
71
+ }
72
+
73
+ return result
74
+ }
75
+
76
+ // ---------------------------------------------------------------------------
77
+ // HIR scanning
78
+ // ---------------------------------------------------------------------------
79
+
80
+ function scanBlock(stmts: HIRBlock, paramNames: Set<string>, macroParams: Set<string>): void {
81
+ for (const stmt of stmts) scanStmt(stmt, paramNames, macroParams)
82
+ }
83
+
84
+ function scanStmt(stmt: HIRStmt, paramNames: Set<string>, macroParams: Set<string>): void {
85
+ switch (stmt.kind) {
86
+ case 'expr': scanExpr(stmt.expr, paramNames, macroParams); break
87
+ case 'let': scanExpr(stmt.init, paramNames, macroParams); break
88
+ case 'return': if (stmt.value) scanExpr(stmt.value, paramNames, macroParams); break
89
+ case 'if':
90
+ scanExpr(stmt.cond, paramNames, macroParams)
91
+ scanBlock(stmt.then, paramNames, macroParams)
92
+ if (stmt.else_) scanBlock(stmt.else_, paramNames, macroParams)
93
+ break
94
+ case 'while':
95
+ scanExpr(stmt.cond, paramNames, macroParams)
96
+ scanBlock(stmt.body, paramNames, macroParams)
97
+ if (stmt.step) scanBlock(stmt.step, paramNames, macroParams)
98
+ break
99
+ case 'foreach': scanBlock(stmt.body, paramNames, macroParams); break
100
+ case 'match':
101
+ scanExpr(stmt.expr, paramNames, macroParams)
102
+ for (const arm of stmt.arms) scanBlock(arm.body, paramNames, macroParams)
103
+ break
104
+ case 'execute': scanBlock(stmt.body, paramNames, macroParams); break
105
+ case 'raw': break
106
+ }
107
+ }
108
+
109
+ function scanExpr(expr: HIRExpr, paramNames: Set<string>, macroParams: Set<string>): void {
110
+ if (expr.kind === 'call' && BUILTIN_SET.has(expr.fn)) {
111
+ // Check if any argument is a param identifier or a coord with a param variable
112
+ for (const arg of expr.args) {
113
+ checkMacroArg(arg, paramNames, macroParams)
114
+ }
115
+ // Recurse into args for nested expressions
116
+ for (const arg of expr.args) scanExpr(arg, paramNames, macroParams)
117
+ return
118
+ }
119
+
120
+ // Recurse into sub-expressions
121
+ switch (expr.kind) {
122
+ case 'call':
123
+ for (const arg of expr.args) scanExpr(arg, paramNames, macroParams)
124
+ break
125
+ case 'invoke':
126
+ scanExpr(expr.callee, paramNames, macroParams)
127
+ for (const arg of expr.args) scanExpr(arg, paramNames, macroParams)
128
+ break
129
+ case 'binary':
130
+ scanExpr(expr.left, paramNames, macroParams)
131
+ scanExpr(expr.right, paramNames, macroParams)
132
+ break
133
+ case 'unary':
134
+ scanExpr(expr.operand, paramNames, macroParams)
135
+ break
136
+ case 'assign':
137
+ scanExpr(expr.value, paramNames, macroParams)
138
+ break
139
+ case 'member_assign':
140
+ scanExpr(expr.obj, paramNames, macroParams)
141
+ scanExpr(expr.value, paramNames, macroParams)
142
+ break
143
+ case 'member':
144
+ scanExpr(expr.obj, paramNames, macroParams)
145
+ break
146
+ case 'index':
147
+ scanExpr(expr.obj, paramNames, macroParams)
148
+ scanExpr(expr.index, paramNames, macroParams)
149
+ break
150
+ case 'static_call':
151
+ for (const arg of expr.args) scanExpr(arg, paramNames, macroParams)
152
+ break
153
+ }
154
+ }
155
+
156
+ /** Check if a single argument expression references a function parameter in a macro position */
157
+ function checkMacroArg(expr: HIRExpr, paramNames: Set<string>, macroParams: Set<string>): void {
158
+ if (expr.kind === 'ident' && paramNames.has(expr.name)) {
159
+ macroParams.add(expr.name)
160
+ } else if (expr.kind === 'local_coord' || expr.kind === 'rel_coord') {
161
+ // ^varname or ~varname — extract the variable part
162
+ const rest = expr.value.slice(1)
163
+ if (rest && /^[a-zA-Z_]\w*$/.test(rest) && paramNames.has(rest)) {
164
+ macroParams.add(rest)
165
+ }
166
+ }
167
+ }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * MIR (Mid-level IR) Types — Stage 3 of the RedScript compiler pipeline.
3
+ *
4
+ * MIR is a 3-address, explicit-CFG representation with versioned temporaries.
5
+ * Every instruction produces at most one result into a fresh temporary.
6
+ *
7
+ * Spec: docs/compiler-pipeline-redesign.md § "MIR Instruction Set"
8
+ */
9
+
10
+ // A temporary variable — unique within a function, named t0, t1, t2...
11
+ export type Temp = string
12
+
13
+ // An operand: either a temp or an inline constant
14
+ export type Operand =
15
+ | { kind: 'temp'; name: Temp }
16
+ | { kind: 'const'; value: number }
17
+
18
+ // A basic block identifier
19
+ export type BlockId = string
20
+
21
+ // Comparison operators (for cmp instruction)
22
+ export type CmpOp = 'eq' | 'ne' | 'lt' | 'le' | 'gt' | 'ge'
23
+
24
+ // NBT value types (for nbt_write)
25
+ export type NBTType = 'int' | 'double' | 'float' | 'long' | 'short' | 'byte'
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // Execute subcommands (used in call_context)
29
+ // ---------------------------------------------------------------------------
30
+
31
+ export type ExecuteSubcmd =
32
+ | { kind: 'as'; selector: string }
33
+ | { kind: 'at'; selector: string }
34
+ | { kind: 'at_self' }
35
+ | { kind: 'positioned'; x: string; y: string; z: string }
36
+ | { kind: 'rotated'; yaw: string; pitch: string }
37
+ | { kind: 'in'; dimension: string }
38
+ | { kind: 'anchored'; anchor: 'eyes' | 'feet' }
39
+ | { kind: 'if_score'; a: string; op: CmpOp; b: string }
40
+ | { kind: 'unless_score'; a: string; op: CmpOp; b: string }
41
+ | { kind: 'if_matches'; score: string; range: string }
42
+ | { kind: 'unless_matches'; score: string; range: string }
43
+
44
+ // ---------------------------------------------------------------------------
45
+ // MIR Instructions
46
+ // ---------------------------------------------------------------------------
47
+
48
+ export type MIRInstr =
49
+ // ── Constants & copies ──────────────────────────────────────────────────
50
+ | { kind: 'const'; dst: Temp; value: number }
51
+ | { kind: 'copy'; dst: Temp; src: Operand }
52
+
53
+ // ── Integer arithmetic ──────────────────────────────────────────────────
54
+ | { kind: 'add'; dst: Temp; a: Operand; b: Operand }
55
+ | { kind: 'sub'; dst: Temp; a: Operand; b: Operand }
56
+ | { kind: 'mul'; dst: Temp; a: Operand; b: Operand }
57
+ | { kind: 'div'; dst: Temp; a: Operand; b: Operand }
58
+ | { kind: 'mod'; dst: Temp; a: Operand; b: Operand }
59
+ | { kind: 'neg'; dst: Temp; src: Operand }
60
+
61
+ // ── Comparison (result is 0 or 1) ────────────────────────────────────────
62
+ | { kind: 'cmp'; dst: Temp; op: CmpOp; a: Operand; b: Operand }
63
+
64
+ // ── Boolean logic ────────────────────────────────────────────────────────
65
+ | { kind: 'and'; dst: Temp; a: Operand; b: Operand }
66
+ | { kind: 'or'; dst: Temp; a: Operand; b: Operand }
67
+ | { kind: 'not'; dst: Temp; src: Operand }
68
+
69
+ // ── NBT storage ──────────────────────────────────────────────────────────
70
+ | { kind: 'nbt_read'; dst: Temp; ns: string; path: string; scale: number }
71
+ | { kind: 'nbt_write'; ns: string; path: string; type: NBTType; scale: number; src: Operand }
72
+
73
+ // ── Function calls ────────────────────────────────────────────────────────
74
+ | { kind: 'call'; dst: Temp | null; fn: string; args: Operand[] }
75
+ | { kind: 'call_macro'; dst: Temp | null; fn: string; args: { name: string; value: Operand; type: NBTType; scale: number }[] }
76
+ | { kind: 'call_context'; fn: string; subcommands: ExecuteSubcmd[] }
77
+
78
+ // ── Terminators (exactly one per basic block, must be last) ──────────────
79
+ | { kind: 'jump'; target: BlockId }
80
+ | { kind: 'branch'; cond: Operand; then: BlockId; else: BlockId }
81
+ | { kind: 'return'; value: Operand | null }
82
+
83
+ // ---------------------------------------------------------------------------
84
+ // Basic block and function structure
85
+ // ---------------------------------------------------------------------------
86
+
87
+ export interface MIRBlock {
88
+ id: BlockId
89
+ instrs: MIRInstr[] // non-terminator instructions
90
+ term: MIRInstr // must be jump | branch | return
91
+ preds: BlockId[] // predecessor block ids (for dataflow)
92
+ }
93
+
94
+ export interface MIRFunction {
95
+ name: string
96
+ params: { name: Temp; isMacroParam: boolean }[]
97
+ blocks: MIRBlock[]
98
+ entry: BlockId // entry block id (always 'entry')
99
+ isMacro: boolean // true if any param is a macro param
100
+ }
101
+
102
+ export interface MIRModule {
103
+ functions: MIRFunction[]
104
+ namespace: string
105
+ objective: string // scoreboard objective (default: __<namespace>)
106
+ }
@@ -0,0 +1,218 @@
1
+ /**
2
+ * MIR Verifier — validates structural invariants of MIR modules.
3
+ *
4
+ * Checks:
5
+ * 1. Every block ends with exactly one terminator (jump | branch | return)
6
+ * 2. Every temp used is defined before use (in the block or as a param)
7
+ * 3. No unreachable blocks (all blocks reachable from entry)
8
+ * 4. Branch/jump targets must exist in the function
9
+ */
10
+
11
+ import type { MIRModule, MIRFunction, MIRBlock, MIRInstr, Operand, Temp } from './types'
12
+
13
+ export interface VerifyError {
14
+ fn: string
15
+ block?: string
16
+ message: string
17
+ }
18
+
19
+ export function verifyMIR(module: MIRModule): VerifyError[] {
20
+ const errors: VerifyError[] = []
21
+ for (const fn of module.functions) {
22
+ errors.push(...verifyFunction(fn))
23
+ }
24
+ return errors
25
+ }
26
+
27
+ function verifyFunction(fn: MIRFunction): VerifyError[] {
28
+ const errors: VerifyError[] = []
29
+
30
+ const blockIds = new Set(fn.blocks.map(b => b.id))
31
+
32
+ // 1. Check terminators
33
+ for (const block of fn.blocks) {
34
+ if (!isTerminator(block.term)) {
35
+ errors.push({
36
+ fn: fn.name,
37
+ block: block.id,
38
+ message: `block '${block.id}' does not end with a terminator (found '${block.term.kind}')`,
39
+ })
40
+ }
41
+
42
+ // Check that no non-terminator instruction is a terminator
43
+ for (const instr of block.instrs) {
44
+ if (isTerminator(instr)) {
45
+ errors.push({
46
+ fn: fn.name,
47
+ block: block.id,
48
+ message: `block '${block.id}' has terminator '${instr.kind}' in non-terminal position`,
49
+ })
50
+ }
51
+ }
52
+ }
53
+
54
+ // 2. Check that branch/jump targets exist
55
+ for (const block of fn.blocks) {
56
+ const targets = getTermTargets(block.term)
57
+ for (const target of targets) {
58
+ if (!blockIds.has(target)) {
59
+ errors.push({
60
+ fn: fn.name,
61
+ block: block.id,
62
+ message: `block '${block.id}' references non-existent target '${target}'`,
63
+ })
64
+ }
65
+ }
66
+ }
67
+
68
+ // 3. Check reachability from entry
69
+ const reachable = new Set<string>()
70
+ const entryBlock = fn.blocks.find(b => b.id === fn.entry)
71
+ if (!entryBlock) {
72
+ errors.push({
73
+ fn: fn.name,
74
+ message: `entry block '${fn.entry}' not found`,
75
+ })
76
+ } else {
77
+ // BFS from entry
78
+ const queue: string[] = [fn.entry]
79
+ while (queue.length > 0) {
80
+ const id = queue.shift()!
81
+ if (reachable.has(id)) continue
82
+ reachable.add(id)
83
+
84
+ const block = fn.blocks.find(b => b.id === id)
85
+ if (block) {
86
+ for (const target of getTermTargets(block.term)) {
87
+ if (!reachable.has(target)) {
88
+ queue.push(target)
89
+ }
90
+ }
91
+ }
92
+ }
93
+
94
+ for (const block of fn.blocks) {
95
+ if (!reachable.has(block.id)) {
96
+ errors.push({
97
+ fn: fn.name,
98
+ block: block.id,
99
+ message: `block '${block.id}' is unreachable from entry`,
100
+ })
101
+ }
102
+ }
103
+ }
104
+
105
+ // 4. Check use-before-def for temporaries
106
+ // Collect all defined temps: params + all dst fields in instructions
107
+ const allDefs = new Set<Temp>()
108
+ for (const p of fn.params) allDefs.add(p.name)
109
+ for (const block of fn.blocks) {
110
+ for (const instr of block.instrs) {
111
+ const dst = getDst(instr)
112
+ if (dst) allDefs.add(dst)
113
+ }
114
+ const termDst = getDst(block.term)
115
+ if (termDst) allDefs.add(termDst)
116
+ }
117
+
118
+ // Check that every temp used in an operand is in allDefs
119
+ for (const block of fn.blocks) {
120
+ for (const instr of block.instrs) {
121
+ for (const used of getUsedTemps(instr)) {
122
+ if (!allDefs.has(used)) {
123
+ errors.push({
124
+ fn: fn.name,
125
+ block: block.id,
126
+ message: `temp '${used}' used but never defined`,
127
+ })
128
+ }
129
+ }
130
+ }
131
+ for (const used of getUsedTemps(block.term)) {
132
+ if (!allDefs.has(used)) {
133
+ errors.push({
134
+ fn: fn.name,
135
+ block: block.id,
136
+ message: `temp '${used}' used in terminator but never defined`,
137
+ })
138
+ }
139
+ }
140
+ }
141
+
142
+ return errors
143
+ }
144
+
145
+ function isTerminator(instr: MIRInstr): boolean {
146
+ return instr.kind === 'jump' || instr.kind === 'branch' || instr.kind === 'return'
147
+ }
148
+
149
+ function getTermTargets(term: MIRInstr): string[] {
150
+ switch (term.kind) {
151
+ case 'jump': return [term.target]
152
+ case 'branch': return [term.then, term.else]
153
+ case 'return': return []
154
+ default: return []
155
+ }
156
+ }
157
+
158
+ function getDst(instr: MIRInstr): Temp | null {
159
+ switch (instr.kind) {
160
+ case 'const':
161
+ case 'copy':
162
+ case 'add': case 'sub': case 'mul': case 'div': case 'mod':
163
+ case 'neg':
164
+ case 'cmp':
165
+ case 'and': case 'or': case 'not':
166
+ case 'nbt_read':
167
+ return instr.dst
168
+ case 'call':
169
+ case 'call_macro':
170
+ return instr.dst
171
+ default:
172
+ return null
173
+ }
174
+ }
175
+
176
+ function getOperandTemps(op: Operand): Temp[] {
177
+ return op.kind === 'temp' ? [op.name] : []
178
+ }
179
+
180
+ function getUsedTemps(instr: MIRInstr): Temp[] {
181
+ const temps: Temp[] = []
182
+ switch (instr.kind) {
183
+ case 'const':
184
+ break
185
+ case 'copy':
186
+ case 'neg':
187
+ case 'not':
188
+ temps.push(...getOperandTemps(instr.src))
189
+ break
190
+ case 'add': case 'sub': case 'mul': case 'div': case 'mod':
191
+ case 'cmp':
192
+ case 'and': case 'or':
193
+ temps.push(...getOperandTemps(instr.a), ...getOperandTemps(instr.b))
194
+ break
195
+ case 'nbt_read':
196
+ break
197
+ case 'nbt_write':
198
+ temps.push(...getOperandTemps(instr.src))
199
+ break
200
+ case 'call':
201
+ for (const arg of instr.args) temps.push(...getOperandTemps(arg))
202
+ break
203
+ case 'call_macro':
204
+ for (const arg of instr.args) temps.push(...getOperandTemps(arg.value))
205
+ break
206
+ case 'call_context':
207
+ break
208
+ case 'jump':
209
+ break
210
+ case 'branch':
211
+ temps.push(...getOperandTemps(instr.cond))
212
+ break
213
+ case 'return':
214
+ if (instr.value) temps.push(...getOperandTemps(instr.value))
215
+ break
216
+ }
217
+ return temps
218
+ }
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Block Merging — MIR optimization pass.
3
+ *
4
+ * Merges a block B into its sole predecessor A when:
5
+ * - A ends with an unconditional jump to B
6
+ * - B has exactly one predecessor (A)
7
+ * - B is not the entry block
8
+ *
9
+ * The merged block keeps A's id and combines A's instrs + B's instrs + B's terminator.
10
+ */
11
+
12
+ import type { MIRFunction, MIRBlock, BlockId } from '../mir/types'
13
+
14
+ export function blockMerge(fn: MIRFunction): MIRFunction {
15
+ let blocks = fn.blocks
16
+
17
+ // Iterate until no more merges possible
18
+ let changed = true
19
+ while (changed) {
20
+ changed = false
21
+ const blockMap = new Map(blocks.map(b => [b.id, b]))
22
+ const predCounts = computePredCounts(blocks)
23
+
24
+ const newBlocks: MIRBlock[] = []
25
+ const removed = new Set<BlockId>()
26
+
27
+ for (const block of blocks) {
28
+ if (removed.has(block.id)) continue
29
+
30
+ // Check: does this block jump unconditionally to a single-pred successor?
31
+ if (block.term.kind === 'jump') {
32
+ const targetId = block.term.target
33
+ const target = blockMap.get(targetId)
34
+ if (target && targetId !== fn.entry && predCounts.get(targetId) === 1) {
35
+ // Merge target into this block
36
+ const merged: MIRBlock = {
37
+ id: block.id,
38
+ instrs: [...block.instrs, ...target.instrs],
39
+ term: target.term,
40
+ preds: block.preds,
41
+ }
42
+ newBlocks.push(merged)
43
+ removed.add(targetId)
44
+ changed = true
45
+ continue
46
+ }
47
+ }
48
+
49
+ newBlocks.push(block)
50
+ }
51
+
52
+ blocks = newBlocks
53
+ }
54
+
55
+ // Recompute preds after merging
56
+ blocks = recomputePreds(blocks)
57
+
58
+ return { ...fn, blocks }
59
+ }
60
+
61
+ function computePredCounts(blocks: MIRBlock[]): Map<BlockId, number> {
62
+ const counts = new Map<BlockId, number>()
63
+ for (const b of blocks) counts.set(b.id, 0)
64
+
65
+ for (const block of blocks) {
66
+ for (const target of getTermTargets(block.term)) {
67
+ counts.set(target, (counts.get(target) ?? 0) + 1)
68
+ }
69
+ }
70
+ return counts
71
+ }
72
+
73
+ function recomputePreds(blocks: MIRBlock[]): MIRBlock[] {
74
+ const predMap = new Map<BlockId, BlockId[]>()
75
+ for (const b of blocks) predMap.set(b.id, [])
76
+
77
+ for (const block of blocks) {
78
+ for (const target of getTermTargets(block.term)) {
79
+ const preds = predMap.get(target)
80
+ if (preds) preds.push(block.id)
81
+ }
82
+ }
83
+
84
+ return blocks.map(b => ({ ...b, preds: predMap.get(b.id) ?? [] }))
85
+ }
86
+
87
+ function getTermTargets(term: MIRBlock['term']): BlockId[] {
88
+ switch (term.kind) {
89
+ case 'jump': return [term.target]
90
+ case 'branch': return [term.then, term.else]
91
+ default: return []
92
+ }
93
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Branch Simplification — MIR optimization pass.
3
+ *
4
+ * Replaces `branch(const, then, else)` with an unconditional `jump`:
5
+ * - branch(nonzero, then, else) → jump(then)
6
+ * - branch(0, then, else) → jump(else)
7
+ */
8
+
9
+ import type { MIRFunction, MIRBlock } from '../mir/types'
10
+
11
+ export function branchSimplify(fn: MIRFunction): MIRFunction {
12
+ return {
13
+ ...fn,
14
+ blocks: fn.blocks.map(simplifyBlock),
15
+ }
16
+ }
17
+
18
+ function simplifyBlock(block: MIRBlock): MIRBlock {
19
+ if (block.term.kind !== 'branch') return block
20
+ if (block.term.cond.kind !== 'const') return block
21
+
22
+ const target = block.term.cond.value !== 0 ? block.term.then : block.term.else
23
+ return {
24
+ ...block,
25
+ term: { kind: 'jump', target },
26
+ }
27
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Constant Folding — MIR optimization pass.
3
+ *
4
+ * Folds instructions where all operands are constants:
5
+ * - Arithmetic: add(3, 4) → 7, neg(5) → -5
6
+ * - Comparison: cmp(lt, 3, 4) → 1
7
+ * - Boolean: and(1, 0) → 0, not(0) → 1
8
+ */
9
+
10
+ import type { MIRFunction, MIRBlock, MIRInstr, Operand } from '../mir/types'
11
+
12
+ export function constantFold(fn: MIRFunction): MIRFunction {
13
+ return {
14
+ ...fn,
15
+ blocks: fn.blocks.map(foldBlock),
16
+ }
17
+ }
18
+
19
+ function foldBlock(block: MIRBlock): MIRBlock {
20
+ const instrs: MIRInstr[] = []
21
+ for (const instr of block.instrs) {
22
+ const folded = tryFold(instr)
23
+ instrs.push(folded ?? instr)
24
+ }
25
+ return { ...block, instrs }
26
+ }
27
+
28
+ function isConst(op: Operand): op is { kind: 'const'; value: number } {
29
+ return op.kind === 'const'
30
+ }
31
+
32
+ function tryFold(instr: MIRInstr): MIRInstr | null {
33
+ switch (instr.kind) {
34
+ case 'add':
35
+ if (isConst(instr.a) && isConst(instr.b))
36
+ return { kind: 'const', dst: instr.dst, value: instr.a.value + instr.b.value }
37
+ break
38
+ case 'sub':
39
+ if (isConst(instr.a) && isConst(instr.b))
40
+ return { kind: 'const', dst: instr.dst, value: instr.a.value - instr.b.value }
41
+ break
42
+ case 'mul':
43
+ if (isConst(instr.a) && isConst(instr.b))
44
+ return { kind: 'const', dst: instr.dst, value: instr.a.value * instr.b.value }
45
+ break
46
+ case 'div':
47
+ if (isConst(instr.a) && isConst(instr.b) && instr.b.value !== 0)
48
+ return { kind: 'const', dst: instr.dst, value: Math.trunc(instr.a.value / instr.b.value) }
49
+ break
50
+ case 'mod':
51
+ if (isConst(instr.a) && isConst(instr.b) && instr.b.value !== 0)
52
+ return { kind: 'const', dst: instr.dst, value: instr.a.value % instr.b.value }
53
+ break
54
+ case 'neg':
55
+ if (isConst(instr.src))
56
+ return { kind: 'const', dst: instr.dst, value: -instr.src.value }
57
+ break
58
+ case 'not':
59
+ if (isConst(instr.src))
60
+ return { kind: 'const', dst: instr.dst, value: instr.src.value === 0 ? 1 : 0 }
61
+ break
62
+ case 'and':
63
+ if (isConst(instr.a) && isConst(instr.b))
64
+ return { kind: 'const', dst: instr.dst, value: (instr.a.value !== 0 && instr.b.value !== 0) ? 1 : 0 }
65
+ break
66
+ case 'or':
67
+ if (isConst(instr.a) && isConst(instr.b))
68
+ return { kind: 'const', dst: instr.dst, value: (instr.a.value !== 0 || instr.b.value !== 0) ? 1 : 0 }
69
+ break
70
+ case 'cmp':
71
+ if (isConst(instr.a) && isConst(instr.b))
72
+ return { kind: 'const', dst: instr.dst, value: evalCmp(instr.op, instr.a.value, instr.b.value) }
73
+ break
74
+ }
75
+ return null
76
+ }
77
+
78
+ function evalCmp(op: string, a: number, b: number): number {
79
+ switch (op) {
80
+ case 'eq': return a === b ? 1 : 0
81
+ case 'ne': return a !== b ? 1 : 0
82
+ case 'lt': return a < b ? 1 : 0
83
+ case 'le': return a <= b ? 1 : 0
84
+ case 'gt': return a > b ? 1 : 0
85
+ case 'ge': return a >= b ? 1 : 0
86
+ default: return 0
87
+ }
88
+ }