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
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MIR → LIR Lowering — Stage 5 of the RedScript compiler pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Converts 3-address MIR (CFG with basic blocks) to 2-address LIR
|
|
5
|
+
* (flat instruction lists with MC scoreboard semantics).
|
|
6
|
+
*
|
|
7
|
+
* Key transformations:
|
|
8
|
+
* - Each MIR Temp → a Slot (player = $tempname, obj = module.objective)
|
|
9
|
+
* - 3-address arithmetic → score_copy dst←a, then score_op dst←b
|
|
10
|
+
* - CFG control flow → call_if_matches / call_unless_matches to extracted functions
|
|
11
|
+
* - MIR calls → parameter slot setup + call instruction
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type {
|
|
15
|
+
MIRModule, MIRFunction, MIRBlock, MIRInstr, Operand, Temp, BlockId,
|
|
16
|
+
} from '../mir/types'
|
|
17
|
+
import type {
|
|
18
|
+
LIRModule, LIRFunction, LIRInstr, Slot,
|
|
19
|
+
} from './types'
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Public API
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
export function lowerToLIR(mir: MIRModule): LIRModule {
|
|
26
|
+
const ctx = new LoweringContext(mir.namespace, mir.objective)
|
|
27
|
+
for (const fn of mir.functions) {
|
|
28
|
+
lowerFunction(fn, ctx)
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
functions: ctx.functions,
|
|
32
|
+
namespace: mir.namespace,
|
|
33
|
+
objective: mir.objective,
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Lowering context
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
class LoweringContext {
|
|
42
|
+
readonly functions: LIRFunction[] = []
|
|
43
|
+
readonly namespace: string
|
|
44
|
+
readonly objective: string
|
|
45
|
+
/** Track which blocks have multiple predecessors (need their own function) */
|
|
46
|
+
private multiPredBlocks = new Set<BlockId>()
|
|
47
|
+
/** Map block id → generated LIR function name for multi-pred blocks */
|
|
48
|
+
private blockFnNames = new Map<BlockId, string>()
|
|
49
|
+
/** Current MIR function being lowered */
|
|
50
|
+
private currentMIRFn: MIRFunction | null = null
|
|
51
|
+
/** Block map for quick lookup */
|
|
52
|
+
private blockMap = new Map<BlockId, MIRBlock>()
|
|
53
|
+
|
|
54
|
+
constructor(namespace: string, objective: string) {
|
|
55
|
+
this.namespace = namespace
|
|
56
|
+
this.objective = objective
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
slot(temp: Temp): Slot {
|
|
60
|
+
// Return field temps are global (shared between caller/callee)
|
|
61
|
+
if (temp.startsWith('__rf_')) {
|
|
62
|
+
return { player: `$ret_${temp.slice(5)}`, obj: this.objective }
|
|
63
|
+
}
|
|
64
|
+
// Prefix temp names with function name to avoid caller/callee collision
|
|
65
|
+
const fn = this.currentMIRFn
|
|
66
|
+
const prefix = fn ? fn.name : ''
|
|
67
|
+
return { player: `$${prefix}_${temp}`, obj: this.objective }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
qualifiedName(fnName: string): string {
|
|
71
|
+
// Convert :: to / and lowercase for MC function paths
|
|
72
|
+
const mcName = fnName.replace(/::/g, '/').toLowerCase()
|
|
73
|
+
return `${this.namespace}:${mcName}`
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
addFunction(fn: LIRFunction): void {
|
|
77
|
+
this.functions.push(fn)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
analyzeBlocks(fn: MIRFunction): void {
|
|
81
|
+
this.currentMIRFn = fn
|
|
82
|
+
this.multiPredBlocks.clear()
|
|
83
|
+
this.blockFnNames.clear()
|
|
84
|
+
this.blockMap.clear()
|
|
85
|
+
|
|
86
|
+
for (const block of fn.blocks) {
|
|
87
|
+
this.blockMap.set(block.id, block)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Count predecessors for each block
|
|
91
|
+
const predCount = new Map<BlockId, number>()
|
|
92
|
+
for (const block of fn.blocks) {
|
|
93
|
+
const targets = getTermTargets(block.term)
|
|
94
|
+
for (const target of targets) {
|
|
95
|
+
predCount.set(target, (predCount.get(target) || 0) + 1)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Blocks with >1 predecessors or that are branch targets need their own function
|
|
100
|
+
for (const [blockId, count] of predCount) {
|
|
101
|
+
if (count > 1 && blockId !== fn.entry) {
|
|
102
|
+
this.multiPredBlocks.add(blockId)
|
|
103
|
+
this.blockFnNames.set(blockId, `${fn.name}__${blockId}`)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
isMultiPred(blockId: BlockId): boolean {
|
|
109
|
+
return this.multiPredBlocks.has(blockId)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
getBlockFnName(blockId: BlockId): string {
|
|
113
|
+
const name = this.blockFnNames.get(blockId)
|
|
114
|
+
if (name) return name
|
|
115
|
+
// Generate one on demand
|
|
116
|
+
const generated = `${this.currentMIRFn!.name}__${blockId}`
|
|
117
|
+
this.blockFnNames.set(blockId, generated)
|
|
118
|
+
return generated
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
getBlock(id: BlockId): MIRBlock | undefined {
|
|
122
|
+
return this.blockMap.get(id)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
// Function lowering
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
|
|
130
|
+
function lowerFunction(fn: MIRFunction, ctx: LoweringContext): void {
|
|
131
|
+
ctx.analyzeBlocks(fn)
|
|
132
|
+
|
|
133
|
+
// Lower the entry block as the main function body
|
|
134
|
+
const instrs: LIRInstr[] = []
|
|
135
|
+
const visited = new Set<BlockId>()
|
|
136
|
+
|
|
137
|
+
// Copy parameter slots ($p0, $p1, ...) into the callee's temp slots
|
|
138
|
+
for (let i = 0; i < fn.params.length; i++) {
|
|
139
|
+
const paramSlot: Slot = { player: `$p${i}`, obj: ctx.objective }
|
|
140
|
+
const tempSlot = ctx.slot(fn.params[i].name)
|
|
141
|
+
instrs.push({ kind: 'score_copy', dst: tempSlot, src: paramSlot })
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
lowerBlock(fn.entry, fn, ctx, instrs, visited)
|
|
145
|
+
|
|
146
|
+
ctx.addFunction({
|
|
147
|
+
name: fn.name,
|
|
148
|
+
instructions: instrs,
|
|
149
|
+
isMacro: fn.isMacro,
|
|
150
|
+
macroParams: fn.params.filter(p => p.isMacroParam).map(p => p.name),
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
// Emit separate functions for multi-pred blocks
|
|
154
|
+
for (const blockId of ctx['multiPredBlocks']) {
|
|
155
|
+
if (!visited.has(blockId)) {
|
|
156
|
+
const blockInstrs: LIRInstr[] = []
|
|
157
|
+
const blockVisited = new Set<BlockId>()
|
|
158
|
+
lowerBlock(blockId, fn, ctx, blockInstrs, blockVisited)
|
|
159
|
+
ctx.addFunction({
|
|
160
|
+
name: ctx.getBlockFnName(blockId),
|
|
161
|
+
instructions: blockInstrs,
|
|
162
|
+
isMacro: false,
|
|
163
|
+
macroParams: [],
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function lowerBlock(
|
|
170
|
+
blockId: BlockId,
|
|
171
|
+
fn: MIRFunction,
|
|
172
|
+
ctx: LoweringContext,
|
|
173
|
+
instrs: LIRInstr[],
|
|
174
|
+
visited: Set<BlockId>,
|
|
175
|
+
): void {
|
|
176
|
+
if (visited.has(blockId)) return
|
|
177
|
+
visited.add(blockId)
|
|
178
|
+
|
|
179
|
+
const block = ctx.getBlock(blockId)
|
|
180
|
+
if (!block) return
|
|
181
|
+
|
|
182
|
+
// Lower all non-terminator instructions
|
|
183
|
+
for (const instr of block.instrs) {
|
|
184
|
+
lowerInstr(instr, fn, ctx, instrs)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Lower the terminator
|
|
188
|
+
lowerTerminator(block.term, fn, ctx, instrs, visited)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
// Instruction lowering
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
|
|
195
|
+
function lowerInstr(
|
|
196
|
+
instr: MIRInstr,
|
|
197
|
+
fn: MIRFunction,
|
|
198
|
+
ctx: LoweringContext,
|
|
199
|
+
instrs: LIRInstr[],
|
|
200
|
+
): void {
|
|
201
|
+
switch (instr.kind) {
|
|
202
|
+
case 'const': {
|
|
203
|
+
instrs.push({ kind: 'score_set', dst: ctx.slot(instr.dst), value: instr.value })
|
|
204
|
+
break
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
case 'copy': {
|
|
208
|
+
lowerOperandToSlot(instr.dst, instr.src, ctx, instrs)
|
|
209
|
+
break
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
case 'add':
|
|
213
|
+
case 'sub':
|
|
214
|
+
case 'mul':
|
|
215
|
+
case 'div':
|
|
216
|
+
case 'mod': {
|
|
217
|
+
// 3-address → 2-address: copy a to dst, then op dst with b
|
|
218
|
+
lowerOperandToSlot(instr.dst, instr.a, ctx, instrs)
|
|
219
|
+
const scoreOp = {
|
|
220
|
+
add: 'score_add',
|
|
221
|
+
sub: 'score_sub',
|
|
222
|
+
mul: 'score_mul',
|
|
223
|
+
div: 'score_div',
|
|
224
|
+
mod: 'score_mod',
|
|
225
|
+
} as const
|
|
226
|
+
lowerBinOp(instr.dst, instr.b, scoreOp[instr.kind], ctx, instrs)
|
|
227
|
+
break
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
case 'neg': {
|
|
231
|
+
// 0 - src: set tmp to 0, then subtract src
|
|
232
|
+
const dst = ctx.slot(instr.dst)
|
|
233
|
+
instrs.push({ kind: 'score_set', dst, value: 0 })
|
|
234
|
+
const srcSlot = operandToSlot(instr.src, ctx, instrs)
|
|
235
|
+
instrs.push({ kind: 'score_sub', dst, src: srcSlot })
|
|
236
|
+
break
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
case 'cmp': {
|
|
240
|
+
// Strategy: set dst=0, then conditionally set to 1
|
|
241
|
+
// MC pattern: execute if score $a <op> $b run scoreboard players set $dst 1
|
|
242
|
+
const dst = ctx.slot(instr.dst)
|
|
243
|
+
const aSlot = operandToSlot(instr.a, ctx, instrs)
|
|
244
|
+
const bSlot = operandToSlot(instr.b, ctx, instrs)
|
|
245
|
+
|
|
246
|
+
instrs.push({ kind: 'score_set', dst, value: 0 })
|
|
247
|
+
|
|
248
|
+
const cmpOps: Record<string, string> = {
|
|
249
|
+
eq: '=', ne: '=', lt: '<', le: '<=', gt: '>', ge: '>=',
|
|
250
|
+
}
|
|
251
|
+
const op = cmpOps[instr.op]
|
|
252
|
+
const guard = instr.op === 'ne' ? 'unless' : 'if'
|
|
253
|
+
const dstStr = `${dst.player} ${dst.obj}`
|
|
254
|
+
const aStr = `${aSlot.player} ${aSlot.obj}`
|
|
255
|
+
const bStr = `${bSlot.player} ${bSlot.obj}`
|
|
256
|
+
instrs.push({
|
|
257
|
+
kind: 'raw',
|
|
258
|
+
cmd: `execute ${guard} score ${aStr} ${op} ${bStr} run scoreboard players set ${dstStr} 1`,
|
|
259
|
+
})
|
|
260
|
+
break
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
case 'and': {
|
|
264
|
+
// Bitwise/logical AND: both are 0/1, so multiply works
|
|
265
|
+
// But more accurately: dst = (a != 0) && (b != 0)
|
|
266
|
+
// Simple approach: copy a, then score_mul with b (since both are 0/1)
|
|
267
|
+
lowerOperandToSlot(instr.dst, instr.a, ctx, instrs)
|
|
268
|
+
lowerBinOp(instr.dst, instr.b, 'score_mul', ctx, instrs)
|
|
269
|
+
break
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
case 'or': {
|
|
273
|
+
// OR for 0/1 values: add then clamp to 1
|
|
274
|
+
// dst = a + b; if dst > 1, dst = 1
|
|
275
|
+
const dst = ctx.slot(instr.dst)
|
|
276
|
+
lowerOperandToSlot(instr.dst, instr.a, ctx, instrs)
|
|
277
|
+
lowerBinOp(instr.dst, instr.b, 'score_add', ctx, instrs)
|
|
278
|
+
// Clamp: use score_min with a const slot set to 1
|
|
279
|
+
const oneSlot = constSlot(1, ctx, instrs)
|
|
280
|
+
instrs.push({ kind: 'score_min', dst, src: oneSlot })
|
|
281
|
+
break
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
case 'not': {
|
|
285
|
+
// NOT for 0/1: dst = 1 - src
|
|
286
|
+
const dst = ctx.slot(instr.dst)
|
|
287
|
+
instrs.push({ kind: 'score_set', dst, value: 1 })
|
|
288
|
+
const srcSlot = operandToSlot(instr.src, ctx, instrs)
|
|
289
|
+
instrs.push({ kind: 'score_sub', dst, src: srcSlot })
|
|
290
|
+
break
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
case 'nbt_read': {
|
|
294
|
+
const dst = ctx.slot(instr.dst)
|
|
295
|
+
instrs.push({
|
|
296
|
+
kind: 'store_nbt_to_score',
|
|
297
|
+
dst,
|
|
298
|
+
ns: instr.ns,
|
|
299
|
+
path: instr.path,
|
|
300
|
+
scale: instr.scale,
|
|
301
|
+
})
|
|
302
|
+
break
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
case 'nbt_write': {
|
|
306
|
+
const srcSlot = operandToSlot(instr.src, ctx, instrs)
|
|
307
|
+
instrs.push({
|
|
308
|
+
kind: 'store_score_to_nbt',
|
|
309
|
+
ns: instr.ns,
|
|
310
|
+
path: instr.path,
|
|
311
|
+
type: instr.type,
|
|
312
|
+
scale: instr.scale,
|
|
313
|
+
src: srcSlot,
|
|
314
|
+
})
|
|
315
|
+
break
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
case 'call': {
|
|
319
|
+
// Set parameter slots $p0, $p1, ...
|
|
320
|
+
for (let i = 0; i < instr.args.length; i++) {
|
|
321
|
+
const paramSlot: Slot = { player: `$p${i}`, obj: ctx.objective }
|
|
322
|
+
lowerOperandToSlotDirect(paramSlot, instr.args[i], ctx, instrs)
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Handle raw commands embedded in call
|
|
326
|
+
if (instr.fn.startsWith('__raw:')) {
|
|
327
|
+
const cmd = instr.fn.slice(6)
|
|
328
|
+
if (cmd.startsWith('\x01')) {
|
|
329
|
+
// Macro sentinel → emit as macro_line ($ prefix added by emit)
|
|
330
|
+
instrs.push({ kind: 'macro_line', template: cmd.slice(1) })
|
|
331
|
+
} else {
|
|
332
|
+
instrs.push({ kind: 'raw', cmd })
|
|
333
|
+
}
|
|
334
|
+
} else {
|
|
335
|
+
instrs.push({ kind: 'call', fn: ctx.qualifiedName(instr.fn) })
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Copy return value to dst if needed
|
|
339
|
+
if (instr.dst) {
|
|
340
|
+
const retSlot: Slot = { player: '$ret', obj: ctx.objective }
|
|
341
|
+
instrs.push({ kind: 'score_copy', dst: ctx.slot(instr.dst), src: retSlot })
|
|
342
|
+
}
|
|
343
|
+
break
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
case 'call_macro': {
|
|
347
|
+
const macroStorage = `rs:macro_args`
|
|
348
|
+
// Store each arg to NBT
|
|
349
|
+
for (const arg of instr.args) {
|
|
350
|
+
const srcSlot = operandToSlot(arg.value, ctx, instrs)
|
|
351
|
+
instrs.push({
|
|
352
|
+
kind: 'store_score_to_nbt',
|
|
353
|
+
ns: 'rs:macro_args',
|
|
354
|
+
path: arg.name,
|
|
355
|
+
type: arg.type,
|
|
356
|
+
scale: arg.scale,
|
|
357
|
+
src: srcSlot,
|
|
358
|
+
})
|
|
359
|
+
}
|
|
360
|
+
instrs.push({ kind: 'call_macro', fn: ctx.qualifiedName(instr.fn), storage: macroStorage })
|
|
361
|
+
|
|
362
|
+
// Copy return value to dst if needed
|
|
363
|
+
if (instr.dst) {
|
|
364
|
+
const retSlot: Slot = { player: '$ret', obj: ctx.objective }
|
|
365
|
+
instrs.push({ kind: 'score_copy', dst: ctx.slot(instr.dst), src: retSlot })
|
|
366
|
+
}
|
|
367
|
+
break
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
case 'call_context': {
|
|
371
|
+
instrs.push({
|
|
372
|
+
kind: 'call_context',
|
|
373
|
+
fn: ctx.qualifiedName(instr.fn),
|
|
374
|
+
subcommands: instr.subcommands,
|
|
375
|
+
})
|
|
376
|
+
break
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
default:
|
|
380
|
+
break
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// ---------------------------------------------------------------------------
|
|
385
|
+
// Terminator lowering
|
|
386
|
+
// ---------------------------------------------------------------------------
|
|
387
|
+
|
|
388
|
+
function lowerTerminator(
|
|
389
|
+
term: MIRInstr,
|
|
390
|
+
fn: MIRFunction,
|
|
391
|
+
ctx: LoweringContext,
|
|
392
|
+
instrs: LIRInstr[],
|
|
393
|
+
visited: Set<BlockId>,
|
|
394
|
+
): void {
|
|
395
|
+
switch (term.kind) {
|
|
396
|
+
case 'return': {
|
|
397
|
+
if (term.value) {
|
|
398
|
+
const retSlot: Slot = { player: '$ret', obj: ctx.objective }
|
|
399
|
+
const srcSlot = operandToSlot(term.value, ctx, instrs)
|
|
400
|
+
instrs.push({ kind: 'return_value', slot: srcSlot })
|
|
401
|
+
}
|
|
402
|
+
break
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
case 'jump': {
|
|
406
|
+
if (ctx.isMultiPred(term.target)) {
|
|
407
|
+
// Target has multiple predecessors — call the extracted function
|
|
408
|
+
instrs.push({ kind: 'call', fn: ctx.qualifiedName(ctx.getBlockFnName(term.target)) })
|
|
409
|
+
} else {
|
|
410
|
+
// Inline the target block's instructions
|
|
411
|
+
lowerBlock(term.target, fn, ctx, instrs, visited)
|
|
412
|
+
}
|
|
413
|
+
break
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
case 'branch': {
|
|
417
|
+
const condSlot = operandToSlot(term.cond, ctx, instrs)
|
|
418
|
+
|
|
419
|
+
// Then branch: use `return run function` to atomically call and exit.
|
|
420
|
+
// This prevents fallthrough to the else-branch even when recursive calls
|
|
421
|
+
// (e.g. continue → loop header → loop body) clobber the condition slot.
|
|
422
|
+
const thenFnName = emitBranchTarget(term.then, fn, ctx, visited)
|
|
423
|
+
instrs.push({
|
|
424
|
+
kind: 'raw',
|
|
425
|
+
cmd: `execute if score ${condSlot.player} ${condSlot.obj} matches 1 run return run function ${ctx.qualifiedName(thenFnName)}`,
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
// Else branch: if we reach here, cond was not 1 (the then-path returned).
|
|
429
|
+
const elseFnName = emitBranchTarget(term.else, fn, ctx, visited)
|
|
430
|
+
instrs.push({ kind: 'call', fn: ctx.qualifiedName(elseFnName) })
|
|
431
|
+
break
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Emit a branch target as a separate LIR function and return its name.
|
|
438
|
+
* If the target is already a multi-pred block with a function, reuse it.
|
|
439
|
+
*/
|
|
440
|
+
function emitBranchTarget(
|
|
441
|
+
blockId: BlockId,
|
|
442
|
+
fn: MIRFunction,
|
|
443
|
+
ctx: LoweringContext,
|
|
444
|
+
parentVisited: Set<BlockId>,
|
|
445
|
+
): string {
|
|
446
|
+
// If already has a function (multi-pred), return its name
|
|
447
|
+
if (ctx.isMultiPred(blockId)) {
|
|
448
|
+
// Make sure the block gets emitted
|
|
449
|
+
if (!parentVisited.has(blockId)) {
|
|
450
|
+
const blockInstrs: LIRInstr[] = []
|
|
451
|
+
const blockVisited = new Set<BlockId>()
|
|
452
|
+
lowerBlock(blockId, fn, ctx, blockInstrs, blockVisited)
|
|
453
|
+
ctx.addFunction({
|
|
454
|
+
name: ctx.getBlockFnName(blockId),
|
|
455
|
+
instructions: blockInstrs,
|
|
456
|
+
isMacro: false,
|
|
457
|
+
macroParams: [],
|
|
458
|
+
})
|
|
459
|
+
parentVisited.add(blockId)
|
|
460
|
+
}
|
|
461
|
+
return ctx.getBlockFnName(blockId)
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Create a new function for this branch target
|
|
465
|
+
const branchFnName = ctx.getBlockFnName(blockId)
|
|
466
|
+
const blockInstrs: LIRInstr[] = []
|
|
467
|
+
const blockVisited = new Set<BlockId>()
|
|
468
|
+
lowerBlock(blockId, fn, ctx, blockInstrs, blockVisited)
|
|
469
|
+
|
|
470
|
+
ctx.addFunction({
|
|
471
|
+
name: branchFnName,
|
|
472
|
+
instructions: blockInstrs,
|
|
473
|
+
isMacro: false,
|
|
474
|
+
macroParams: [],
|
|
475
|
+
})
|
|
476
|
+
|
|
477
|
+
// Mark visited so parent doesn't re-inline
|
|
478
|
+
parentVisited.add(blockId)
|
|
479
|
+
|
|
480
|
+
return branchFnName
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// ---------------------------------------------------------------------------
|
|
484
|
+
// Helpers
|
|
485
|
+
// ---------------------------------------------------------------------------
|
|
486
|
+
|
|
487
|
+
/** Lower an operand into a named temp slot (copy const or score_copy) */
|
|
488
|
+
function lowerOperandToSlot(
|
|
489
|
+
dstTemp: Temp,
|
|
490
|
+
src: Operand,
|
|
491
|
+
ctx: LoweringContext,
|
|
492
|
+
instrs: LIRInstr[],
|
|
493
|
+
): void {
|
|
494
|
+
const dst = ctx.slot(dstTemp)
|
|
495
|
+
if (src.kind === 'const') {
|
|
496
|
+
instrs.push({ kind: 'score_set', dst, value: src.value })
|
|
497
|
+
} else {
|
|
498
|
+
instrs.push({ kind: 'score_copy', dst, src: ctx.slot(src.name) })
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/** Lower an operand into a specific slot (not by temp name) */
|
|
503
|
+
function lowerOperandToSlotDirect(
|
|
504
|
+
dst: Slot,
|
|
505
|
+
src: Operand,
|
|
506
|
+
ctx: LoweringContext,
|
|
507
|
+
instrs: LIRInstr[],
|
|
508
|
+
): void {
|
|
509
|
+
if (src.kind === 'const') {
|
|
510
|
+
instrs.push({ kind: 'score_set', dst, value: src.value })
|
|
511
|
+
} else {
|
|
512
|
+
instrs.push({ kind: 'score_copy', dst, src: ctx.slot(src.name) })
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/** Get a slot for an operand, emitting a score_set for constants into a temp */
|
|
517
|
+
function operandToSlot(
|
|
518
|
+
op: Operand,
|
|
519
|
+
ctx: LoweringContext,
|
|
520
|
+
instrs: LIRInstr[],
|
|
521
|
+
): Slot {
|
|
522
|
+
if (op.kind === 'temp') {
|
|
523
|
+
return ctx.slot(op.name)
|
|
524
|
+
}
|
|
525
|
+
// Constant → need a temporary slot
|
|
526
|
+
return constSlot(op.value, ctx, instrs)
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/** Create a constant slot with a given value */
|
|
530
|
+
function constSlot(value: number, ctx: LoweringContext, instrs: LIRInstr[]): Slot {
|
|
531
|
+
const slot: Slot = { player: `$__const_${value}`, obj: ctx.objective }
|
|
532
|
+
instrs.push({ kind: 'score_set', dst: slot, value })
|
|
533
|
+
return slot
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/** Apply a binary score operation: dst op= src */
|
|
537
|
+
function lowerBinOp(
|
|
538
|
+
dstTemp: Temp,
|
|
539
|
+
b: Operand,
|
|
540
|
+
scoreKind: 'score_add' | 'score_sub' | 'score_mul' | 'score_div' | 'score_mod',
|
|
541
|
+
ctx: LoweringContext,
|
|
542
|
+
instrs: LIRInstr[],
|
|
543
|
+
): void {
|
|
544
|
+
const dst = ctx.slot(dstTemp)
|
|
545
|
+
const srcSlot = operandToSlot(b, ctx, instrs)
|
|
546
|
+
instrs.push({ kind: scoreKind, dst, src: srcSlot })
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function getTermTargets(term: MIRInstr): BlockId[] {
|
|
550
|
+
switch (term.kind) {
|
|
551
|
+
case 'jump': return [term.target]
|
|
552
|
+
case 'branch': return [term.then, term.else]
|
|
553
|
+
case 'return': return []
|
|
554
|
+
default: return []
|
|
555
|
+
}
|
|
556
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LIR (Low-level IR) Types — Stage 5 of the RedScript compiler pipeline.
|
|
3
|
+
*
|
|
4
|
+
* LIR is 2-address, MC-specific, typed nodes — no raw strings.
|
|
5
|
+
* Each LIR instruction maps 1:1 (or near) to one MC command.
|
|
6
|
+
*
|
|
7
|
+
* Spec: docs/compiler-pipeline-redesign.md § "LIR Instruction Set"
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { CmpOp, NBTType, ExecuteSubcmd } from '../mir/types'
|
|
11
|
+
|
|
12
|
+
// A scoreboard slot: fake-player name + objective
|
|
13
|
+
export interface Slot {
|
|
14
|
+
player: string
|
|
15
|
+
obj: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Re-export types used in LIR from MIR
|
|
19
|
+
export type { CmpOp, NBTType, ExecuteSubcmd }
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// LIR Instructions
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
export type LIRInstr =
|
|
26
|
+
// ── Scoreboard ───────────────────────────────────────────────────────────
|
|
27
|
+
| { kind: 'score_set'; dst: Slot; value: number }
|
|
28
|
+
// scoreboard players set <dst.player> <dst.obj> value
|
|
29
|
+
|
|
30
|
+
| { kind: 'score_copy'; dst: Slot; src: Slot }
|
|
31
|
+
// scoreboard players operation <dst> = <src>
|
|
32
|
+
|
|
33
|
+
| { kind: 'score_add'; dst: Slot; src: Slot } // +=
|
|
34
|
+
| { kind: 'score_sub'; dst: Slot; src: Slot } // -=
|
|
35
|
+
| { kind: 'score_mul'; dst: Slot; src: Slot } // *=
|
|
36
|
+
| { kind: 'score_div'; dst: Slot; src: Slot } // /=
|
|
37
|
+
| { kind: 'score_mod'; dst: Slot; src: Slot } // %=
|
|
38
|
+
| { kind: 'score_min'; dst: Slot; src: Slot } // < (min)
|
|
39
|
+
| { kind: 'score_max'; dst: Slot; src: Slot } // > (max)
|
|
40
|
+
| { kind: 'score_swap'; a: Slot; b: Slot } // ><
|
|
41
|
+
|
|
42
|
+
// ── Execute store ────────────────────────────────────────────────────────
|
|
43
|
+
| { kind: 'store_cmd_to_score'; dst: Slot; cmd: LIRInstr }
|
|
44
|
+
// execute store result score <dst> run <cmd>
|
|
45
|
+
|
|
46
|
+
| { kind: 'store_score_to_nbt';
|
|
47
|
+
ns: string; path: string; type: NBTType; scale: number;
|
|
48
|
+
src: Slot }
|
|
49
|
+
// execute store result storage <ns> <path> <type> <scale> run scoreboard players get <src>
|
|
50
|
+
|
|
51
|
+
| { kind: 'store_nbt_to_score';
|
|
52
|
+
dst: Slot; ns: string; path: string; scale: number }
|
|
53
|
+
// execute store result score <dst> run data get storage <ns> <path> <scale>
|
|
54
|
+
|
|
55
|
+
// ── NBT ──────────────────────────────────────────────────────────────────
|
|
56
|
+
| { kind: 'nbt_set_literal'; ns: string; path: string; value: string }
|
|
57
|
+
// data modify storage <ns> <path> set value <value>
|
|
58
|
+
|
|
59
|
+
| { kind: 'nbt_copy'; srcNs: string; srcPath: string; dstNs: string; dstPath: string }
|
|
60
|
+
// data modify storage <dstNs> <dstPath> set from storage <srcNs> <srcPath>
|
|
61
|
+
|
|
62
|
+
// ── Control flow ─────────────────────────────────────────────────────────
|
|
63
|
+
| { kind: 'call'; fn: string }
|
|
64
|
+
// function <fn>
|
|
65
|
+
|
|
66
|
+
| { kind: 'call_macro'; fn: string; storage: string }
|
|
67
|
+
// function <fn> with storage <storage>
|
|
68
|
+
|
|
69
|
+
| { kind: 'call_if_matches'; fn: string; slot: Slot; range: string }
|
|
70
|
+
// execute if score <slot> matches <range> run function <fn>
|
|
71
|
+
|
|
72
|
+
| { kind: 'call_unless_matches'; fn: string; slot: Slot; range: string }
|
|
73
|
+
|
|
74
|
+
| { kind: 'call_if_score'; fn: string; a: Slot; op: CmpOp; b: Slot }
|
|
75
|
+
// execute if score <a> <op> <b> run function <fn>
|
|
76
|
+
|
|
77
|
+
| { kind: 'call_unless_score'; fn: string; a: Slot; op: CmpOp; b: Slot }
|
|
78
|
+
|
|
79
|
+
| { kind: 'call_context'; fn: string; subcommands: ExecuteSubcmd[] }
|
|
80
|
+
// execute [subcommands] run function <fn>
|
|
81
|
+
|
|
82
|
+
| { kind: 'return_value'; slot: Slot }
|
|
83
|
+
// scoreboard players operation $ret <obj> = <slot> (then implicit return)
|
|
84
|
+
|
|
85
|
+
// ── Macro line ────────────────────────────────────────────────────────────
|
|
86
|
+
| { kind: 'macro_line'; template: string }
|
|
87
|
+
// A line starting with $ in a macro function.
|
|
88
|
+
// template uses $(param) substitutions
|
|
89
|
+
|
|
90
|
+
// ── Arbitrary MC command ─────────────────────────────────────────────────
|
|
91
|
+
| { kind: 'raw'; cmd: string }
|
|
92
|
+
// Emitted verbatim. Use sparingly — prefer typed instructions.
|
|
93
|
+
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// LIR function and module structure
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
export interface LIRFunction {
|
|
99
|
+
name: string
|
|
100
|
+
instructions: LIRInstr[] // flat list (no blocks; control flow is via call_if_*)
|
|
101
|
+
isMacro: boolean
|
|
102
|
+
macroParams: string[] // names of $(param) substitution keys
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface LIRModule {
|
|
106
|
+
functions: LIRFunction[]
|
|
107
|
+
namespace: string
|
|
108
|
+
objective: string
|
|
109
|
+
}
|