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,534 +0,0 @@
1
- import type { IRCommand } from '../ir/types'
2
-
3
- export interface OptimizationStats {
4
- licmHoists: number
5
- licmLoopBodies: number
6
- cseRedundantReads: number
7
- cseArithmetic: number
8
- setblockMergedCommands: number
9
- setblockFillCommands: number
10
- setblockSavedCommands: number
11
- deadCodeRemoved: number
12
- constantFolds: number
13
- inlinedTrivialFunctions: number
14
- totalCommandsBefore: number
15
- totalCommandsAfter: number
16
- }
17
-
18
- export interface CommandFunction {
19
- name: string
20
- commands: IRCommand[]
21
- }
22
-
23
- // Matches scoreboard reads for LICM/CSE — objective is captured in group 2
24
- // so the optimizer can reconstruct the command with the same objective.
25
- let _OBJ_PATTERN = 'rs'
26
- export function setOptimizerObjective(obj: string): void { _OBJ_PATTERN = obj }
27
-
28
- function scoreboardReadRe(): RegExp {
29
- return new RegExp(`^execute store result score (\\$[A-Za-z0-9_]+) ${_OBJ_PATTERN} run scoreboard players get (\\S+) (\\S+)$`)
30
- }
31
- const SCOREBOARD_WRITE_RE =
32
- /^(?:scoreboard players (?:set|add|remove|reset)\s+(\S+)\s+(\S+)|scoreboard players operation\s+(\S+)\s+(\S+)\s+[+\-*/%]?= )/
33
- const EXECUTE_STORE_SCORE_RE =
34
- /^execute store result score (\S+) (\S+) run /
35
- const FUNCTION_CALL_RE = /^execute as (.+) run function ([^:]+):(.+)$/
36
- const TEMP_RE = /\$[A-Za-z0-9_]+/g
37
- const SETBLOCK_RE = /^setblock (-?\d+) (-?\d+) (-?\d+) (\S+)$/
38
-
39
- export function createEmptyOptimizationStats(): OptimizationStats {
40
- return {
41
- licmHoists: 0,
42
- licmLoopBodies: 0,
43
- cseRedundantReads: 0,
44
- cseArithmetic: 0,
45
- setblockMergedCommands: 0,
46
- setblockFillCommands: 0,
47
- setblockSavedCommands: 0,
48
- deadCodeRemoved: 0,
49
- constantFolds: 0,
50
- inlinedTrivialFunctions: 0,
51
- totalCommandsBefore: 0,
52
- totalCommandsAfter: 0,
53
- }
54
- }
55
-
56
- function cloneCommand(command: IRCommand): IRCommand {
57
- return { ...command }
58
- }
59
-
60
- function cloneFunctions(functions: CommandFunction[]): CommandFunction[] {
61
- return functions.map(fn => ({
62
- name: fn.name,
63
- commands: fn.commands.map(cloneCommand),
64
- }))
65
- }
66
-
67
- export function mergeOptimizationStats(base: OptimizationStats, delta: Partial<OptimizationStats>): void {
68
- for (const [key, value] of Object.entries(delta)) {
69
- base[key as keyof OptimizationStats] += value as number
70
- }
71
- }
72
-
73
- function parseScoreboardWrite(command: string): { player: string; objective: string } | null {
74
- const executeStoreMatch = command.match(EXECUTE_STORE_SCORE_RE)
75
- if (executeStoreMatch) {
76
- return { player: executeStoreMatch[1], objective: executeStoreMatch[2] }
77
- }
78
-
79
- const match = command.match(SCOREBOARD_WRITE_RE)
80
- if (!match) {
81
- return null
82
- }
83
-
84
- if (match[1] && match[2]) {
85
- return { player: match[1], objective: match[2] }
86
- }
87
-
88
- if (match[3] && match[4]) {
89
- return { player: match[3], objective: match[4] }
90
- }
91
-
92
- return null
93
- }
94
-
95
- function replaceTemp(command: string, from: string, to: string): string {
96
- const re = new RegExp(`\\${from}(?![A-Za-z0-9_])`, 'g')
97
- return command.replace(re, to)
98
- }
99
-
100
- function collectObjectiveWrites(functions: CommandFunction[]): Map<string, number> {
101
- const writes = new Map<string, number>()
102
-
103
- for (const fn of functions) {
104
- for (const command of fn.commands) {
105
- const write = parseScoreboardWrite(command.cmd)
106
- if (!write) continue
107
- writes.set(write.objective, (writes.get(write.objective) ?? 0) + 1)
108
- }
109
- }
110
-
111
- return writes
112
- }
113
-
114
- function applyLICMInternal(functions: CommandFunction[]): Partial<OptimizationStats> {
115
- const stats: Partial<OptimizationStats> = { licmHoists: 0, licmLoopBodies: 0 }
116
- const functionMap = new Map(functions.map(fn => [fn.name, fn]))
117
- const objectiveWrites = collectObjectiveWrites(functions)
118
-
119
- for (const fn of functions) {
120
- const nextCommands: IRCommand[] = []
121
-
122
- for (const command of fn.commands) {
123
- const match = command.cmd.match(FUNCTION_CALL_RE)
124
- if (!match) {
125
- nextCommands.push(command)
126
- continue
127
- }
128
-
129
- const loopFn = functionMap.get(match[3])
130
- if (!loopFn) {
131
- nextCommands.push(command)
132
- continue
133
- }
134
-
135
- const readInfo = new Map<string, { temp: string; player: string; objective: string; uses: number }>()
136
- const scoreboardWrites = new Set<string>()
137
-
138
- for (const inner of loopFn.commands) {
139
- const readMatch = inner.cmd.match(scoreboardReadRe())
140
- if (readMatch) {
141
- const [, temp, player, objective] = readMatch
142
- const key = `${player} ${objective}`
143
- readInfo.set(key, { temp, player, objective, uses: 0 })
144
- }
145
-
146
- const write = parseScoreboardWrite(inner.cmd)
147
- if (write) {
148
- scoreboardWrites.add(`${write.player} ${write.objective}`)
149
- }
150
- }
151
-
152
- for (const inner of loopFn.commands) {
153
- for (const info of readInfo.values()) {
154
- const matches = inner.cmd.match(TEMP_RE) ?? []
155
- const usageCount = matches.filter(name => name === info.temp).length
156
- const isDef = inner.cmd.startsWith(`execute store result score ${info.temp} ${_OBJ_PATTERN} run scoreboard players get `)
157
- if (!isDef) {
158
- info.uses += usageCount
159
- }
160
- }
161
- }
162
-
163
- const hoistable = Array.from(readInfo.entries())
164
- .filter(([key, info]) => {
165
- if (info.uses < 2) return false
166
- if ((objectiveWrites.get(info.objective) ?? 0) !== 0) return false
167
- if (scoreboardWrites.has(key)) return false
168
- return true
169
- })
170
- .map(([, info]) => info)
171
-
172
- if (hoistable.length === 0) {
173
- nextCommands.push(command)
174
- continue
175
- }
176
-
177
- const hoistedTemps = new Set(hoistable.map(item => item.temp))
178
- const rewrittenLoopCommands: IRCommand[] = []
179
-
180
- for (const inner of loopFn.commands) {
181
- const readMatch = inner.cmd.match(scoreboardReadRe())
182
- if (readMatch && hoistedTemps.has(readMatch[1])) {
183
- continue
184
- }
185
- rewrittenLoopCommands.push(inner)
186
- }
187
-
188
- loopFn.commands = rewrittenLoopCommands
189
- nextCommands.push(
190
- ...hoistable.map(item => ({
191
- cmd: `execute store result score ${item.temp} ${_OBJ_PATTERN} run scoreboard players get ${item.player} ${item.objective}`,
192
- })),
193
- command
194
- )
195
- stats.licmHoists = (stats.licmHoists ?? 0) + hoistable.length
196
- stats.licmLoopBodies = (stats.licmLoopBodies ?? 0) + 1
197
- }
198
-
199
- fn.commands = nextCommands
200
- }
201
-
202
- return stats
203
- }
204
-
205
- function extractArithmeticExpression(commands: IRCommand[], index: number): { key: string; dst: string } | null {
206
- const assign =
207
- commands[index]?.cmd.match(new RegExp(`^scoreboard players operation (\\$[A-Za-z0-9_]+) ${_OBJ_PATTERN} = (\\$[A-Za-z0-9_]+|\\$const_-?\\d+) ${_OBJ_PATTERN}$`)) ??
208
- commands[index]?.cmd.match(new RegExp(`^scoreboard players set (\\$[A-Za-z0-9_]+) ${_OBJ_PATTERN} (-?\\d+)$`))
209
- const op = commands[index + 1]?.cmd.match(new RegExp(`^scoreboard players operation (\\$[A-Za-z0-9_]+) ${_OBJ_PATTERN} ([+\\-*/%]=) (\\$[A-Za-z0-9_]+|\\$const_-?\\d+) ${_OBJ_PATTERN}$`))
210
- if (!assign || !op || assign[1] !== op[1]) {
211
- return null
212
- }
213
- return {
214
- key: `${assign[2]} ${op[2]} ${op[3]}`,
215
- dst: assign[1],
216
- }
217
- }
218
-
219
- function applyCSEInternal(functions: CommandFunction[]): Partial<OptimizationStats> {
220
- const stats: Partial<OptimizationStats> = { cseRedundantReads: 0, cseArithmetic: 0 }
221
-
222
- for (const fn of functions) {
223
- const commands = fn.commands.map(cloneCommand)
224
- const readCache = new Map<string, string>()
225
- const exprCache = new Map<string, string>()
226
- const rewritten: IRCommand[] = []
227
-
228
- function invalidateByTemp(temp: string): void {
229
- for (const [key, value] of readCache.entries()) {
230
- if (value === temp || key.includes(`${temp} `) || key.endsWith(` ${temp}`)) {
231
- readCache.delete(key)
232
- }
233
- }
234
- for (const [key, value] of exprCache.entries()) {
235
- if (value === temp || key.includes(temp)) {
236
- exprCache.delete(key)
237
- }
238
- }
239
- }
240
-
241
- for (let i = 0; i < commands.length; i++) {
242
- const command = commands[i]
243
- const readMatch = command.cmd.match(scoreboardReadRe())
244
- if (readMatch) {
245
- const [, dst, player, objective] = readMatch
246
- const key = `${player} ${objective}`
247
- const cached = readCache.get(key)
248
- if (cached) {
249
- stats.cseRedundantReads = (stats.cseRedundantReads ?? 0) + 1
250
- rewritten.push({ ...command, cmd: `scoreboard players operation ${dst} ${_OBJ_PATTERN} = ${cached} ${_OBJ_PATTERN}` })
251
- } else {
252
- readCache.set(key, dst)
253
- rewritten.push(command)
254
- }
255
- invalidateByTemp(dst)
256
- readCache.set(key, dst)
257
- continue
258
- }
259
-
260
- const expr = extractArithmeticExpression(commands, i)
261
- if (expr) {
262
- const cached = exprCache.get(expr.key)
263
- if (cached) {
264
- rewritten.push({ ...commands[i], cmd: `scoreboard players operation ${expr.dst} ${_OBJ_PATTERN} = ${cached} ${_OBJ_PATTERN}` })
265
- stats.cseArithmetic = (stats.cseArithmetic ?? 0) + 1
266
- i += 1
267
- } else {
268
- rewritten.push(command)
269
- rewritten.push(commands[i + 1])
270
- exprCache.set(expr.key, expr.dst)
271
- i += 1
272
- }
273
- invalidateByTemp(expr.dst)
274
- exprCache.set(expr.key, expr.dst)
275
- continue
276
- }
277
-
278
- const write = parseScoreboardWrite(command.cmd)
279
- if (write) {
280
- readCache.delete(`${write.player} ${write.objective}`)
281
- if (write.player.startsWith('$')) {
282
- invalidateByTemp(write.player)
283
- }
284
- }
285
-
286
- rewritten.push(command)
287
- }
288
-
289
- fn.commands = rewritten
290
- }
291
-
292
- return stats
293
- }
294
-
295
- function batchSetblocksInCommands(commands: IRCommand[]): { commands: IRCommand[]; stats: Partial<OptimizationStats> } {
296
- const rewritten: IRCommand[] = []
297
- const stats: Partial<OptimizationStats> = {
298
- setblockMergedCommands: 0,
299
- setblockFillCommands: 0,
300
- setblockSavedCommands: 0,
301
- }
302
-
303
- for (let i = 0; i < commands.length; ) {
304
- const start = commands[i].cmd.match(SETBLOCK_RE)
305
- if (!start) {
306
- rewritten.push(commands[i])
307
- i++
308
- continue
309
- }
310
-
311
- const block = start[4]
312
- const run = [{ index: i, x: Number(start[1]), y: Number(start[2]), z: Number(start[3]) }]
313
- let axis: 'x' | 'z' | null = null
314
- let j = i + 1
315
-
316
- while (j < commands.length) {
317
- const next = commands[j].cmd.match(SETBLOCK_RE)
318
- if (!next || next[4] !== block) break
319
-
320
- const point = { x: Number(next[1]), y: Number(next[2]), z: Number(next[3]) }
321
- const prev = run[run.length - 1]
322
- if (point.y !== prev.y) break
323
-
324
- const stepX = point.x - prev.x
325
- const stepZ = point.z - prev.z
326
- if (axis === null) {
327
- if (stepX === 1 && stepZ === 0) axis = 'x'
328
- else if (stepX === 0 && stepZ === 1) axis = 'z'
329
- else break
330
- }
331
-
332
- const valid = axis === 'x'
333
- ? point.z === prev.z && stepX === 1 && stepZ === 0
334
- : point.x === prev.x && stepX === 0 && stepZ === 1
335
- if (!valid) break
336
-
337
- run.push({ index: j, ...point })
338
- j++
339
- }
340
-
341
- if (run.length >= 2) {
342
- const first = run[0]
343
- const last = run[run.length - 1]
344
- rewritten.push({
345
- ...commands[i],
346
- cmd: `fill ${first.x} ${first.y} ${first.z} ${last.x} ${last.y} ${last.z} ${block}`,
347
- })
348
- stats.setblockMergedCommands = (stats.setblockMergedCommands ?? 0) + run.length
349
- stats.setblockFillCommands = (stats.setblockFillCommands ?? 0) + 1
350
- stats.setblockSavedCommands = (stats.setblockSavedCommands ?? 0) + (run.length - 1)
351
- i = j
352
- continue
353
- }
354
-
355
- rewritten.push(commands[i])
356
- i++
357
- }
358
-
359
- return { commands: rewritten, stats }
360
- }
361
-
362
- function applySetblockBatchingInternal(functions: CommandFunction[]): Partial<OptimizationStats> {
363
- const stats: Partial<OptimizationStats> = {
364
- setblockMergedCommands: 0,
365
- setblockFillCommands: 0,
366
- setblockSavedCommands: 0,
367
- }
368
-
369
- for (const fn of functions) {
370
- const batched = batchSetblocksInCommands(fn.commands)
371
- fn.commands = batched.commands
372
- mergeOptimizationStats(stats as OptimizationStats, batched.stats)
373
- }
374
-
375
- return stats
376
- }
377
-
378
- export function applyLICM(functions: CommandFunction[]): { functions: CommandFunction[]; stats: OptimizationStats } {
379
- const optimized = cloneFunctions(functions)
380
- const stats = createEmptyOptimizationStats()
381
- stats.totalCommandsBefore = optimized.reduce((sum, fn) => sum + fn.commands.length, 0)
382
- mergeOptimizationStats(stats, applyLICMInternal(optimized))
383
- stats.totalCommandsAfter = optimized.reduce((sum, fn) => sum + fn.commands.length, 0)
384
- return { functions: optimized, stats }
385
- }
386
-
387
- export function applyCSE(functions: CommandFunction[]): { functions: CommandFunction[]; stats: OptimizationStats } {
388
- const optimized = cloneFunctions(functions)
389
- const stats = createEmptyOptimizationStats()
390
- stats.totalCommandsBefore = optimized.reduce((sum, fn) => sum + fn.commands.length, 0)
391
- mergeOptimizationStats(stats, applyCSEInternal(optimized))
392
- stats.totalCommandsAfter = optimized.reduce((sum, fn) => sum + fn.commands.length, 0)
393
- return { functions: optimized, stats }
394
- }
395
-
396
- export function batchSetblocks(functions: CommandFunction[]): { functions: CommandFunction[]; stats: OptimizationStats } {
397
- const optimized = cloneFunctions(functions)
398
- const stats = createEmptyOptimizationStats()
399
- stats.totalCommandsBefore = optimized.reduce((sum, fn) => sum + fn.commands.length, 0)
400
- mergeOptimizationStats(stats, applySetblockBatchingInternal(optimized))
401
- stats.totalCommandsAfter = optimized.reduce((sum, fn) => sum + fn.commands.length, 0)
402
- return { functions: optimized, stats }
403
- }
404
-
405
- /**
406
- * Inline trivial functions:
407
- * 1. Functions that only contain a single `function` call → inline the call
408
- * 2. Empty functions (no commands) → remove and eliminate all calls to them
409
- */
410
- function inlineTrivialFunctions(functions: CommandFunction[]): { functions: CommandFunction[]; stats: Partial<OptimizationStats> } {
411
- const FUNCTION_CMD_RE = /^function ([^:]+):(.+)$/
412
-
413
- // Find trivial functions (only a single function call, no other commands)
414
- const trivialMap = new Map<string, string>() // fn name -> target fn name
415
- const emptyFunctions = new Set<string>() // functions with no commands
416
-
417
- // System functions that should never be removed
418
- const SYSTEM_FUNCTIONS = new Set(['__tick', '__load'])
419
-
420
- for (const fn of functions) {
421
- // Never remove system functions
422
- if (SYSTEM_FUNCTIONS.has(fn.name) || fn.name.startsWith('__trigger_')) {
423
- continue
424
- }
425
-
426
- const nonCommentCmds = fn.commands.filter(cmd => !cmd.cmd.startsWith('#'))
427
- if (nonCommentCmds.length === 0 && fn.name.includes('/')) {
428
- // Empty control-flow block (e.g., main/merge_5) - mark for removal
429
- // Only remove if it's a sub-block (contains /), not a top-level function
430
- emptyFunctions.add(fn.name)
431
- } else if (nonCommentCmds.length === 1 && fn.name.includes('/')) {
432
- const match = nonCommentCmds[0].cmd.match(FUNCTION_CMD_RE)
433
- if (match) {
434
- // This function only calls another function
435
- trivialMap.set(fn.name, match[2])
436
- }
437
- }
438
- }
439
-
440
- // Resolve chains: if A -> B -> C, then A -> C
441
- // Also handle: A -> B where B is empty → A is effectively empty
442
- let changed = true
443
- while (changed) {
444
- changed = false
445
- for (const [from, to] of trivialMap) {
446
- if (emptyFunctions.has(to)) {
447
- // Target is empty, so this function is effectively empty too
448
- trivialMap.delete(from)
449
- emptyFunctions.add(from)
450
- changed = true
451
- } else {
452
- const finalTarget = trivialMap.get(to)
453
- if (finalTarget && finalTarget !== to) {
454
- trivialMap.set(from, finalTarget)
455
- changed = true
456
- }
457
- }
458
- }
459
- }
460
-
461
- const totalRemoved = trivialMap.size + emptyFunctions.size
462
- if (totalRemoved === 0) {
463
- return { functions, stats: {} }
464
- }
465
-
466
- // Set of all functions to remove
467
- const removedNames = new Set([...trivialMap.keys(), ...emptyFunctions])
468
-
469
- // Rewrite all function calls to skip trivial wrappers or remove empty calls
470
- const result: CommandFunction[] = []
471
-
472
- for (const fn of functions) {
473
- // Skip removed functions
474
- if (removedNames.has(fn.name)) {
475
- continue
476
- }
477
-
478
- // Rewrite function calls in this function
479
- const rewrittenCmds: typeof fn.commands = []
480
- for (const cmd of fn.commands) {
481
- // Check if this is a call to an empty function
482
- const emptyCallMatch = cmd.cmd.match(/^(?:execute .* run )?function ([^:]+):([^\s]+)$/)
483
- if (emptyCallMatch) {
484
- const targetFn = emptyCallMatch[2]
485
- if (emptyFunctions.has(targetFn)) {
486
- // Skip calls to empty functions entirely
487
- continue
488
- }
489
- }
490
-
491
- // Rewrite calls to trivial wrapper functions
492
- const rewritten = cmd.cmd.replace(
493
- /function ([^:]+):([^\s]+)/g,
494
- (match, ns, fnPath) => {
495
- const target = trivialMap.get(fnPath)
496
- return target ? `function ${ns}:${target}` : match
497
- }
498
- )
499
- rewrittenCmds.push({ ...cmd, cmd: rewritten })
500
- }
501
-
502
- result.push({ name: fn.name, commands: rewrittenCmds })
503
- }
504
-
505
- return {
506
- functions: result,
507
- stats: { inlinedTrivialFunctions: totalRemoved }
508
- }
509
- }
510
-
511
- export function optimizeCommandFunctions(functions: CommandFunction[]): { functions: CommandFunction[]; stats: OptimizationStats } {
512
- const initial = cloneFunctions(functions)
513
- const stats = createEmptyOptimizationStats()
514
- stats.totalCommandsBefore = initial.reduce((sum, fn) => sum + fn.commands.length, 0)
515
-
516
- // First pass: inline trivial functions
517
- const inlined = inlineTrivialFunctions(initial)
518
- mergeOptimizationStats(stats, inlined.stats)
519
-
520
- const licm = applyLICM(inlined.functions)
521
- mergeOptimizationStats(stats, licm.stats)
522
-
523
- const cse = applyCSE(licm.functions)
524
- mergeOptimizationStats(stats, cse.stats)
525
-
526
- const batched = batchSetblocks(cse.functions)
527
- mergeOptimizationStats(stats, batched.stats)
528
- stats.totalCommandsAfter = batched.functions.reduce((sum, fn) => sum + fn.commands.length, 0)
529
-
530
- return {
531
- functions: batched.functions,
532
- stats,
533
- }
534
- }