redscript-mc 1.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 (272) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +31 -0
  3. package/.github/ISSUE_TEMPLATE/wrong_output.md +33 -0
  4. package/.github/PULL_REQUEST_TEMPLATE.md +34 -0
  5. package/.github/workflows/ci.yml +29 -0
  6. package/.github/workflows/publish-extension.yml +35 -0
  7. package/LICENSE +21 -0
  8. package/README.md +261 -0
  9. package/README.zh.md +261 -0
  10. package/dist/__tests__/cli.test.d.ts +1 -0
  11. package/dist/__tests__/cli.test.js +140 -0
  12. package/dist/__tests__/codegen.test.d.ts +1 -0
  13. package/dist/__tests__/codegen.test.js +121 -0
  14. package/dist/__tests__/diagnostics.test.d.ts +4 -0
  15. package/dist/__tests__/diagnostics.test.js +149 -0
  16. package/dist/__tests__/e2e.test.d.ts +6 -0
  17. package/dist/__tests__/e2e.test.js +1528 -0
  18. package/dist/__tests__/lexer.test.d.ts +1 -0
  19. package/dist/__tests__/lexer.test.js +316 -0
  20. package/dist/__tests__/lowering.test.d.ts +1 -0
  21. package/dist/__tests__/lowering.test.js +819 -0
  22. package/dist/__tests__/mc-integration.test.d.ts +12 -0
  23. package/dist/__tests__/mc-integration.test.js +395 -0
  24. package/dist/__tests__/mc-syntax.test.d.ts +1 -0
  25. package/dist/__tests__/mc-syntax.test.js +112 -0
  26. package/dist/__tests__/nbt.test.d.ts +1 -0
  27. package/dist/__tests__/nbt.test.js +82 -0
  28. package/dist/__tests__/optimizer-advanced.test.d.ts +1 -0
  29. package/dist/__tests__/optimizer-advanced.test.js +124 -0
  30. package/dist/__tests__/optimizer.test.d.ts +1 -0
  31. package/dist/__tests__/optimizer.test.js +118 -0
  32. package/dist/__tests__/parser.test.d.ts +1 -0
  33. package/dist/__tests__/parser.test.js +717 -0
  34. package/dist/__tests__/repl.test.d.ts +1 -0
  35. package/dist/__tests__/repl.test.js +27 -0
  36. package/dist/__tests__/runtime.test.d.ts +1 -0
  37. package/dist/__tests__/runtime.test.js +276 -0
  38. package/dist/__tests__/structure-optimizer.test.d.ts +1 -0
  39. package/dist/__tests__/structure-optimizer.test.js +33 -0
  40. package/dist/__tests__/typechecker.test.d.ts +1 -0
  41. package/dist/__tests__/typechecker.test.js +364 -0
  42. package/dist/ast/types.d.ts +357 -0
  43. package/dist/ast/types.js +9 -0
  44. package/dist/cli.d.ts +11 -0
  45. package/dist/cli.js +407 -0
  46. package/dist/codegen/cmdblock/index.d.ts +26 -0
  47. package/dist/codegen/cmdblock/index.js +45 -0
  48. package/dist/codegen/mcfunction/index.d.ts +34 -0
  49. package/dist/codegen/mcfunction/index.js +413 -0
  50. package/dist/codegen/structure/index.d.ts +18 -0
  51. package/dist/codegen/structure/index.js +249 -0
  52. package/dist/compile.d.ts +30 -0
  53. package/dist/compile.js +152 -0
  54. package/dist/data/arena/function/__load.mcfunction +6 -0
  55. package/dist/data/arena/function/__tick.mcfunction +2 -0
  56. package/dist/data/arena/function/announce_leaders/else_1.mcfunction +3 -0
  57. package/dist/data/arena/function/announce_leaders/foreach_0/merge_2.mcfunction +1 -0
  58. package/dist/data/arena/function/announce_leaders/foreach_0/then_0.mcfunction +3 -0
  59. package/dist/data/arena/function/announce_leaders/foreach_0.mcfunction +7 -0
  60. package/dist/data/arena/function/announce_leaders/foreach_1/merge_2.mcfunction +1 -0
  61. package/dist/data/arena/function/announce_leaders/foreach_1/then_0.mcfunction +4 -0
  62. package/dist/data/arena/function/announce_leaders/foreach_1.mcfunction +6 -0
  63. package/dist/data/arena/function/announce_leaders/merge_2.mcfunction +1 -0
  64. package/dist/data/arena/function/announce_leaders/then_0.mcfunction +4 -0
  65. package/dist/data/arena/function/announce_leaders.mcfunction +6 -0
  66. package/dist/data/arena/function/arena_tick/merge_2.mcfunction +1 -0
  67. package/dist/data/arena/function/arena_tick/then_0.mcfunction +4 -0
  68. package/dist/data/arena/function/arena_tick.mcfunction +11 -0
  69. package/dist/data/counter/function/__load.mcfunction +5 -0
  70. package/dist/data/counter/function/__tick.mcfunction +2 -0
  71. package/dist/data/counter/function/counter_tick/merge_2.mcfunction +1 -0
  72. package/dist/data/counter/function/counter_tick/then_0.mcfunction +3 -0
  73. package/dist/data/counter/function/counter_tick.mcfunction +11 -0
  74. package/dist/data/minecraft/tags/function/load.json +5 -0
  75. package/dist/data/minecraft/tags/function/tick.json +5 -0
  76. package/dist/data/quiz/function/__load.mcfunction +16 -0
  77. package/dist/data/quiz/function/__tick.mcfunction +6 -0
  78. package/dist/data/quiz/function/__trigger_quiz_a_dispatch.mcfunction +4 -0
  79. package/dist/data/quiz/function/__trigger_quiz_b_dispatch.mcfunction +4 -0
  80. package/dist/data/quiz/function/__trigger_quiz_c_dispatch.mcfunction +4 -0
  81. package/dist/data/quiz/function/__trigger_quiz_start_dispatch.mcfunction +4 -0
  82. package/dist/data/quiz/function/answer_a.mcfunction +4 -0
  83. package/dist/data/quiz/function/answer_b.mcfunction +4 -0
  84. package/dist/data/quiz/function/answer_c.mcfunction +4 -0
  85. package/dist/data/quiz/function/ask_question/else_1.mcfunction +5 -0
  86. package/dist/data/quiz/function/ask_question/else_4.mcfunction +5 -0
  87. package/dist/data/quiz/function/ask_question/else_7.mcfunction +4 -0
  88. package/dist/data/quiz/function/ask_question/merge_2.mcfunction +1 -0
  89. package/dist/data/quiz/function/ask_question/merge_5.mcfunction +2 -0
  90. package/dist/data/quiz/function/ask_question/merge_8.mcfunction +2 -0
  91. package/dist/data/quiz/function/ask_question/then_0.mcfunction +4 -0
  92. package/dist/data/quiz/function/ask_question/then_3.mcfunction +4 -0
  93. package/dist/data/quiz/function/ask_question/then_6.mcfunction +4 -0
  94. package/dist/data/quiz/function/ask_question.mcfunction +7 -0
  95. package/dist/data/quiz/function/finish_quiz.mcfunction +6 -0
  96. package/dist/data/quiz/function/handle_answer/else_1.mcfunction +5 -0
  97. package/dist/data/quiz/function/handle_answer/else_10.mcfunction +3 -0
  98. package/dist/data/quiz/function/handle_answer/else_16.mcfunction +3 -0
  99. package/dist/data/quiz/function/handle_answer/else_4.mcfunction +3 -0
  100. package/dist/data/quiz/function/handle_answer/else_7.mcfunction +5 -0
  101. package/dist/data/quiz/function/handle_answer/merge_11.mcfunction +2 -0
  102. package/dist/data/quiz/function/handle_answer/merge_14.mcfunction +2 -0
  103. package/dist/data/quiz/function/handle_answer/merge_17.mcfunction +2 -0
  104. package/dist/data/quiz/function/handle_answer/merge_2.mcfunction +8 -0
  105. package/dist/data/quiz/function/handle_answer/merge_5.mcfunction +2 -0
  106. package/dist/data/quiz/function/handle_answer/merge_8.mcfunction +2 -0
  107. package/dist/data/quiz/function/handle_answer/then_0.mcfunction +5 -0
  108. package/dist/data/quiz/function/handle_answer/then_12.mcfunction +5 -0
  109. package/dist/data/quiz/function/handle_answer/then_15.mcfunction +6 -0
  110. package/dist/data/quiz/function/handle_answer/then_3.mcfunction +6 -0
  111. package/dist/data/quiz/function/handle_answer/then_6.mcfunction +5 -0
  112. package/dist/data/quiz/function/handle_answer/then_9.mcfunction +6 -0
  113. package/dist/data/quiz/function/handle_answer.mcfunction +11 -0
  114. package/dist/data/quiz/function/start_quiz.mcfunction +5 -0
  115. package/dist/data/shop/function/__load.mcfunction +7 -0
  116. package/dist/data/shop/function/__tick.mcfunction +3 -0
  117. package/dist/data/shop/function/__trigger_shop_buy_dispatch.mcfunction +4 -0
  118. package/dist/data/shop/function/complete_purchase/else_1.mcfunction +5 -0
  119. package/dist/data/shop/function/complete_purchase/else_4.mcfunction +5 -0
  120. package/dist/data/shop/function/complete_purchase/else_7.mcfunction +3 -0
  121. package/dist/data/shop/function/complete_purchase/merge_2.mcfunction +2 -0
  122. package/dist/data/shop/function/complete_purchase/merge_5.mcfunction +2 -0
  123. package/dist/data/shop/function/complete_purchase/merge_8.mcfunction +2 -0
  124. package/dist/data/shop/function/complete_purchase/then_0.mcfunction +4 -0
  125. package/dist/data/shop/function/complete_purchase/then_3.mcfunction +4 -0
  126. package/dist/data/shop/function/complete_purchase/then_6.mcfunction +4 -0
  127. package/dist/data/shop/function/complete_purchase.mcfunction +7 -0
  128. package/dist/data/shop/function/handle_shop_trigger.mcfunction +3 -0
  129. package/dist/data/turret/function/__load.mcfunction +5 -0
  130. package/dist/data/turret/function/__tick.mcfunction +4 -0
  131. package/dist/data/turret/function/__trigger_deploy_turret_dispatch.mcfunction +4 -0
  132. package/dist/data/turret/function/deploy_turret.mcfunction +8 -0
  133. package/dist/data/turret/function/turret_tick/at_1.mcfunction +2 -0
  134. package/dist/data/turret/function/turret_tick/foreach_0.mcfunction +2 -0
  135. package/dist/data/turret/function/turret_tick/foreach_2.mcfunction +2 -0
  136. package/dist/data/turret/function/turret_tick/tick_body.mcfunction +3 -0
  137. package/dist/data/turret/function/turret_tick/tick_skip.mcfunction +1 -0
  138. package/dist/data/turret/function/turret_tick.mcfunction +5 -0
  139. package/dist/diagnostics/index.d.ts +44 -0
  140. package/dist/diagnostics/index.js +140 -0
  141. package/dist/index.d.ts +53 -0
  142. package/dist/index.js +126 -0
  143. package/dist/ir/builder.d.ts +32 -0
  144. package/dist/ir/builder.js +99 -0
  145. package/dist/ir/types.d.ts +117 -0
  146. package/dist/ir/types.js +15 -0
  147. package/dist/lexer/index.d.ts +36 -0
  148. package/dist/lexer/index.js +458 -0
  149. package/dist/lowering/index.d.ts +106 -0
  150. package/dist/lowering/index.js +2041 -0
  151. package/dist/mc-test/client.d.ts +128 -0
  152. package/dist/mc-test/client.js +174 -0
  153. package/dist/mc-test/runner.d.ts +28 -0
  154. package/dist/mc-test/runner.js +150 -0
  155. package/dist/mc-test/setup.d.ts +11 -0
  156. package/dist/mc-test/setup.js +98 -0
  157. package/dist/mc-validator/index.d.ts +17 -0
  158. package/dist/mc-validator/index.js +322 -0
  159. package/dist/nbt/index.d.ts +86 -0
  160. package/dist/nbt/index.js +250 -0
  161. package/dist/optimizer/commands.d.ts +36 -0
  162. package/dist/optimizer/commands.js +349 -0
  163. package/dist/optimizer/passes.d.ts +34 -0
  164. package/dist/optimizer/passes.js +227 -0
  165. package/dist/optimizer/structure.d.ts +8 -0
  166. package/dist/optimizer/structure.js +344 -0
  167. package/dist/pack.mcmeta +6 -0
  168. package/dist/parser/index.d.ts +76 -0
  169. package/dist/parser/index.js +1193 -0
  170. package/dist/repl.d.ts +16 -0
  171. package/dist/repl.js +165 -0
  172. package/dist/runtime/index.d.ts +101 -0
  173. package/dist/runtime/index.js +1288 -0
  174. package/dist/typechecker/index.d.ts +42 -0
  175. package/dist/typechecker/index.js +629 -0
  176. package/docs/COMPILATION_STATS.md +142 -0
  177. package/docs/IMPLEMENTATION_GUIDE.md +512 -0
  178. package/docs/LANGUAGE_REFERENCE.md +415 -0
  179. package/docs/MC_MAPPING.md +280 -0
  180. package/docs/STRUCTURE_TARGET.md +80 -0
  181. package/docs/mc-reference/commands.md +259 -0
  182. package/editors/vscode/.vscodeignore +10 -0
  183. package/editors/vscode/LICENSE +21 -0
  184. package/editors/vscode/README.md +78 -0
  185. package/editors/vscode/build.mjs +28 -0
  186. package/editors/vscode/icon.png +0 -0
  187. package/editors/vscode/mcfunction-language-configuration.json +28 -0
  188. package/editors/vscode/out/extension.js +7236 -0
  189. package/editors/vscode/package-lock.json +566 -0
  190. package/editors/vscode/package.json +137 -0
  191. package/editors/vscode/redscript-language-configuration.json +28 -0
  192. package/editors/vscode/snippets/redscript.json +114 -0
  193. package/editors/vscode/src/codeactions.ts +89 -0
  194. package/editors/vscode/src/completion.ts +130 -0
  195. package/editors/vscode/src/extension.ts +239 -0
  196. package/editors/vscode/src/hover.ts +1120 -0
  197. package/editors/vscode/src/symbols.ts +207 -0
  198. package/editors/vscode/syntaxes/mcfunction.tmLanguage.json +740 -0
  199. package/editors/vscode/syntaxes/redscript.tmLanguage.json +357 -0
  200. package/editors/vscode/tsconfig.json +13 -0
  201. package/jest.config.js +5 -0
  202. package/package.json +38 -0
  203. package/src/__tests__/cli.test.ts +130 -0
  204. package/src/__tests__/codegen.test.ts +128 -0
  205. package/src/__tests__/diagnostics.test.ts +195 -0
  206. package/src/__tests__/e2e.test.ts +1721 -0
  207. package/src/__tests__/fixtures/mc-commands-1.21.4.json +18734 -0
  208. package/src/__tests__/formatter.test.ts +46 -0
  209. package/src/__tests__/lexer.test.ts +356 -0
  210. package/src/__tests__/lowering.test.ts +962 -0
  211. package/src/__tests__/mc-integration.test.ts +409 -0
  212. package/src/__tests__/mc-syntax.test.ts +96 -0
  213. package/src/__tests__/nbt.test.ts +58 -0
  214. package/src/__tests__/optimizer-advanced.test.ts +144 -0
  215. package/src/__tests__/optimizer.test.ts +129 -0
  216. package/src/__tests__/parser.test.ts +800 -0
  217. package/src/__tests__/repl.test.ts +33 -0
  218. package/src/__tests__/runtime.test.ts +289 -0
  219. package/src/__tests__/structure-optimizer.test.ts +38 -0
  220. package/src/__tests__/typechecker.test.ts +395 -0
  221. package/src/ast/types.ts +248 -0
  222. package/src/cli.ts +445 -0
  223. package/src/codegen/cmdblock/index.ts +63 -0
  224. package/src/codegen/mcfunction/index.ts +471 -0
  225. package/src/codegen/structure/index.ts +305 -0
  226. package/src/compile.ts +188 -0
  227. package/src/diagnostics/index.ts +186 -0
  228. package/src/examples/README.md +77 -0
  229. package/src/examples/SHOWCASE_GAME.md +43 -0
  230. package/src/examples/arena.rs +44 -0
  231. package/src/examples/counter.rs +12 -0
  232. package/src/examples/pvp_arena.rs +131 -0
  233. package/src/examples/quiz.rs +90 -0
  234. package/src/examples/rpg.rs +13 -0
  235. package/src/examples/shop.rs +30 -0
  236. package/src/examples/showcase_game.rs +552 -0
  237. package/src/examples/stdlib_demo.rs +181 -0
  238. package/src/examples/turret.rs +27 -0
  239. package/src/examples/world_manager.rs +23 -0
  240. package/src/formatter/index.ts +22 -0
  241. package/src/index.ts +161 -0
  242. package/src/ir/builder.ts +114 -0
  243. package/src/ir/types.ts +119 -0
  244. package/src/lexer/index.ts +555 -0
  245. package/src/lowering/index.ts +2406 -0
  246. package/src/mc-test/client.ts +259 -0
  247. package/src/mc-test/runner.ts +140 -0
  248. package/src/mc-test/setup.ts +70 -0
  249. package/src/mc-validator/index.ts +367 -0
  250. package/src/nbt/index.ts +321 -0
  251. package/src/optimizer/commands.ts +416 -0
  252. package/src/optimizer/passes.ts +233 -0
  253. package/src/optimizer/structure.ts +441 -0
  254. package/src/parser/index.ts +1437 -0
  255. package/src/repl.ts +165 -0
  256. package/src/runtime/index.ts +1403 -0
  257. package/src/stdlib/README.md +156 -0
  258. package/src/stdlib/combat.rs +20 -0
  259. package/src/stdlib/cooldown.rs +45 -0
  260. package/src/stdlib/math.rs +49 -0
  261. package/src/stdlib/mobs.rs +99 -0
  262. package/src/stdlib/player.rs +29 -0
  263. package/src/stdlib/strings.rs +7 -0
  264. package/src/stdlib/timer.rs +51 -0
  265. package/src/templates/README.md +126 -0
  266. package/src/templates/combat.rs +96 -0
  267. package/src/templates/economy.rs +40 -0
  268. package/src/templates/mini-game-framework.rs +117 -0
  269. package/src/templates/quest.rs +78 -0
  270. package/src/test_programs/zombie_game.rs +25 -0
  271. package/src/typechecker/index.ts +737 -0
  272. package/tsconfig.json +16 -0
