redscript-mc 1.2.30 → 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.
- package/.claude/commands/build-test.md +10 -0
- package/.claude/commands/deploy-demo.md +12 -0
- package/.claude/commands/stage-status.md +13 -0
- package/.claude/settings.json +12 -0
- package/.github/workflows/ci.yml +1 -0
- package/CLAUDE.md +231 -0
- package/demo.gif +0 -0
- package/dist/cli.js +2 -554
- package/dist/compile.js +2 -266
- package/dist/index.js +2 -159
- package/dist/lowering/index.js +5 -3
- package/dist/src/__tests__/cli.test.d.ts +1 -0
- package/dist/src/__tests__/cli.test.js +104 -0
- package/dist/src/__tests__/codegen.test.d.ts +1 -0
- package/dist/src/__tests__/codegen.test.js +152 -0
- package/dist/src/__tests__/compile-all.test.d.ts +10 -0
- package/dist/src/__tests__/compile-all.test.js +108 -0
- package/dist/src/__tests__/dce.test.d.ts +1 -0
- package/dist/src/__tests__/dce.test.js +102 -0
- package/dist/src/__tests__/diagnostics.test.d.ts +4 -0
- package/dist/src/__tests__/diagnostics.test.js +177 -0
- package/dist/src/__tests__/e2e.test.d.ts +6 -0
- package/dist/src/__tests__/e2e.test.js +1789 -0
- package/dist/src/__tests__/entity-types.test.d.ts +1 -0
- package/dist/src/__tests__/entity-types.test.js +203 -0
- package/dist/src/__tests__/formatter.test.d.ts +1 -0
- package/dist/src/__tests__/formatter.test.js +40 -0
- package/dist/src/__tests__/lexer.test.d.ts +1 -0
- package/dist/src/__tests__/lexer.test.js +343 -0
- package/dist/src/__tests__/lowering.test.d.ts +1 -0
- package/dist/src/__tests__/lowering.test.js +1015 -0
- package/dist/src/__tests__/macro.test.d.ts +8 -0
- package/dist/src/__tests__/macro.test.js +306 -0
- package/dist/src/__tests__/mc-integration.test.d.ts +12 -0
- package/dist/src/__tests__/mc-integration.test.js +817 -0
- package/dist/src/__tests__/mc-syntax.test.d.ts +1 -0
- package/dist/src/__tests__/mc-syntax.test.js +124 -0
- package/dist/src/__tests__/nbt.test.d.ts +1 -0
- package/dist/src/__tests__/nbt.test.js +82 -0
- package/dist/src/__tests__/optimizer-advanced.test.d.ts +1 -0
- package/dist/src/__tests__/optimizer-advanced.test.js +124 -0
- package/dist/src/__tests__/optimizer.test.d.ts +1 -0
- package/dist/src/__tests__/optimizer.test.js +149 -0
- package/dist/src/__tests__/parser.test.d.ts +1 -0
- package/dist/src/__tests__/parser.test.js +807 -0
- package/dist/src/__tests__/repl.test.d.ts +1 -0
- package/dist/src/__tests__/repl.test.js +27 -0
- package/dist/src/__tests__/runtime.test.d.ts +1 -0
- package/dist/src/__tests__/runtime.test.js +289 -0
- package/dist/src/__tests__/stdlib-advanced.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib-advanced.test.js +374 -0
- package/dist/src/__tests__/stdlib-bigint.test.d.ts +7 -0
- package/dist/src/__tests__/stdlib-bigint.test.js +426 -0
- package/dist/src/__tests__/stdlib-math.test.d.ts +7 -0
- package/dist/src/__tests__/stdlib-math.test.js +351 -0
- package/dist/src/__tests__/stdlib-vec.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib-vec.test.js +263 -0
- package/dist/src/__tests__/structure-optimizer.test.d.ts +1 -0
- package/dist/src/__tests__/structure-optimizer.test.js +33 -0
- package/dist/src/__tests__/typechecker.test.d.ts +1 -0
- package/dist/src/__tests__/typechecker.test.js +552 -0
- package/dist/src/__tests__/var-allocator.test.d.ts +1 -0
- package/dist/src/__tests__/var-allocator.test.js +69 -0
- package/dist/src/ast/types.d.ts +515 -0
- package/dist/src/ast/types.js +9 -0
- package/dist/src/builtins/metadata.d.ts +36 -0
- package/dist/src/builtins/metadata.js +1014 -0
- package/dist/src/cli.d.ts +11 -0
- package/dist/src/cli.js +443 -0
- package/dist/src/codegen/cmdblock/index.d.ts +26 -0
- package/dist/src/codegen/cmdblock/index.js +45 -0
- package/dist/src/codegen/mcfunction/index.d.ts +40 -0
- package/dist/src/codegen/mcfunction/index.js +606 -0
- package/dist/src/codegen/structure/index.d.ts +24 -0
- package/dist/src/codegen/structure/index.js +279 -0
- package/dist/src/codegen/var-allocator.d.ts +45 -0
- package/dist/src/codegen/var-allocator.js +104 -0
- package/dist/src/compile.d.ts +37 -0
- package/dist/src/compile.js +165 -0
- package/dist/src/diagnostics/index.d.ts +44 -0
- package/dist/src/diagnostics/index.js +140 -0
- package/dist/src/events/types.d.ts +35 -0
- package/dist/src/events/types.js +59 -0
- package/dist/src/formatter/index.d.ts +1 -0
- package/dist/src/formatter/index.js +26 -0
- package/dist/src/index.d.ts +22 -0
- package/dist/src/index.js +45 -0
- package/dist/src/ir/builder.d.ts +33 -0
- package/dist/src/ir/builder.js +99 -0
- package/dist/src/ir/types.d.ts +132 -0
- package/dist/src/ir/types.js +15 -0
- package/dist/src/lexer/index.d.ts +37 -0
- package/dist/src/lexer/index.js +569 -0
- package/dist/src/lowering/index.d.ts +188 -0
- package/dist/src/lowering/index.js +3405 -0
- package/dist/src/mc-test/client.d.ts +128 -0
- package/dist/src/mc-test/client.js +174 -0
- package/dist/src/mc-test/runner.d.ts +28 -0
- package/dist/src/mc-test/runner.js +151 -0
- package/dist/src/mc-test/setup.d.ts +11 -0
- package/dist/src/mc-test/setup.js +98 -0
- package/dist/src/mc-validator/index.d.ts +17 -0
- package/dist/src/mc-validator/index.js +322 -0
- package/dist/src/nbt/index.d.ts +86 -0
- package/dist/src/nbt/index.js +250 -0
- package/dist/src/optimizer/commands.d.ts +38 -0
- package/dist/src/optimizer/commands.js +451 -0
- package/dist/src/optimizer/dce.d.ts +34 -0
- package/dist/src/optimizer/dce.js +639 -0
- package/dist/src/optimizer/passes.d.ts +34 -0
- package/dist/src/optimizer/passes.js +243 -0
- package/dist/src/optimizer/structure.d.ts +9 -0
- package/dist/src/optimizer/structure.js +356 -0
- package/dist/src/parser/index.d.ts +93 -0
- package/dist/src/parser/index.js +1687 -0
- package/dist/src/repl.d.ts +16 -0
- package/dist/src/repl.js +165 -0
- package/dist/src/runtime/index.d.ts +107 -0
- package/dist/src/runtime/index.js +1409 -0
- package/dist/src/typechecker/index.d.ts +61 -0
- package/dist/src/typechecker/index.js +1034 -0
- package/dist/src/types/entity-hierarchy.d.ts +29 -0
- package/dist/src/types/entity-hierarchy.js +107 -0
- package/dist/src2/__tests__/e2e/basic.test.d.ts +8 -0
- package/dist/src2/__tests__/e2e/basic.test.js +140 -0
- package/dist/src2/__tests__/e2e/macros.test.d.ts +9 -0
- package/dist/src2/__tests__/e2e/macros.test.js +182 -0
- package/dist/src2/__tests__/e2e/migrate.test.d.ts +13 -0
- package/dist/src2/__tests__/e2e/migrate.test.js +2739 -0
- package/dist/src2/__tests__/hir/desugar.test.d.ts +1 -0
- package/dist/src2/__tests__/hir/desugar.test.js +234 -0
- package/dist/src2/__tests__/lir/lower.test.d.ts +1 -0
- package/dist/src2/__tests__/lir/lower.test.js +559 -0
- package/dist/src2/__tests__/lir/types.test.d.ts +1 -0
- package/dist/src2/__tests__/lir/types.test.js +185 -0
- package/dist/src2/__tests__/lir/verify.test.d.ts +1 -0
- package/dist/src2/__tests__/lir/verify.test.js +221 -0
- package/dist/src2/__tests__/mir/arithmetic.test.d.ts +1 -0
- package/dist/src2/__tests__/mir/arithmetic.test.js +130 -0
- package/dist/src2/__tests__/mir/control-flow.test.d.ts +1 -0
- package/dist/src2/__tests__/mir/control-flow.test.js +205 -0
- package/dist/src2/__tests__/mir/verify.test.d.ts +1 -0
- package/dist/src2/__tests__/mir/verify.test.js +223 -0
- package/dist/src2/__tests__/optimizer/block_merge.test.d.ts +1 -0
- package/dist/src2/__tests__/optimizer/block_merge.test.js +78 -0
- package/dist/src2/__tests__/optimizer/branch_simplify.test.d.ts +1 -0
- package/dist/src2/__tests__/optimizer/branch_simplify.test.js +58 -0
- package/dist/src2/__tests__/optimizer/constant_fold.test.d.ts +1 -0
- package/dist/src2/__tests__/optimizer/constant_fold.test.js +131 -0
- package/dist/src2/__tests__/optimizer/copy_prop.test.d.ts +1 -0
- package/dist/src2/__tests__/optimizer/copy_prop.test.js +91 -0
- package/dist/src2/__tests__/optimizer/dce.test.d.ts +1 -0
- package/dist/src2/__tests__/optimizer/dce.test.js +76 -0
- package/dist/src2/__tests__/optimizer/pipeline.test.d.ts +1 -0
- package/dist/src2/__tests__/optimizer/pipeline.test.js +102 -0
- package/dist/src2/emit/compile.d.ts +19 -0
- package/dist/src2/emit/compile.js +80 -0
- package/dist/src2/emit/index.d.ts +17 -0
- package/dist/src2/emit/index.js +172 -0
- package/dist/src2/hir/lower.d.ts +15 -0
- package/dist/src2/hir/lower.js +378 -0
- package/dist/src2/hir/types.d.ts +373 -0
- package/dist/src2/hir/types.js +16 -0
- package/dist/src2/lir/lower.d.ts +15 -0
- package/dist/src2/lir/lower.js +453 -0
- package/dist/src2/lir/types.d.ts +136 -0
- package/dist/src2/lir/types.js +11 -0
- package/dist/src2/lir/verify.d.ts +14 -0
- package/dist/src2/lir/verify.js +113 -0
- package/dist/src2/mir/lower.d.ts +9 -0
- package/dist/src2/mir/lower.js +1030 -0
- package/dist/src2/mir/macro.d.ts +22 -0
- package/dist/src2/mir/macro.js +168 -0
- package/dist/src2/mir/types.d.ts +183 -0
- package/dist/src2/mir/types.js +11 -0
- package/dist/src2/mir/verify.d.ts +16 -0
- package/dist/src2/mir/verify.js +216 -0
- package/dist/src2/optimizer/block_merge.d.ts +12 -0
- package/dist/src2/optimizer/block_merge.js +84 -0
- package/dist/src2/optimizer/branch_simplify.d.ts +9 -0
- package/dist/src2/optimizer/branch_simplify.js +28 -0
- package/dist/src2/optimizer/constant_fold.d.ts +10 -0
- package/dist/src2/optimizer/constant_fold.js +85 -0
- package/dist/src2/optimizer/copy_prop.d.ts +9 -0
- package/dist/src2/optimizer/copy_prop.js +113 -0
- package/dist/src2/optimizer/dce.d.ts +8 -0
- package/dist/src2/optimizer/dce.js +155 -0
- package/dist/src2/optimizer/pipeline.d.ts +10 -0
- package/dist/src2/optimizer/pipeline.js +42 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/docs/compiler-pipeline-redesign.md +2243 -0
- package/docs/optimization-ideas.md +1076 -0
- package/editors/vscode/package-lock.json +3 -3
- package/editors/vscode/package.json +1 -1
- package/jest.config.js +1 -1
- package/package.json +6 -5
- package/scripts/postbuild.js +15 -0
- package/src/__tests__/cli.test.ts +8 -220
- package/src/__tests__/dce.test.ts +11 -56
- package/src/__tests__/diagnostics.test.ts +59 -38
- package/src/__tests__/mc-integration.test.ts +1 -2
- package/src/ast/types.ts +6 -1
- package/src/cli.ts +29 -156
- package/src/compile.ts +6 -162
- package/src/index.ts +14 -178
- package/src/mc-test/runner.ts +4 -3
- package/src/parser/index.ts +1 -1
- package/src/repl.ts +1 -1
- package/src/runtime/index.ts +1 -1
- package/src2/__tests__/e2e/basic.test.ts +154 -0
- package/src2/__tests__/e2e/macros.test.ts +199 -0
- package/src2/__tests__/e2e/migrate.test.ts +3008 -0
- package/src2/__tests__/hir/desugar.test.ts +263 -0
- package/src2/__tests__/lir/lower.test.ts +619 -0
- package/src2/__tests__/lir/types.test.ts +207 -0
- package/src2/__tests__/lir/verify.test.ts +249 -0
- package/src2/__tests__/mir/arithmetic.test.ts +156 -0
- package/src2/__tests__/mir/control-flow.test.ts +242 -0
- package/src2/__tests__/mir/verify.test.ts +254 -0
- package/src2/__tests__/optimizer/block_merge.test.ts +84 -0
- package/src2/__tests__/optimizer/branch_simplify.test.ts +64 -0
- package/src2/__tests__/optimizer/constant_fold.test.ts +145 -0
- package/src2/__tests__/optimizer/copy_prop.test.ts +99 -0
- package/src2/__tests__/optimizer/dce.test.ts +83 -0
- package/src2/__tests__/optimizer/pipeline.test.ts +116 -0
- package/src2/emit/compile.ts +99 -0
- package/src2/emit/index.ts +222 -0
- package/src2/hir/lower.ts +428 -0
- package/src2/hir/types.ts +216 -0
- package/src2/lir/lower.ts +556 -0
- package/src2/lir/types.ts +109 -0
- package/src2/lir/verify.ts +129 -0
- package/src2/mir/lower.ts +1160 -0
- package/src2/mir/macro.ts +167 -0
- package/src2/mir/types.ts +106 -0
- package/src2/mir/verify.ts +218 -0
- package/src2/optimizer/block_merge.ts +93 -0
- package/src2/optimizer/branch_simplify.ts +27 -0
- package/src2/optimizer/constant_fold.ts +88 -0
- package/src2/optimizer/copy_prop.ts +106 -0
- package/src2/optimizer/dce.ts +133 -0
- package/src2/optimizer/pipeline.ts +44 -0
- package/tsconfig.json +2 -2
- package/src/__tests__/codegen.test.ts +0 -161
- package/src/__tests__/e2e.test.ts +0 -2039
- package/src/__tests__/entity-types.test.ts +0 -236
- package/src/__tests__/lowering.test.ts +0 -1185
- package/src/__tests__/macro.test.ts +0 -343
- package/src/__tests__/nbt.test.ts +0 -58
- package/src/__tests__/optimizer-advanced.test.ts +0 -144
- package/src/__tests__/optimizer.test.ts +0 -162
- package/src/__tests__/runtime.test.ts +0 -305
- package/src/__tests__/stdlib-advanced.test.ts +0 -379
- package/src/__tests__/stdlib-bigint.test.ts +0 -427
- package/src/__tests__/stdlib-math.test.ts +0 -374
- package/src/__tests__/stdlib-vec.test.ts +0 -259
- package/src/__tests__/structure-optimizer.test.ts +0 -38
- package/src/__tests__/var-allocator.test.ts +0 -75
- package/src/codegen/cmdblock/index.ts +0 -63
- package/src/codegen/mcfunction/index.ts +0 -662
- package/src/codegen/structure/index.ts +0 -346
- package/src/codegen/var-allocator.ts +0 -104
- package/src/ir/builder.ts +0 -116
- package/src/ir/types.ts +0 -134
- package/src/lowering/index.ts +0 -3876
- package/src/optimizer/commands.ts +0 -534
- package/src/optimizer/dce.ts +0 -679
- package/src/optimizer/passes.ts +0 -250
- 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
|
-
}
|