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
@@ -1,662 +0,0 @@
1
- /**
2
- * Code generator: IR → mcfunction datapack
3
- *
4
- * Output structure:
5
- * <namespace>/
6
- * functions/
7
- * <fn_name>.mcfunction
8
- * <fn_name>/<block_label>.mcfunction (for control-flow continuations)
9
- * load.mcfunction (objective setup)
10
- *
11
- * Variable mapping:
12
- * scoreboard objective: "rs"
13
- * fake player: "$<varname>"
14
- * temporaries: "$_0", "$_1", ...
15
- * return value: "$ret"
16
- * parameters: "$p0", "$p1", ...
17
- */
18
-
19
- import type { IRBlock, IRFunction, IRInstr, IRModule, Operand, Terminator } from '../../ir/types'
20
- import { optimizeCommandFunctions, setOptimizerObjective, type OptimizationStats, createEmptyOptimizationStats, mergeOptimizationStats } from '../../optimizer/commands'
21
- import { EVENT_TYPES, isEventTypeName, type EventTypeName } from '../../events/types'
22
- import { VarAllocator } from '../var-allocator'
23
-
24
- // ---------------------------------------------------------------------------
25
- // Utilities
26
- // ---------------------------------------------------------------------------
27
-
28
- // Default scoreboard objective — overridden per-compilation via DatapackGenerationOptions.scoreboardObjective
29
- let OBJ = 'rs'
30
-
31
- function operandToScore(op: Operand, alloc: VarAllocator): string {
32
- if (op.kind === 'var') return `${alloc.alloc(op.name)} ${OBJ}`
33
- if (op.kind === 'const') return `${alloc.constant(op.value)} ${OBJ}`
34
- if (op.kind === 'param') return `${alloc.internal(`p${op.index}`)} ${OBJ}`
35
- throw new Error(`Cannot convert storage operand to score: ${op.path}`)
36
- }
37
-
38
- // Collect all constants used in a function for pre-setup
39
- function collectConsts(fn: IRFunction): Set<number> {
40
- const consts = new Set<number>()
41
- for (const block of fn.blocks) {
42
- for (const instr of block.instrs) {
43
- if (instr.op === 'assign' && instr.src.kind === 'const') consts.add(instr.src.value)
44
- if (instr.op === 'binop') {
45
- if (instr.lhs.kind === 'const') consts.add(instr.lhs.value)
46
- if (instr.rhs.kind === 'const') consts.add(instr.rhs.value)
47
- }
48
- if (instr.op === 'cmp') {
49
- if (instr.lhs.kind === 'const') consts.add(instr.lhs.value)
50
- if (instr.rhs.kind === 'const') consts.add(instr.rhs.value)
51
- }
52
- }
53
- const t = block.term
54
- if (t.op === 'return' && t.value?.kind === 'const') consts.add(t.value.value)
55
- }
56
- return consts
57
- }
58
-
59
- // MC scoreboard operation suffix
60
- const BOP_OP: Record<string, string> = {
61
- '+': '+=', '-': '-=', '*': '*=', '/': '/=', '%': '%=',
62
- }
63
-
64
- // ---------------------------------------------------------------------------
65
- // Instruction codegen
66
- // ---------------------------------------------------------------------------
67
-
68
- function emitInstr(instr: ReturnType<typeof Object.assign> & { op: string }, ns: string, alloc: VarAllocator): string[] {
69
- const lines: string[] = []
70
-
71
- switch (instr.op) {
72
- case 'assign': {
73
- const dst = alloc.alloc(instr.dst)
74
- const src = instr.src as Operand
75
- if (src.kind === 'const') {
76
- lines.push(`scoreboard players set ${dst} ${OBJ} ${src.value}`)
77
- } else if (src.kind === 'var') {
78
- lines.push(`scoreboard players operation ${dst} ${OBJ} = ${alloc.alloc(src.name)} ${OBJ}`)
79
- } else if (src.kind === 'param') {
80
- lines.push(`scoreboard players operation ${dst} ${OBJ} = ${alloc.internal(`p${src.index}`)} ${OBJ}`)
81
- } else {
82
- lines.push(`execute store result score ${dst} ${OBJ} run data get storage ${src.path}`)
83
- }
84
- break
85
- }
86
-
87
- case 'binop': {
88
- const dst = alloc.alloc(instr.dst)
89
- const bop = BOP_OP[instr.bop as string] ?? '+='
90
- // Copy lhs → dst, then apply op with rhs
91
- lines.push(...emitInstr({ op: 'assign', dst: instr.dst, src: instr.lhs }, ns, alloc))
92
- lines.push(`scoreboard players operation ${dst} ${OBJ} ${bop} ${operandToScore(instr.rhs, alloc)}`)
93
- break
94
- }
95
-
96
- case 'cmp': {
97
- // MC doesn't have a direct compare-to-register; use execute store
98
- const dst = alloc.alloc(instr.dst)
99
- const lhsScore = operandToScore(instr.lhs, alloc)
100
- const rhsScore = operandToScore(instr.rhs, alloc)
101
- lines.push(`scoreboard players set ${dst} ${OBJ} 0`)
102
- switch (instr.cop) {
103
- case '==':
104
- lines.push(`execute if score ${lhsScore} = ${rhsScore} run scoreboard players set ${dst} ${OBJ} 1`)
105
- break
106
- case '!=':
107
- lines.push(`execute unless score ${lhsScore} = ${rhsScore} run scoreboard players set ${dst} ${OBJ} 1`)
108
- break
109
- case '<':
110
- lines.push(`execute if score ${lhsScore} < ${rhsScore} run scoreboard players set ${dst} ${OBJ} 1`)
111
- break
112
- case '<=':
113
- lines.push(`execute if score ${lhsScore} <= ${rhsScore} run scoreboard players set ${dst} ${OBJ} 1`)
114
- break
115
- case '>':
116
- lines.push(`execute if score ${lhsScore} > ${rhsScore} run scoreboard players set ${dst} ${OBJ} 1`)
117
- break
118
- case '>=':
119
- lines.push(`execute if score ${lhsScore} >= ${rhsScore} run scoreboard players set ${dst} ${OBJ} 1`)
120
- break
121
- }
122
- break
123
- }
124
-
125
- case 'call': {
126
- // Push args into the internal parameter slots ($p0, $p1, ...).
127
- // We emit the copy commands directly (not via emitInstr/alloc.alloc) to
128
- // ensure the destination resolves to alloc.internal('p{i}') rather than
129
- // alloc.alloc('p{i}') which would create a *different* user-var slot.
130
- for (let i = 0; i < instr.args.length; i++) {
131
- const paramSlot = alloc.internal(`p${i}`)
132
- const arg = instr.args[i] as Operand
133
- if (arg.kind === 'const') {
134
- lines.push(`scoreboard players set ${paramSlot} ${OBJ} ${arg.value}`)
135
- } else if (arg.kind === 'var') {
136
- lines.push(`scoreboard players operation ${paramSlot} ${OBJ} = ${alloc.alloc(arg.name)} ${OBJ}`)
137
- } else if (arg.kind === 'param') {
138
- lines.push(`scoreboard players operation ${paramSlot} ${OBJ} = ${alloc.internal(`p${arg.index}`)} ${OBJ}`)
139
- }
140
- // storage args are rare for call sites; fall through to no-op
141
- }
142
- lines.push(`function ${ns}:${instr.fn}`)
143
- if (instr.dst) {
144
- const retSlot = alloc.internal('ret')
145
- lines.push(`scoreboard players operation ${alloc.alloc(instr.dst)} ${OBJ} = ${retSlot} ${OBJ}`)
146
- }
147
- break
148
- }
149
-
150
- case 'raw': {
151
- // resolveRaw rewrites $var tokens that are registered in the allocator
152
- // so that mangle=true mode produces correct mangled names instead of
153
- // the raw IR names embedded by the lowering phase.
154
- // \x01 is a sentinel for the MC macro line-start '$' (used by
155
- // storage_get_int sub-functions). Replace it last, after resolveRaw,
156
- // so '$execute' is never treated as a variable reference.
157
- const rawResolved = alloc.resolveRaw(instr.cmd as string).replace(/^\x01/, '$')
158
- lines.push(rawResolved)
159
- break
160
- }
161
- }
162
-
163
- return lines
164
- }
165
-
166
- // ---------------------------------------------------------------------------
167
- // Terminator codegen
168
- // ---------------------------------------------------------------------------
169
-
170
- function emitTerm(term: Terminator, ns: string, fnName: string, alloc: VarAllocator): string[] {
171
- const lines: string[] = []
172
- switch (term.op) {
173
- case 'jump':
174
- lines.push(`function ${ns}:${fnName}/${term.target}`)
175
- break
176
- case 'jump_if':
177
- lines.push(`execute if score ${alloc.alloc(term.cond)} ${OBJ} matches 1.. run function ${ns}:${fnName}/${term.then}`)
178
- lines.push(`execute if score ${alloc.alloc(term.cond)} ${OBJ} matches ..0 run function ${ns}:${fnName}/${term.else_}`)
179
- break
180
- case 'jump_unless':
181
- lines.push(`execute if score ${alloc.alloc(term.cond)} ${OBJ} matches ..0 run function ${ns}:${fnName}/${term.then}`)
182
- lines.push(`execute if score ${alloc.alloc(term.cond)} ${OBJ} matches 1.. run function ${ns}:${fnName}/${term.else_}`)
183
- break
184
- case 'return': {
185
- // Emit the copy to the shared return slot directly — do NOT go through
186
- // emitInstr/alloc.alloc(retSlot) which would allocate a *user* var slot
187
- // (different from the internal slot) and break mangle mode.
188
- const retSlot = alloc.internal('ret')
189
- if (term.value) {
190
- if (term.value.kind === 'const') {
191
- lines.push(`scoreboard players set ${retSlot} ${OBJ} ${term.value.value}`)
192
- } else if (term.value.kind === 'var') {
193
- lines.push(`scoreboard players operation ${retSlot} ${OBJ} = ${alloc.alloc(term.value.name)} ${OBJ}`)
194
- } else if (term.value.kind === 'param') {
195
- lines.push(`scoreboard players operation ${retSlot} ${OBJ} = ${alloc.internal(`p${term.value.index}`)} ${OBJ}`)
196
- }
197
- }
198
- // MC 1.20+: use `return` to propagate the value back to the caller's
199
- // `execute store result … run function …` without an extra scoreboard read.
200
- if (term.value?.kind === 'const') {
201
- lines.push(`return ${term.value.value}`)
202
- } else if (term.value?.kind === 'var') {
203
- lines.push(`return run scoreboard players get ${alloc.alloc(term.value.name)} ${OBJ}`)
204
- } else if (term.value?.kind === 'param') {
205
- lines.push(`return run scoreboard players get ${alloc.internal(`p${term.value.index}`)} ${OBJ}`)
206
- }
207
- break
208
- }
209
- case 'tick_yield':
210
- lines.push(`schedule function ${ns}:${fnName}/${term.continuation} 1t replace`)
211
- break
212
- }
213
- return lines
214
- }
215
-
216
- // ---------------------------------------------------------------------------
217
- // Public API
218
- // ---------------------------------------------------------------------------
219
-
220
- export interface DatapackFile {
221
- path: string // relative to datapack root, e.g. "data/mypack/functions/add.mcfunction"
222
- content: string
223
- }
224
-
225
- function toFunctionName(file: DatapackFile): string | null {
226
- const match = file.path.match(/^data\/[^/]+\/function\/(.+)\.mcfunction$/)
227
- return match?.[1] ?? null
228
- }
229
-
230
- function applyFunctionOptimization(
231
- files: DatapackFile[],
232
- ): { files: DatapackFile[]; stats: OptimizationStats } {
233
- const functionFiles = files
234
- .map(file => {
235
- const functionName = toFunctionName(file)
236
- if (!functionName) return null
237
- const commands = file.content
238
- .split('\n')
239
- .map(line => line.trim())
240
- .filter(line => line !== '' && !line.startsWith('#'))
241
- .map(cmd => ({ cmd }))
242
- return { file, functionName, commands }
243
- })
244
- .filter((entry): entry is NonNullable<typeof entry> => entry !== null)
245
-
246
- const optimized = optimizeCommandFunctions(functionFiles.map(entry => ({
247
- name: entry.functionName,
248
- commands: entry.commands,
249
- })))
250
- const commandMap = new Map(optimized.functions.map(fn => [fn.name, fn.commands]))
251
-
252
- // Filter out files for functions that were removed (inlined trivial functions)
253
- const optimizedNames = new Set(optimized.functions.map(fn => fn.name))
254
-
255
- return {
256
- files: files
257
- .filter(file => {
258
- const functionName = toFunctionName(file)
259
- // Keep non-function files and functions that weren't removed
260
- return !functionName || optimizedNames.has(functionName)
261
- })
262
- .map(file => {
263
- const functionName = toFunctionName(file)
264
- if (!functionName) return file
265
- const commands = commandMap.get(functionName)
266
- if (!commands) return file
267
- const lines = file.content.split('\n')
268
- const header = lines.filter(line => line.trim().startsWith('#'))
269
- return {
270
- ...file,
271
- content: [...header, ...commands.map(command => command.cmd)].join('\n'),
272
- }
273
- }),
274
- stats: optimized.stats,
275
- }
276
- }
277
-
278
- export interface DatapackGenerationResult {
279
- files: DatapackFile[]
280
- advancements: DatapackFile[]
281
- stats: OptimizationStats
282
- sourceMap?: Record<string, string>
283
- }
284
-
285
- export interface DatapackGenerationOptions {
286
- optimizeCommands?: boolean
287
- mangle?: boolean
288
- /** Scoreboard objective used for all scoreboard variables.
289
- * Defaults to 'rs'. Override per-datapack to avoid collisions
290
- * when multiple RedScript datapacks are loaded simultaneously. */
291
- scoreboardObjective?: string
292
- }
293
-
294
- export function countMcfunctionCommands(files: DatapackFile[]): number {
295
- return files.reduce((sum, file) => {
296
- if (!toFunctionName(file)) {
297
- return sum
298
- }
299
-
300
- return sum + file.content
301
- .split('\n')
302
- .map(line => line.trim())
303
- .filter(line => line !== '' && !line.startsWith('#'))
304
- .length
305
- }, 0)
306
- }
307
-
308
- // ---------------------------------------------------------------------------
309
- // Pre-allocation helpers for the two-pass mangle strategy
310
- // ---------------------------------------------------------------------------
311
-
312
- /** Register every variable referenced in an instruction with the allocator. */
313
- function preAllocInstr(instr: IRInstr, alloc: VarAllocator): void {
314
- switch (instr.op) {
315
- case 'assign':
316
- alloc.alloc(instr.dst)
317
- if (instr.src.kind === 'var') alloc.alloc(instr.src.name)
318
- break
319
- case 'binop':
320
- alloc.alloc(instr.dst)
321
- if (instr.lhs.kind === 'var') alloc.alloc(instr.lhs.name)
322
- if (instr.rhs.kind === 'var') alloc.alloc(instr.rhs.name)
323
- break
324
- case 'cmp':
325
- alloc.alloc(instr.dst)
326
- if (instr.lhs.kind === 'var') alloc.alloc(instr.lhs.name)
327
- if (instr.rhs.kind === 'var') alloc.alloc(instr.rhs.name)
328
- break
329
- case 'call':
330
- for (const arg of instr.args) {
331
- if (arg.kind === 'var') alloc.alloc(arg.name)
332
- }
333
- if (instr.dst) alloc.alloc(instr.dst)
334
- break
335
- case 'raw':
336
- // Scan for $varname tokens and pre-register each one
337
- ;(instr.cmd as string).replace(/\$[A-Za-z_][A-Za-z0-9_]*/g, (tok) => {
338
- alloc.alloc(tok)
339
- return tok
340
- })
341
- break
342
- }
343
- }
344
-
345
- /** Register every variable referenced in a terminator with the allocator. */
346
- function preAllocTerm(term: Terminator, alloc: VarAllocator): void {
347
- switch (term.op) {
348
- case 'jump_if':
349
- case 'jump_unless':
350
- alloc.alloc(term.cond)
351
- break
352
- case 'return':
353
- if (term.value?.kind === 'var') alloc.alloc(term.value.name)
354
- break
355
- }
356
- }
357
-
358
- export function generateDatapackWithStats(
359
- module: IRModule,
360
- options: DatapackGenerationOptions = {},
361
- ): DatapackGenerationResult {
362
- const { optimizeCommands = true, mangle = false, scoreboardObjective = 'rs' } = options
363
- // Set module-level OBJ so all helper functions in this module use the correct objective.
364
- // This is safe because compilation is synchronous.
365
- OBJ = scoreboardObjective
366
- setOptimizerObjective(scoreboardObjective)
367
- const alloc = new VarAllocator(mangle)
368
- const files: DatapackFile[] = []
369
- const advancements: DatapackFile[] = []
370
- const ns = module.namespace
371
-
372
- // Collect all trigger handlers
373
- const triggerHandlers = module.functions.filter(fn => fn.isTriggerHandler && fn.triggerName)
374
- const triggerNames = new Set(triggerHandlers.map(fn => fn.triggerName!))
375
- const eventHandlers = module.functions.filter((fn): fn is IRFunction & { eventHandler: { eventType: EventTypeName; tag: string } } =>
376
- !!fn.eventHandler && isEventTypeName(fn.eventHandler.eventType)
377
- )
378
- const eventTypes = new Set<EventTypeName>(eventHandlers.map(fn => fn.eventHandler.eventType))
379
-
380
- // Collect all tick functions
381
- const tickFunctionNames: string[] = []
382
- for (const fn of module.functions) {
383
- if (fn.isTickLoop) {
384
- tickFunctionNames.push(fn.name)
385
- }
386
- }
387
-
388
- // pack.mcmeta
389
- files.push({
390
- path: 'pack.mcmeta',
391
- content: JSON.stringify({
392
- pack: { pack_format: 26, description: `${ns} datapack — compiled by redscript` }
393
- }, null, 2),
394
- })
395
-
396
- // __load.mcfunction — create scoreboard objective + trigger registrations
397
- const loadLines = [
398
- `# RedScript runtime init`,
399
- `scoreboard objectives add ${OBJ} dummy`,
400
- ]
401
- for (const g of module.globals) {
402
- loadLines.push(`scoreboard players set ${alloc.alloc(g.name)} ${OBJ} ${g.init}`)
403
- }
404
-
405
- // Add trigger objectives
406
- for (const triggerName of triggerNames) {
407
- loadLines.push(`scoreboard objectives add ${triggerName} trigger`)
408
- loadLines.push(`scoreboard players enable @a ${triggerName}`)
409
- }
410
-
411
- for (const eventType of eventTypes) {
412
- const detection = EVENT_TYPES[eventType].detection
413
- if (eventType === 'PlayerDeath') {
414
- loadLines.push(`scoreboard objectives add ${OBJ}.deaths deathCount`)
415
- } else if (eventType === 'EntityKill') {
416
- loadLines.push(`scoreboard objectives add ${OBJ}.kills totalKillCount`)
417
- } else if (eventType === 'ItemUse') {
418
- loadLines.push('# ItemUse detection requires a project-specific objective/tag setup')
419
- } else if (detection === 'tag' || detection === 'advancement') {
420
- loadLines.push(`# ${eventType} detection expects tag ${EVENT_TYPES[eventType].tag} to be set externally`)
421
- }
422
- }
423
-
424
- // Generate trigger dispatch functions
425
- for (const triggerName of triggerNames) {
426
- const handlers = triggerHandlers.filter(fn => fn.triggerName === triggerName)
427
-
428
- // __trigger_{name}_dispatch.mcfunction
429
- const dispatchLines = [
430
- `# Trigger dispatch for ${triggerName}`,
431
- ]
432
- for (const handler of handlers) {
433
- dispatchLines.push(`function ${ns}:${handler.name}`)
434
- }
435
- dispatchLines.push(`scoreboard players set @s ${triggerName} 0`)
436
- dispatchLines.push(`scoreboard players enable @s ${triggerName}`)
437
-
438
- files.push({
439
- path: `data/${ns}/function/__trigger_${triggerName}_dispatch.mcfunction`,
440
- content: dispatchLines.join('\n'),
441
- })
442
- }
443
-
444
- // Collect all constants across all functions first (deduplicated)
445
- const allConsts = new Set<number>()
446
- for (const fn of module.functions) {
447
- for (const c of collectConsts(fn)) allConsts.add(c)
448
- }
449
- if (allConsts.size > 0) {
450
- loadLines.push(...Array.from(allConsts).sort((a, b) => a - b).map(
451
- value => `scoreboard players set ${alloc.constant(value)} ${OBJ} ${value}`
452
- ))
453
- }
454
-
455
- // ─────────────────────────────────────────────────────────────────────────
456
- // Pre-allocation pass (mangle mode only)
457
- //
458
- // When mangle=true, the codegen assigns sequential names ($a, $b, …) the
459
- // FIRST time alloc.alloc() is called for a given variable. Raw IR commands
460
- // embed variable names (e.g. "$_0") as plain strings; resolveRaw() can only
461
- // substitute them if the name was already registered in the allocator.
462
- //
463
- // Problem: a freshTemp ($\_0) used in a `raw` instruction and then in the
464
- // immediately following `assign` gets registered by the `assign` AFTER the
465
- // `raw` has already been emitted — so resolveRaw sees an unknown name and
466
- // passes it through verbatim ($\_0), while the assign emits a different
467
- // mangled slot ($e). The two slots never meet and the value is lost.
468
- //
469
- // Fix: walk every instruction (and terminator) of every function in order
470
- // and call alloc.alloc() for each variable reference. This registers all
471
- // names — with the same sequential order the main emit pass will encounter
472
- // them — so that resolveRaw() can always find the correct mangled name.
473
- // ─────────────────────────────────────────────────────────────────────────
474
- if (mangle) {
475
- for (const fn of module.functions) {
476
- // Register internals used by the calling convention
477
- for (let i = 0; i < fn.params.length; i++) alloc.internal(`p${i}`)
478
- alloc.internal('ret')
479
-
480
- for (const block of fn.blocks) {
481
- for (const instr of block.instrs) {
482
- preAllocInstr(instr as IRInstr, alloc)
483
- }
484
- preAllocTerm(block.term, alloc)
485
- }
486
- }
487
- }
488
-
489
- // Generate each function
490
- for (const fn of module.functions) {
491
-
492
- // Entry block → <fn_name>.mcfunction
493
- // Continuation blocks → <fn_name>/<label>.mcfunction
494
- for (let i = 0; i < fn.blocks.length; i++) {
495
- const block = fn.blocks[i]
496
- const lines: string[] = [`# block: ${block.label}`]
497
-
498
- // Param setup is now handled by the lowering IR itself via { kind: 'param' }
499
- // operands, so we no longer need a separate codegen param-copy loop here.
500
- // (Removing it prevents the double-assignment that caused mangle-mode collisions.)
501
-
502
- for (const instr of block.instrs) {
503
- lines.push(...emitInstr(instr as any, ns, alloc))
504
- }
505
- lines.push(...emitTerm(block.term, ns, fn.name, alloc))
506
-
507
- const filePath = i === 0
508
- ? `data/${ns}/function/${fn.name}.mcfunction`
509
- : `data/${ns}/function/${fn.name}/${block.label}.mcfunction`
510
-
511
- // Skip empty continuation blocks (only contain the block comment, no real commands)
512
- // Entry block (i === 0) is always emitted so the function file exists
513
- const hasRealContent = lines.some(l => !l.startsWith('#') && l.trim() !== '')
514
- if (i !== 0 && !hasRealContent) continue
515
-
516
- files.push({ path: filePath, content: lines.join('\n') })
517
- }
518
- }
519
-
520
- // Call @load functions and @requires-referenced load helpers from __load.
521
- // We collect them in a set to deduplicate (multiple fns might @requires the same dep).
522
- const loadCalls = new Set<string>()
523
- for (const fn of module.functions) {
524
- if (fn.isLoadInit) {
525
- loadCalls.add(fn.name)
526
- }
527
- // @requires: if this fn is compiled in, its required load-helpers must also run
528
- for (const dep of fn.requiredLoads ?? []) {
529
- loadCalls.add(dep)
530
- }
531
- }
532
- for (const name of loadCalls) {
533
- loadLines.push(`function ${ns}:${name}`)
534
- }
535
-
536
- // Write __load.mcfunction
537
- files.push({
538
- path: `data/${ns}/function/__load.mcfunction`,
539
- content: loadLines.join('\n'),
540
- })
541
-
542
- // minecraft:load tag pointing to __load
543
- files.push({
544
- path: `data/minecraft/tags/function/load.json`,
545
- content: JSON.stringify({ values: [`${ns}:__load`] }, null, 2),
546
- })
547
-
548
- // __tick.mcfunction — calls all @tick functions + trigger check
549
- const tickLines = ['# RedScript tick dispatcher']
550
-
551
- // Call all @tick functions
552
- for (const fnName of tickFunctionNames) {
553
- tickLines.push(`function ${ns}:${fnName}`)
554
- }
555
-
556
- // Call trigger check if there are triggers
557
- if (triggerNames.size > 0) {
558
- tickLines.push(`# Trigger checks`)
559
- for (const triggerName of triggerNames) {
560
- tickLines.push(`execute as @a[scores={${triggerName}=1..}] run function ${ns}:__trigger_${triggerName}_dispatch`)
561
- }
562
- }
563
-
564
- if (eventHandlers.length > 0) {
565
- tickLines.push('# Event checks')
566
- for (const eventType of eventTypes) {
567
- const tag = EVENT_TYPES[eventType].tag
568
- const handlers = eventHandlers.filter(fn => fn.eventHandler?.eventType === eventType)
569
- for (const handler of handlers) {
570
- tickLines.push(`execute as @a[tag=${tag}] run function ${ns}:${handler.name}`)
571
- }
572
- tickLines.push(`tag @a[tag=${tag}] remove ${tag}`)
573
- }
574
- }
575
-
576
- // Only generate __tick if there's something to run
577
- if (tickFunctionNames.length > 0 || triggerNames.size > 0 || eventHandlers.length > 0) {
578
- files.push({
579
- path: `data/${ns}/function/__tick.mcfunction`,
580
- content: tickLines.join('\n'),
581
- })
582
-
583
- // minecraft:tick tag pointing to __tick
584
- files.push({
585
- path: `data/minecraft/tags/function/tick.json`,
586
- content: JSON.stringify({ values: [`${ns}:__tick`] }, null, 2),
587
- })
588
- }
589
-
590
- for (const fn of module.functions) {
591
- const eventTrigger = fn.eventTrigger
592
- if (!eventTrigger) {
593
- continue
594
- }
595
-
596
- let path = ''
597
- let criteria: Record<string, unknown> = {}
598
-
599
- switch (eventTrigger.kind) {
600
- case 'advancement':
601
- path = `data/${ns}/advancements/on_advancement_${fn.name}.json`
602
- criteria = {
603
- trigger: {
604
- trigger: `minecraft:${eventTrigger.value}`,
605
- },
606
- }
607
- break
608
- case 'craft':
609
- path = `data/${ns}/advancements/on_craft_${fn.name}.json`
610
- criteria = {
611
- crafted: {
612
- trigger: 'minecraft:inventory_changed',
613
- conditions: {
614
- items: [
615
- {
616
- items: [eventTrigger.value],
617
- },
618
- ],
619
- },
620
- },
621
- }
622
- break
623
- case 'death':
624
- path = `data/${ns}/advancements/on_death_${fn.name}.json`
625
- criteria = {
626
- death: {
627
- trigger: 'minecraft:entity_killed_player',
628
- },
629
- }
630
- break
631
- case 'login':
632
- case 'join_team':
633
- continue
634
- }
635
-
636
- advancements.push({
637
- path,
638
- content: JSON.stringify({
639
- criteria,
640
- rewards: {
641
- function: `${ns}:${fn.name}`,
642
- },
643
- }, null, 2),
644
- })
645
- }
646
-
647
- const stats = createEmptyOptimizationStats()
648
- const sourceMap = mangle ? alloc.toSourceMap() : undefined
649
-
650
- if (!optimizeCommands) {
651
- return { files, advancements, stats, sourceMap }
652
- }
653
-
654
- const optimized = applyFunctionOptimization(files)
655
- mergeOptimizationStats(stats, optimized.stats)
656
- return { files: optimized.files, advancements, stats, sourceMap }
657
- }
658
-
659
- export function generateDatapack(module: IRModule): DatapackFile[] {
660
- const generated = generateDatapackWithStats(module)
661
- return [...generated.files, ...generated.advancements]
662
- }