@@ -0,0 +1,471 @@
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: "$t0", "$t1", ...
15
+ * return value: "$ret"
16
+ * parameters: "$p0", "$p1", ...
17
+ */
18
+
19
+ import type { IRBlock, IRFunction, IRModule, Operand, Terminator } from '../../ir/types'
20
+ import { optimizeCommandFunctions, type OptimizationStats, createEmptyOptimizationStats, mergeOptimizationStats } from '../../optimizer/commands'
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Utilities
24
+ // ---------------------------------------------------------------------------
25
+
26
+ const OBJ = 'rs' // scoreboard objective name
27
+
28
+ function varRef(name: string): string {
29
+ // Ensure fake player prefix
30
+ return name.startsWith('$') ? name : `$${name}`
31
+ }
32
+
33
+ function operandToScore(op: Operand): string {
34
+ if (op.kind === 'var') return `${varRef(op.name)} ${OBJ}`
35
+ if (op.kind === 'const') return `$const_${op.value} ${OBJ}`
36
+ throw new Error(`Cannot convert storage operand to score: ${op.path}`)
37
+ }
38
+
39
+ function constSetup(value: number): string {
40
+ return `scoreboard players set $const_${value} ${OBJ} ${value}`
41
+ }
42
+
43
+ // Collect all constants used in a function for pre-setup
44
+ function collectConsts(fn: IRFunction): Set<number> {
45
+ const consts = new Set<number>()
46
+ for (const block of fn.blocks) {
47
+ for (const instr of block.instrs) {
48
+ if (instr.op === 'assign' && instr.src.kind === 'const') consts.add(instr.src.value)
49
+ if (instr.op === 'binop') {
50
+ if (instr.lhs.kind === 'const') consts.add(instr.lhs.value)
51
+ if (instr.rhs.kind === 'const') consts.add(instr.rhs.value)
52
+ }
53
+ if (instr.op === 'cmp') {
54
+ if (instr.lhs.kind === 'const') consts.add(instr.lhs.value)
55
+ if (instr.rhs.kind === 'const') consts.add(instr.rhs.value)
56
+ }
57
+ }
58
+ const t = block.term
59
+ if (t.op === 'return' && t.value?.kind === 'const') consts.add(t.value.value)
60
+ }
61
+ return consts
62
+ }
63
+
64
+ // MC scoreboard operation suffix
65
+ const BOP_OP: Record<string, string> = {
66
+ '+': '+=', '-': '-=', '*': '*=', '/': '/=', '%': '%=',
67
+ }
68
+
69
+ // ---------------------------------------------------------------------------
70
+ // Instruction codegen
71
+ // ---------------------------------------------------------------------------
72
+
73
+ function emitInstr(instr: ReturnType<typeof Object.assign> & { op: string }, ns: string): string[] {
74
+ const lines: string[] = []
75
+
76
+ switch (instr.op) {
77
+ case 'assign': {
78
+ const dst = varRef(instr.dst)
79
+ const src = instr.src as Operand
80
+ if (src.kind === 'const') {
81
+ lines.push(`scoreboard players set ${dst} ${OBJ} ${src.value}`)
82
+ } else if (src.kind === 'var') {
83
+ lines.push(`scoreboard players operation ${dst} ${OBJ} = ${varRef(src.name)} ${OBJ}`)
84
+ } else {
85
+ lines.push(`execute store result score ${dst} ${OBJ} run data get storage ${src.path}`)
86
+ }
87
+ break
88
+ }
89
+
90
+ case 'binop': {
91
+ const dst = varRef(instr.dst)
92
+ const bop = BOP_OP[instr.bop as string] ?? '+='
93
+ // Copy lhs → dst, then apply op with rhs
94
+ lines.push(...emitInstr({ op: 'assign', dst: instr.dst, src: instr.lhs }, ns))
95
+ lines.push(`scoreboard players operation ${dst} ${OBJ} ${bop} ${operandToScore(instr.rhs)}`)
96
+ break
97
+ }
98
+
99
+ case 'cmp': {
100
+ // MC doesn't have a direct compare-to-register; use execute store
101
+ const dst = varRef(instr.dst)
102
+ const lhsScore = operandToScore(instr.lhs)
103
+ const rhsScore = operandToScore(instr.rhs)
104
+ lines.push(`scoreboard players set ${dst} ${OBJ} 0`)
105
+ switch (instr.cop) {
106
+ case '==':
107
+ lines.push(`execute if score ${lhsScore} = ${rhsScore} run scoreboard players set ${dst} ${OBJ} 1`)
108
+ break
109
+ case '!=':
110
+ lines.push(`execute unless 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
+ case '>=':
122
+ lines.push(`execute if score ${lhsScore} >= ${rhsScore} run scoreboard players set ${dst} ${OBJ} 1`)
123
+ break
124
+ }
125
+ break
126
+ }
127
+
128
+ case 'call': {
129
+ // Push args as fake players $p0, $p1, ...
130
+ for (let i = 0; i < instr.args.length; i++) {
131
+ lines.push(...emitInstr({ op: 'assign', dst: `$p${i}`, src: instr.args[i] }, ns))
132
+ }
133
+ lines.push(`function ${ns}:${instr.fn}`)
134
+ if (instr.dst) {
135
+ lines.push(`scoreboard players operation ${varRef(instr.dst)} ${OBJ} = $ret ${OBJ}`)
136
+ }
137
+ break
138
+ }
139
+
140
+ case 'raw':
141
+ lines.push(instr.cmd as string)
142
+ break
143
+ }
144
+
145
+ return lines
146
+ }
147
+
148
+ // ---------------------------------------------------------------------------
149
+ // Terminator codegen
150
+ // ---------------------------------------------------------------------------
151
+
152
+ function emitTerm(term: Terminator, ns: string, fnName: string): string[] {
153
+ const lines: string[] = []
154
+ switch (term.op) {
155
+ case 'jump':
156
+ lines.push(`function ${ns}:${fnName}/${term.target}`)
157
+ break
158
+ case 'jump_if':
159
+ lines.push(`execute if score ${varRef(term.cond)} ${OBJ} matches 1.. run function ${ns}:${fnName}/${term.then}`)
160
+ lines.push(`execute if score ${varRef(term.cond)} ${OBJ} matches ..0 run function ${ns}:${fnName}/${term.else_}`)
161
+ break
162
+ case 'jump_unless':
163
+ lines.push(`execute if score ${varRef(term.cond)} ${OBJ} matches ..0 run function ${ns}:${fnName}/${term.then}`)
164
+ lines.push(`execute if score ${varRef(term.cond)} ${OBJ} matches 1.. run function ${ns}:${fnName}/${term.else_}`)
165
+ break
166
+ case 'return':
167
+ if (term.value) {
168
+ lines.push(...emitInstr({ op: 'assign', dst: '$ret', src: term.value }, ns))
169
+ }
170
+ // In MC 1.20+, use `return` command
171
+ if (term.value?.kind === 'const') {
172
+ lines.push(`return ${term.value.value}`)
173
+ } else if (term.value?.kind === 'var') {
174
+ lines.push(`return run scoreboard players get ${varRef(term.value.name)} ${OBJ}`)
175
+ }
176
+ break
177
+ case 'tick_yield':
178
+ lines.push(`schedule function ${ns}:${fnName}/${term.continuation} 1t replace`)
179
+ break
180
+ }
181
+ return lines
182
+ }
183
+
184
+ // ---------------------------------------------------------------------------
185
+ // Public API
186
+ // ---------------------------------------------------------------------------
187
+
188
+ export interface DatapackFile {
189
+ path: string // relative to datapack root, e.g. "data/mypack/functions/add.mcfunction"
190
+ content: string
191
+ }
192
+
193
+ function toFunctionName(file: DatapackFile): string | null {
194
+ const match = file.path.match(/^data\/[^/]+\/function\/(.+)\.mcfunction$/)
195
+ return match?.[1] ?? null
196
+ }
197
+
198
+ function applyFunctionOptimization(
199
+ files: DatapackFile[],
200
+ ): { files: DatapackFile[]; stats: OptimizationStats } {
201
+ const functionFiles = files
202
+ .map(file => {
203
+ const functionName = toFunctionName(file)
204
+ if (!functionName) return null
205
+ const commands = file.content
206
+ .split('\n')
207
+ .map(line => line.trim())
208
+ .filter(line => line !== '' && !line.startsWith('#'))
209
+ .map(cmd => ({ cmd }))
210
+ return { file, functionName, commands }
211
+ })
212
+ .filter((entry): entry is NonNullable<typeof entry> => entry !== null)
213
+
214
+ const optimized = optimizeCommandFunctions(functionFiles.map(entry => ({
215
+ name: entry.functionName,
216
+ commands: entry.commands,
217
+ })))
218
+ const commandMap = new Map(optimized.functions.map(fn => [fn.name, fn.commands]))
219
+
220
+ return {
221
+ files: files.map(file => {
222
+ const functionName = toFunctionName(file)
223
+ if (!functionName) return file
224
+ const commands = commandMap.get(functionName)
225
+ if (!commands) return file
226
+ const lines = file.content.split('\n')
227
+ const header = lines.filter(line => line.trim().startsWith('#'))
228
+ return {
229
+ ...file,
230
+ content: [...header, ...commands.map(command => command.cmd)].join('\n'),
231
+ }
232
+ }),
233
+ stats: optimized.stats,
234
+ }
235
+ }
236
+
237
+ export interface DatapackGenerationResult {
238
+ files: DatapackFile[]
239
+ advancements: DatapackFile[]
240
+ stats: OptimizationStats
241
+ }
242
+
243
+ export interface DatapackGenerationOptions {
244
+ optimizeCommands?: boolean
245
+ }
246
+
247
+ export function countMcfunctionCommands(files: DatapackFile[]): number {
248
+ return files.reduce((sum, file) => {
249
+ if (!toFunctionName(file)) {
250
+ return sum
251
+ }
252
+
253
+ return sum + file.content
254
+ .split('\n')
255
+ .map(line => line.trim())
256
+ .filter(line => line !== '' && !line.startsWith('#'))
257
+ .length
258
+ }, 0)
259
+ }
260
+
261
+ export function generateDatapackWithStats(
262
+ module: IRModule,
263
+ options: DatapackGenerationOptions = {},
264
+ ): DatapackGenerationResult {
265
+ const { optimizeCommands = true } = options
266
+ const files: DatapackFile[] = []
267
+ const advancements: DatapackFile[] = []
268
+ const ns = module.namespace
269
+
270
+ // Collect all trigger handlers
271
+ const triggerHandlers = module.functions.filter(fn => fn.isTriggerHandler && fn.triggerName)
272
+ const triggerNames = new Set(triggerHandlers.map(fn => fn.triggerName!))
273
+
274
+ // Collect all tick functions
275
+ const tickFunctionNames: string[] = []
276
+ for (const fn of module.functions) {
277
+ if (fn.isTickLoop) {
278
+ tickFunctionNames.push(fn.name)
279
+ }
280
+ }
281
+
282
+ // pack.mcmeta
283
+ files.push({
284
+ path: 'pack.mcmeta',
285
+ content: JSON.stringify({
286
+ pack: { pack_format: 26, description: `${ns} datapack — compiled by redscript` }
287
+ }, null, 2),
288
+ })
289
+
290
+ // __load.mcfunction — create scoreboard objective + trigger registrations
291
+ const loadLines = [
292
+ `# RedScript runtime init`,
293
+ `scoreboard objectives add ${OBJ} dummy`,
294
+ ]
295
+ for (const g of module.globals) {
296
+ loadLines.push(`scoreboard players set ${varRef(g)} ${OBJ} 0`)
297
+ }
298
+
299
+ // Add trigger objectives
300
+ for (const triggerName of triggerNames) {
301
+ loadLines.push(`scoreboard objectives add ${triggerName} trigger`)
302
+ loadLines.push(`scoreboard players enable @a ${triggerName}`)
303
+ }
304
+
305
+ // Generate trigger dispatch functions
306
+ for (const triggerName of triggerNames) {
307
+ const handlers = triggerHandlers.filter(fn => fn.triggerName === triggerName)
308
+
309
+ // __trigger_{name}_dispatch.mcfunction
310
+ const dispatchLines = [
311
+ `# Trigger dispatch for ${triggerName}`,
312
+ ]
313
+ for (const handler of handlers) {
314
+ dispatchLines.push(`function ${ns}:${handler.name}`)
315
+ }
316
+ dispatchLines.push(`scoreboard players set @s ${triggerName} 0`)
317
+ dispatchLines.push(`scoreboard players enable @s ${triggerName}`)
318
+
319
+ files.push({
320
+ path: `data/${ns}/function/__trigger_${triggerName}_dispatch.mcfunction`,
321
+ content: dispatchLines.join('\n'),
322
+ })
323
+ }
324
+
325
+ // Generate each function (and collect constants for load)
326
+ for (const fn of module.functions) {
327
+ // Constant setup — place constants in __load.mcfunction
328
+ const consts = collectConsts(fn)
329
+ if (consts.size > 0) {
330
+ loadLines.push(...Array.from(consts).map(constSetup))
331
+ }
332
+
333
+ // Entry block → <fn_name>.mcfunction
334
+ // Continuation blocks → <fn_name>/<label>.mcfunction
335
+ for (let i = 0; i < fn.blocks.length; i++) {
336
+ const block = fn.blocks[i]
337
+ const lines: string[] = [`# block: ${block.label}`]
338
+
339
+ // Param setup in entry block
340
+ if (i === 0) {
341
+ for (let j = 0; j < fn.params.length; j++) {
342
+ lines.push(`scoreboard players operation ${varRef(fn.params[j])} ${OBJ} = $p${j} ${OBJ}`)
343
+ }
344
+ }
345
+
346
+ for (const instr of block.instrs) {
347
+ lines.push(...emitInstr(instr as any, ns))
348
+ }
349
+ lines.push(...emitTerm(block.term, ns, fn.name))
350
+
351
+ const filePath = i === 0
352
+ ? `data/${ns}/function/${fn.name}.mcfunction`
353
+ : `data/${ns}/function/${fn.name}/${block.label}.mcfunction`
354
+
355
+ files.push({ path: filePath, content: lines.join('\n') })
356
+ }
357
+ }
358
+
359
+ // Write __load.mcfunction
360
+ files.push({
361
+ path: `data/${ns}/function/__load.mcfunction`,
362
+ content: loadLines.join('\n'),
363
+ })
364
+
365
+ // minecraft:load tag pointing to __load
366
+ files.push({
367
+ path: `data/minecraft/tags/function/load.json`,
368
+ content: JSON.stringify({ values: [`${ns}:__load`] }, null, 2),
369
+ })
370
+
371
+ // __tick.mcfunction — calls all @tick functions + trigger check
372
+ const tickLines = ['# RedScript tick dispatcher']
373
+
374
+ // Call all @tick functions
375
+ for (const fnName of tickFunctionNames) {
376
+ tickLines.push(`function ${ns}:${fnName}`)
377
+ }
378
+
379
+ // Call trigger check if there are triggers
380
+ if (triggerNames.size > 0) {
381
+ tickLines.push(`# Trigger checks`)
382
+ for (const triggerName of triggerNames) {
383
+ tickLines.push(`execute as @a[scores={${triggerName}=1..}] run function ${ns}:__trigger_${triggerName}_dispatch`)
384
+ }
385
+ }
386
+
387
+ // Only generate __tick if there's something to run
388
+ if (tickFunctionNames.length > 0 || triggerNames.size > 0) {
389
+ files.push({
390
+ path: `data/${ns}/function/__tick.mcfunction`,
391
+ content: tickLines.join('\n'),
392
+ })
393
+
394
+ // minecraft:tick tag pointing to __tick
395
+ files.push({
396
+ path: `data/minecraft/tags/function/tick.json`,
397
+ content: JSON.stringify({ values: [`${ns}:__tick`] }, null, 2),
398
+ })
399
+ }
400
+
401
+ for (const fn of module.functions) {
402
+ const eventTrigger = fn.eventTrigger
403
+ if (!eventTrigger) {
404
+ continue
405
+ }
406
+
407
+ let path = ''
408
+ let criteria: Record<string, unknown> = {}
409
+
410
+ switch (eventTrigger.kind) {
411
+ case 'advancement':
412
+ path = `data/${ns}/advancements/on_advancement_${fn.name}.json`
413
+ criteria = {
414
+ trigger: {
415
+ trigger: `minecraft:${eventTrigger.value}`,
416
+ },
417
+ }
418
+ break
419
+ case 'craft':
420
+ path = `data/${ns}/advancements/on_craft_${fn.name}.json`
421
+ criteria = {
422
+ crafted: {
423
+ trigger: 'minecraft:inventory_changed',
424
+ conditions: {
425
+ items: [
426
+ {
427
+ items: [eventTrigger.value],
428
+ },
429
+ ],
430
+ },
431
+ },
432
+ }
433
+ break
434
+ case 'death':
435
+ path = `data/${ns}/advancements/on_death_${fn.name}.json`
436
+ criteria = {
437
+ death: {
438
+ trigger: 'minecraft:entity_killed_player',
439
+ },
440
+ }
441
+ break
442
+ case 'login':
443
+ case 'join_team':
444
+ continue
445
+ }
446
+
447
+ advancements.push({
448
+ path,
449
+ content: JSON.stringify({
450
+ criteria,
451
+ rewards: {
452
+ function: `${ns}:${fn.name}`,
453
+ },
454
+ }, null, 2),
455
+ })
456
+ }
457
+
458
+ const stats = createEmptyOptimizationStats()
459
+ if (!optimizeCommands) {
460
+ return { files, advancements, stats }
461
+ }
462
+
463
+ const optimized = applyFunctionOptimization(files)
464
+ mergeOptimizationStats(stats, optimized.stats)
465
+ return { files: optimized.files, advancements, stats }
466
+ }
467
+
468
+ export function generateDatapack(module: IRModule): DatapackFile[] {
469
+ const generated = generateDatapackWithStats(module)
470
+ return [...generated.files, ...generated.advancements]
471
+ }