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,222 @@
1
+ /**
2
+ * Stage 7 — LIR → .mcfunction Emission
3
+ *
4
+ * Converts a LIRModule into DatapackFile[] representing a Minecraft datapack.
5
+ * Each LIRFunction becomes a .mcfunction file under data/<ns>/function/.
6
+ */
7
+
8
+ import type { LIRModule, LIRFunction, LIRInstr, Slot, CmpOp, ExecuteSubcmd } from '../lir/types'
9
+
10
+ export interface DatapackFile {
11
+ path: string
12
+ content: string
13
+ }
14
+
15
+ export interface EmitOptions {
16
+ namespace: string
17
+ tickFunctions?: string[]
18
+ loadFunctions?: string[]
19
+ }
20
+
21
+ // ---------------------------------------------------------------------------
22
+ // Public API
23
+ // ---------------------------------------------------------------------------
24
+
25
+ export function emit(module: LIRModule, options: EmitOptions): DatapackFile[] {
26
+ const { namespace } = options
27
+ const tickFns = options.tickFunctions ?? []
28
+ const loadFns = options.loadFunctions ?? []
29
+ const objective = module.objective
30
+ const files: DatapackFile[] = []
31
+
32
+ // pack.mcmeta
33
+ files.push({
34
+ path: 'pack.mcmeta',
35
+ content: JSON.stringify({
36
+ pack: { pack_format: 26, description: `RedScript datapack: ${namespace}` },
37
+ }, null, 2) + '\n',
38
+ })
39
+
40
+ // load.mcfunction — creates the scoreboard objective
41
+ const loadCmds = [`scoreboard objectives add ${objective} dummy`]
42
+ files.push({
43
+ path: `data/${namespace}/function/load.mcfunction`,
44
+ content: loadCmds.join('\n') + '\n',
45
+ })
46
+
47
+ // Each LIR function → .mcfunction file
48
+ for (const fn of module.functions) {
49
+ const lines = emitFunction(fn, namespace, objective)
50
+ const fnPath = fnNameToPath(fn.name, namespace)
51
+ files.push({ path: fnPath, content: lines.join('\n') + '\n' })
52
+ }
53
+
54
+ // Tag files for tick/load
55
+ if (loadFns.length > 0 || true) {
56
+ // Always include load.json — it must reference the load.mcfunction
57
+ const loadValues = [`${namespace}:load`, ...loadFns.map(fn => `${namespace}:${fn}`)]
58
+ files.push({
59
+ path: 'data/minecraft/tags/function/load.json',
60
+ content: JSON.stringify({ values: loadValues }, null, 2) + '\n',
61
+ })
62
+ }
63
+
64
+ if (tickFns.length > 0) {
65
+ const tickValues = tickFns.map(fn => `${namespace}:${fn}`)
66
+ files.push({
67
+ path: 'data/minecraft/tags/function/tick.json',
68
+ content: JSON.stringify({ values: tickValues }, null, 2) + '\n',
69
+ })
70
+ }
71
+
72
+ return files
73
+ }
74
+
75
+ // ---------------------------------------------------------------------------
76
+ // Function emission
77
+ // ---------------------------------------------------------------------------
78
+
79
+ function emitFunction(fn: LIRFunction, namespace: string, objective: string): string[] {
80
+ const lines: string[] = []
81
+ for (const instr of fn.instructions) {
82
+ lines.push(emitInstr(instr, namespace, objective))
83
+ }
84
+ return lines
85
+ }
86
+
87
+ function fnNameToPath(name: string, namespace: string): string {
88
+ // LIR function names may contain :: for methods — convert to /
89
+ const mcName = name.replace(/::/g, '/').toLowerCase()
90
+ return `data/${namespace}/function/${mcName}.mcfunction`
91
+ }
92
+
93
+ // ---------------------------------------------------------------------------
94
+ // Instruction emission
95
+ // ---------------------------------------------------------------------------
96
+
97
+ function emitInstr(instr: LIRInstr, ns: string, obj: string): string {
98
+ switch (instr.kind) {
99
+ case 'score_set':
100
+ return `scoreboard players set ${slot(instr.dst)} ${instr.value}`
101
+
102
+ case 'score_copy':
103
+ return `scoreboard players operation ${slot(instr.dst)} = ${slot(instr.src)}`
104
+
105
+ case 'score_add':
106
+ return `scoreboard players operation ${slot(instr.dst)} += ${slot(instr.src)}`
107
+
108
+ case 'score_sub':
109
+ return `scoreboard players operation ${slot(instr.dst)} -= ${slot(instr.src)}`
110
+
111
+ case 'score_mul':
112
+ return `scoreboard players operation ${slot(instr.dst)} *= ${slot(instr.src)}`
113
+
114
+ case 'score_div':
115
+ return `scoreboard players operation ${slot(instr.dst)} /= ${slot(instr.src)}`
116
+
117
+ case 'score_mod':
118
+ return `scoreboard players operation ${slot(instr.dst)} %= ${slot(instr.src)}`
119
+
120
+ case 'score_min':
121
+ return `scoreboard players operation ${slot(instr.dst)} < ${slot(instr.src)}`
122
+
123
+ case 'score_max':
124
+ return `scoreboard players operation ${slot(instr.dst)} > ${slot(instr.src)}`
125
+
126
+ case 'score_swap':
127
+ return `scoreboard players operation ${slot(instr.a)} >< ${slot(instr.b)}`
128
+
129
+ case 'store_cmd_to_score':
130
+ return `execute store result score ${slot(instr.dst)} run ${emitInstr(instr.cmd, ns, obj)}`
131
+
132
+ case 'store_score_to_nbt':
133
+ return `execute store result storage ${instr.ns} ${instr.path} ${instr.type} ${instr.scale} run scoreboard players get ${slot(instr.src)}`
134
+
135
+ case 'store_nbt_to_score':
136
+ return `execute store result score ${slot(instr.dst)} run data get storage ${instr.ns} ${instr.path} ${instr.scale}`
137
+
138
+ case 'nbt_set_literal':
139
+ return `data modify storage ${instr.ns} ${instr.path} set value ${instr.value}`
140
+
141
+ case 'nbt_copy':
142
+ return `data modify storage ${instr.dstNs} ${instr.dstPath} set from storage ${instr.srcNs} ${instr.srcPath}`
143
+
144
+ case 'call':
145
+ return `function ${instr.fn}`
146
+
147
+ case 'call_macro':
148
+ return `function ${instr.fn} with storage ${instr.storage}`
149
+
150
+ case 'call_if_matches':
151
+ return `execute if score ${slot(instr.slot)} matches ${instr.range} run function ${instr.fn}`
152
+
153
+ case 'call_unless_matches':
154
+ return `execute unless score ${slot(instr.slot)} matches ${instr.range} run function ${instr.fn}`
155
+
156
+ case 'call_if_score':
157
+ return `execute if score ${slot(instr.a)} ${cmpToMC(instr.op)} ${slot(instr.b)} run function ${instr.fn}`
158
+
159
+ case 'call_unless_score':
160
+ return `execute unless score ${slot(instr.a)} ${cmpToMC(instr.op)} ${slot(instr.b)} run function ${instr.fn}`
161
+
162
+ case 'call_context': {
163
+ const subcmds = instr.subcommands.map(emitSubcmd).join(' ')
164
+ return `execute ${subcmds} run function ${instr.fn}`
165
+ }
166
+
167
+ case 'return_value':
168
+ return `scoreboard players operation $ret ${instr.slot.obj} = ${slot(instr.slot)}`
169
+
170
+ case 'macro_line':
171
+ return `$${instr.template}`
172
+
173
+ case 'raw':
174
+ return instr.cmd
175
+ }
176
+ }
177
+
178
+ // ---------------------------------------------------------------------------
179
+ // Helpers
180
+ // ---------------------------------------------------------------------------
181
+
182
+ function slot(s: Slot): string {
183
+ return `${s.player} ${s.obj}`
184
+ }
185
+
186
+ function cmpToMC(op: CmpOp): string {
187
+ switch (op) {
188
+ case 'eq': return '='
189
+ case 'ne': return '=' // ne uses "unless" form, but when used in if score context
190
+ case 'lt': return '<'
191
+ case 'le': return '<='
192
+ case 'gt': return '>'
193
+ case 'ge': return '>='
194
+ }
195
+ }
196
+
197
+ function emitSubcmd(sub: ExecuteSubcmd): string {
198
+ switch (sub.kind) {
199
+ case 'as':
200
+ return `as ${sub.selector}`
201
+ case 'at':
202
+ return `at ${sub.selector}`
203
+ case 'at_self':
204
+ return 'at @s'
205
+ case 'positioned':
206
+ return `positioned ${sub.x} ${sub.y} ${sub.z}`
207
+ case 'rotated':
208
+ return `rotated ${sub.yaw} ${sub.pitch}`
209
+ case 'in':
210
+ return `in ${sub.dimension}`
211
+ case 'anchored':
212
+ return `anchored ${sub.anchor}`
213
+ case 'if_score':
214
+ return `if score ${sub.a} ${cmpToMC(sub.op)} ${sub.b}`
215
+ case 'unless_score':
216
+ return `unless score ${sub.a} ${cmpToMC(sub.op)} ${sub.b}`
217
+ case 'if_matches':
218
+ return `if score ${sub.score} matches ${sub.range}`
219
+ case 'unless_matches':
220
+ return `unless score ${sub.score} matches ${sub.range}`
221
+ }
222
+ }
@@ -0,0 +1,428 @@
1
+ /**
2
+ * AST → HIR Lowering — Stage 2 of the RedScript compiler pipeline.
3
+ *
4
+ * Desugaring transforms:
5
+ * - for(init;cond;step) → block { init; while(cond) { body; step } }
6
+ * - for_range(v,start,end) → block { let v = start; while(v < end) { body; v = v + 1 } }
7
+ * - a += b / -= / *= / /= / %= → a = a OP b
8
+ * - a && b → if(a) { b } else { false }
9
+ * - a || b → if(a) { true } else { b }
10
+ * - as_block / at_block / as_at → unified execute with subcommands
11
+ * - All other nodes pass through with field-wise recursion
12
+ */
13
+
14
+ import type {
15
+ Program, Expr, Stmt, Block, FnDecl, Param,
16
+ AssignOp, ExecuteSubcommand,
17
+ } from '../../src/ast/types'
18
+ import type {
19
+ HIRModule, HIRFunction, HIRParam, HIRExpr, HIRStmt, HIRBlock,
20
+ HIRExecuteSubcommand, HIRStruct, HIRImplBlock, HIREnum, HIRConst, HIRGlobal,
21
+ } from './types'
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Public API
25
+ // ---------------------------------------------------------------------------
26
+
27
+ export function lowerToHIR(program: Program): HIRModule {
28
+ return {
29
+ namespace: program.namespace,
30
+ globals: program.globals.map(lowerGlobal),
31
+ functions: program.declarations.map(lowerFunction),
32
+ structs: program.structs.map((s): HIRStruct => ({
33
+ name: s.name,
34
+ fields: s.fields.map(f => ({ name: f.name, type: f.type })),
35
+ span: s.span,
36
+ })),
37
+ implBlocks: program.implBlocks.map((ib): HIRImplBlock => ({
38
+ typeName: ib.typeName,
39
+ methods: ib.methods.map(lowerFunction),
40
+ span: ib.span,
41
+ })),
42
+ enums: program.enums.map((e): HIREnum => ({
43
+ name: e.name,
44
+ variants: e.variants.map(v => ({ name: v.name, value: v.value })),
45
+ span: e.span,
46
+ })),
47
+ consts: program.consts.map((c): HIRConst => ({
48
+ name: c.name,
49
+ type: c.type,
50
+ value: lowerExpr(c.value),
51
+ span: c.span,
52
+ })),
53
+ isLibrary: program.isLibrary,
54
+ }
55
+ }
56
+
57
+ // ---------------------------------------------------------------------------
58
+ // Functions & globals
59
+ // ---------------------------------------------------------------------------
60
+
61
+ function lowerGlobal(g: Program['globals'][0]): HIRGlobal {
62
+ return {
63
+ name: g.name,
64
+ type: g.type,
65
+ init: lowerExpr(g.init),
66
+ mutable: g.mutable,
67
+ span: g.span,
68
+ }
69
+ }
70
+
71
+ function lowerFunction(fn: FnDecl): HIRFunction {
72
+ return {
73
+ name: fn.name,
74
+ params: fn.params.map(lowerParam),
75
+ returnType: fn.returnType,
76
+ decorators: fn.decorators,
77
+ body: lowerBlock(fn.body),
78
+ isLibraryFn: fn.isLibraryFn,
79
+ span: fn.span,
80
+ }
81
+ }
82
+
83
+ function lowerParam(p: Param): HIRParam {
84
+ return {
85
+ name: p.name,
86
+ type: p.type,
87
+ default: p.default ? lowerExpr(p.default) : undefined,
88
+ }
89
+ }
90
+
91
+ // ---------------------------------------------------------------------------
92
+ // Blocks & statements
93
+ // ---------------------------------------------------------------------------
94
+
95
+ function lowerBlock(block: Block): HIRBlock {
96
+ const result: HIRBlock = []
97
+ for (const stmt of block) {
98
+ const lowered = lowerStmt(stmt)
99
+ // lowerStmt may return an array (e.g. for → [init, while])
100
+ if (Array.isArray(lowered)) {
101
+ result.push(...lowered)
102
+ } else {
103
+ result.push(lowered)
104
+ }
105
+ }
106
+ return result
107
+ }
108
+
109
+ function lowerStmt(stmt: Stmt): HIRStmt | HIRStmt[] {
110
+ switch (stmt.kind) {
111
+ case 'let':
112
+ return { kind: 'let', name: stmt.name, type: stmt.type, init: lowerExpr(stmt.init), span: stmt.span }
113
+
114
+ case 'expr':
115
+ return { kind: 'expr', expr: lowerExpr(stmt.expr), span: stmt.span }
116
+
117
+ case 'return':
118
+ return { kind: 'return', value: stmt.value ? lowerExpr(stmt.value) : undefined, span: stmt.span }
119
+
120
+ case 'break':
121
+ return { kind: 'break', span: stmt.span }
122
+
123
+ case 'continue':
124
+ return { kind: 'continue', span: stmt.span }
125
+
126
+ case 'if':
127
+ return {
128
+ kind: 'if',
129
+ cond: lowerExpr(stmt.cond),
130
+ then: lowerBlock(stmt.then),
131
+ else_: stmt.else_ ? lowerBlock(stmt.else_) : undefined,
132
+ span: stmt.span,
133
+ }
134
+
135
+ case 'while':
136
+ return { kind: 'while', cond: lowerExpr(stmt.cond), body: lowerBlock(stmt.body), span: stmt.span }
137
+
138
+ // --- Desugaring: for → while ---
139
+ case 'for': {
140
+ const stmts: HIRStmt[] = []
141
+ // Init
142
+ if (stmt.init) {
143
+ const init = lowerStmt(stmt.init)
144
+ if (Array.isArray(init)) stmts.push(...init)
145
+ else stmts.push(init)
146
+ }
147
+ // while(cond) { body } step { step_expr }
148
+ const body = lowerBlock(stmt.body)
149
+ const step: HIRStmt[] = [{ kind: 'expr', expr: lowerExpr(stmt.step), span: stmt.span }]
150
+ stmts.push({ kind: 'while', cond: lowerExpr(stmt.cond), body, step, span: stmt.span })
151
+ return stmts
152
+ }
153
+
154
+ // --- Desugaring: for_range → let + while(cond) { body } step { v++ } ---
155
+ case 'for_range': {
156
+ const varName = stmt.varName
157
+ const initStmt: HIRStmt = {
158
+ kind: 'let',
159
+ name: varName,
160
+ type: { kind: 'named', name: 'int' },
161
+ init: lowerExpr(stmt.start),
162
+ span: stmt.span,
163
+ }
164
+ const body = lowerBlock(stmt.body)
165
+ // step: v = v + 1 (in separate step block so continue still increments)
166
+ const step: HIRStmt[] = [{
167
+ kind: 'expr',
168
+ expr: {
169
+ kind: 'assign',
170
+ target: varName,
171
+ value: {
172
+ kind: 'binary',
173
+ op: '+',
174
+ left: { kind: 'ident', name: varName },
175
+ right: { kind: 'int_lit', value: 1 },
176
+ },
177
+ },
178
+ }]
179
+ const whileStmt: HIRStmt = {
180
+ kind: 'while',
181
+ cond: {
182
+ kind: 'binary',
183
+ op: '<',
184
+ left: { kind: 'ident', name: varName },
185
+ right: lowerExpr(stmt.end),
186
+ },
187
+ body,
188
+ step,
189
+ span: stmt.span,
190
+ }
191
+ return [initStmt, whileStmt]
192
+ }
193
+
194
+ case 'foreach':
195
+ return {
196
+ kind: 'foreach',
197
+ binding: stmt.binding,
198
+ iterable: lowerExpr(stmt.iterable),
199
+ body: lowerBlock(stmt.body),
200
+ executeContext: stmt.executeContext,
201
+ span: stmt.span,
202
+ }
203
+
204
+ case 'match':
205
+ return {
206
+ kind: 'match',
207
+ expr: lowerExpr(stmt.expr),
208
+ arms: stmt.arms.map(arm => ({
209
+ pattern: arm.pattern ? lowerExpr(arm.pattern) : null,
210
+ body: lowerBlock(arm.body),
211
+ })),
212
+ span: stmt.span,
213
+ }
214
+
215
+ // --- Desugaring: as_block → execute [as] ---
216
+ case 'as_block':
217
+ return {
218
+ kind: 'execute',
219
+ subcommands: [{ kind: 'as', selector: stmt.selector }],
220
+ body: lowerBlock(stmt.body),
221
+ span: stmt.span,
222
+ }
223
+
224
+ // --- Desugaring: at_block → execute [at] ---
225
+ case 'at_block':
226
+ return {
227
+ kind: 'execute',
228
+ subcommands: [{ kind: 'at', selector: stmt.selector }],
229
+ body: lowerBlock(stmt.body),
230
+ span: stmt.span,
231
+ }
232
+
233
+ // --- Desugaring: as_at → execute [as, at] ---
234
+ case 'as_at':
235
+ return {
236
+ kind: 'execute',
237
+ subcommands: [
238
+ { kind: 'as', selector: stmt.as_sel },
239
+ { kind: 'at', selector: stmt.at_sel },
240
+ ],
241
+ body: lowerBlock(stmt.body),
242
+ span: stmt.span,
243
+ }
244
+
245
+ case 'execute':
246
+ return {
247
+ kind: 'execute',
248
+ subcommands: stmt.subcommands.map(lowerExecuteSubcommand),
249
+ body: lowerBlock(stmt.body),
250
+ span: stmt.span,
251
+ }
252
+
253
+ case 'raw':
254
+ return { kind: 'raw', cmd: stmt.cmd, span: stmt.span }
255
+
256
+ default: {
257
+ const _exhaustive: never = stmt
258
+ throw new Error(`Unknown statement kind: ${(_exhaustive as any).kind}`)
259
+ }
260
+ }
261
+ }
262
+
263
+ // ---------------------------------------------------------------------------
264
+ // Execute subcommands (pass-through — same shape)
265
+ // ---------------------------------------------------------------------------
266
+
267
+ function lowerExecuteSubcommand(sub: ExecuteSubcommand): HIRExecuteSubcommand {
268
+ // All subcommand types share the same shape between AST and HIR
269
+ return sub as HIRExecuteSubcommand
270
+ }
271
+
272
+ // ---------------------------------------------------------------------------
273
+ // Expressions
274
+ // ---------------------------------------------------------------------------
275
+
276
+ /** Map compound assignment operator to its base binary op */
277
+ const COMPOUND_TO_BINOP: Record<string, string> = {
278
+ '+=': '+',
279
+ '-=': '-',
280
+ '*=': '*',
281
+ '/=': '/',
282
+ '%=': '%',
283
+ }
284
+
285
+ function lowerExpr(expr: Expr): HIRExpr {
286
+ switch (expr.kind) {
287
+ // --- Pass-through literals ---
288
+ case 'int_lit':
289
+ case 'float_lit':
290
+ case 'byte_lit':
291
+ case 'short_lit':
292
+ case 'long_lit':
293
+ case 'double_lit':
294
+ case 'bool_lit':
295
+ case 'str_lit':
296
+ case 'range_lit':
297
+ case 'rel_coord':
298
+ case 'local_coord':
299
+ case 'mc_name':
300
+ case 'blockpos':
301
+ return expr as HIRExpr
302
+
303
+ case 'ident':
304
+ return { kind: 'ident', name: expr.name, span: expr.span }
305
+
306
+ case 'selector':
307
+ return { kind: 'selector', raw: expr.raw, isSingle: expr.isSingle, sel: expr.sel, span: expr.span }
308
+
309
+ case 'array_lit':
310
+ return { kind: 'array_lit', elements: expr.elements.map(lowerExpr), span: expr.span }
311
+
312
+ case 'struct_lit':
313
+ return {
314
+ kind: 'struct_lit',
315
+ fields: expr.fields.map(f => ({ name: f.name, value: lowerExpr(f.value) })),
316
+ span: expr.span,
317
+ }
318
+
319
+ case 'str_interp':
320
+ return {
321
+ kind: 'str_interp',
322
+ parts: expr.parts.map(p => typeof p === 'string' ? p : lowerExpr(p)),
323
+ span: expr.span,
324
+ }
325
+
326
+ case 'f_string':
327
+ return { kind: 'f_string', parts: expr.parts, span: expr.span }
328
+
329
+ // Binary ops — && and || preserved as-is (short-circuit → control flow in MIR)
330
+ case 'binary':
331
+ return {
332
+ kind: 'binary',
333
+ op: expr.op,
334
+ left: lowerExpr(expr.left),
335
+ right: lowerExpr(expr.right),
336
+ span: expr.span,
337
+ }
338
+
339
+ case 'unary':
340
+ return { kind: 'unary', op: expr.op, operand: lowerExpr(expr.operand), span: expr.span }
341
+
342
+ case 'is_check':
343
+ return { kind: 'is_check', expr: lowerExpr(expr.expr), entityType: expr.entityType, span: expr.span }
344
+
345
+ // --- Desugaring: compound assignment → plain assign ---
346
+ case 'assign':
347
+ if (expr.op !== '=') {
348
+ const binOp = COMPOUND_TO_BINOP[expr.op]
349
+ return {
350
+ kind: 'assign',
351
+ target: expr.target,
352
+ value: {
353
+ kind: 'binary',
354
+ op: binOp as any,
355
+ left: { kind: 'ident', name: expr.target },
356
+ right: lowerExpr(expr.value),
357
+ span: expr.span,
358
+ },
359
+ span: expr.span,
360
+ }
361
+ }
362
+ return { kind: 'assign', target: expr.target, value: lowerExpr(expr.value), span: expr.span }
363
+
364
+ // --- Desugaring: compound member_assign → plain member_assign ---
365
+ case 'member_assign':
366
+ if (expr.op !== '=') {
367
+ const binOp = COMPOUND_TO_BINOP[expr.op]
368
+ const obj = lowerExpr(expr.obj)
369
+ return {
370
+ kind: 'member_assign',
371
+ obj,
372
+ field: expr.field,
373
+ value: {
374
+ kind: 'binary',
375
+ op: binOp as any,
376
+ left: { kind: 'member', obj, field: expr.field },
377
+ right: lowerExpr(expr.value),
378
+ span: expr.span,
379
+ },
380
+ span: expr.span,
381
+ }
382
+ }
383
+ return {
384
+ kind: 'member_assign',
385
+ obj: lowerExpr(expr.obj),
386
+ field: expr.field,
387
+ value: lowerExpr(expr.value),
388
+ span: expr.span,
389
+ }
390
+
391
+ case 'member':
392
+ return { kind: 'member', obj: lowerExpr(expr.obj), field: expr.field, span: expr.span }
393
+
394
+ case 'index':
395
+ return { kind: 'index', obj: lowerExpr(expr.obj), index: lowerExpr(expr.index), span: expr.span }
396
+
397
+ case 'call':
398
+ return { kind: 'call', fn: expr.fn, args: expr.args.map(lowerExpr), span: expr.span }
399
+
400
+ case 'invoke':
401
+ return { kind: 'invoke', callee: lowerExpr(expr.callee), args: expr.args.map(lowerExpr), span: expr.span }
402
+
403
+ case 'static_call':
404
+ return {
405
+ kind: 'static_call',
406
+ type: expr.type,
407
+ method: expr.method,
408
+ args: expr.args.map(lowerExpr),
409
+ span: expr.span,
410
+ }
411
+
412
+ case 'lambda': {
413
+ const body = Array.isArray(expr.body) ? lowerBlock(expr.body) : lowerExpr(expr.body)
414
+ return {
415
+ kind: 'lambda',
416
+ params: expr.params,
417
+ returnType: expr.returnType,
418
+ body,
419
+ span: expr.span,
420
+ }
421
+ }
422
+
423
+ default: {
424
+ const _exhaustive: never = expr
425
+ throw new Error(`Unknown expression kind: ${(_exhaustive as any).kind}`)
426
+ }
427
+ }
428
+ }