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.
- 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/README.md +29 -28
- package/README.zh.md +28 -28
- 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/lexer/index.js +9 -1
- package/dist/lowering/index.js +22 -5
- 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/examples/readme-demo.mcrs +44 -66
- 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/lexer/index.ts +9 -1
- 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 -3860
- 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,1030 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* HIR → MIR Lowering — Stage 3 of the RedScript compiler pipeline.
|
|
4
|
+
*
|
|
5
|
+
* Converts structured HIR (if/while/break/continue) into an explicit CFG
|
|
6
|
+
* with 3-address instructions and unlimited fresh temporaries.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.lowerToMIR = lowerToMIR;
|
|
10
|
+
const macro_1 = require("./macro");
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// Public API
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
function lowerToMIR(hir) {
|
|
15
|
+
// Build struct definitions: name → field names
|
|
16
|
+
const structDefs = new Map();
|
|
17
|
+
for (const s of hir.structs) {
|
|
18
|
+
structDefs.set(s.name, s.fields.map(f => f.name));
|
|
19
|
+
}
|
|
20
|
+
// Build impl method info: typeName → methodName → { hasSelf }
|
|
21
|
+
const implMethods = new Map();
|
|
22
|
+
for (const ib of hir.implBlocks) {
|
|
23
|
+
const methods = new Map();
|
|
24
|
+
for (const m of ib.methods) {
|
|
25
|
+
const hasSelf = m.params.length > 0 && m.params[0].name === 'self';
|
|
26
|
+
methods.set(m.name, { hasSelf });
|
|
27
|
+
}
|
|
28
|
+
implMethods.set(ib.typeName, methods);
|
|
29
|
+
}
|
|
30
|
+
// Pre-scan for macro functions
|
|
31
|
+
const macroInfo = (0, macro_1.detectMacroFunctions)(hir);
|
|
32
|
+
// Build function param info for call_macro generation at call sites
|
|
33
|
+
const fnParamInfo = new Map();
|
|
34
|
+
for (const f of hir.functions) {
|
|
35
|
+
fnParamInfo.set(f.name, f.params);
|
|
36
|
+
}
|
|
37
|
+
for (const ib of hir.implBlocks) {
|
|
38
|
+
for (const m of ib.methods) {
|
|
39
|
+
fnParamInfo.set(`${ib.typeName}::${m.name}`, m.params);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const allFunctions = [];
|
|
43
|
+
for (const f of hir.functions) {
|
|
44
|
+
const { fn, helpers } = lowerFunction(f, hir.namespace, structDefs, implMethods, macroInfo, fnParamInfo);
|
|
45
|
+
allFunctions.push(fn, ...helpers);
|
|
46
|
+
}
|
|
47
|
+
// Lower impl block methods
|
|
48
|
+
for (const ib of hir.implBlocks) {
|
|
49
|
+
for (const m of ib.methods) {
|
|
50
|
+
const { fn, helpers } = lowerImplMethod(m, ib.typeName, hir.namespace, structDefs, implMethods, macroInfo, fnParamInfo);
|
|
51
|
+
allFunctions.push(fn, ...helpers);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
functions: allFunctions,
|
|
56
|
+
namespace: hir.namespace,
|
|
57
|
+
objective: `__${hir.namespace}`,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Function lowering context
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
class FnContext {
|
|
64
|
+
constructor(namespace, fnName, structDefs = new Map(), implMethods = new Map(), macroInfo = new Map(), fnParamInfo = new Map()) {
|
|
65
|
+
this.tempCounter = 0;
|
|
66
|
+
this.blockCounter = 0;
|
|
67
|
+
this.blocks = [];
|
|
68
|
+
/** Stack of (loopHeader, loopExit, continueTo) for break/continue */
|
|
69
|
+
this.loopStack = [];
|
|
70
|
+
/** Extracted helper functions for execute blocks */
|
|
71
|
+
this.helperFunctions = [];
|
|
72
|
+
/** Struct variable tracking: varName → { typeName, fields: fieldName → temp } */
|
|
73
|
+
this.structVars = new Map();
|
|
74
|
+
this.namespace = namespace;
|
|
75
|
+
this.fnName = fnName;
|
|
76
|
+
this.structDefs = structDefs;
|
|
77
|
+
this.implMethods = implMethods;
|
|
78
|
+
this.macroInfo = macroInfo;
|
|
79
|
+
this.fnParamInfo = fnParamInfo;
|
|
80
|
+
this.currentMacroParams = macroInfo.get(fnName)?.macroParams ?? new Set();
|
|
81
|
+
const entry = this.makeBlock('entry');
|
|
82
|
+
this.currentBlock = entry;
|
|
83
|
+
}
|
|
84
|
+
freshTemp() {
|
|
85
|
+
return `t${this.tempCounter++}`;
|
|
86
|
+
}
|
|
87
|
+
makeBlock(id) {
|
|
88
|
+
const block = {
|
|
89
|
+
id: id ?? `bb${this.blockCounter++}`,
|
|
90
|
+
instrs: [],
|
|
91
|
+
term: { kind: 'return', value: null }, // placeholder
|
|
92
|
+
preds: [],
|
|
93
|
+
};
|
|
94
|
+
this.blocks.push(block);
|
|
95
|
+
return block;
|
|
96
|
+
}
|
|
97
|
+
newBlock(prefix) {
|
|
98
|
+
return this.makeBlock(prefix ? `${prefix}_${this.blockCounter++}` : undefined);
|
|
99
|
+
}
|
|
100
|
+
emit(instr) {
|
|
101
|
+
this.currentBlock.instrs.push(instr);
|
|
102
|
+
}
|
|
103
|
+
terminate(term) {
|
|
104
|
+
this.currentBlock.term = term;
|
|
105
|
+
}
|
|
106
|
+
switchTo(block) {
|
|
107
|
+
this.currentBlock = block;
|
|
108
|
+
}
|
|
109
|
+
current() {
|
|
110
|
+
return this.currentBlock;
|
|
111
|
+
}
|
|
112
|
+
pushLoop(header, exit, continueTo) {
|
|
113
|
+
this.loopStack.push({ header, exit, continueTo: continueTo ?? header });
|
|
114
|
+
}
|
|
115
|
+
popLoop() {
|
|
116
|
+
this.loopStack.pop();
|
|
117
|
+
}
|
|
118
|
+
currentLoop() {
|
|
119
|
+
return this.loopStack[this.loopStack.length - 1];
|
|
120
|
+
}
|
|
121
|
+
getNamespace() {
|
|
122
|
+
return this.namespace;
|
|
123
|
+
}
|
|
124
|
+
getFnName() {
|
|
125
|
+
return this.fnName;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// Function lowering
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
function lowerFunction(fn, namespace, structDefs = new Map(), implMethods = new Map(), macroInfo = new Map(), fnParamInfo = new Map()) {
|
|
132
|
+
const ctx = new FnContext(namespace, fn.name, structDefs, implMethods, macroInfo, fnParamInfo);
|
|
133
|
+
const fnMacroInfo = macroInfo.get(fn.name);
|
|
134
|
+
// Create temps for parameters
|
|
135
|
+
const params = fn.params.map(p => {
|
|
136
|
+
const t = ctx.freshTemp();
|
|
137
|
+
return { name: t, isMacroParam: fnMacroInfo?.macroParams.has(p.name) ?? false };
|
|
138
|
+
});
|
|
139
|
+
// Map parameter names to their temps
|
|
140
|
+
const scope = new Map();
|
|
141
|
+
fn.params.forEach((p, i) => {
|
|
142
|
+
scope.set(p.name, params[i].name);
|
|
143
|
+
});
|
|
144
|
+
lowerBlock(fn.body, ctx, scope);
|
|
145
|
+
// If the current block doesn't have a real terminator, add void return
|
|
146
|
+
const cur = ctx.current();
|
|
147
|
+
if (isPlaceholderTerm(cur.term)) {
|
|
148
|
+
ctx.terminate({ kind: 'return', value: null });
|
|
149
|
+
}
|
|
150
|
+
// Remove unreachable blocks (dead continuations after return/break/continue)
|
|
151
|
+
const reachable = computeReachable(ctx.blocks, 'entry');
|
|
152
|
+
const liveBlocks = ctx.blocks.filter(b => reachable.has(b.id));
|
|
153
|
+
// Fill predecessor lists
|
|
154
|
+
computePreds(liveBlocks);
|
|
155
|
+
const result = {
|
|
156
|
+
name: fn.name,
|
|
157
|
+
params,
|
|
158
|
+
blocks: liveBlocks,
|
|
159
|
+
entry: 'entry',
|
|
160
|
+
isMacro: fnMacroInfo != null,
|
|
161
|
+
};
|
|
162
|
+
return { fn: result, helpers: ctx.helperFunctions };
|
|
163
|
+
}
|
|
164
|
+
function lowerImplMethod(method, typeName, namespace, structDefs, implMethods, macroInfo = new Map(), fnParamInfo = new Map()) {
|
|
165
|
+
const fnName = `${typeName}::${method.name}`;
|
|
166
|
+
const ctx = new FnContext(namespace, fnName, structDefs, implMethods, macroInfo, fnParamInfo);
|
|
167
|
+
const fields = structDefs.get(typeName) ?? [];
|
|
168
|
+
const hasSelf = method.params.length > 0 && method.params[0].name === 'self';
|
|
169
|
+
const params = [];
|
|
170
|
+
const scope = new Map();
|
|
171
|
+
if (hasSelf) {
|
|
172
|
+
// Self fields become the first N params (one per struct field)
|
|
173
|
+
const selfFields = new Map();
|
|
174
|
+
for (const fieldName of fields) {
|
|
175
|
+
const t = ctx.freshTemp();
|
|
176
|
+
params.push({ name: t, isMacroParam: false });
|
|
177
|
+
selfFields.set(fieldName, t);
|
|
178
|
+
}
|
|
179
|
+
ctx.structVars.set('self', { typeName, fields: selfFields });
|
|
180
|
+
// Remaining params (after self)
|
|
181
|
+
for (let i = 1; i < method.params.length; i++) {
|
|
182
|
+
const t = ctx.freshTemp();
|
|
183
|
+
params.push({ name: t, isMacroParam: false });
|
|
184
|
+
scope.set(method.params[i].name, t);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
// Static method — regular params
|
|
189
|
+
for (const p of method.params) {
|
|
190
|
+
const t = ctx.freshTemp();
|
|
191
|
+
params.push({ name: t, isMacroParam: false });
|
|
192
|
+
scope.set(p.name, t);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
lowerBlock(method.body, ctx, scope);
|
|
196
|
+
const cur = ctx.current();
|
|
197
|
+
if (isPlaceholderTerm(cur.term)) {
|
|
198
|
+
ctx.terminate({ kind: 'return', value: null });
|
|
199
|
+
}
|
|
200
|
+
const reachable = computeReachable(ctx.blocks, 'entry');
|
|
201
|
+
const liveBlocks = ctx.blocks.filter(b => reachable.has(b.id));
|
|
202
|
+
computePreds(liveBlocks);
|
|
203
|
+
const result = {
|
|
204
|
+
name: fnName,
|
|
205
|
+
params,
|
|
206
|
+
blocks: liveBlocks,
|
|
207
|
+
entry: 'entry',
|
|
208
|
+
isMacro: macroInfo.has(fnName),
|
|
209
|
+
};
|
|
210
|
+
return { fn: result, helpers: ctx.helperFunctions };
|
|
211
|
+
}
|
|
212
|
+
function isPlaceholderTerm(term) {
|
|
213
|
+
// Our placeholder is a return null that was set in makeBlock
|
|
214
|
+
return term.kind === 'return' && term.value === null;
|
|
215
|
+
}
|
|
216
|
+
function computeReachable(blocks, entry) {
|
|
217
|
+
const reachable = new Set();
|
|
218
|
+
const queue = [entry];
|
|
219
|
+
while (queue.length > 0) {
|
|
220
|
+
const id = queue.shift();
|
|
221
|
+
if (reachable.has(id))
|
|
222
|
+
continue;
|
|
223
|
+
reachable.add(id);
|
|
224
|
+
const block = blocks.find(b => b.id === id);
|
|
225
|
+
if (block) {
|
|
226
|
+
for (const t of getTermTargets(block.term)) {
|
|
227
|
+
if (!reachable.has(t))
|
|
228
|
+
queue.push(t);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return reachable;
|
|
233
|
+
}
|
|
234
|
+
function computePreds(blocks) {
|
|
235
|
+
// Clear all preds
|
|
236
|
+
for (const b of blocks)
|
|
237
|
+
b.preds = [];
|
|
238
|
+
for (const b of blocks) {
|
|
239
|
+
const targets = getTermTargets(b.term);
|
|
240
|
+
for (const t of targets) {
|
|
241
|
+
const target = blocks.find(bb => bb.id === t);
|
|
242
|
+
if (target && !target.preds.includes(b.id)) {
|
|
243
|
+
target.preds.push(b.id);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
function getTermTargets(term) {
|
|
249
|
+
switch (term.kind) {
|
|
250
|
+
case 'jump': return [term.target];
|
|
251
|
+
case 'branch': return [term.then, term.else];
|
|
252
|
+
case 'return': return [];
|
|
253
|
+
default: return [];
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// ---------------------------------------------------------------------------
|
|
257
|
+
// Block / statement lowering
|
|
258
|
+
// ---------------------------------------------------------------------------
|
|
259
|
+
function lowerBlock(stmts, ctx, scope) {
|
|
260
|
+
for (const stmt of stmts) {
|
|
261
|
+
lowerStmt(stmt, ctx, scope);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function lowerStmt(stmt, ctx, scope) {
|
|
265
|
+
switch (stmt.kind) {
|
|
266
|
+
case 'let': {
|
|
267
|
+
if (stmt.init.kind === 'struct_lit') {
|
|
268
|
+
// Struct literal: create per-field temps
|
|
269
|
+
const typeName = (stmt.type?.kind === 'struct') ? stmt.type.name : '__anon';
|
|
270
|
+
const fieldTemps = new Map();
|
|
271
|
+
for (const field of stmt.init.fields) {
|
|
272
|
+
const val = lowerExpr(field.value, ctx, scope);
|
|
273
|
+
const t = ctx.freshTemp();
|
|
274
|
+
ctx.emit({ kind: 'copy', dst: t, src: val });
|
|
275
|
+
fieldTemps.set(field.name, t);
|
|
276
|
+
}
|
|
277
|
+
ctx.structVars.set(stmt.name, { typeName, fields: fieldTemps });
|
|
278
|
+
}
|
|
279
|
+
else if (stmt.type?.kind === 'struct') {
|
|
280
|
+
// Struct-typed let with non-literal init (e.g., call returning struct)
|
|
281
|
+
const fields = ctx.structDefs.get(stmt.type.name);
|
|
282
|
+
if (fields) {
|
|
283
|
+
lowerExpr(stmt.init, ctx, scope);
|
|
284
|
+
// Copy from return field slots into struct variable temps
|
|
285
|
+
const fieldTemps = new Map();
|
|
286
|
+
for (const fieldName of fields) {
|
|
287
|
+
const t = ctx.freshTemp();
|
|
288
|
+
ctx.emit({ kind: 'copy', dst: t, src: { kind: 'temp', name: `__rf_${fieldName}` } });
|
|
289
|
+
fieldTemps.set(fieldName, t);
|
|
290
|
+
}
|
|
291
|
+
ctx.structVars.set(stmt.name, { typeName: stmt.type.name, fields: fieldTemps });
|
|
292
|
+
}
|
|
293
|
+
else {
|
|
294
|
+
const valOp = lowerExpr(stmt.init, ctx, scope);
|
|
295
|
+
const t = ctx.freshTemp();
|
|
296
|
+
ctx.emit({ kind: 'copy', dst: t, src: valOp });
|
|
297
|
+
scope.set(stmt.name, t);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
const valOp = lowerExpr(stmt.init, ctx, scope);
|
|
302
|
+
const t = ctx.freshTemp();
|
|
303
|
+
ctx.emit({ kind: 'copy', dst: t, src: valOp });
|
|
304
|
+
scope.set(stmt.name, t);
|
|
305
|
+
}
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
case 'expr': {
|
|
309
|
+
lowerExpr(stmt.expr, ctx, scope);
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
case 'return': {
|
|
313
|
+
if (stmt.value?.kind === 'struct_lit') {
|
|
314
|
+
// Struct return — copy each field to return field slots
|
|
315
|
+
for (const field of stmt.value.fields) {
|
|
316
|
+
const val = lowerExpr(field.value, ctx, scope);
|
|
317
|
+
ctx.emit({ kind: 'copy', dst: `__rf_${field.name}`, src: val });
|
|
318
|
+
}
|
|
319
|
+
ctx.terminate({ kind: 'return', value: null });
|
|
320
|
+
}
|
|
321
|
+
else {
|
|
322
|
+
const val = stmt.value ? lowerExpr(stmt.value, ctx, scope) : null;
|
|
323
|
+
ctx.terminate({ kind: 'return', value: val });
|
|
324
|
+
}
|
|
325
|
+
// Create a dead block for any subsequent statements
|
|
326
|
+
const dead = ctx.newBlock('post_ret');
|
|
327
|
+
ctx.switchTo(dead);
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
case 'break': {
|
|
331
|
+
const loop = ctx.currentLoop();
|
|
332
|
+
if (!loop)
|
|
333
|
+
throw new Error('break outside loop');
|
|
334
|
+
ctx.terminate({ kind: 'jump', target: loop.exit });
|
|
335
|
+
const dead = ctx.newBlock('post_break');
|
|
336
|
+
ctx.switchTo(dead);
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
case 'continue': {
|
|
340
|
+
const loop = ctx.currentLoop();
|
|
341
|
+
if (!loop)
|
|
342
|
+
throw new Error('continue outside loop');
|
|
343
|
+
ctx.terminate({ kind: 'jump', target: loop.continueTo });
|
|
344
|
+
const dead = ctx.newBlock('post_continue');
|
|
345
|
+
ctx.switchTo(dead);
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
case 'if': {
|
|
349
|
+
const condOp = lowerExpr(stmt.cond, ctx, scope);
|
|
350
|
+
const thenBlock = ctx.newBlock('then');
|
|
351
|
+
const mergeBlock = ctx.newBlock('merge');
|
|
352
|
+
const elseBlock = stmt.else_ ? ctx.newBlock('else') : mergeBlock;
|
|
353
|
+
ctx.terminate({ kind: 'branch', cond: condOp, then: thenBlock.id, else: elseBlock.id });
|
|
354
|
+
// Then branch
|
|
355
|
+
ctx.switchTo(thenBlock);
|
|
356
|
+
lowerBlock(stmt.then, ctx, new Map(scope));
|
|
357
|
+
if (isPlaceholderTerm(ctx.current().term)) {
|
|
358
|
+
ctx.terminate({ kind: 'jump', target: mergeBlock.id });
|
|
359
|
+
}
|
|
360
|
+
// Else branch
|
|
361
|
+
if (stmt.else_) {
|
|
362
|
+
ctx.switchTo(elseBlock);
|
|
363
|
+
lowerBlock(stmt.else_, ctx, new Map(scope));
|
|
364
|
+
if (isPlaceholderTerm(ctx.current().term)) {
|
|
365
|
+
ctx.terminate({ kind: 'jump', target: mergeBlock.id });
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
ctx.switchTo(mergeBlock);
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
371
|
+
case 'while': {
|
|
372
|
+
const headerBlock = ctx.newBlock('loop_header');
|
|
373
|
+
const bodyBlock = ctx.newBlock('loop_body');
|
|
374
|
+
const exitBlock = ctx.newBlock('loop_exit');
|
|
375
|
+
// If there's a step block (for/for_range), create a latch block that
|
|
376
|
+
// executes the step and then jumps to the header. Continue targets the
|
|
377
|
+
// latch so the increment always runs.
|
|
378
|
+
let latchBlock = null;
|
|
379
|
+
if (stmt.step && stmt.step.length > 0) {
|
|
380
|
+
latchBlock = ctx.newBlock('loop_latch');
|
|
381
|
+
}
|
|
382
|
+
const continueTarget = latchBlock ? latchBlock.id : headerBlock.id;
|
|
383
|
+
// Jump from current block to header
|
|
384
|
+
ctx.terminate({ kind: 'jump', target: headerBlock.id });
|
|
385
|
+
// Header: evaluate condition
|
|
386
|
+
ctx.switchTo(headerBlock);
|
|
387
|
+
const condOp = lowerExpr(stmt.cond, ctx, scope);
|
|
388
|
+
ctx.terminate({ kind: 'branch', cond: condOp, then: bodyBlock.id, else: exitBlock.id });
|
|
389
|
+
// Body
|
|
390
|
+
ctx.switchTo(bodyBlock);
|
|
391
|
+
ctx.pushLoop(headerBlock.id, exitBlock.id, continueTarget);
|
|
392
|
+
lowerBlock(stmt.body, ctx, new Map(scope));
|
|
393
|
+
ctx.popLoop();
|
|
394
|
+
if (isPlaceholderTerm(ctx.current().term)) {
|
|
395
|
+
ctx.terminate({ kind: 'jump', target: continueTarget });
|
|
396
|
+
}
|
|
397
|
+
// Latch block (step): execute increment, then jump to header
|
|
398
|
+
if (latchBlock && stmt.step) {
|
|
399
|
+
ctx.switchTo(latchBlock);
|
|
400
|
+
lowerBlock(stmt.step, ctx, new Map(scope));
|
|
401
|
+
if (isPlaceholderTerm(ctx.current().term)) {
|
|
402
|
+
ctx.terminate({ kind: 'jump', target: headerBlock.id });
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
ctx.switchTo(exitBlock);
|
|
406
|
+
break;
|
|
407
|
+
}
|
|
408
|
+
case 'foreach': {
|
|
409
|
+
// foreach is MC-specific entity iteration — lower to call_context
|
|
410
|
+
// For now, extract body into a helper and emit call_context
|
|
411
|
+
const helperName = `${ctx.getFnName()}__foreach_${ctx.freshTemp()}`;
|
|
412
|
+
const subcommands = [];
|
|
413
|
+
// The iterable should be a selector expression
|
|
414
|
+
if (stmt.iterable.kind === 'selector') {
|
|
415
|
+
subcommands.push({ kind: 'as', selector: stmt.iterable.raw });
|
|
416
|
+
}
|
|
417
|
+
if (stmt.executeContext === '@s') {
|
|
418
|
+
subcommands.push({ kind: 'at_self' });
|
|
419
|
+
}
|
|
420
|
+
// Build helper function body as MIR
|
|
421
|
+
const helperCtx = new FnContext(ctx.getNamespace(), helperName, ctx.structDefs, ctx.implMethods);
|
|
422
|
+
const helperScope = new Map(scope);
|
|
423
|
+
lowerBlock(stmt.body, helperCtx, helperScope);
|
|
424
|
+
if (isPlaceholderTerm(helperCtx.current().term)) {
|
|
425
|
+
helperCtx.terminate({ kind: 'return', value: null });
|
|
426
|
+
}
|
|
427
|
+
const helperReachable = computeReachable(helperCtx.blocks, 'entry');
|
|
428
|
+
const helperBlocks = helperCtx.blocks.filter(b => helperReachable.has(b.id));
|
|
429
|
+
computePreds(helperBlocks);
|
|
430
|
+
ctx.helperFunctions.push({
|
|
431
|
+
name: helperName,
|
|
432
|
+
params: [],
|
|
433
|
+
blocks: helperBlocks,
|
|
434
|
+
entry: 'entry',
|
|
435
|
+
isMacro: false,
|
|
436
|
+
});
|
|
437
|
+
ctx.emit({ kind: 'call_context', fn: helperName, subcommands });
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
case 'execute': {
|
|
441
|
+
// Extract body into a helper function, emit call_context
|
|
442
|
+
const helperName = `${ctx.getFnName()}__exec_${ctx.freshTemp()}`;
|
|
443
|
+
const subcommands = stmt.subcommands.map(lowerExecuteSubcmd);
|
|
444
|
+
const helperCtx = new FnContext(ctx.getNamespace(), helperName, ctx.structDefs, ctx.implMethods);
|
|
445
|
+
const helperScope = new Map(scope);
|
|
446
|
+
lowerBlock(stmt.body, helperCtx, helperScope);
|
|
447
|
+
if (isPlaceholderTerm(helperCtx.current().term)) {
|
|
448
|
+
helperCtx.terminate({ kind: 'return', value: null });
|
|
449
|
+
}
|
|
450
|
+
const execReachable = computeReachable(helperCtx.blocks, 'entry');
|
|
451
|
+
const execBlocks = helperCtx.blocks.filter(b => execReachable.has(b.id));
|
|
452
|
+
computePreds(execBlocks);
|
|
453
|
+
ctx.helperFunctions.push({
|
|
454
|
+
name: helperName,
|
|
455
|
+
params: [],
|
|
456
|
+
blocks: execBlocks,
|
|
457
|
+
entry: 'entry',
|
|
458
|
+
isMacro: false,
|
|
459
|
+
});
|
|
460
|
+
ctx.emit({ kind: 'call_context', fn: helperName, subcommands });
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
case 'match': {
|
|
464
|
+
// Lower match as chained if/else
|
|
465
|
+
const matchVal = lowerExpr(stmt.expr, ctx, scope);
|
|
466
|
+
const mergeBlock = ctx.newBlock('match_merge');
|
|
467
|
+
for (let i = 0; i < stmt.arms.length; i++) {
|
|
468
|
+
const arm = stmt.arms[i];
|
|
469
|
+
if (arm.pattern === null) {
|
|
470
|
+
// Default arm — just emit the body
|
|
471
|
+
lowerBlock(arm.body, ctx, new Map(scope));
|
|
472
|
+
if (isPlaceholderTerm(ctx.current().term)) {
|
|
473
|
+
ctx.terminate({ kind: 'jump', target: mergeBlock.id });
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
const patOp = lowerExpr(arm.pattern, ctx, scope);
|
|
478
|
+
const cmpTemp = ctx.freshTemp();
|
|
479
|
+
ctx.emit({ kind: 'cmp', dst: cmpTemp, op: 'eq', a: matchVal, b: patOp });
|
|
480
|
+
const armBody = ctx.newBlock('match_arm');
|
|
481
|
+
const nextArm = ctx.newBlock('match_next');
|
|
482
|
+
ctx.terminate({ kind: 'branch', cond: { kind: 'temp', name: cmpTemp }, then: armBody.id, else: nextArm.id });
|
|
483
|
+
ctx.switchTo(armBody);
|
|
484
|
+
lowerBlock(arm.body, ctx, new Map(scope));
|
|
485
|
+
if (isPlaceholderTerm(ctx.current().term)) {
|
|
486
|
+
ctx.terminate({ kind: 'jump', target: mergeBlock.id });
|
|
487
|
+
}
|
|
488
|
+
ctx.switchTo(nextArm);
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
// If no default arm matched, jump to merge
|
|
492
|
+
if (isPlaceholderTerm(ctx.current().term)) {
|
|
493
|
+
ctx.terminate({ kind: 'jump', target: mergeBlock.id });
|
|
494
|
+
}
|
|
495
|
+
ctx.switchTo(mergeBlock);
|
|
496
|
+
break;
|
|
497
|
+
}
|
|
498
|
+
case 'raw': {
|
|
499
|
+
// Raw commands are opaque at MIR level — emit as a call to a synthetic raw function
|
|
500
|
+
// For now, pass through as a call with no args (will be handled in LIR)
|
|
501
|
+
ctx.emit({ kind: 'call', dst: null, fn: `__raw:${stmt.cmd}`, args: [] });
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
504
|
+
default: {
|
|
505
|
+
const _exhaustive = stmt;
|
|
506
|
+
throw new Error(`Unknown HIR statement kind: ${_exhaustive.kind}`);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
// ---------------------------------------------------------------------------
|
|
511
|
+
// Expression lowering → produces an Operand (temp or const)
|
|
512
|
+
// ---------------------------------------------------------------------------
|
|
513
|
+
function lowerExpr(expr, ctx, scope) {
|
|
514
|
+
switch (expr.kind) {
|
|
515
|
+
case 'int_lit':
|
|
516
|
+
return { kind: 'const', value: expr.value };
|
|
517
|
+
case 'float_lit':
|
|
518
|
+
// float is ×1000 fixed-point in RedScript
|
|
519
|
+
return { kind: 'const', value: expr.value };
|
|
520
|
+
case 'byte_lit':
|
|
521
|
+
case 'short_lit':
|
|
522
|
+
case 'long_lit':
|
|
523
|
+
case 'double_lit':
|
|
524
|
+
return { kind: 'const', value: expr.value };
|
|
525
|
+
case 'bool_lit': {
|
|
526
|
+
return { kind: 'const', value: expr.value ? 1 : 0 };
|
|
527
|
+
}
|
|
528
|
+
case 'struct_lit': {
|
|
529
|
+
// Struct literal in expression context (not let/return — those handle it directly).
|
|
530
|
+
// Lower each field value but return a placeholder since the struct
|
|
531
|
+
// is tracked via structVars at the statement level.
|
|
532
|
+
for (const field of expr.fields) {
|
|
533
|
+
lowerExpr(field.value, ctx, scope);
|
|
534
|
+
}
|
|
535
|
+
const t = ctx.freshTemp();
|
|
536
|
+
ctx.emit({ kind: 'const', dst: t, value: 0 });
|
|
537
|
+
return { kind: 'temp', name: t };
|
|
538
|
+
}
|
|
539
|
+
case 'str_lit':
|
|
540
|
+
case 'range_lit':
|
|
541
|
+
case 'array_lit':
|
|
542
|
+
case 'rel_coord':
|
|
543
|
+
case 'local_coord':
|
|
544
|
+
case 'mc_name':
|
|
545
|
+
case 'blockpos':
|
|
546
|
+
case 'selector':
|
|
547
|
+
case 'str_interp':
|
|
548
|
+
case 'f_string':
|
|
549
|
+
case 'is_check':
|
|
550
|
+
case 'lambda': {
|
|
551
|
+
// MC-specific / complex types — opaque at MIR level
|
|
552
|
+
// Emit as const 0 placeholder; these are handled in LIR lowering
|
|
553
|
+
const t = ctx.freshTemp();
|
|
554
|
+
ctx.emit({ kind: 'const', dst: t, value: 0 });
|
|
555
|
+
return { kind: 'temp', name: t };
|
|
556
|
+
}
|
|
557
|
+
case 'ident': {
|
|
558
|
+
const temp = scope.get(expr.name);
|
|
559
|
+
if (temp)
|
|
560
|
+
return { kind: 'temp', name: temp };
|
|
561
|
+
// Unresolved ident — could be a global or external reference
|
|
562
|
+
const t = ctx.freshTemp();
|
|
563
|
+
ctx.emit({ kind: 'copy', dst: t, src: { kind: 'const', value: 0 } });
|
|
564
|
+
scope.set(expr.name, t);
|
|
565
|
+
return { kind: 'temp', name: t };
|
|
566
|
+
}
|
|
567
|
+
case 'binary': {
|
|
568
|
+
// Handle short-circuit && and ||
|
|
569
|
+
if (expr.op === '&&') {
|
|
570
|
+
return lowerShortCircuitAnd(expr, ctx, scope);
|
|
571
|
+
}
|
|
572
|
+
if (expr.op === '||') {
|
|
573
|
+
return lowerShortCircuitOr(expr, ctx, scope);
|
|
574
|
+
}
|
|
575
|
+
const left = lowerExpr(expr.left, ctx, scope);
|
|
576
|
+
const right = lowerExpr(expr.right, ctx, scope);
|
|
577
|
+
const t = ctx.freshTemp();
|
|
578
|
+
// Map HIR binary ops to MIR instructions
|
|
579
|
+
const arithmeticOps = {
|
|
580
|
+
'+': 'add', '-': 'sub', '*': 'mul', '/': 'div', '%': 'mod',
|
|
581
|
+
};
|
|
582
|
+
const cmpOps = {
|
|
583
|
+
'==': 'eq', '!=': 'ne', '<': 'lt', '<=': 'le', '>': 'gt', '>=': 'ge',
|
|
584
|
+
};
|
|
585
|
+
if (expr.op in arithmeticOps) {
|
|
586
|
+
ctx.emit({ kind: arithmeticOps[expr.op], dst: t, a: left, b: right });
|
|
587
|
+
}
|
|
588
|
+
else if (expr.op in cmpOps) {
|
|
589
|
+
ctx.emit({ kind: 'cmp', dst: t, op: cmpOps[expr.op], a: left, b: right });
|
|
590
|
+
}
|
|
591
|
+
else {
|
|
592
|
+
throw new Error(`Unknown binary op: ${expr.op}`);
|
|
593
|
+
}
|
|
594
|
+
return { kind: 'temp', name: t };
|
|
595
|
+
}
|
|
596
|
+
case 'unary': {
|
|
597
|
+
const operand = lowerExpr(expr.operand, ctx, scope);
|
|
598
|
+
const t = ctx.freshTemp();
|
|
599
|
+
if (expr.op === '-') {
|
|
600
|
+
ctx.emit({ kind: 'neg', dst: t, src: operand });
|
|
601
|
+
}
|
|
602
|
+
else if (expr.op === '!') {
|
|
603
|
+
ctx.emit({ kind: 'not', dst: t, src: operand });
|
|
604
|
+
}
|
|
605
|
+
return { kind: 'temp', name: t };
|
|
606
|
+
}
|
|
607
|
+
case 'assign': {
|
|
608
|
+
const val = lowerExpr(expr.value, ctx, scope);
|
|
609
|
+
// Reuse the existing temp for this variable so that updates inside
|
|
610
|
+
// if/while bodies are visible to outer code (we target mutable
|
|
611
|
+
// scoreboard slots, not true SSA registers).
|
|
612
|
+
const existing = scope.get(expr.target);
|
|
613
|
+
const t = existing ?? ctx.freshTemp();
|
|
614
|
+
ctx.emit({ kind: 'copy', dst: t, src: val });
|
|
615
|
+
scope.set(expr.target, t);
|
|
616
|
+
return val;
|
|
617
|
+
}
|
|
618
|
+
case 'member_assign': {
|
|
619
|
+
const val = lowerExpr(expr.value, ctx, scope);
|
|
620
|
+
// Struct field assignment: v.x = val → copy val to v's x temp
|
|
621
|
+
if (expr.obj.kind === 'ident') {
|
|
622
|
+
const sv = ctx.structVars.get(expr.obj.name);
|
|
623
|
+
if (sv) {
|
|
624
|
+
const fieldTemp = sv.fields.get(expr.field);
|
|
625
|
+
if (fieldTemp) {
|
|
626
|
+
ctx.emit({ kind: 'copy', dst: fieldTemp, src: val });
|
|
627
|
+
return val;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
return val;
|
|
632
|
+
}
|
|
633
|
+
case 'member': {
|
|
634
|
+
// Struct field access: v.x → return v's x temp
|
|
635
|
+
if (expr.obj.kind === 'ident') {
|
|
636
|
+
const sv = ctx.structVars.get(expr.obj.name);
|
|
637
|
+
if (sv) {
|
|
638
|
+
const fieldTemp = sv.fields.get(expr.field);
|
|
639
|
+
if (fieldTemp)
|
|
640
|
+
return { kind: 'temp', name: fieldTemp };
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
// Fallback: opaque
|
|
644
|
+
const obj = lowerExpr(expr.obj, ctx, scope);
|
|
645
|
+
const t = ctx.freshTemp();
|
|
646
|
+
ctx.emit({ kind: 'copy', dst: t, src: obj });
|
|
647
|
+
return { kind: 'temp', name: t };
|
|
648
|
+
}
|
|
649
|
+
case 'index': {
|
|
650
|
+
const obj = lowerExpr(expr.obj, ctx, scope);
|
|
651
|
+
const idx = lowerExpr(expr.index, ctx, scope);
|
|
652
|
+
const t = ctx.freshTemp();
|
|
653
|
+
ctx.emit({ kind: 'copy', dst: t, src: obj });
|
|
654
|
+
return { kind: 'temp', name: t };
|
|
655
|
+
}
|
|
656
|
+
case 'call': {
|
|
657
|
+
// Handle builtin calls → raw MC commands
|
|
658
|
+
if (macro_1.BUILTIN_SET.has(expr.fn)) {
|
|
659
|
+
const cmd = formatBuiltinCall(expr.fn, expr.args, ctx.currentMacroParams);
|
|
660
|
+
ctx.emit({ kind: 'call', dst: null, fn: `__raw:${cmd}`, args: [] });
|
|
661
|
+
const t = ctx.freshTemp();
|
|
662
|
+
ctx.emit({ kind: 'const', dst: t, value: 0 });
|
|
663
|
+
return { kind: 'temp', name: t };
|
|
664
|
+
}
|
|
665
|
+
// Check for struct instance method call: parser desugars v.method() → call('method', [v, ...])
|
|
666
|
+
if (expr.args.length > 0 && expr.args[0].kind === 'ident') {
|
|
667
|
+
const sv = ctx.structVars.get(expr.args[0].name);
|
|
668
|
+
if (sv) {
|
|
669
|
+
const methodInfo = ctx.implMethods.get(sv.typeName)?.get(expr.fn);
|
|
670
|
+
if (methodInfo?.hasSelf) {
|
|
671
|
+
// Build args: self fields first, then remaining explicit args
|
|
672
|
+
const fields = ctx.structDefs.get(sv.typeName) ?? [];
|
|
673
|
+
const selfArgs = fields.map(f => {
|
|
674
|
+
const temp = sv.fields.get(f);
|
|
675
|
+
return temp ? { kind: 'temp', name: temp } : { kind: 'const', value: 0 };
|
|
676
|
+
});
|
|
677
|
+
const explicitArgs = expr.args.slice(1).map(a => lowerExpr(a, ctx, scope));
|
|
678
|
+
const allArgs = [...selfArgs, ...explicitArgs];
|
|
679
|
+
const t = ctx.freshTemp();
|
|
680
|
+
ctx.emit({ kind: 'call', dst: t, fn: `${sv.typeName}::${expr.fn}`, args: allArgs });
|
|
681
|
+
return { kind: 'temp', name: t };
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
// Check if calling a macro function → emit call_macro
|
|
686
|
+
const targetMacro = ctx.macroInfo.get(expr.fn);
|
|
687
|
+
if (targetMacro) {
|
|
688
|
+
const args = expr.args.map(a => lowerExpr(a, ctx, scope));
|
|
689
|
+
const targetParams = ctx.fnParamInfo.get(expr.fn) ?? [];
|
|
690
|
+
const macroArgs = [];
|
|
691
|
+
for (let i = 0; i < targetParams.length && i < args.length; i++) {
|
|
692
|
+
const paramName = targetParams[i].name;
|
|
693
|
+
if (targetMacro.macroParams.has(paramName)) {
|
|
694
|
+
const paramTypeName = targetMacro.paramTypes.get(paramName) ?? 'int';
|
|
695
|
+
const isFloat = paramTypeName === 'float';
|
|
696
|
+
macroArgs.push({
|
|
697
|
+
name: paramName,
|
|
698
|
+
value: args[i],
|
|
699
|
+
type: isFloat ? 'double' : 'int',
|
|
700
|
+
scale: isFloat ? 0.01 : 1,
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
const t = ctx.freshTemp();
|
|
705
|
+
ctx.emit({ kind: 'call_macro', dst: t, fn: expr.fn, args: macroArgs });
|
|
706
|
+
return { kind: 'temp', name: t };
|
|
707
|
+
}
|
|
708
|
+
const args = expr.args.map(a => lowerExpr(a, ctx, scope));
|
|
709
|
+
const t = ctx.freshTemp();
|
|
710
|
+
ctx.emit({ kind: 'call', dst: t, fn: expr.fn, args });
|
|
711
|
+
return { kind: 'temp', name: t };
|
|
712
|
+
}
|
|
713
|
+
case 'invoke': {
|
|
714
|
+
// Check for struct method call: v.method(args)
|
|
715
|
+
if (expr.callee.kind === 'member' && expr.callee.obj.kind === 'ident') {
|
|
716
|
+
const sv = ctx.structVars.get(expr.callee.obj.name);
|
|
717
|
+
if (sv) {
|
|
718
|
+
const methodInfo = ctx.implMethods.get(sv.typeName)?.get(expr.callee.field);
|
|
719
|
+
if (methodInfo?.hasSelf) {
|
|
720
|
+
// Build args: self fields first, then explicit args
|
|
721
|
+
const fields = ctx.structDefs.get(sv.typeName) ?? [];
|
|
722
|
+
const selfArgs = fields.map(f => {
|
|
723
|
+
const temp = sv.fields.get(f);
|
|
724
|
+
return temp ? { kind: 'temp', name: temp } : { kind: 'const', value: 0 };
|
|
725
|
+
});
|
|
726
|
+
const explicitArgs = expr.args.map(a => lowerExpr(a, ctx, scope));
|
|
727
|
+
const allArgs = [...selfArgs, ...explicitArgs];
|
|
728
|
+
const t = ctx.freshTemp();
|
|
729
|
+
ctx.emit({ kind: 'call', dst: t, fn: `${sv.typeName}::${expr.callee.field}`, args: allArgs });
|
|
730
|
+
return { kind: 'temp', name: t };
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
// Fallback: generic invoke
|
|
735
|
+
const calleeOp = lowerExpr(expr.callee, ctx, scope);
|
|
736
|
+
const args = expr.args.map(a => lowerExpr(a, ctx, scope));
|
|
737
|
+
const t = ctx.freshTemp();
|
|
738
|
+
ctx.emit({ kind: 'call', dst: t, fn: '__invoke', args: [calleeOp, ...args] });
|
|
739
|
+
return { kind: 'temp', name: t };
|
|
740
|
+
}
|
|
741
|
+
case 'static_call': {
|
|
742
|
+
const args = expr.args.map(a => lowerExpr(a, ctx, scope));
|
|
743
|
+
const t = ctx.freshTemp();
|
|
744
|
+
ctx.emit({ kind: 'call', dst: t, fn: `${expr.type}::${expr.method}`, args });
|
|
745
|
+
return { kind: 'temp', name: t };
|
|
746
|
+
}
|
|
747
|
+
default: {
|
|
748
|
+
const _exhaustive = expr;
|
|
749
|
+
throw new Error(`Unknown HIR expression kind: ${_exhaustive.kind}`);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
// ---------------------------------------------------------------------------
|
|
754
|
+
// Short-circuit lowering
|
|
755
|
+
// ---------------------------------------------------------------------------
|
|
756
|
+
function lowerShortCircuitAnd(expr, ctx, scope) {
|
|
757
|
+
// a && b → if(a) { b } else { 0 }
|
|
758
|
+
const left = lowerExpr(expr.left, ctx, scope);
|
|
759
|
+
const result = ctx.freshTemp();
|
|
760
|
+
const evalRight = ctx.newBlock('and_right');
|
|
761
|
+
const merge = ctx.newBlock('and_merge');
|
|
762
|
+
const falseBlock = ctx.newBlock('and_false');
|
|
763
|
+
ctx.terminate({ kind: 'branch', cond: left, then: evalRight.id, else: falseBlock.id });
|
|
764
|
+
ctx.switchTo(evalRight);
|
|
765
|
+
const right = lowerExpr(expr.right, ctx, scope);
|
|
766
|
+
ctx.emit({ kind: 'copy', dst: result, src: right });
|
|
767
|
+
ctx.terminate({ kind: 'jump', target: merge.id });
|
|
768
|
+
ctx.switchTo(falseBlock);
|
|
769
|
+
ctx.emit({ kind: 'const', dst: result, value: 0 });
|
|
770
|
+
ctx.terminate({ kind: 'jump', target: merge.id });
|
|
771
|
+
ctx.switchTo(merge);
|
|
772
|
+
return { kind: 'temp', name: result };
|
|
773
|
+
}
|
|
774
|
+
function lowerShortCircuitOr(expr, ctx, scope) {
|
|
775
|
+
// a || b → if(a) { 1 } else { b }
|
|
776
|
+
const left = lowerExpr(expr.left, ctx, scope);
|
|
777
|
+
const result = ctx.freshTemp();
|
|
778
|
+
const trueBlock = ctx.newBlock('or_true');
|
|
779
|
+
const evalRight = ctx.newBlock('or_right');
|
|
780
|
+
const merge = ctx.newBlock('or_merge');
|
|
781
|
+
ctx.terminate({ kind: 'branch', cond: left, then: trueBlock.id, else: evalRight.id });
|
|
782
|
+
ctx.switchTo(trueBlock);
|
|
783
|
+
ctx.emit({ kind: 'const', dst: result, value: 1 });
|
|
784
|
+
ctx.terminate({ kind: 'jump', target: merge.id });
|
|
785
|
+
ctx.switchTo(evalRight);
|
|
786
|
+
const right = lowerExpr(expr.right, ctx, scope);
|
|
787
|
+
ctx.emit({ kind: 'copy', dst: result, src: right });
|
|
788
|
+
ctx.terminate({ kind: 'jump', target: merge.id });
|
|
789
|
+
ctx.switchTo(merge);
|
|
790
|
+
return { kind: 'temp', name: result };
|
|
791
|
+
}
|
|
792
|
+
// ---------------------------------------------------------------------------
|
|
793
|
+
// Execute subcommand lowering
|
|
794
|
+
// ---------------------------------------------------------------------------
|
|
795
|
+
function lowerExecuteSubcmd(sub) {
|
|
796
|
+
switch (sub.kind) {
|
|
797
|
+
case 'as':
|
|
798
|
+
return { kind: 'as', selector: selectorToString(sub.selector) };
|
|
799
|
+
case 'at':
|
|
800
|
+
return { kind: 'at', selector: selectorToString(sub.selector) };
|
|
801
|
+
case 'positioned':
|
|
802
|
+
return { kind: 'positioned', x: sub.x, y: sub.y, z: sub.z };
|
|
803
|
+
case 'rotated':
|
|
804
|
+
return { kind: 'rotated', yaw: sub.yaw, pitch: sub.pitch };
|
|
805
|
+
case 'in':
|
|
806
|
+
return { kind: 'in', dimension: sub.dimension };
|
|
807
|
+
case 'anchored':
|
|
808
|
+
return { kind: 'anchored', anchor: sub.anchor };
|
|
809
|
+
case 'positioned_as':
|
|
810
|
+
return { kind: 'at', selector: selectorToString(sub.selector) };
|
|
811
|
+
case 'rotated_as':
|
|
812
|
+
return { kind: 'rotated', yaw: '0', pitch: '0' };
|
|
813
|
+
case 'facing':
|
|
814
|
+
return { kind: 'positioned', x: sub.x, y: sub.y, z: sub.z };
|
|
815
|
+
case 'facing_entity':
|
|
816
|
+
return { kind: 'at', selector: selectorToString(sub.selector) };
|
|
817
|
+
case 'align':
|
|
818
|
+
return { kind: 'positioned', x: '0', y: '0', z: '0' };
|
|
819
|
+
case 'on':
|
|
820
|
+
return { kind: 'at_self' };
|
|
821
|
+
case 'summon':
|
|
822
|
+
return { kind: 'at_self' };
|
|
823
|
+
case 'if_entity':
|
|
824
|
+
case 'unless_entity':
|
|
825
|
+
case 'if_block':
|
|
826
|
+
case 'unless_block':
|
|
827
|
+
case 'if_score':
|
|
828
|
+
case 'unless_score':
|
|
829
|
+
case 'if_score_range':
|
|
830
|
+
case 'unless_score_range':
|
|
831
|
+
case 'store_result':
|
|
832
|
+
case 'store_success':
|
|
833
|
+
// These are condition subcommands — pass through as-is for now
|
|
834
|
+
return { kind: 'at_self' };
|
|
835
|
+
default: {
|
|
836
|
+
const _exhaustive = sub;
|
|
837
|
+
throw new Error(`Unknown execute subcommand kind: ${_exhaustive.kind}`);
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
function selectorToString(sel) {
|
|
842
|
+
// EntitySelector has kind like '@a', '@e', '@s', etc.
|
|
843
|
+
// Filters are key=value pairs that become [key=value,key=value]
|
|
844
|
+
if (!sel.filters || Object.keys(sel.filters).length === 0) {
|
|
845
|
+
return sel.kind;
|
|
846
|
+
}
|
|
847
|
+
const parts = [];
|
|
848
|
+
for (const [key, value] of Object.entries(sel.filters)) {
|
|
849
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
850
|
+
// Range filter: { min, max } → key=min..max
|
|
851
|
+
const rangeObj = value;
|
|
852
|
+
if (rangeObj.min !== undefined && rangeObj.max !== undefined) {
|
|
853
|
+
parts.push(`${key}=${rangeObj.min}..${rangeObj.max}`);
|
|
854
|
+
}
|
|
855
|
+
else if (rangeObj.max !== undefined) {
|
|
856
|
+
parts.push(`${key}=..${rangeObj.max}`);
|
|
857
|
+
}
|
|
858
|
+
else if (rangeObj.min !== undefined) {
|
|
859
|
+
parts.push(`${key}=${rangeObj.min}..`);
|
|
860
|
+
}
|
|
861
|
+
}
|
|
862
|
+
else {
|
|
863
|
+
parts.push(`${key}=${value}`);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
return `${sel.kind}[${parts.join(',')}]`;
|
|
867
|
+
}
|
|
868
|
+
// ---------------------------------------------------------------------------
|
|
869
|
+
// Builtin call formatting → raw MC command strings
|
|
870
|
+
// ---------------------------------------------------------------------------
|
|
871
|
+
const MACRO_SENTINEL = '\x01';
|
|
872
|
+
/**
|
|
873
|
+
* Format a builtin call as a raw MC command string.
|
|
874
|
+
* If any argument uses a macro param, the command is prefixed with \x01
|
|
875
|
+
* (converted to $ in LIR emission).
|
|
876
|
+
*/
|
|
877
|
+
function formatBuiltinCall(fn, args, macroParams) {
|
|
878
|
+
const fmtArgs = args.map(a => exprToCommandArg(a, macroParams));
|
|
879
|
+
const strs = fmtArgs.map(a => a.str);
|
|
880
|
+
const hasMacro = fmtArgs.some(a => a.isMacro);
|
|
881
|
+
let cmd;
|
|
882
|
+
switch (fn) {
|
|
883
|
+
case 'summon': {
|
|
884
|
+
const [type, x, y, z, nbt] = strs;
|
|
885
|
+
const pos = [x ?? '~', y ?? '~', z ?? '~'].join(' ');
|
|
886
|
+
cmd = nbt ? `summon ${type} ${pos} ${nbt}` : `summon ${type} ${pos}`;
|
|
887
|
+
break;
|
|
888
|
+
}
|
|
889
|
+
case 'particle': {
|
|
890
|
+
const [name, x, y, z, ...rest] = strs;
|
|
891
|
+
const pos = [x ?? '~', y ?? '~', z ?? '~'].join(' ');
|
|
892
|
+
const extra = rest.filter(v => v !== undefined);
|
|
893
|
+
cmd = extra.length > 0 ? `particle ${name} ${pos} ${extra.join(' ')}` : `particle ${name} ${pos}`;
|
|
894
|
+
break;
|
|
895
|
+
}
|
|
896
|
+
case 'setblock': {
|
|
897
|
+
const [x, y, z, block] = strs;
|
|
898
|
+
cmd = `setblock ${x} ${y} ${z} ${block}`;
|
|
899
|
+
break;
|
|
900
|
+
}
|
|
901
|
+
case 'fill': {
|
|
902
|
+
const [x1, y1, z1, x2, y2, z2, block] = strs;
|
|
903
|
+
cmd = `fill ${x1} ${y1} ${z1} ${x2} ${y2} ${z2} ${block}`;
|
|
904
|
+
break;
|
|
905
|
+
}
|
|
906
|
+
case 'say':
|
|
907
|
+
cmd = `say ${strs[0] ?? ''}`;
|
|
908
|
+
break;
|
|
909
|
+
case 'tell':
|
|
910
|
+
case 'tellraw':
|
|
911
|
+
cmd = `tellraw ${strs[0]} {"text":"${strs[1]}"}`;
|
|
912
|
+
break;
|
|
913
|
+
case 'title':
|
|
914
|
+
cmd = `title ${strs[0]} title {"text":"${strs[1]}"}`;
|
|
915
|
+
break;
|
|
916
|
+
case 'actionbar':
|
|
917
|
+
cmd = `title ${strs[0]} actionbar {"text":"${strs[1]}"}`;
|
|
918
|
+
break;
|
|
919
|
+
case 'subtitle':
|
|
920
|
+
cmd = `title ${strs[0]} subtitle {"text":"${strs[1]}"}`;
|
|
921
|
+
break;
|
|
922
|
+
case 'title_times':
|
|
923
|
+
cmd = `title ${strs[0]} times ${strs[1]} ${strs[2]} ${strs[3]}`;
|
|
924
|
+
break;
|
|
925
|
+
case 'announce':
|
|
926
|
+
cmd = `tellraw @a {"text":"${strs[0]}"}`;
|
|
927
|
+
break;
|
|
928
|
+
case 'give': {
|
|
929
|
+
const nbt = strs[3] ? strs[3] : '';
|
|
930
|
+
cmd = `give ${strs[0]} ${strs[1]}${nbt} ${strs[2] ?? '1'}`;
|
|
931
|
+
break;
|
|
932
|
+
}
|
|
933
|
+
case 'kill':
|
|
934
|
+
cmd = `kill ${strs[0] ?? '@s'}`;
|
|
935
|
+
break;
|
|
936
|
+
case 'effect':
|
|
937
|
+
cmd = `effect give ${strs[0]} ${strs[1]} ${strs[2] ?? '30'} ${strs[3] ?? '0'}`;
|
|
938
|
+
break;
|
|
939
|
+
case 'effect_clear':
|
|
940
|
+
cmd = strs[1] ? `effect clear ${strs[0]} ${strs[1]}` : `effect clear ${strs[0]}`;
|
|
941
|
+
break;
|
|
942
|
+
case 'playsound':
|
|
943
|
+
cmd = ['playsound', ...strs].filter(Boolean).join(' ');
|
|
944
|
+
break;
|
|
945
|
+
case 'clear':
|
|
946
|
+
cmd = `clear ${strs[0]} ${strs[1] ?? ''}`.trim();
|
|
947
|
+
break;
|
|
948
|
+
case 'weather':
|
|
949
|
+
cmd = `weather ${strs[0]}`;
|
|
950
|
+
break;
|
|
951
|
+
case 'time_set':
|
|
952
|
+
cmd = `time set ${strs[0]}`;
|
|
953
|
+
break;
|
|
954
|
+
case 'time_add':
|
|
955
|
+
cmd = `time add ${strs[0]}`;
|
|
956
|
+
break;
|
|
957
|
+
case 'gamerule':
|
|
958
|
+
cmd = `gamerule ${strs[0]} ${strs[1]}`;
|
|
959
|
+
break;
|
|
960
|
+
case 'tag_add':
|
|
961
|
+
cmd = `tag ${strs[0]} add ${strs[1]}`;
|
|
962
|
+
break;
|
|
963
|
+
case 'tag_remove':
|
|
964
|
+
cmd = `tag ${strs[0]} remove ${strs[1]}`;
|
|
965
|
+
break;
|
|
966
|
+
case 'kick':
|
|
967
|
+
cmd = `kick ${strs[0]} ${strs[1] ?? ''}`.trim();
|
|
968
|
+
break;
|
|
969
|
+
case 'clone':
|
|
970
|
+
cmd = `clone ${strs.join(' ')}`;
|
|
971
|
+
break;
|
|
972
|
+
case 'difficulty':
|
|
973
|
+
cmd = `difficulty ${strs[0]}`;
|
|
974
|
+
break;
|
|
975
|
+
case 'xp_add':
|
|
976
|
+
cmd = `xp add ${strs[0]} ${strs[1]} ${strs[2] ?? 'points'}`;
|
|
977
|
+
break;
|
|
978
|
+
case 'xp_set':
|
|
979
|
+
cmd = `xp set ${strs[0]} ${strs[1]} ${strs[2] ?? 'points'}`;
|
|
980
|
+
break;
|
|
981
|
+
default: cmd = `${fn} ${strs.join(' ')}`;
|
|
982
|
+
}
|
|
983
|
+
return hasMacro ? `${MACRO_SENTINEL}${cmd}` : cmd;
|
|
984
|
+
}
|
|
985
|
+
/** Convert an HIR expression to its MC command string representation */
|
|
986
|
+
function exprToCommandArg(expr, macroParams) {
|
|
987
|
+
switch (expr.kind) {
|
|
988
|
+
case 'int_lit': return { str: String(expr.value), isMacro: false };
|
|
989
|
+
case 'float_lit': return { str: String(expr.value), isMacro: false };
|
|
990
|
+
case 'byte_lit': return { str: String(expr.value), isMacro: false };
|
|
991
|
+
case 'short_lit': return { str: String(expr.value), isMacro: false };
|
|
992
|
+
case 'long_lit': return { str: String(expr.value), isMacro: false };
|
|
993
|
+
case 'double_lit': return { str: String(expr.value), isMacro: false };
|
|
994
|
+
case 'bool_lit': return { str: expr.value ? 'true' : 'false', isMacro: false };
|
|
995
|
+
case 'str_lit': return { str: expr.value, isMacro: false };
|
|
996
|
+
case 'mc_name': return { str: expr.value, isMacro: false };
|
|
997
|
+
case 'selector': return { str: expr.raw, isMacro: false };
|
|
998
|
+
case 'ident':
|
|
999
|
+
if (macroParams.has(expr.name))
|
|
1000
|
+
return { str: `$(${expr.name})`, isMacro: true };
|
|
1001
|
+
return { str: expr.name, isMacro: false };
|
|
1002
|
+
case 'local_coord': {
|
|
1003
|
+
const prefix = expr.value[0]; // ^
|
|
1004
|
+
const rest = expr.value.slice(1);
|
|
1005
|
+
if (rest && /^[a-zA-Z_]\w*$/.test(rest) && macroParams.has(rest)) {
|
|
1006
|
+
return { str: `${prefix}$(${rest})`, isMacro: true };
|
|
1007
|
+
}
|
|
1008
|
+
return { str: expr.value, isMacro: false };
|
|
1009
|
+
}
|
|
1010
|
+
case 'rel_coord': {
|
|
1011
|
+
const prefix = expr.value[0]; // ~
|
|
1012
|
+
const rest = expr.value.slice(1);
|
|
1013
|
+
if (rest && /^[a-zA-Z_]\w*$/.test(rest) && macroParams.has(rest)) {
|
|
1014
|
+
return { str: `${prefix}$(${rest})`, isMacro: true };
|
|
1015
|
+
}
|
|
1016
|
+
return { str: expr.value, isMacro: false };
|
|
1017
|
+
}
|
|
1018
|
+
case 'unary':
|
|
1019
|
+
if (expr.op === '-' && expr.operand.kind === 'float_lit') {
|
|
1020
|
+
return { str: String(-expr.operand.value), isMacro: false };
|
|
1021
|
+
}
|
|
1022
|
+
if (expr.op === '-' && expr.operand.kind === 'int_lit') {
|
|
1023
|
+
return { str: String(-expr.operand.value), isMacro: false };
|
|
1024
|
+
}
|
|
1025
|
+
return { str: '~', isMacro: false };
|
|
1026
|
+
default:
|
|
1027
|
+
return { str: '~', isMacro: false };
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
//# sourceMappingURL=lower.js.map
|