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,3405 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* RedScript Lowering
|
|
4
|
+
*
|
|
5
|
+
* Transforms AST into IR (Three-Address Code).
|
|
6
|
+
* Handles control flow, function extraction for foreach, and builtin calls.
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.Lowering = exports.LOWERING_OBJ = void 0;
|
|
43
|
+
exports.setScoreboardObjective = setScoreboardObjective;
|
|
44
|
+
const builder_1 = require("../ir/builder");
|
|
45
|
+
const diagnostics_1 = require("../diagnostics");
|
|
46
|
+
const path = __importStar(require("path"));
|
|
47
|
+
const types_1 = require("../events/types");
|
|
48
|
+
const entity_hierarchy_1 = require("../types/entity-hierarchy");
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// Macro-aware builtins (MC 1.20.2+)
|
|
51
|
+
// These builtins generate commands where parameter variables cannot appear
|
|
52
|
+
// as literal values (coordinates, entity types, block types), so they
|
|
53
|
+
// require MC macro syntax when called with runtime variables.
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
// All builtins support macro parameters - any arg that's a function param
|
|
56
|
+
// will automatically use MC 1.20.2+ macro syntax when needed
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Scoreboard Objective
|
|
59
|
+
// Set per-compilation via setScoreboardObjective() — defaults to 'rs'.
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
/** Current scoreboard objective. Set once per compile() call. */
|
|
62
|
+
exports.LOWERING_OBJ = 'rs';
|
|
63
|
+
function setScoreboardObjective(obj) { exports.LOWERING_OBJ = obj; }
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// Builtin Functions
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
const BUILTINS = {
|
|
68
|
+
say: ([msg]) => `say ${msg}`,
|
|
69
|
+
tell: ([sel, msg]) => `tellraw ${sel} {"text":"${msg}"}`,
|
|
70
|
+
tellraw: ([sel, msg]) => `tellraw ${sel} {"text":"${msg}"}`,
|
|
71
|
+
title: ([sel, msg]) => `title ${sel} title {"text":"${msg}"}`,
|
|
72
|
+
actionbar: ([sel, msg]) => `title ${sel} actionbar {"text":"${msg}"}`,
|
|
73
|
+
subtitle: ([sel, msg]) => `title ${sel} subtitle {"text":"${msg}"}`,
|
|
74
|
+
title_times: ([sel, fadeIn, stay, fadeOut]) => `title ${sel} times ${fadeIn} ${stay} ${fadeOut}`,
|
|
75
|
+
announce: ([msg]) => `tellraw @a {"text":"${msg}"}`,
|
|
76
|
+
give: ([sel, item, count, nbt]) => nbt ? `give ${sel} ${item}${nbt} ${count ?? '1'}` : `give ${sel} ${item} ${count ?? '1'}`,
|
|
77
|
+
kill: ([sel]) => `kill ${sel ?? '@s'}`,
|
|
78
|
+
effect: ([sel, eff, dur, amp]) => `effect give ${sel} ${eff} ${dur ?? '30'} ${amp ?? '0'}`,
|
|
79
|
+
effect_clear: ([sel, eff]) => eff ? `effect clear ${sel} ${eff}` : `effect clear ${sel}`,
|
|
80
|
+
summon: ([type, x, y, z, nbt]) => {
|
|
81
|
+
const pos = [x ?? '~', y ?? '~', z ?? '~'].join(' ');
|
|
82
|
+
return nbt ? `summon ${type} ${pos} ${nbt}` : `summon ${type} ${pos}`;
|
|
83
|
+
},
|
|
84
|
+
particle: ([name, x, y, z, dx, dy, dz, speed, count]) => {
|
|
85
|
+
const pos = [x ?? '~', y ?? '~', z ?? '~'].join(' ');
|
|
86
|
+
// dx/dy/dz/speed/count are optional; omit trailing undefineds
|
|
87
|
+
const extra = [dx, dy, dz, speed, count].filter(v => v !== undefined && v !== null);
|
|
88
|
+
return extra.length > 0
|
|
89
|
+
? `particle ${name} ${pos} ${extra.join(' ')}`
|
|
90
|
+
: `particle ${name} ${pos}`;
|
|
91
|
+
},
|
|
92
|
+
playsound: ([sound, source, sel, x, y, z, volume, pitch, minVolume]) => ['playsound', sound, source, sel, x, y, z, volume, pitch, minVolume].filter(Boolean).join(' '),
|
|
93
|
+
tp: () => null, // Special handling
|
|
94
|
+
tp_to: () => null, // Special handling (deprecated alias)
|
|
95
|
+
clear: ([sel, item]) => `clear ${sel} ${item ?? ''}`.trim(),
|
|
96
|
+
weather: ([type]) => `weather ${type}`,
|
|
97
|
+
time_set: ([val]) => `time set ${val}`,
|
|
98
|
+
time_add: ([val]) => `time add ${val}`,
|
|
99
|
+
gamerule: ([rule, val]) => `gamerule ${rule} ${val}`,
|
|
100
|
+
tag_add: ([sel, tag]) => `tag ${sel} add ${tag}`,
|
|
101
|
+
tag_remove: ([sel, tag]) => `tag ${sel} remove ${tag}`,
|
|
102
|
+
kick: ([player, reason]) => `kick ${player} ${reason ?? ''}`.trim(),
|
|
103
|
+
setblock: ([x, y, z, block]) => `setblock ${x} ${y} ${z} ${block}`,
|
|
104
|
+
fill: ([x1, y1, z1, x2, y2, z2, block]) => `fill ${x1} ${y1} ${z1} ${x2} ${y2} ${z2} ${block}`,
|
|
105
|
+
clone: ([x1, y1, z1, x2, y2, z2, dx, dy, dz]) => `clone ${x1} ${y1} ${z1} ${x2} ${y2} ${z2} ${dx} ${dy} ${dz}`,
|
|
106
|
+
difficulty: ([level]) => `difficulty ${level}`,
|
|
107
|
+
xp_add: ([sel, amount, type]) => `xp add ${sel} ${amount} ${type ?? 'points'}`,
|
|
108
|
+
xp_set: ([sel, amount, type]) => `xp set ${sel} ${amount} ${type ?? 'points'}`,
|
|
109
|
+
random: () => null, // Special handling
|
|
110
|
+
random_native: () => null, // Special handling
|
|
111
|
+
random_sequence: () => null, // Special handling
|
|
112
|
+
scoreboard_get: () => null, // Special handling (returns value)
|
|
113
|
+
scoreboard_set: () => null, // Special handling
|
|
114
|
+
score: () => null, // Special handling (same as scoreboard_get)
|
|
115
|
+
scoreboard_display: () => null, // Special handling
|
|
116
|
+
scoreboard_hide: () => null, // Special handling
|
|
117
|
+
scoreboard_add_objective: () => null, // Special handling
|
|
118
|
+
scoreboard_remove_objective: () => null, // Special handling
|
|
119
|
+
bossbar_add: () => null, // Special handling
|
|
120
|
+
bossbar_set_value: () => null, // Special handling
|
|
121
|
+
bossbar_set_max: () => null, // Special handling
|
|
122
|
+
bossbar_set_color: () => null, // Special handling
|
|
123
|
+
bossbar_set_style: () => null, // Special handling
|
|
124
|
+
bossbar_set_visible: () => null, // Special handling
|
|
125
|
+
bossbar_set_players: () => null, // Special handling
|
|
126
|
+
bossbar_remove: () => null, // Special handling
|
|
127
|
+
bossbar_get_value: () => null, // Special handling
|
|
128
|
+
team_add: () => null, // Special handling
|
|
129
|
+
team_remove: () => null, // Special handling
|
|
130
|
+
team_join: () => null, // Special handling
|
|
131
|
+
team_leave: () => null, // Special handling
|
|
132
|
+
team_option: () => null, // Special handling
|
|
133
|
+
data_get: () => null, // Special handling (returns value from NBT)
|
|
134
|
+
data_merge: () => null, // Special handling (merge NBT)
|
|
135
|
+
set_new: () => null, // Special handling (returns set ID)
|
|
136
|
+
set_add: () => null, // Special handling
|
|
137
|
+
set_contains: () => null, // Special handling (returns 1/0)
|
|
138
|
+
set_remove: () => null, // Special handling
|
|
139
|
+
set_clear: () => null, // Special handling
|
|
140
|
+
setTimeout: () => null, // Special handling
|
|
141
|
+
setInterval: () => null, // Special handling
|
|
142
|
+
clearInterval: () => null, // Special handling
|
|
143
|
+
storage_get_int: () => null, // Special handling (dynamic NBT array read via macro)
|
|
144
|
+
storage_set_array: () => null, // Special handling (write literal NBT array to storage)
|
|
145
|
+
storage_set_int: () => null, // Special handling (dynamic NBT array write via macro)
|
|
146
|
+
};
|
|
147
|
+
function getSpan(node) {
|
|
148
|
+
return node?.span;
|
|
149
|
+
}
|
|
150
|
+
const NAMESPACED_ENTITY_TYPE_RE = /^[a-z0-9_.-]+:[a-z0-9_./-]+$/;
|
|
151
|
+
const BARE_ENTITY_TYPE_RE = /^[a-z0-9_./-]+$/;
|
|
152
|
+
const ENTITY_TO_MC_TYPE = {
|
|
153
|
+
Player: 'minecraft:player',
|
|
154
|
+
Zombie: 'minecraft:zombie',
|
|
155
|
+
Skeleton: 'minecraft:skeleton',
|
|
156
|
+
Creeper: 'minecraft:creeper',
|
|
157
|
+
Spider: 'minecraft:spider',
|
|
158
|
+
Enderman: 'minecraft:enderman',
|
|
159
|
+
Blaze: 'minecraft:blaze',
|
|
160
|
+
Witch: 'minecraft:witch',
|
|
161
|
+
Slime: 'minecraft:slime',
|
|
162
|
+
ZombieVillager: 'minecraft:zombie_villager',
|
|
163
|
+
Husk: 'minecraft:husk',
|
|
164
|
+
Drowned: 'minecraft:drowned',
|
|
165
|
+
Stray: 'minecraft:stray',
|
|
166
|
+
WitherSkeleton: 'minecraft:wither_skeleton',
|
|
167
|
+
CaveSpider: 'minecraft:cave_spider',
|
|
168
|
+
Pig: 'minecraft:pig',
|
|
169
|
+
Cow: 'minecraft:cow',
|
|
170
|
+
Sheep: 'minecraft:sheep',
|
|
171
|
+
Chicken: 'minecraft:chicken',
|
|
172
|
+
Villager: 'minecraft:villager',
|
|
173
|
+
WanderingTrader: 'minecraft:wandering_trader',
|
|
174
|
+
ArmorStand: 'minecraft:armor_stand',
|
|
175
|
+
Item: 'minecraft:item',
|
|
176
|
+
Arrow: 'minecraft:arrow',
|
|
177
|
+
};
|
|
178
|
+
function normalizeSelector(selector, warnings) {
|
|
179
|
+
return selector.replace(/type=([^,\]]+)/g, (match, entityType) => {
|
|
180
|
+
const trimmed = entityType.trim();
|
|
181
|
+
if (trimmed.includes(':')) {
|
|
182
|
+
if (!NAMESPACED_ENTITY_TYPE_RE.test(trimmed)) {
|
|
183
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', `Invalid entity type format: "${trimmed}" (must be namespace:name)`, { line: 1, col: 1 });
|
|
184
|
+
}
|
|
185
|
+
return match;
|
|
186
|
+
}
|
|
187
|
+
if (!BARE_ENTITY_TYPE_RE.test(trimmed)) {
|
|
188
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', `Invalid entity type format: "${trimmed}" (must be namespace:name or bare_name)`, { line: 1, col: 1 });
|
|
189
|
+
}
|
|
190
|
+
warnings.push({
|
|
191
|
+
message: `Unnamespaced entity type "${trimmed}", auto-qualifying to "minecraft:${trimmed}"`,
|
|
192
|
+
code: 'W_UNNAMESPACED_TYPE',
|
|
193
|
+
});
|
|
194
|
+
return `type=minecraft:${trimmed}`;
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
function emitCoord(component) {
|
|
198
|
+
switch (component.kind) {
|
|
199
|
+
case 'absolute':
|
|
200
|
+
return String(component.value);
|
|
201
|
+
case 'relative':
|
|
202
|
+
return component.offset === 0 ? '~' : `~${component.offset}`;
|
|
203
|
+
case 'local':
|
|
204
|
+
return component.offset === 0 ? '^' : `^${component.offset}`;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function emitBlockPos(pos) {
|
|
208
|
+
return `${emitCoord(pos.x)} ${emitCoord(pos.y)} ${emitCoord(pos.z)}`;
|
|
209
|
+
}
|
|
210
|
+
// ---------------------------------------------------------------------------
|
|
211
|
+
// Lowering Class
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
class Lowering {
|
|
214
|
+
/** Unique IR variable name for a local variable, scoped to the current function.
|
|
215
|
+
* Prevents cross-function scoreboard slot collisions: $fn_x ≠ $gn_x.
|
|
216
|
+
* Only applies to user-defined locals/params; internal slots ($p0, $ret) are
|
|
217
|
+
* intentionally global (calling convention). */
|
|
218
|
+
fnVar(name) {
|
|
219
|
+
return `$${this.currentFn}_${name}`;
|
|
220
|
+
}
|
|
221
|
+
currentEntityContext() {
|
|
222
|
+
return this.entityContextStack.length > 0
|
|
223
|
+
? this.entityContextStack[this.entityContextStack.length - 1]
|
|
224
|
+
: 'Entity';
|
|
225
|
+
}
|
|
226
|
+
constructor(namespace, sourceRanges = []) {
|
|
227
|
+
this.functions = [];
|
|
228
|
+
this.globals = [];
|
|
229
|
+
this.globalNames = new Map();
|
|
230
|
+
this.fnDecls = new Map();
|
|
231
|
+
this.implMethods = new Map();
|
|
232
|
+
this.specializedFunctions = new Map();
|
|
233
|
+
this.currentFn = '';
|
|
234
|
+
this.foreachCounter = 0;
|
|
235
|
+
this.lambdaCounter = 0;
|
|
236
|
+
this.timeoutCounter = 0;
|
|
237
|
+
this.intervalCounter = 0;
|
|
238
|
+
this.warnings = [];
|
|
239
|
+
// Entity type context stack for W_IMPOSSIBLE_AS warnings
|
|
240
|
+
this.entityContextStack = [];
|
|
241
|
+
this.varMap = new Map();
|
|
242
|
+
this.lambdaBindings = new Map();
|
|
243
|
+
this.intervalBindings = new Map();
|
|
244
|
+
this.intervalFunctions = new Map();
|
|
245
|
+
this.currentCallbackBindings = new Map();
|
|
246
|
+
this.currentContext = {};
|
|
247
|
+
this.blockPosVars = new Map();
|
|
248
|
+
// Struct definitions: name → { fieldName: TypeNode }
|
|
249
|
+
this.structDefs = new Map();
|
|
250
|
+
// Full struct declarations for field iteration
|
|
251
|
+
this.structDecls = new Map();
|
|
252
|
+
this.enumDefs = new Map();
|
|
253
|
+
this.functionDefaults = new Map();
|
|
254
|
+
this.constValues = new Map();
|
|
255
|
+
this.stringValues = new Map();
|
|
256
|
+
// Variable types: varName → TypeNode
|
|
257
|
+
this.varTypes = new Map();
|
|
258
|
+
// Float variables (stored as fixed-point × 1000)
|
|
259
|
+
this.floatVars = new Set();
|
|
260
|
+
// World object counter for unique tags
|
|
261
|
+
this.worldObjCounter = 0;
|
|
262
|
+
// Loop context stack for break/continue
|
|
263
|
+
this.loopStack = [];
|
|
264
|
+
// MC 1.20.2+ macro function support
|
|
265
|
+
// Names of params in the current function being lowered
|
|
266
|
+
this.currentFnParamNames = new Set();
|
|
267
|
+
// Params in the current function that need macro treatment (used in literal positions)
|
|
268
|
+
this.currentFnMacroParams = new Set();
|
|
269
|
+
// Global registry: fnName → macroParamNames (populated by pre-scan + lowering)
|
|
270
|
+
this.macroFunctionInfo = new Map();
|
|
271
|
+
this.namespace = namespace;
|
|
272
|
+
this.sourceRanges = sourceRanges;
|
|
273
|
+
LoweringBuilder.resetTempCounter();
|
|
274
|
+
}
|
|
275
|
+
// ---------------------------------------------------------------------------
|
|
276
|
+
// MC Macro pre-scan: identify which function params need macro treatment
|
|
277
|
+
// ---------------------------------------------------------------------------
|
|
278
|
+
preScanMacroFunctions(program) {
|
|
279
|
+
for (const fn of program.declarations) {
|
|
280
|
+
const paramNames = new Set(fn.params.map(p => p.name));
|
|
281
|
+
const macroParams = new Set();
|
|
282
|
+
this.preScanStmts(fn.body, paramNames, macroParams);
|
|
283
|
+
if (macroParams.size > 0) {
|
|
284
|
+
this.macroFunctionInfo.set(fn.name, [...macroParams]);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
for (const implBlock of program.implBlocks ?? []) {
|
|
288
|
+
for (const method of implBlock.methods) {
|
|
289
|
+
const paramNames = new Set(method.params.map(p => p.name));
|
|
290
|
+
const macroParams = new Set();
|
|
291
|
+
this.preScanStmts(method.body, paramNames, macroParams);
|
|
292
|
+
if (macroParams.size > 0) {
|
|
293
|
+
this.macroFunctionInfo.set(`${implBlock.typeName}_${method.name}`, [...macroParams]);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
preScanStmts(stmts, paramNames, macroParams) {
|
|
299
|
+
for (const stmt of stmts) {
|
|
300
|
+
this.preScanStmt(stmt, paramNames, macroParams);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
preScanStmt(stmt, paramNames, macroParams) {
|
|
304
|
+
switch (stmt.kind) {
|
|
305
|
+
case 'expr':
|
|
306
|
+
this.preScanExpr(stmt.expr, paramNames, macroParams);
|
|
307
|
+
break;
|
|
308
|
+
case 'let':
|
|
309
|
+
this.preScanExpr(stmt.init, paramNames, macroParams);
|
|
310
|
+
break;
|
|
311
|
+
case 'return':
|
|
312
|
+
if (stmt.value)
|
|
313
|
+
this.preScanExpr(stmt.value, paramNames, macroParams);
|
|
314
|
+
break;
|
|
315
|
+
case 'if':
|
|
316
|
+
this.preScanExpr(stmt.cond, paramNames, macroParams);
|
|
317
|
+
this.preScanStmts(stmt.then, paramNames, macroParams);
|
|
318
|
+
if (stmt.else_)
|
|
319
|
+
this.preScanStmts(stmt.else_, paramNames, macroParams);
|
|
320
|
+
break;
|
|
321
|
+
case 'while':
|
|
322
|
+
this.preScanExpr(stmt.cond, paramNames, macroParams);
|
|
323
|
+
this.preScanStmts(stmt.body, paramNames, macroParams);
|
|
324
|
+
break;
|
|
325
|
+
case 'for':
|
|
326
|
+
if (stmt.init)
|
|
327
|
+
this.preScanStmt(stmt.init, paramNames, macroParams);
|
|
328
|
+
this.preScanExpr(stmt.cond, paramNames, macroParams);
|
|
329
|
+
this.preScanStmts(stmt.body, paramNames, macroParams);
|
|
330
|
+
break;
|
|
331
|
+
case 'for_range':
|
|
332
|
+
this.preScanStmts(stmt.body, paramNames, macroParams);
|
|
333
|
+
break;
|
|
334
|
+
case 'foreach':
|
|
335
|
+
this.preScanStmts(stmt.body, paramNames, macroParams);
|
|
336
|
+
break;
|
|
337
|
+
case 'match':
|
|
338
|
+
this.preScanExpr(stmt.expr, paramNames, macroParams);
|
|
339
|
+
for (const arm of stmt.arms) {
|
|
340
|
+
this.preScanStmts(arm.body, paramNames, macroParams);
|
|
341
|
+
}
|
|
342
|
+
break;
|
|
343
|
+
case 'as_block':
|
|
344
|
+
case 'at_block':
|
|
345
|
+
this.preScanStmts(stmt.body, paramNames, macroParams);
|
|
346
|
+
break;
|
|
347
|
+
case 'execute':
|
|
348
|
+
this.preScanStmts(stmt.body, paramNames, macroParams);
|
|
349
|
+
break;
|
|
350
|
+
// raw, break, continue have no nested exprs of interest
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
preScanExpr(expr, paramNames, macroParams) {
|
|
354
|
+
if (expr.kind === 'call' && BUILTINS[expr.fn] !== undefined) {
|
|
355
|
+
// Only trigger macro param detection for builtins that actually emit
|
|
356
|
+
// MC commands with $(param) inline in the current function body.
|
|
357
|
+
// Special-handled builtins (storage_get_int / storage_set_int / etc.) are
|
|
358
|
+
// declared as `() => null` — they create their own sub-functions for macro
|
|
359
|
+
// indirection and do NOT require the surrounding function to be a macro.
|
|
360
|
+
const handler = BUILTINS[expr.fn];
|
|
361
|
+
const isSpecialHandled = (() => {
|
|
362
|
+
try {
|
|
363
|
+
return handler() === null;
|
|
364
|
+
}
|
|
365
|
+
catch {
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
})();
|
|
369
|
+
if (!isSpecialHandled) {
|
|
370
|
+
for (const arg of expr.args) {
|
|
371
|
+
if (arg.kind === 'ident' && paramNames.has(arg.name)) {
|
|
372
|
+
macroParams.add(arg.name);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
// Always recurse into args for nested calls/expressions
|
|
377
|
+
for (const arg of expr.args)
|
|
378
|
+
this.preScanExpr(arg, paramNames, macroParams);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
// Recurse into sub-expressions for other call types
|
|
382
|
+
if (expr.kind === 'call') {
|
|
383
|
+
for (const arg of expr.args)
|
|
384
|
+
this.preScanExpr(arg, paramNames, macroParams);
|
|
385
|
+
}
|
|
386
|
+
else if (expr.kind === 'binary') {
|
|
387
|
+
this.preScanExpr(expr.left, paramNames, macroParams);
|
|
388
|
+
this.preScanExpr(expr.right, paramNames, macroParams);
|
|
389
|
+
}
|
|
390
|
+
else if (expr.kind === 'unary') {
|
|
391
|
+
this.preScanExpr(expr.operand, paramNames, macroParams);
|
|
392
|
+
}
|
|
393
|
+
else if (expr.kind === 'assign') {
|
|
394
|
+
this.preScanExpr(expr.value, paramNames, macroParams);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
// ---------------------------------------------------------------------------
|
|
398
|
+
// Macro helpers
|
|
399
|
+
// ---------------------------------------------------------------------------
|
|
400
|
+
/**
|
|
401
|
+
* If `expr` is a function parameter that needs macro treatment (runtime value
|
|
402
|
+
* used in a literal position), returns the param name; otherwise null.
|
|
403
|
+
*/
|
|
404
|
+
tryGetMacroParam(expr) {
|
|
405
|
+
if (expr.kind !== 'ident')
|
|
406
|
+
return null;
|
|
407
|
+
if (!this.currentFnParamNames.has(expr.name))
|
|
408
|
+
return null;
|
|
409
|
+
if (this.constValues.has(expr.name))
|
|
410
|
+
return null;
|
|
411
|
+
if (this.stringValues.has(expr.name))
|
|
412
|
+
return null;
|
|
413
|
+
return expr.name;
|
|
414
|
+
}
|
|
415
|
+
tryGetMacroParamByName(name) {
|
|
416
|
+
if (!this.currentFnParamNames.has(name))
|
|
417
|
+
return null;
|
|
418
|
+
if (this.constValues.has(name))
|
|
419
|
+
return null;
|
|
420
|
+
if (this.stringValues.has(name))
|
|
421
|
+
return null;
|
|
422
|
+
return name;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Converts an expression to a string for use as a builtin arg.
|
|
426
|
+
* If the expression is a macro param, returns `$(name)` and sets macroParam.
|
|
427
|
+
*/
|
|
428
|
+
exprToBuiltinArg(expr) {
|
|
429
|
+
const macroParam = this.tryGetMacroParam(expr);
|
|
430
|
+
if (macroParam) {
|
|
431
|
+
return { str: `$(${macroParam})`, macroParam };
|
|
432
|
+
}
|
|
433
|
+
// Handle ~ident / ^ident syntax — relative/local coord with a VARIABLE offset.
|
|
434
|
+
//
|
|
435
|
+
// WHY macros are required here:
|
|
436
|
+
// Minecraft's ~N and ^N coordinate syntax requires N to be a compile-time
|
|
437
|
+
// literal number. There is no command that accepts a scoreboard value as a
|
|
438
|
+
// relative offset. Therefore `~height` (where height is a runtime int) can
|
|
439
|
+
// only be expressed at the MC level via the 1.20.2+ function macro system,
|
|
440
|
+
// which substitutes $(height) into the command text at call time.
|
|
441
|
+
//
|
|
442
|
+
// Contrast with absolute coords: `tp(target, x, y, z)` where x/y/z are
|
|
443
|
+
// plain ints — those become $(x) etc. as literal replacements, same mechanism,
|
|
444
|
+
// but the distinction matters to callers: ~$(height) means "relative by height
|
|
445
|
+
// blocks from current pos", not "teleport to absolute scoreboard value".
|
|
446
|
+
//
|
|
447
|
+
// Example:
|
|
448
|
+
// fn launch_up(target: selector, height: int) {
|
|
449
|
+
// tp(target, ~0, ~height, ~0); // "~height" parsed as rel_coord
|
|
450
|
+
// }
|
|
451
|
+
// Emits: $tp $(target) ~0 ~$(height) ~0
|
|
452
|
+
// Called: function ns:launch_up with storage rs:macro_args
|
|
453
|
+
if (expr.kind === 'rel_coord' || expr.kind === 'local_coord') {
|
|
454
|
+
const val = expr.value; // e.g. "~height" or "^depth"
|
|
455
|
+
const prefix = val[0]; // ~ or ^
|
|
456
|
+
const rest = val.slice(1);
|
|
457
|
+
// If rest is an identifier (not a number), treat as macro param
|
|
458
|
+
if (rest && /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(rest)) {
|
|
459
|
+
const paramName = this.tryGetMacroParamByName(rest);
|
|
460
|
+
if (paramName) {
|
|
461
|
+
return { str: `${prefix}$(${paramName})`, macroParam: paramName };
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
if (expr.kind === 'struct_lit' || expr.kind === 'array_lit') {
|
|
466
|
+
return { str: this.exprToSnbt(expr) };
|
|
467
|
+
}
|
|
468
|
+
// Float literals: preserve the float value for MC commands that accept floats
|
|
469
|
+
// (particle spread, playsound volume/pitch, etc.)
|
|
470
|
+
// We do NOT truncate here — that only applies to scoreboard/IR contexts.
|
|
471
|
+
if (expr.kind === 'float_lit') {
|
|
472
|
+
return { str: expr.value.toString() };
|
|
473
|
+
}
|
|
474
|
+
// Unary minus applied to a float literal (e.g. -0.5)
|
|
475
|
+
if (expr.kind === 'unary' && expr.op === '-' && expr.operand.kind === 'float_lit') {
|
|
476
|
+
return { str: (-expr.operand.value).toString() };
|
|
477
|
+
}
|
|
478
|
+
return { str: this.exprToString(expr) };
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Emits a call to a macro function, setting up both scoreboard params
|
|
482
|
+
* (for arithmetic use) and NBT macro args (for coordinate/literal use).
|
|
483
|
+
*/
|
|
484
|
+
emitMacroFunctionCall(fnName, args, macroParamNames, fnDecl) {
|
|
485
|
+
const params = fnDecl?.params ?? [];
|
|
486
|
+
const loweredArgs = args.map(arg => this.lowerExpr(arg));
|
|
487
|
+
// Set up regular scoreboard params (for arithmetic within the function)
|
|
488
|
+
for (let i = 0; i < loweredArgs.length; i++) {
|
|
489
|
+
const operand = loweredArgs[i];
|
|
490
|
+
if (operand.kind === 'const') {
|
|
491
|
+
this.builder.emitRaw(`scoreboard players set $p${i} ${exports.LOWERING_OBJ} ${operand.value}`);
|
|
492
|
+
}
|
|
493
|
+
else if (operand.kind === 'var') {
|
|
494
|
+
this.builder.emitRaw(`scoreboard players operation $p${i} ${exports.LOWERING_OBJ} = ${operand.name} ${exports.LOWERING_OBJ}`);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
// Set up NBT storage for each macro param
|
|
498
|
+
// float-typed params are stored as `double 0.01` so that an integer value N
|
|
499
|
+
// becomes N/100.0 in the command (e.g. scoreboard value 975 → NBT 9.75d → ^9.75)
|
|
500
|
+
for (const macroParam of macroParamNames) {
|
|
501
|
+
const paramIdx = params.findIndex(p => p.name === macroParam);
|
|
502
|
+
if (paramIdx < 0 || paramIdx >= loweredArgs.length)
|
|
503
|
+
continue;
|
|
504
|
+
const operand = loweredArgs[paramIdx];
|
|
505
|
+
const paramType = params[paramIdx]?.type;
|
|
506
|
+
const isFloat = paramType?.kind === 'named' && paramType.name === 'float';
|
|
507
|
+
if (operand.kind === 'const') {
|
|
508
|
+
if (isFloat) {
|
|
509
|
+
const floatVal = (operand.value / 100).toFixed(6);
|
|
510
|
+
this.builder.emitRaw(`data modify storage rs:macro_args ${macroParam} set value ${floatVal}d`);
|
|
511
|
+
}
|
|
512
|
+
else {
|
|
513
|
+
this.builder.emitRaw(`data modify storage rs:macro_args ${macroParam} set value ${operand.value}`);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
else if (operand.kind === 'var') {
|
|
517
|
+
if (isFloat) {
|
|
518
|
+
this.builder.emitRaw(`execute store result storage rs:macro_args ${macroParam} double 0.01 run scoreboard players get ${operand.name} ${exports.LOWERING_OBJ}`);
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
this.builder.emitRaw(`execute store result storage rs:macro_args ${macroParam} int 1 run scoreboard players get ${operand.name} ${exports.LOWERING_OBJ}`);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
// Call with macro storage
|
|
526
|
+
this.builder.emitRaw(`function ${this.namespace}:${fnName} with storage rs:macro_args`);
|
|
527
|
+
// Copy return value (callers may use it)
|
|
528
|
+
const dst = this.builder.freshTemp();
|
|
529
|
+
this.builder.emitRaw(`scoreboard players operation ${dst} ${exports.LOWERING_OBJ} = $ret ${exports.LOWERING_OBJ}`);
|
|
530
|
+
return { kind: 'var', name: dst };
|
|
531
|
+
}
|
|
532
|
+
lower(program) {
|
|
533
|
+
this.namespace = program.namespace;
|
|
534
|
+
// Pre-scan for macro functions before main lowering (so call sites can detect them)
|
|
535
|
+
this.preScanMacroFunctions(program);
|
|
536
|
+
// Load struct definitions
|
|
537
|
+
for (const struct of program.structs ?? []) {
|
|
538
|
+
const fields = new Map();
|
|
539
|
+
for (const field of struct.fields) {
|
|
540
|
+
fields.set(field.name, field.type);
|
|
541
|
+
}
|
|
542
|
+
this.structDefs.set(struct.name, fields);
|
|
543
|
+
this.structDecls.set(struct.name, struct);
|
|
544
|
+
}
|
|
545
|
+
for (const enumDecl of program.enums ?? []) {
|
|
546
|
+
const variants = new Map();
|
|
547
|
+
for (const variant of enumDecl.variants) {
|
|
548
|
+
variants.set(variant.name, variant.value ?? 0);
|
|
549
|
+
}
|
|
550
|
+
this.enumDefs.set(enumDecl.name, variants);
|
|
551
|
+
}
|
|
552
|
+
for (const constDecl of program.consts ?? []) {
|
|
553
|
+
this.constValues.set(constDecl.name, constDecl.value);
|
|
554
|
+
this.varTypes.set(constDecl.name, this.normalizeType(constDecl.type));
|
|
555
|
+
}
|
|
556
|
+
// Process global variable declarations (top-level let)
|
|
557
|
+
for (const g of program.globals ?? []) {
|
|
558
|
+
this.globalNames.set(g.name, { mutable: g.mutable });
|
|
559
|
+
this.varTypes.set(g.name, this.normalizeType(g.type));
|
|
560
|
+
const initValue = g.init.kind === 'int_lit' ? g.init.value : 0;
|
|
561
|
+
this.globals.push({ name: `$${g.name}`, init: initValue });
|
|
562
|
+
}
|
|
563
|
+
for (const fn of program.declarations) {
|
|
564
|
+
this.fnDecls.set(fn.name, fn);
|
|
565
|
+
this.functionDefaults.set(fn.name, fn.params.map(param => param.default));
|
|
566
|
+
}
|
|
567
|
+
for (const implBlock of program.implBlocks ?? []) {
|
|
568
|
+
let methods = this.implMethods.get(implBlock.typeName);
|
|
569
|
+
if (!methods) {
|
|
570
|
+
methods = new Map();
|
|
571
|
+
this.implMethods.set(implBlock.typeName, methods);
|
|
572
|
+
}
|
|
573
|
+
for (const method of implBlock.methods) {
|
|
574
|
+
const loweredName = `${implBlock.typeName}_${method.name}`;
|
|
575
|
+
methods.set(method.name, { fn: method, loweredName });
|
|
576
|
+
this.fnDecls.set(loweredName, method);
|
|
577
|
+
this.functionDefaults.set(loweredName, method.params.map(param => param.default));
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
for (const fn of program.declarations) {
|
|
581
|
+
this.lowerFn(fn);
|
|
582
|
+
}
|
|
583
|
+
for (const implBlock of program.implBlocks ?? []) {
|
|
584
|
+
for (const method of implBlock.methods) {
|
|
585
|
+
this.lowerFn(method, { name: `${implBlock.typeName}_${method.name}` });
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
return (0, builder_1.buildModule)(this.namespace, this.functions, this.globals);
|
|
589
|
+
}
|
|
590
|
+
// -------------------------------------------------------------------------
|
|
591
|
+
// Function Lowering
|
|
592
|
+
// -------------------------------------------------------------------------
|
|
593
|
+
lowerFn(fn, options = {}) {
|
|
594
|
+
const loweredName = options.name ?? fn.name;
|
|
595
|
+
const callbackBindings = options.callbackBindings ?? new Map();
|
|
596
|
+
const stdlibCallSite = options.stdlibCallSite;
|
|
597
|
+
const staticEventDec = fn.decorators.find(d => d.name === 'on');
|
|
598
|
+
const eventType = staticEventDec?.args?.eventType;
|
|
599
|
+
const eventParamSpecs = eventType && (0, types_1.isEventTypeName)(eventType) ? (0, types_1.getEventParamSpecs)(eventType) : [];
|
|
600
|
+
const runtimeParams = staticEventDec
|
|
601
|
+
? []
|
|
602
|
+
: fn.params.filter(param => !callbackBindings.has(param.name));
|
|
603
|
+
this.currentFn = loweredName;
|
|
604
|
+
this.currentStdlibCallSite = stdlibCallSite;
|
|
605
|
+
this.foreachCounter = 0;
|
|
606
|
+
this.varMap = new Map();
|
|
607
|
+
this.lambdaBindings = new Map();
|
|
608
|
+
this.intervalBindings = new Map();
|
|
609
|
+
this.currentCallbackBindings = new Map(callbackBindings);
|
|
610
|
+
this.currentContext = {};
|
|
611
|
+
this.blockPosVars = new Map();
|
|
612
|
+
this.stringValues = new Map();
|
|
613
|
+
this.builder = new LoweringBuilder();
|
|
614
|
+
// Initialize macro tracking for this function
|
|
615
|
+
this.currentFnParamNames = new Set(runtimeParams.map(p => p.name));
|
|
616
|
+
this.currentFnMacroParams = new Set();
|
|
617
|
+
// Map parameters
|
|
618
|
+
if (staticEventDec) {
|
|
619
|
+
for (let i = 0; i < fn.params.length; i++) {
|
|
620
|
+
const param = fn.params[i];
|
|
621
|
+
const expected = eventParamSpecs[i];
|
|
622
|
+
const normalizedType = this.normalizeType(param.type);
|
|
623
|
+
this.varTypes.set(param.name, normalizedType);
|
|
624
|
+
if (expected?.type.kind === 'entity') {
|
|
625
|
+
this.varMap.set(param.name, '@s');
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
if (expected?.type.kind === 'named' && expected.type.name === 'string') {
|
|
629
|
+
this.stringValues.set(param.name, '');
|
|
630
|
+
continue;
|
|
631
|
+
}
|
|
632
|
+
this.varMap.set(param.name, this.fnVar(param.name));
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
else {
|
|
636
|
+
for (const param of runtimeParams) {
|
|
637
|
+
const paramName = param.name;
|
|
638
|
+
this.varMap.set(paramName, this.fnVar(paramName));
|
|
639
|
+
this.varTypes.set(paramName, this.normalizeType(param.type));
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
for (const param of fn.params) {
|
|
643
|
+
if (callbackBindings.has(param.name)) {
|
|
644
|
+
this.varTypes.set(param.name, this.normalizeType(param.type));
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
// Start entry block
|
|
648
|
+
this.builder.startBlock('entry');
|
|
649
|
+
// Copy params from the parameter-passing slots to named local variables.
|
|
650
|
+
// Use { kind: 'param', index: i } so the codegen resolves to
|
|
651
|
+
// alloc.internal('p{i}') consistently in both mangle and no-mangle modes,
|
|
652
|
+
// avoiding the slot-collision between the internal register and a user variable
|
|
653
|
+
// named 'p0'/'p1' that occurred with { kind: 'var', name: '$p0' }.
|
|
654
|
+
for (let i = 0; i < runtimeParams.length; i++) {
|
|
655
|
+
const paramName = runtimeParams[i].name;
|
|
656
|
+
const varName = this.fnVar(paramName);
|
|
657
|
+
this.builder.emitAssign(varName, { kind: 'param', index: i });
|
|
658
|
+
}
|
|
659
|
+
if (staticEventDec) {
|
|
660
|
+
for (let i = 0; i < fn.params.length; i++) {
|
|
661
|
+
const param = fn.params[i];
|
|
662
|
+
const expected = eventParamSpecs[i];
|
|
663
|
+
if (expected?.type.kind === 'named' && expected.type.name !== 'string') {
|
|
664
|
+
this.builder.emitAssign(this.fnVar(param.name), { kind: 'const', value: 0 });
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
// Lower body
|
|
669
|
+
this.lowerBlock(fn.body);
|
|
670
|
+
// If no explicit return, add void return
|
|
671
|
+
if (!this.builder.isBlockSealed()) {
|
|
672
|
+
this.builder.emitReturn();
|
|
673
|
+
}
|
|
674
|
+
// Build function
|
|
675
|
+
const isTickLoop = fn.decorators.some(d => d.name === 'tick');
|
|
676
|
+
const tickRate = this.getTickRate(fn.decorators);
|
|
677
|
+
// Check for trigger handler
|
|
678
|
+
const triggerDec = fn.decorators.find(d => d.name === 'on_trigger');
|
|
679
|
+
const isTriggerHandler = !!triggerDec;
|
|
680
|
+
const triggerName = triggerDec?.args?.trigger;
|
|
681
|
+
const irFn = this.builder.build(loweredName, runtimeParams.map(p => `$${p.name}`), isTickLoop);
|
|
682
|
+
// Add trigger metadata if applicable
|
|
683
|
+
if (isTriggerHandler && triggerName) {
|
|
684
|
+
irFn.isTriggerHandler = true;
|
|
685
|
+
irFn.triggerName = triggerName;
|
|
686
|
+
}
|
|
687
|
+
const eventDec = fn.decorators.find(d => d.name === 'on_advancement' ||
|
|
688
|
+
d.name === 'on_craft' ||
|
|
689
|
+
d.name === 'on_death' ||
|
|
690
|
+
d.name === 'on_login' ||
|
|
691
|
+
d.name === 'on_join_team');
|
|
692
|
+
if (eventDec) {
|
|
693
|
+
switch (eventDec.name) {
|
|
694
|
+
case 'on_advancement':
|
|
695
|
+
irFn.eventTrigger = { kind: 'advancement', value: eventDec.args?.advancement };
|
|
696
|
+
break;
|
|
697
|
+
case 'on_craft':
|
|
698
|
+
irFn.eventTrigger = { kind: 'craft', value: eventDec.args?.item };
|
|
699
|
+
break;
|
|
700
|
+
case 'on_death':
|
|
701
|
+
irFn.eventTrigger = { kind: 'death' };
|
|
702
|
+
break;
|
|
703
|
+
case 'on_login':
|
|
704
|
+
irFn.eventTrigger = { kind: 'login' };
|
|
705
|
+
break;
|
|
706
|
+
case 'on_join_team':
|
|
707
|
+
irFn.eventTrigger = { kind: 'join_team', value: eventDec.args?.team };
|
|
708
|
+
break;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
if (eventType && (0, types_1.isEventTypeName)(eventType)) {
|
|
712
|
+
irFn.eventHandler = {
|
|
713
|
+
eventType,
|
|
714
|
+
tag: types_1.EVENT_TYPES[eventType].tag,
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
// Check for @load decorator
|
|
718
|
+
if (fn.decorators.some(d => d.name === 'load')) {
|
|
719
|
+
irFn.isLoadInit = true;
|
|
720
|
+
}
|
|
721
|
+
// @requires("dep_fn") — when this function is compiled in, dep_fn is also
|
|
722
|
+
// called from __load. The dep_fn itself does NOT need @load; it can be a
|
|
723
|
+
// private (_) function that only runs at load time when this fn is used.
|
|
724
|
+
const requiredLoads = [];
|
|
725
|
+
for (const d of fn.decorators) {
|
|
726
|
+
if (d.name === 'require_on_load') {
|
|
727
|
+
for (const arg of d.rawArgs ?? []) {
|
|
728
|
+
if (arg.kind === 'string') {
|
|
729
|
+
requiredLoads.push(arg.value);
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
if (requiredLoads.length > 0) {
|
|
735
|
+
irFn.requiredLoads = requiredLoads;
|
|
736
|
+
}
|
|
737
|
+
// Handle tick rate counter if needed
|
|
738
|
+
if (tickRate && tickRate > 1) {
|
|
739
|
+
this.wrapWithTickRate(irFn, tickRate);
|
|
740
|
+
}
|
|
741
|
+
// Set macro metadata if this function uses MC macro syntax
|
|
742
|
+
if (this.currentFnMacroParams.size > 0) {
|
|
743
|
+
irFn.isMacroFunction = true;
|
|
744
|
+
irFn.macroParamNames = [...this.currentFnMacroParams];
|
|
745
|
+
// Update registry (may refine the pre-scan result)
|
|
746
|
+
this.macroFunctionInfo.set(loweredName, irFn.macroParamNames);
|
|
747
|
+
}
|
|
748
|
+
this.functions.push(irFn);
|
|
749
|
+
}
|
|
750
|
+
getTickRate(decorators) {
|
|
751
|
+
const tickDec = decorators.find(d => d.name === 'tick');
|
|
752
|
+
return tickDec?.args?.rate;
|
|
753
|
+
}
|
|
754
|
+
wrapWithTickRate(fn, rate) {
|
|
755
|
+
// Add tick counter logic to entry block
|
|
756
|
+
const counterVar = `$__tick_${fn.name}`;
|
|
757
|
+
this.globals.push({ name: counterVar, init: 0 });
|
|
758
|
+
// Prepend counter logic to entry block
|
|
759
|
+
const entry = fn.blocks[0];
|
|
760
|
+
const originalInstrs = [...entry.instrs];
|
|
761
|
+
const originalTerm = entry.term;
|
|
762
|
+
entry.instrs = [
|
|
763
|
+
{ op: 'raw', cmd: `scoreboard players add ${counterVar} ${exports.LOWERING_OBJ} 1` },
|
|
764
|
+
];
|
|
765
|
+
// Create conditional jump
|
|
766
|
+
const bodyLabel = 'tick_body';
|
|
767
|
+
const skipLabel = 'tick_skip';
|
|
768
|
+
entry.term = {
|
|
769
|
+
op: 'jump_if',
|
|
770
|
+
cond: `${counterVar}_check`,
|
|
771
|
+
then: bodyLabel,
|
|
772
|
+
else_: skipLabel,
|
|
773
|
+
};
|
|
774
|
+
// Add check instruction
|
|
775
|
+
entry.instrs.push({
|
|
776
|
+
op: 'raw',
|
|
777
|
+
cmd: `execute store success score ${counterVar}_check ${exports.LOWERING_OBJ} if score ${counterVar} ${exports.LOWERING_OBJ} matches ${rate}..`,
|
|
778
|
+
});
|
|
779
|
+
// Body block (original logic + counter reset)
|
|
780
|
+
fn.blocks.push({
|
|
781
|
+
label: bodyLabel,
|
|
782
|
+
instrs: [
|
|
783
|
+
{ op: 'raw', cmd: `scoreboard players set ${counterVar} ${exports.LOWERING_OBJ} 0` },
|
|
784
|
+
...originalInstrs,
|
|
785
|
+
],
|
|
786
|
+
term: originalTerm,
|
|
787
|
+
});
|
|
788
|
+
// Skip block (just return)
|
|
789
|
+
fn.blocks.push({
|
|
790
|
+
label: skipLabel,
|
|
791
|
+
instrs: [],
|
|
792
|
+
term: { op: 'return' },
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
// -------------------------------------------------------------------------
|
|
796
|
+
// Statement Lowering
|
|
797
|
+
// -------------------------------------------------------------------------
|
|
798
|
+
lowerBlock(stmts) {
|
|
799
|
+
for (const stmt of stmts) {
|
|
800
|
+
this.lowerStmt(stmt);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
lowerStmt(stmt) {
|
|
804
|
+
switch (stmt.kind) {
|
|
805
|
+
case 'let':
|
|
806
|
+
this.lowerLetStmt(stmt);
|
|
807
|
+
break;
|
|
808
|
+
case 'expr':
|
|
809
|
+
this.lowerExpr(stmt.expr);
|
|
810
|
+
break;
|
|
811
|
+
case 'return':
|
|
812
|
+
this.lowerReturnStmt(stmt);
|
|
813
|
+
break;
|
|
814
|
+
case 'break':
|
|
815
|
+
this.lowerBreakStmt();
|
|
816
|
+
break;
|
|
817
|
+
case 'continue':
|
|
818
|
+
this.lowerContinueStmt();
|
|
819
|
+
break;
|
|
820
|
+
case 'if':
|
|
821
|
+
this.lowerIfStmt(stmt);
|
|
822
|
+
break;
|
|
823
|
+
case 'while':
|
|
824
|
+
this.lowerWhileStmt(stmt);
|
|
825
|
+
break;
|
|
826
|
+
case 'for':
|
|
827
|
+
this.lowerForStmt(stmt);
|
|
828
|
+
break;
|
|
829
|
+
case 'foreach':
|
|
830
|
+
this.lowerForeachStmt(stmt);
|
|
831
|
+
break;
|
|
832
|
+
case 'for_range':
|
|
833
|
+
this.lowerForRangeStmt(stmt);
|
|
834
|
+
break;
|
|
835
|
+
case 'match':
|
|
836
|
+
this.lowerMatchStmt(stmt);
|
|
837
|
+
break;
|
|
838
|
+
case 'as_block':
|
|
839
|
+
this.lowerAsBlockStmt(stmt);
|
|
840
|
+
break;
|
|
841
|
+
case 'at_block':
|
|
842
|
+
this.lowerAtBlockStmt(stmt);
|
|
843
|
+
break;
|
|
844
|
+
case 'as_at':
|
|
845
|
+
this.lowerAsAtStmt(stmt);
|
|
846
|
+
break;
|
|
847
|
+
case 'execute':
|
|
848
|
+
this.lowerExecuteStmt(stmt);
|
|
849
|
+
break;
|
|
850
|
+
case 'raw':
|
|
851
|
+
this.checkRawCommandInterpolation(stmt.cmd, stmt.span);
|
|
852
|
+
this.builder.emitRaw(stmt.cmd);
|
|
853
|
+
break;
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
lowerLetStmt(stmt) {
|
|
857
|
+
// Check for duplicate declaration of foreach binding
|
|
858
|
+
if (this.currentContext.binding === stmt.name) {
|
|
859
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', `Cannot redeclare foreach binding '${stmt.name}'`, stmt.span ?? { line: 0, col: 0 });
|
|
860
|
+
}
|
|
861
|
+
const varName = this.fnVar(stmt.name);
|
|
862
|
+
this.varMap.set(stmt.name, varName);
|
|
863
|
+
// Track variable type
|
|
864
|
+
const declaredType = stmt.type ? this.normalizeType(stmt.type) : this.inferExprType(stmt.init);
|
|
865
|
+
if (declaredType) {
|
|
866
|
+
this.varTypes.set(stmt.name, declaredType);
|
|
867
|
+
// Track float variables for fixed-point arithmetic
|
|
868
|
+
if (declaredType.kind === 'named' && declaredType.name === 'float') {
|
|
869
|
+
this.floatVars.add(stmt.name);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
if (stmt.init.kind === 'lambda') {
|
|
873
|
+
const lambdaName = this.lowerLambdaExpr(stmt.init);
|
|
874
|
+
this.lambdaBindings.set(stmt.name, lambdaName);
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
if (stmt.init.kind === 'call' && stmt.init.fn === 'setInterval') {
|
|
878
|
+
const value = this.lowerExpr(stmt.init);
|
|
879
|
+
const intervalFn = this.intervalFunctions.get(value.kind === 'const' ? value.value : NaN);
|
|
880
|
+
if (intervalFn) {
|
|
881
|
+
this.intervalBindings.set(stmt.name, intervalFn);
|
|
882
|
+
}
|
|
883
|
+
this.builder.emitAssign(varName, value);
|
|
884
|
+
return;
|
|
885
|
+
}
|
|
886
|
+
// Handle struct literal initialization
|
|
887
|
+
if (stmt.init.kind === 'struct_lit' && stmt.type?.kind === 'struct') {
|
|
888
|
+
const structName = stmt.type.name.toLowerCase();
|
|
889
|
+
for (const field of stmt.init.fields) {
|
|
890
|
+
const path = `rs:heap ${structName}_${stmt.name}.${field.name}`;
|
|
891
|
+
const fieldValue = this.lowerExpr(field.value);
|
|
892
|
+
if (fieldValue.kind === 'const') {
|
|
893
|
+
this.builder.emitRaw(`data modify storage ${path} set value ${fieldValue.value}`);
|
|
894
|
+
}
|
|
895
|
+
else if (fieldValue.kind === 'var') {
|
|
896
|
+
// Copy from scoreboard to NBT
|
|
897
|
+
this.builder.emitRaw(`execute store result storage ${path} int 1 run scoreboard players get ${fieldValue.name} ${exports.LOWERING_OBJ}`);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
// Handle struct initialization from function call (copy from __ret_struct)
|
|
903
|
+
if ((stmt.init.kind === 'call' || stmt.init.kind === 'static_call') && stmt.type?.kind === 'struct') {
|
|
904
|
+
// First, execute the function call
|
|
905
|
+
this.lowerExpr(stmt.init);
|
|
906
|
+
// Then copy all fields from __ret_struct to the variable's storage
|
|
907
|
+
const structDecl = this.structDecls.get(stmt.type.name);
|
|
908
|
+
if (structDecl) {
|
|
909
|
+
const structName = stmt.type.name.toLowerCase();
|
|
910
|
+
for (const field of structDecl.fields) {
|
|
911
|
+
const srcPath = `rs:heap __ret_struct.${field.name}`;
|
|
912
|
+
const dstPath = `rs:heap ${structName}_${stmt.name}.${field.name}`;
|
|
913
|
+
this.builder.emitRaw(`data modify storage ${dstPath} set from storage ${srcPath}`);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
// Handle array literal initialization
|
|
919
|
+
if (stmt.init.kind === 'array_lit') {
|
|
920
|
+
// Initialize empty NBT array
|
|
921
|
+
this.builder.emitRaw(`data modify storage rs:heap ${stmt.name} set value []`);
|
|
922
|
+
// Add each element
|
|
923
|
+
for (const elem of stmt.init.elements) {
|
|
924
|
+
const elemValue = this.lowerExpr(elem);
|
|
925
|
+
if (elemValue.kind === 'const') {
|
|
926
|
+
this.builder.emitRaw(`data modify storage rs:heap ${stmt.name} append value ${elemValue.value}`);
|
|
927
|
+
}
|
|
928
|
+
else if (elemValue.kind === 'var') {
|
|
929
|
+
this.builder.emitRaw(`data modify storage rs:heap ${stmt.name} append value 0`);
|
|
930
|
+
this.builder.emitRaw(`execute store result storage rs:heap ${stmt.name}[-1] int 1 run scoreboard players get ${elemValue.name} ${exports.LOWERING_OBJ}`);
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
// Handle set_new returning a set ID string
|
|
936
|
+
if (stmt.init.kind === 'call' && stmt.init.fn === 'set_new') {
|
|
937
|
+
const setId = `__set_${this.foreachCounter++}`;
|
|
938
|
+
this.builder.emitRaw(`data modify storage rs:sets ${setId} set value []`);
|
|
939
|
+
this.stringValues.set(stmt.name, setId);
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
// Handle spawn_object returning entity handle
|
|
943
|
+
if (stmt.init.kind === 'call' && stmt.init.fn === 'spawn_object') {
|
|
944
|
+
const value = this.lowerExpr(stmt.init);
|
|
945
|
+
// value is the selector like @e[tag=__rs_obj_0,limit=1]
|
|
946
|
+
if (value.kind === 'var' && value.name.startsWith('@e[tag=__rs_obj_')) {
|
|
947
|
+
this.varMap.set(stmt.name, value.name);
|
|
948
|
+
// Mark as entity type for later member access
|
|
949
|
+
this.varTypes.set(stmt.name, { kind: 'named', name: 'void' }); // Marker
|
|
950
|
+
}
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
const blockPosValue = this.resolveBlockPosExpr(stmt.init);
|
|
954
|
+
if (blockPosValue) {
|
|
955
|
+
this.blockPosVars.set(stmt.name, blockPosValue);
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
const stmtType = stmt.type ? this.normalizeType(stmt.type) : this.inferExprType(stmt.init);
|
|
959
|
+
if (stmtType?.kind === 'named' && stmtType.name === 'string' && this.storeStringValue(stmt.name, stmt.init)) {
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
const value = this.lowerExpr(stmt.init);
|
|
963
|
+
this.builder.emitAssign(varName, value);
|
|
964
|
+
}
|
|
965
|
+
lowerReturnStmt(stmt) {
|
|
966
|
+
if (stmt.value) {
|
|
967
|
+
// Handle struct literal return: store fields to __ret_struct storage
|
|
968
|
+
if (stmt.value.kind === 'struct_lit') {
|
|
969
|
+
for (const field of stmt.value.fields) {
|
|
970
|
+
const path = `rs:heap __ret_struct.${field.name}`;
|
|
971
|
+
const fieldValue = this.lowerExpr(field.value);
|
|
972
|
+
if (fieldValue.kind === 'const') {
|
|
973
|
+
this.builder.emitRaw(`data modify storage ${path} set value ${fieldValue.value}`);
|
|
974
|
+
}
|
|
975
|
+
else if (fieldValue.kind === 'var') {
|
|
976
|
+
this.builder.emitRaw(`execute store result storage ${path} int 1 run scoreboard players get ${fieldValue.name} ${exports.LOWERING_OBJ}`);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
this.builder.emitReturn({ kind: 'const', value: 0 });
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
const value = this.lowerExpr(stmt.value);
|
|
983
|
+
this.builder.emitReturn(value);
|
|
984
|
+
}
|
|
985
|
+
else {
|
|
986
|
+
this.builder.emitReturn();
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
lowerBreakStmt() {
|
|
990
|
+
if (this.loopStack.length === 0) {
|
|
991
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', 'break statement outside of loop', { line: 1, col: 1 });
|
|
992
|
+
}
|
|
993
|
+
const loop = this.loopStack[this.loopStack.length - 1];
|
|
994
|
+
this.builder.emitJump(loop.breakLabel);
|
|
995
|
+
}
|
|
996
|
+
lowerContinueStmt() {
|
|
997
|
+
if (this.loopStack.length === 0) {
|
|
998
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', 'continue statement outside of loop', { line: 1, col: 1 });
|
|
999
|
+
}
|
|
1000
|
+
const loop = this.loopStack[this.loopStack.length - 1];
|
|
1001
|
+
this.builder.emitJump(loop.continueLabel);
|
|
1002
|
+
}
|
|
1003
|
+
lowerIfStmt(stmt) {
|
|
1004
|
+
if (stmt.cond.kind === 'is_check') {
|
|
1005
|
+
this.lowerIsCheckIfStmt(stmt);
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
const condVar = this.lowerExpr(stmt.cond);
|
|
1009
|
+
const condName = this.operandToVar(condVar);
|
|
1010
|
+
const thenLabel = this.builder.freshLabel('then');
|
|
1011
|
+
const elseLabel = this.builder.freshLabel('else');
|
|
1012
|
+
const mergeLabel = this.builder.freshLabel('merge');
|
|
1013
|
+
this.builder.emitJumpIf(condName, thenLabel, stmt.else_ ? elseLabel : mergeLabel);
|
|
1014
|
+
// Then block
|
|
1015
|
+
this.builder.startBlock(thenLabel);
|
|
1016
|
+
this.lowerBlock(stmt.then);
|
|
1017
|
+
if (!this.builder.isBlockSealed()) {
|
|
1018
|
+
this.builder.emitJump(mergeLabel);
|
|
1019
|
+
}
|
|
1020
|
+
// Else block (if present)
|
|
1021
|
+
if (stmt.else_) {
|
|
1022
|
+
this.builder.startBlock(elseLabel);
|
|
1023
|
+
this.lowerBlock(stmt.else_);
|
|
1024
|
+
if (!this.builder.isBlockSealed()) {
|
|
1025
|
+
this.builder.emitJump(mergeLabel);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
// Merge block
|
|
1029
|
+
this.builder.startBlock(mergeLabel);
|
|
1030
|
+
}
|
|
1031
|
+
lowerIsCheckIfStmt(stmt) {
|
|
1032
|
+
const cond = stmt.cond;
|
|
1033
|
+
if (cond.kind !== 'is_check') {
|
|
1034
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', "Internal error: expected 'is' check condition", stmt.span ?? { line: 0, col: 0 });
|
|
1035
|
+
}
|
|
1036
|
+
if (stmt.else_) {
|
|
1037
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', "'is' checks with else branches are not yet supported", cond.span ?? stmt.span ?? { line: 0, col: 0 });
|
|
1038
|
+
}
|
|
1039
|
+
const selector = this.exprToEntitySelector(cond.expr);
|
|
1040
|
+
if (!selector) {
|
|
1041
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', "'is' checks require an entity selector or entity binding", cond.span ?? stmt.span ?? { line: 0, col: 0 });
|
|
1042
|
+
}
|
|
1043
|
+
const mcType = ENTITY_TO_MC_TYPE[cond.entityType];
|
|
1044
|
+
const thenFnName = `${this.currentFn}/then_${this.foreachCounter++}`;
|
|
1045
|
+
if (!mcType) {
|
|
1046
|
+
// Abstract type — check all concrete subtypes
|
|
1047
|
+
const subtypes = (0, entity_hierarchy_1.getConcreteSubtypes)(cond.entityType);
|
|
1048
|
+
if (subtypes.length === 0) {
|
|
1049
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', `Cannot lower entity type check for '${cond.entityType}'`, cond.span ?? stmt.span ?? { line: 0, col: 0 });
|
|
1050
|
+
}
|
|
1051
|
+
// Use a temp scoreboard variable to OR multiple type checks
|
|
1052
|
+
this.builder.emitRaw(`scoreboard players set __is_result rs:temp 0`);
|
|
1053
|
+
for (const subtype of subtypes) {
|
|
1054
|
+
if (subtype.mcId) {
|
|
1055
|
+
this.builder.emitRaw(`execute if entity ${this.appendTypeFilter(selector, subtype.mcId)} run scoreboard players set __is_result rs:temp 1`);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
this.builder.emitRaw(`execute if score __is_result rs:temp matches 1 run function ${this.namespace}:${thenFnName}`);
|
|
1059
|
+
}
|
|
1060
|
+
else {
|
|
1061
|
+
// Concrete type — single check
|
|
1062
|
+
this.builder.emitRaw(`execute if entity ${this.appendTypeFilter(selector, mcType)} run function ${this.namespace}:${thenFnName}`);
|
|
1063
|
+
}
|
|
1064
|
+
const savedBuilder = this.builder;
|
|
1065
|
+
const savedVarMap = new Map(this.varMap);
|
|
1066
|
+
const savedBlockPosVars = new Map(this.blockPosVars);
|
|
1067
|
+
this.builder = new LoweringBuilder();
|
|
1068
|
+
this.varMap = new Map(savedVarMap);
|
|
1069
|
+
this.blockPosVars = new Map(savedBlockPosVars);
|
|
1070
|
+
this.builder.startBlock('entry');
|
|
1071
|
+
this.lowerBlock(stmt.then);
|
|
1072
|
+
if (!this.builder.isBlockSealed()) {
|
|
1073
|
+
this.builder.emitReturn();
|
|
1074
|
+
}
|
|
1075
|
+
this.functions.push(this.builder.build(thenFnName, [], false));
|
|
1076
|
+
this.builder = savedBuilder;
|
|
1077
|
+
this.varMap = savedVarMap;
|
|
1078
|
+
this.blockPosVars = savedBlockPosVars;
|
|
1079
|
+
}
|
|
1080
|
+
lowerWhileStmt(stmt) {
|
|
1081
|
+
const checkLabel = this.builder.freshLabel('loop_check');
|
|
1082
|
+
const bodyLabel = this.builder.freshLabel('loop_body');
|
|
1083
|
+
const exitLabel = this.builder.freshLabel('loop_exit');
|
|
1084
|
+
this.builder.emitJump(checkLabel);
|
|
1085
|
+
// Check block
|
|
1086
|
+
this.builder.startBlock(checkLabel);
|
|
1087
|
+
const condVar = this.lowerExpr(stmt.cond);
|
|
1088
|
+
const condName = this.operandToVar(condVar);
|
|
1089
|
+
this.builder.emitJumpIf(condName, bodyLabel, exitLabel);
|
|
1090
|
+
// Push loop context for break/continue (while has no step, so continue goes to check)
|
|
1091
|
+
this.loopStack.push({ breakLabel: exitLabel, continueLabel: checkLabel });
|
|
1092
|
+
// Body block
|
|
1093
|
+
this.builder.startBlock(bodyLabel);
|
|
1094
|
+
this.lowerBlock(stmt.body);
|
|
1095
|
+
if (!this.builder.isBlockSealed()) {
|
|
1096
|
+
this.builder.emitJump(checkLabel);
|
|
1097
|
+
}
|
|
1098
|
+
// Pop loop context
|
|
1099
|
+
this.loopStack.pop();
|
|
1100
|
+
// Exit block
|
|
1101
|
+
this.builder.startBlock(exitLabel);
|
|
1102
|
+
}
|
|
1103
|
+
lowerForStmt(stmt) {
|
|
1104
|
+
// For loop is lowered to: init; while(cond) { body; step; }
|
|
1105
|
+
// Init statement (if present)
|
|
1106
|
+
if (stmt.init) {
|
|
1107
|
+
this.lowerStmt(stmt.init);
|
|
1108
|
+
}
|
|
1109
|
+
const checkLabel = this.builder.freshLabel('for_check');
|
|
1110
|
+
const bodyLabel = this.builder.freshLabel('for_body');
|
|
1111
|
+
const continueLabel = this.builder.freshLabel('for_continue');
|
|
1112
|
+
const exitLabel = this.builder.freshLabel('for_exit');
|
|
1113
|
+
this.builder.emitJump(checkLabel);
|
|
1114
|
+
// Check block
|
|
1115
|
+
this.builder.startBlock(checkLabel);
|
|
1116
|
+
const condVar = this.lowerExpr(stmt.cond);
|
|
1117
|
+
const condName = this.operandToVar(condVar);
|
|
1118
|
+
this.builder.emitJumpIf(condName, bodyLabel, exitLabel);
|
|
1119
|
+
// Push loop context for break/continue
|
|
1120
|
+
this.loopStack.push({ breakLabel: exitLabel, continueLabel });
|
|
1121
|
+
// Body block
|
|
1122
|
+
this.builder.startBlock(bodyLabel);
|
|
1123
|
+
this.lowerBlock(stmt.body);
|
|
1124
|
+
if (!this.builder.isBlockSealed()) {
|
|
1125
|
+
this.builder.emitJump(continueLabel);
|
|
1126
|
+
}
|
|
1127
|
+
// Continue block (step + loop back)
|
|
1128
|
+
this.builder.startBlock(continueLabel);
|
|
1129
|
+
this.lowerExpr(stmt.step);
|
|
1130
|
+
this.builder.emitJump(checkLabel);
|
|
1131
|
+
// Pop loop context
|
|
1132
|
+
this.loopStack.pop();
|
|
1133
|
+
// Exit block
|
|
1134
|
+
this.builder.startBlock(exitLabel);
|
|
1135
|
+
}
|
|
1136
|
+
lowerForRangeStmt(stmt) {
|
|
1137
|
+
const loopVar = this.fnVar(stmt.varName);
|
|
1138
|
+
const subFnName = `${this.currentFn}/__for_${this.foreachCounter++}`;
|
|
1139
|
+
// Initialize loop variable
|
|
1140
|
+
this.varMap.set(stmt.varName, loopVar);
|
|
1141
|
+
const startVal = this.lowerExpr(stmt.start);
|
|
1142
|
+
if (startVal.kind === 'const') {
|
|
1143
|
+
this.builder.emitRaw(`scoreboard players set ${loopVar} ${exports.LOWERING_OBJ} ${startVal.value}`);
|
|
1144
|
+
}
|
|
1145
|
+
else if (startVal.kind === 'var') {
|
|
1146
|
+
this.builder.emitRaw(`scoreboard players operation ${loopVar} ${exports.LOWERING_OBJ} = ${startVal.name} ${exports.LOWERING_OBJ}`);
|
|
1147
|
+
}
|
|
1148
|
+
// Call loop function
|
|
1149
|
+
this.builder.emitRaw(`function ${this.namespace}:${subFnName}`);
|
|
1150
|
+
// Generate loop sub-function
|
|
1151
|
+
const savedBuilder = this.builder;
|
|
1152
|
+
const savedVarMap = new Map(this.varMap);
|
|
1153
|
+
const savedContext = this.currentContext;
|
|
1154
|
+
const savedBlockPosVars = new Map(this.blockPosVars);
|
|
1155
|
+
this.builder = new LoweringBuilder();
|
|
1156
|
+
this.varMap = new Map(savedVarMap);
|
|
1157
|
+
this.currentContext = savedContext;
|
|
1158
|
+
this.blockPosVars = new Map(savedBlockPosVars);
|
|
1159
|
+
this.builder.startBlock('entry');
|
|
1160
|
+
// Body
|
|
1161
|
+
this.lowerBlock(stmt.body);
|
|
1162
|
+
// Increment
|
|
1163
|
+
this.builder.emitRaw(`scoreboard players add ${loopVar} ${exports.LOWERING_OBJ} 1`);
|
|
1164
|
+
// Loop condition: execute if score matches ..<end-1> run function
|
|
1165
|
+
const endVal = this.lowerExpr(stmt.end);
|
|
1166
|
+
const endNum = endVal.kind === 'const' ? endVal.value - 1 : '?';
|
|
1167
|
+
this.builder.emitRaw(`execute if score ${loopVar} ${exports.LOWERING_OBJ} matches ..${endNum} run function ${this.namespace}:${subFnName}`);
|
|
1168
|
+
if (!this.builder.isBlockSealed()) {
|
|
1169
|
+
this.builder.emitReturn();
|
|
1170
|
+
}
|
|
1171
|
+
const subFn = this.builder.build(subFnName, [], false);
|
|
1172
|
+
this.functions.push(subFn);
|
|
1173
|
+
// Restore
|
|
1174
|
+
this.builder = savedBuilder;
|
|
1175
|
+
this.varMap = savedVarMap;
|
|
1176
|
+
this.currentContext = savedContext;
|
|
1177
|
+
this.blockPosVars = savedBlockPosVars;
|
|
1178
|
+
}
|
|
1179
|
+
lowerForeachStmt(stmt) {
|
|
1180
|
+
if (stmt.iterable.kind !== 'selector') {
|
|
1181
|
+
this.lowerArrayForeachStmt(stmt);
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
// Extract body into a separate function
|
|
1185
|
+
const subFnName = `${this.currentFn}/foreach_${this.foreachCounter++}`;
|
|
1186
|
+
const selector = this.exprToString(stmt.iterable);
|
|
1187
|
+
// Emit execute as ... [context modifiers] run function ...
|
|
1188
|
+
const execContext = stmt.executeContext ? ` ${stmt.executeContext}` : '';
|
|
1189
|
+
this.builder.emitRaw(`execute as ${selector}${execContext} run function ${this.namespace}:${subFnName}`);
|
|
1190
|
+
// Create the sub-function
|
|
1191
|
+
const savedBuilder = this.builder;
|
|
1192
|
+
const savedVarMap = new Map(this.varMap);
|
|
1193
|
+
const savedContext = this.currentContext;
|
|
1194
|
+
const savedBlockPosVars = new Map(this.blockPosVars);
|
|
1195
|
+
this.builder = new LoweringBuilder();
|
|
1196
|
+
this.varMap = new Map(savedVarMap);
|
|
1197
|
+
this.currentContext = { binding: stmt.binding };
|
|
1198
|
+
this.blockPosVars = new Map(savedBlockPosVars);
|
|
1199
|
+
// In foreach body, the binding maps to @s
|
|
1200
|
+
this.varMap.set(stmt.binding, '@s');
|
|
1201
|
+
// Track entity context for type narrowing
|
|
1202
|
+
const selectorEntityType = (0, entity_hierarchy_1.getBaseSelectorType)(selector);
|
|
1203
|
+
if (selectorEntityType) {
|
|
1204
|
+
this.entityContextStack.push(selectorEntityType);
|
|
1205
|
+
}
|
|
1206
|
+
this.builder.startBlock('entry');
|
|
1207
|
+
this.lowerBlock(stmt.body);
|
|
1208
|
+
if (!this.builder.isBlockSealed()) {
|
|
1209
|
+
this.builder.emitReturn();
|
|
1210
|
+
}
|
|
1211
|
+
if (selectorEntityType) {
|
|
1212
|
+
this.entityContextStack.pop();
|
|
1213
|
+
}
|
|
1214
|
+
const subFn = this.builder.build(subFnName, [], false);
|
|
1215
|
+
this.functions.push(subFn);
|
|
1216
|
+
// Restore
|
|
1217
|
+
this.builder = savedBuilder;
|
|
1218
|
+
this.varMap = savedVarMap;
|
|
1219
|
+
this.currentContext = savedContext;
|
|
1220
|
+
this.blockPosVars = savedBlockPosVars;
|
|
1221
|
+
}
|
|
1222
|
+
lowerMatchStmt(stmt) {
|
|
1223
|
+
const subject = this.operandToVar(this.lowerExpr(stmt.expr));
|
|
1224
|
+
const matchedVar = this.builder.freshTemp();
|
|
1225
|
+
this.builder.emitAssign(matchedVar, { kind: 'const', value: 0 });
|
|
1226
|
+
let defaultArm = null;
|
|
1227
|
+
for (const arm of stmt.arms) {
|
|
1228
|
+
if (arm.pattern === null) {
|
|
1229
|
+
defaultArm = arm;
|
|
1230
|
+
continue;
|
|
1231
|
+
}
|
|
1232
|
+
// Handle range patterns specially
|
|
1233
|
+
let matchCondition;
|
|
1234
|
+
if (arm.pattern.kind === 'range_lit') {
|
|
1235
|
+
const range = arm.pattern.range;
|
|
1236
|
+
if (range.min !== undefined && range.max !== undefined) {
|
|
1237
|
+
matchCondition = `${range.min}..${range.max}`;
|
|
1238
|
+
}
|
|
1239
|
+
else if (range.min !== undefined) {
|
|
1240
|
+
matchCondition = `${range.min}..`;
|
|
1241
|
+
}
|
|
1242
|
+
else if (range.max !== undefined) {
|
|
1243
|
+
matchCondition = `..${range.max}`;
|
|
1244
|
+
}
|
|
1245
|
+
else {
|
|
1246
|
+
matchCondition = '0..'; // Match any
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
else {
|
|
1250
|
+
const patternValue = this.lowerExpr(arm.pattern);
|
|
1251
|
+
if (patternValue.kind !== 'const') {
|
|
1252
|
+
throw new Error('Match patterns must lower to compile-time constants');
|
|
1253
|
+
}
|
|
1254
|
+
matchCondition = String(patternValue.value);
|
|
1255
|
+
}
|
|
1256
|
+
const subFnName = `${this.currentFn}/match_${this.foreachCounter++}`;
|
|
1257
|
+
this.builder.emitRaw(`execute if score ${matchedVar} ${exports.LOWERING_OBJ} matches ..0 if score ${subject} ${exports.LOWERING_OBJ} matches ${matchCondition} run function ${this.namespace}:${subFnName}`);
|
|
1258
|
+
this.emitMatchArmSubFunction(subFnName, matchedVar, arm.body, true);
|
|
1259
|
+
}
|
|
1260
|
+
if (defaultArm) {
|
|
1261
|
+
const subFnName = `${this.currentFn}/match_${this.foreachCounter++}`;
|
|
1262
|
+
this.builder.emitRaw(`execute if score ${matchedVar} ${exports.LOWERING_OBJ} matches ..0 run function ${this.namespace}:${subFnName}`);
|
|
1263
|
+
this.emitMatchArmSubFunction(subFnName, matchedVar, defaultArm.body, false);
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
emitMatchArmSubFunction(name, matchedVar, body, setMatched) {
|
|
1267
|
+
const savedBuilder = this.builder;
|
|
1268
|
+
const savedVarMap = new Map(this.varMap);
|
|
1269
|
+
const savedContext = this.currentContext;
|
|
1270
|
+
const savedBlockPosVars = new Map(this.blockPosVars);
|
|
1271
|
+
this.builder = new LoweringBuilder();
|
|
1272
|
+
this.varMap = new Map(savedVarMap);
|
|
1273
|
+
this.currentContext = savedContext;
|
|
1274
|
+
this.blockPosVars = new Map(savedBlockPosVars);
|
|
1275
|
+
this.builder.startBlock('entry');
|
|
1276
|
+
if (setMatched) {
|
|
1277
|
+
this.builder.emitRaw(`scoreboard players set ${matchedVar} ${exports.LOWERING_OBJ} 1`);
|
|
1278
|
+
}
|
|
1279
|
+
this.lowerBlock(body);
|
|
1280
|
+
if (!this.builder.isBlockSealed()) {
|
|
1281
|
+
this.builder.emitReturn();
|
|
1282
|
+
}
|
|
1283
|
+
this.functions.push(this.builder.build(name, [], false));
|
|
1284
|
+
this.builder = savedBuilder;
|
|
1285
|
+
this.varMap = savedVarMap;
|
|
1286
|
+
this.currentContext = savedContext;
|
|
1287
|
+
this.blockPosVars = savedBlockPosVars;
|
|
1288
|
+
}
|
|
1289
|
+
lowerArrayForeachStmt(stmt) {
|
|
1290
|
+
const arrayName = this.getArrayStorageName(stmt.iterable);
|
|
1291
|
+
if (!arrayName) {
|
|
1292
|
+
this.builder.emitRaw('# Unsupported foreach iterable');
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
const arrayType = this.inferExprType(stmt.iterable);
|
|
1296
|
+
const bindingVar = this.fnVar(stmt.binding);
|
|
1297
|
+
const indexVar = this.builder.freshTemp();
|
|
1298
|
+
const lengthVar = this.builder.freshTemp();
|
|
1299
|
+
const condVar = this.builder.freshTemp();
|
|
1300
|
+
const oneVar = this.builder.freshTemp();
|
|
1301
|
+
const savedBinding = this.varMap.get(stmt.binding);
|
|
1302
|
+
const savedType = this.varTypes.get(stmt.binding);
|
|
1303
|
+
this.varMap.set(stmt.binding, bindingVar);
|
|
1304
|
+
if (arrayType?.kind === 'array') {
|
|
1305
|
+
this.varTypes.set(stmt.binding, arrayType.elem);
|
|
1306
|
+
}
|
|
1307
|
+
this.builder.emitAssign(indexVar, { kind: 'const', value: 0 });
|
|
1308
|
+
this.builder.emitAssign(oneVar, { kind: 'const', value: 1 });
|
|
1309
|
+
this.builder.emitRaw(`execute store result score ${lengthVar} ${exports.LOWERING_OBJ} run data get storage rs:heap ${arrayName}`);
|
|
1310
|
+
const checkLabel = this.builder.freshLabel('foreach_array_check');
|
|
1311
|
+
const bodyLabel = this.builder.freshLabel('foreach_array_body');
|
|
1312
|
+
const exitLabel = this.builder.freshLabel('foreach_array_exit');
|
|
1313
|
+
this.builder.emitJump(checkLabel);
|
|
1314
|
+
this.builder.startBlock(checkLabel);
|
|
1315
|
+
this.builder.emitCmp(condVar, { kind: 'var', name: indexVar }, '<', { kind: 'var', name: lengthVar });
|
|
1316
|
+
this.builder.emitJumpIf(condVar, bodyLabel, exitLabel);
|
|
1317
|
+
this.builder.startBlock(bodyLabel);
|
|
1318
|
+
const element = this.readArrayElement(arrayName, { kind: 'var', name: indexVar });
|
|
1319
|
+
this.builder.emitAssign(bindingVar, element);
|
|
1320
|
+
this.lowerBlock(stmt.body);
|
|
1321
|
+
if (!this.builder.isBlockSealed()) {
|
|
1322
|
+
this.builder.emitRaw(`scoreboard players operation ${indexVar} ${exports.LOWERING_OBJ} += ${oneVar} ${exports.LOWERING_OBJ}`);
|
|
1323
|
+
this.builder.emitJump(checkLabel);
|
|
1324
|
+
}
|
|
1325
|
+
this.builder.startBlock(exitLabel);
|
|
1326
|
+
if (savedBinding) {
|
|
1327
|
+
this.varMap.set(stmt.binding, savedBinding);
|
|
1328
|
+
}
|
|
1329
|
+
else {
|
|
1330
|
+
this.varMap.delete(stmt.binding);
|
|
1331
|
+
}
|
|
1332
|
+
if (savedType) {
|
|
1333
|
+
this.varTypes.set(stmt.binding, savedType);
|
|
1334
|
+
}
|
|
1335
|
+
else {
|
|
1336
|
+
this.varTypes.delete(stmt.binding);
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
lowerAsBlockStmt(stmt) {
|
|
1340
|
+
const selector = this.selectorToString(stmt.selector);
|
|
1341
|
+
const subFnName = `${this.currentFn}/as_${this.foreachCounter++}`;
|
|
1342
|
+
// Check for impossible type assertions (W_IMPOSSIBLE_AS)
|
|
1343
|
+
const innerType = (0, entity_hierarchy_1.getBaseSelectorType)(selector);
|
|
1344
|
+
const outerType = this.currentEntityContext();
|
|
1345
|
+
if (innerType && outerType !== 'Entity' && innerType !== 'Entity' && !(0, entity_hierarchy_1.areCompatibleTypes)(outerType, innerType)) {
|
|
1346
|
+
this.warnings.push({
|
|
1347
|
+
message: `Impossible type assertion: @s is ${outerType} but as-block targets ${innerType}`,
|
|
1348
|
+
code: 'W_IMPOSSIBLE_AS',
|
|
1349
|
+
line: stmt.span?.line,
|
|
1350
|
+
col: stmt.span?.col,
|
|
1351
|
+
});
|
|
1352
|
+
}
|
|
1353
|
+
this.builder.emitRaw(`execute as ${selector} run function ${this.namespace}:${subFnName}`);
|
|
1354
|
+
// Create sub-function
|
|
1355
|
+
const savedBuilder = this.builder;
|
|
1356
|
+
const savedVarMap = new Map(this.varMap);
|
|
1357
|
+
const savedBlockPosVars = new Map(this.blockPosVars);
|
|
1358
|
+
this.builder = new LoweringBuilder();
|
|
1359
|
+
this.varMap = new Map(savedVarMap);
|
|
1360
|
+
this.blockPosVars = new Map(savedBlockPosVars);
|
|
1361
|
+
// Track entity context inside as-block
|
|
1362
|
+
if (innerType) {
|
|
1363
|
+
this.entityContextStack.push(innerType);
|
|
1364
|
+
}
|
|
1365
|
+
this.builder.startBlock('entry');
|
|
1366
|
+
this.lowerBlock(stmt.body);
|
|
1367
|
+
if (!this.builder.isBlockSealed()) {
|
|
1368
|
+
this.builder.emitReturn();
|
|
1369
|
+
}
|
|
1370
|
+
if (innerType) {
|
|
1371
|
+
this.entityContextStack.pop();
|
|
1372
|
+
}
|
|
1373
|
+
const subFn = this.builder.build(subFnName, [], false);
|
|
1374
|
+
this.functions.push(subFn);
|
|
1375
|
+
this.builder = savedBuilder;
|
|
1376
|
+
this.varMap = savedVarMap;
|
|
1377
|
+
this.blockPosVars = savedBlockPosVars;
|
|
1378
|
+
}
|
|
1379
|
+
lowerAtBlockStmt(stmt) {
|
|
1380
|
+
const selector = this.selectorToString(stmt.selector);
|
|
1381
|
+
const subFnName = `${this.currentFn}/at_${this.foreachCounter++}`;
|
|
1382
|
+
this.builder.emitRaw(`execute at ${selector} run function ${this.namespace}:${subFnName}`);
|
|
1383
|
+
// Create sub-function
|
|
1384
|
+
const savedBuilder = this.builder;
|
|
1385
|
+
const savedVarMap = new Map(this.varMap);
|
|
1386
|
+
const savedBlockPosVars = new Map(this.blockPosVars);
|
|
1387
|
+
this.builder = new LoweringBuilder();
|
|
1388
|
+
this.varMap = new Map(savedVarMap);
|
|
1389
|
+
this.blockPosVars = new Map(savedBlockPosVars);
|
|
1390
|
+
this.builder.startBlock('entry');
|
|
1391
|
+
this.lowerBlock(stmt.body);
|
|
1392
|
+
if (!this.builder.isBlockSealed()) {
|
|
1393
|
+
this.builder.emitReturn();
|
|
1394
|
+
}
|
|
1395
|
+
const subFn = this.builder.build(subFnName, [], false);
|
|
1396
|
+
this.functions.push(subFn);
|
|
1397
|
+
this.builder = savedBuilder;
|
|
1398
|
+
this.varMap = savedVarMap;
|
|
1399
|
+
this.blockPosVars = savedBlockPosVars;
|
|
1400
|
+
}
|
|
1401
|
+
lowerAsAtStmt(stmt) {
|
|
1402
|
+
const asSel = this.selectorToString(stmt.as_sel);
|
|
1403
|
+
const atSel = this.selectorToString(stmt.at_sel);
|
|
1404
|
+
const subFnName = `${this.currentFn}/as_at_${this.foreachCounter++}`;
|
|
1405
|
+
this.builder.emitRaw(`execute as ${asSel} at ${atSel} run function ${this.namespace}:${subFnName}`);
|
|
1406
|
+
// Create sub-function
|
|
1407
|
+
const savedBuilder = this.builder;
|
|
1408
|
+
const savedVarMap = new Map(this.varMap);
|
|
1409
|
+
const savedBlockPosVars = new Map(this.blockPosVars);
|
|
1410
|
+
this.builder = new LoweringBuilder();
|
|
1411
|
+
this.varMap = new Map(savedVarMap);
|
|
1412
|
+
this.blockPosVars = new Map(savedBlockPosVars);
|
|
1413
|
+
this.builder.startBlock('entry');
|
|
1414
|
+
this.lowerBlock(stmt.body);
|
|
1415
|
+
if (!this.builder.isBlockSealed()) {
|
|
1416
|
+
this.builder.emitReturn();
|
|
1417
|
+
}
|
|
1418
|
+
const subFn = this.builder.build(subFnName, [], false);
|
|
1419
|
+
this.functions.push(subFn);
|
|
1420
|
+
this.builder = savedBuilder;
|
|
1421
|
+
this.varMap = savedVarMap;
|
|
1422
|
+
this.blockPosVars = savedBlockPosVars;
|
|
1423
|
+
}
|
|
1424
|
+
lowerExecuteStmt(stmt) {
|
|
1425
|
+
// Build the execute prefix from subcommands
|
|
1426
|
+
const parts = ['execute'];
|
|
1427
|
+
for (const sub of stmt.subcommands) {
|
|
1428
|
+
switch (sub.kind) {
|
|
1429
|
+
// Context modifiers
|
|
1430
|
+
case 'as':
|
|
1431
|
+
parts.push(`as ${this.selectorToString(sub.selector)}`);
|
|
1432
|
+
break;
|
|
1433
|
+
case 'at':
|
|
1434
|
+
parts.push(`at ${this.selectorToString(sub.selector)}`);
|
|
1435
|
+
break;
|
|
1436
|
+
case 'positioned':
|
|
1437
|
+
parts.push(`positioned ${sub.x} ${sub.y} ${sub.z}`);
|
|
1438
|
+
break;
|
|
1439
|
+
case 'positioned_as':
|
|
1440
|
+
parts.push(`positioned as ${this.selectorToString(sub.selector)}`);
|
|
1441
|
+
break;
|
|
1442
|
+
case 'rotated':
|
|
1443
|
+
parts.push(`rotated ${sub.yaw} ${sub.pitch}`);
|
|
1444
|
+
break;
|
|
1445
|
+
case 'rotated_as':
|
|
1446
|
+
parts.push(`rotated as ${this.selectorToString(sub.selector)}`);
|
|
1447
|
+
break;
|
|
1448
|
+
case 'facing':
|
|
1449
|
+
parts.push(`facing ${sub.x} ${sub.y} ${sub.z}`);
|
|
1450
|
+
break;
|
|
1451
|
+
case 'facing_entity':
|
|
1452
|
+
parts.push(`facing entity ${this.selectorToString(sub.selector)} ${sub.anchor}`);
|
|
1453
|
+
break;
|
|
1454
|
+
case 'anchored':
|
|
1455
|
+
parts.push(`anchored ${sub.anchor}`);
|
|
1456
|
+
break;
|
|
1457
|
+
case 'align':
|
|
1458
|
+
parts.push(`align ${sub.axes}`);
|
|
1459
|
+
break;
|
|
1460
|
+
case 'in':
|
|
1461
|
+
parts.push(`in ${sub.dimension}`);
|
|
1462
|
+
break;
|
|
1463
|
+
case 'on':
|
|
1464
|
+
parts.push(`on ${sub.relation}`);
|
|
1465
|
+
break;
|
|
1466
|
+
case 'summon':
|
|
1467
|
+
parts.push(`summon ${sub.entity}`);
|
|
1468
|
+
break;
|
|
1469
|
+
// Conditions
|
|
1470
|
+
case 'if_entity':
|
|
1471
|
+
if (sub.selector) {
|
|
1472
|
+
parts.push(`if entity ${this.selectorToString(sub.selector)}`);
|
|
1473
|
+
}
|
|
1474
|
+
else if (sub.varName) {
|
|
1475
|
+
const sel = { kind: '@s', filters: sub.filters };
|
|
1476
|
+
parts.push(`if entity ${this.selectorToString(sel)}`);
|
|
1477
|
+
}
|
|
1478
|
+
break;
|
|
1479
|
+
case 'unless_entity':
|
|
1480
|
+
if (sub.selector) {
|
|
1481
|
+
parts.push(`unless entity ${this.selectorToString(sub.selector)}`);
|
|
1482
|
+
}
|
|
1483
|
+
else if (sub.varName) {
|
|
1484
|
+
const sel = { kind: '@s', filters: sub.filters };
|
|
1485
|
+
parts.push(`unless entity ${this.selectorToString(sel)}`);
|
|
1486
|
+
}
|
|
1487
|
+
break;
|
|
1488
|
+
case 'if_block':
|
|
1489
|
+
parts.push(`if block ${sub.pos[0]} ${sub.pos[1]} ${sub.pos[2]} ${sub.block}`);
|
|
1490
|
+
break;
|
|
1491
|
+
case 'unless_block':
|
|
1492
|
+
parts.push(`unless block ${sub.pos[0]} ${sub.pos[1]} ${sub.pos[2]} ${sub.block}`);
|
|
1493
|
+
break;
|
|
1494
|
+
case 'if_score':
|
|
1495
|
+
parts.push(`if score ${sub.target} ${sub.targetObj} ${sub.op} ${sub.source} ${sub.sourceObj}`);
|
|
1496
|
+
break;
|
|
1497
|
+
case 'unless_score':
|
|
1498
|
+
parts.push(`unless score ${sub.target} ${sub.targetObj} ${sub.op} ${sub.source} ${sub.sourceObj}`);
|
|
1499
|
+
break;
|
|
1500
|
+
case 'if_score_range':
|
|
1501
|
+
parts.push(`if score ${sub.target} ${sub.targetObj} matches ${sub.range}`);
|
|
1502
|
+
break;
|
|
1503
|
+
case 'unless_score_range':
|
|
1504
|
+
parts.push(`unless score ${sub.target} ${sub.targetObj} matches ${sub.range}`);
|
|
1505
|
+
break;
|
|
1506
|
+
// Store
|
|
1507
|
+
case 'store_result':
|
|
1508
|
+
parts.push(`store result score ${sub.target} ${sub.targetObj}`);
|
|
1509
|
+
break;
|
|
1510
|
+
case 'store_success':
|
|
1511
|
+
parts.push(`store success score ${sub.target} ${sub.targetObj}`);
|
|
1512
|
+
break;
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
const subFnName = `${this.currentFn}/exec_${this.foreachCounter++}`;
|
|
1516
|
+
this.builder.emitRaw(`${parts.join(' ')} run function ${this.namespace}:${subFnName}`);
|
|
1517
|
+
// Create sub-function for the body
|
|
1518
|
+
const savedBuilder = this.builder;
|
|
1519
|
+
const savedVarMap = new Map(this.varMap);
|
|
1520
|
+
const savedBlockPosVars = new Map(this.blockPosVars);
|
|
1521
|
+
this.builder = new LoweringBuilder();
|
|
1522
|
+
this.varMap = new Map(savedVarMap);
|
|
1523
|
+
this.blockPosVars = new Map(savedBlockPosVars);
|
|
1524
|
+
this.builder.startBlock('entry');
|
|
1525
|
+
this.lowerBlock(stmt.body);
|
|
1526
|
+
if (!this.builder.isBlockSealed()) {
|
|
1527
|
+
this.builder.emitReturn();
|
|
1528
|
+
}
|
|
1529
|
+
const subFn = this.builder.build(subFnName, [], false);
|
|
1530
|
+
this.functions.push(subFn);
|
|
1531
|
+
this.builder = savedBuilder;
|
|
1532
|
+
this.varMap = savedVarMap;
|
|
1533
|
+
this.blockPosVars = savedBlockPosVars;
|
|
1534
|
+
}
|
|
1535
|
+
// -------------------------------------------------------------------------
|
|
1536
|
+
// Expression Lowering
|
|
1537
|
+
// -------------------------------------------------------------------------
|
|
1538
|
+
lowerExpr(expr) {
|
|
1539
|
+
switch (expr.kind) {
|
|
1540
|
+
case 'int_lit':
|
|
1541
|
+
return { kind: 'const', value: expr.value };
|
|
1542
|
+
case 'float_lit':
|
|
1543
|
+
// Float stored as fixed-point × 1000
|
|
1544
|
+
return { kind: 'const', value: Math.round(expr.value * 1000) };
|
|
1545
|
+
case 'byte_lit':
|
|
1546
|
+
return { kind: 'const', value: expr.value };
|
|
1547
|
+
case 'short_lit':
|
|
1548
|
+
return { kind: 'const', value: expr.value };
|
|
1549
|
+
case 'long_lit':
|
|
1550
|
+
return { kind: 'const', value: expr.value };
|
|
1551
|
+
case 'double_lit':
|
|
1552
|
+
return { kind: 'const', value: Math.round(expr.value * 1000) };
|
|
1553
|
+
case 'bool_lit':
|
|
1554
|
+
return { kind: 'const', value: expr.value ? 1 : 0 };
|
|
1555
|
+
case 'str_lit':
|
|
1556
|
+
// Strings are handled inline in builtins
|
|
1557
|
+
return { kind: 'const', value: 0 }; // Placeholder
|
|
1558
|
+
case 'mc_name':
|
|
1559
|
+
// MC names (#health, #red) treated as string constants
|
|
1560
|
+
return { kind: 'const', value: 0 }; // Handled inline in exprToString
|
|
1561
|
+
case 'str_interp':
|
|
1562
|
+
case 'f_string':
|
|
1563
|
+
// Interpolated strings are handled inline in message builtins.
|
|
1564
|
+
return { kind: 'const', value: 0 };
|
|
1565
|
+
case 'range_lit':
|
|
1566
|
+
// Ranges are handled in context (selectors, etc.)
|
|
1567
|
+
return { kind: 'const', value: 0 };
|
|
1568
|
+
case 'blockpos':
|
|
1569
|
+
return { kind: 'const', value: 0 };
|
|
1570
|
+
case 'ident': {
|
|
1571
|
+
const constValue = this.constValues.get(expr.name);
|
|
1572
|
+
if (constValue) {
|
|
1573
|
+
return this.lowerConstLiteral(constValue);
|
|
1574
|
+
}
|
|
1575
|
+
const mapped = this.varMap.get(expr.name);
|
|
1576
|
+
if (mapped) {
|
|
1577
|
+
// Check if it's a selector reference (like @s)
|
|
1578
|
+
if (mapped.startsWith('@')) {
|
|
1579
|
+
return { kind: 'var', name: mapped };
|
|
1580
|
+
}
|
|
1581
|
+
return { kind: 'var', name: mapped };
|
|
1582
|
+
}
|
|
1583
|
+
return { kind: 'var', name: `$${expr.name}` };
|
|
1584
|
+
}
|
|
1585
|
+
case 'member':
|
|
1586
|
+
if (expr.obj.kind === 'ident' && this.enumDefs.has(expr.obj.name)) {
|
|
1587
|
+
const variants = this.enumDefs.get(expr.obj.name);
|
|
1588
|
+
const value = variants.get(expr.field);
|
|
1589
|
+
if (value === undefined) {
|
|
1590
|
+
throw new Error(`Unknown enum variant ${expr.obj.name}.${expr.field}`);
|
|
1591
|
+
}
|
|
1592
|
+
return { kind: 'const', value };
|
|
1593
|
+
}
|
|
1594
|
+
return this.lowerMemberExpr(expr);
|
|
1595
|
+
case 'selector':
|
|
1596
|
+
// Selectors are handled inline in builtins
|
|
1597
|
+
return { kind: 'var', name: this.selectorToString(expr.sel) };
|
|
1598
|
+
case 'binary':
|
|
1599
|
+
return this.lowerBinaryExpr(expr);
|
|
1600
|
+
case 'is_check':
|
|
1601
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', "'is' checks are only supported as if conditions", expr.span ?? { line: 0, col: 0 });
|
|
1602
|
+
case 'unary':
|
|
1603
|
+
return this.lowerUnaryExpr(expr);
|
|
1604
|
+
case 'assign':
|
|
1605
|
+
return this.lowerAssignExpr(expr);
|
|
1606
|
+
case 'call':
|
|
1607
|
+
return this.lowerCallExpr(expr);
|
|
1608
|
+
case 'static_call':
|
|
1609
|
+
return this.lowerStaticCallExpr(expr);
|
|
1610
|
+
case 'invoke':
|
|
1611
|
+
return this.lowerInvokeExpr(expr);
|
|
1612
|
+
case 'member_assign':
|
|
1613
|
+
return this.lowerMemberAssign(expr);
|
|
1614
|
+
case 'index':
|
|
1615
|
+
return this.lowerIndexExpr(expr);
|
|
1616
|
+
case 'struct_lit':
|
|
1617
|
+
// Struct literals should be handled in let statement
|
|
1618
|
+
return { kind: 'const', value: 0 };
|
|
1619
|
+
case 'array_lit':
|
|
1620
|
+
// Array literals should be handled in let statement
|
|
1621
|
+
return { kind: 'const', value: 0 };
|
|
1622
|
+
case 'lambda':
|
|
1623
|
+
throw new Error('Lambda expressions must be used in a function context');
|
|
1624
|
+
}
|
|
1625
|
+
throw new Error(`Unhandled expression kind: ${expr.kind}`);
|
|
1626
|
+
}
|
|
1627
|
+
lowerMemberExpr(expr) {
|
|
1628
|
+
// Check if this is a struct field access
|
|
1629
|
+
if (expr.obj.kind === 'ident') {
|
|
1630
|
+
const varType = this.varTypes.get(expr.obj.name);
|
|
1631
|
+
// Check for world object handle (entity selector)
|
|
1632
|
+
const mapped = this.varMap.get(expr.obj.name);
|
|
1633
|
+
if (mapped && mapped.startsWith('@e[tag=__rs_obj_')) {
|
|
1634
|
+
// World object field access → scoreboard get
|
|
1635
|
+
const dst = this.builder.freshTemp();
|
|
1636
|
+
this.builder.emitRaw(`scoreboard players operation ${dst} ${exports.LOWERING_OBJ} = ${mapped} ${exports.LOWERING_OBJ}`);
|
|
1637
|
+
return { kind: 'var', name: dst };
|
|
1638
|
+
}
|
|
1639
|
+
if (varType?.kind === 'struct') {
|
|
1640
|
+
const structName = varType.name.toLowerCase();
|
|
1641
|
+
const path = `rs:heap ${structName}_${expr.obj.name}.${expr.field}`;
|
|
1642
|
+
const dst = this.builder.freshTemp();
|
|
1643
|
+
// Read from NBT storage into scoreboard
|
|
1644
|
+
this.builder.emitRaw(`execute store result score ${dst} ${exports.LOWERING_OBJ} run data get storage ${path}`);
|
|
1645
|
+
return { kind: 'var', name: dst };
|
|
1646
|
+
}
|
|
1647
|
+
// Array length property
|
|
1648
|
+
if (varType?.kind === 'array' && expr.field === 'len') {
|
|
1649
|
+
const dst = this.builder.freshTemp();
|
|
1650
|
+
this.builder.emitRaw(`execute store result score ${dst} ${exports.LOWERING_OBJ} run data get storage rs:heap ${expr.obj.name}`);
|
|
1651
|
+
return { kind: 'var', name: dst };
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
// Default behavior: simple member access
|
|
1655
|
+
return { kind: 'var', name: `$${expr.obj.name}_${expr.field}` };
|
|
1656
|
+
}
|
|
1657
|
+
lowerMemberAssign(expr) {
|
|
1658
|
+
if (expr.obj.kind === 'ident') {
|
|
1659
|
+
const varType = this.varTypes.get(expr.obj.name);
|
|
1660
|
+
// Check for world object handle
|
|
1661
|
+
const mapped = this.varMap.get(expr.obj.name);
|
|
1662
|
+
if (mapped && mapped.startsWith('@e[tag=__rs_obj_')) {
|
|
1663
|
+
const value = this.lowerExpr(expr.value);
|
|
1664
|
+
if (expr.op === '=') {
|
|
1665
|
+
if (value.kind === 'const') {
|
|
1666
|
+
this.builder.emitRaw(`scoreboard players set ${mapped} ${exports.LOWERING_OBJ} ${value.value}`);
|
|
1667
|
+
}
|
|
1668
|
+
else if (value.kind === 'var') {
|
|
1669
|
+
this.builder.emitRaw(`scoreboard players operation ${mapped} ${exports.LOWERING_OBJ} = ${value.name} ${exports.LOWERING_OBJ}`);
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
else {
|
|
1673
|
+
// Compound assignment
|
|
1674
|
+
const binOp = expr.op.slice(0, -1);
|
|
1675
|
+
const opMap = { '+': '+=', '-': '-=', '*': '*=', '/': '/=', '%': '%=' };
|
|
1676
|
+
if (value.kind === 'const') {
|
|
1677
|
+
const constTemp = this.builder.freshTemp();
|
|
1678
|
+
this.builder.emitAssign(constTemp, value);
|
|
1679
|
+
this.builder.emitRaw(`scoreboard players operation ${mapped} ${exports.LOWERING_OBJ} ${opMap[binOp]} ${constTemp} ${exports.LOWERING_OBJ}`);
|
|
1680
|
+
}
|
|
1681
|
+
else if (value.kind === 'var') {
|
|
1682
|
+
this.builder.emitRaw(`scoreboard players operation ${mapped} ${exports.LOWERING_OBJ} ${opMap[binOp]} ${value.name} ${exports.LOWERING_OBJ}`);
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
return { kind: 'const', value: 0 };
|
|
1686
|
+
}
|
|
1687
|
+
if (varType?.kind === 'struct') {
|
|
1688
|
+
const structName = varType.name.toLowerCase();
|
|
1689
|
+
const path = `rs:heap ${structName}_${expr.obj.name}.${expr.field}`;
|
|
1690
|
+
const value = this.lowerExpr(expr.value);
|
|
1691
|
+
if (expr.op === '=') {
|
|
1692
|
+
if (value.kind === 'const') {
|
|
1693
|
+
this.builder.emitRaw(`data modify storage ${path} set value ${value.value}`);
|
|
1694
|
+
}
|
|
1695
|
+
else if (value.kind === 'var') {
|
|
1696
|
+
this.builder.emitRaw(`execute store result storage ${path} int 1 run scoreboard players get ${value.name} ${exports.LOWERING_OBJ}`);
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
else {
|
|
1700
|
+
// Compound assignment: read, modify, write back
|
|
1701
|
+
const dst = this.builder.freshTemp();
|
|
1702
|
+
this.builder.emitRaw(`execute store result score ${dst} ${exports.LOWERING_OBJ} run data get storage ${path}`);
|
|
1703
|
+
const binOp = expr.op.slice(0, -1);
|
|
1704
|
+
this.builder.emitBinop(dst, { kind: 'var', name: dst }, binOp, value);
|
|
1705
|
+
this.builder.emitRaw(`execute store result storage ${path} int 1 run scoreboard players get ${dst} ${exports.LOWERING_OBJ}`);
|
|
1706
|
+
}
|
|
1707
|
+
return { kind: 'const', value: 0 };
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
// Default: simple assignment
|
|
1711
|
+
const varName = `$${expr.obj.name}_${expr.field}`;
|
|
1712
|
+
const value = this.lowerExpr(expr.value);
|
|
1713
|
+
this.builder.emitAssign(varName, value);
|
|
1714
|
+
return { kind: 'var', name: varName };
|
|
1715
|
+
}
|
|
1716
|
+
lowerIndexExpr(expr) {
|
|
1717
|
+
const arrayName = this.getArrayStorageName(expr.obj);
|
|
1718
|
+
if (arrayName) {
|
|
1719
|
+
return this.readArrayElement(arrayName, this.lowerExpr(expr.index));
|
|
1720
|
+
}
|
|
1721
|
+
return { kind: 'const', value: 0 };
|
|
1722
|
+
}
|
|
1723
|
+
lowerBinaryExpr(expr) {
|
|
1724
|
+
const left = this.lowerExpr(expr.left);
|
|
1725
|
+
const right = this.lowerExpr(expr.right);
|
|
1726
|
+
const dst = this.builder.freshTemp();
|
|
1727
|
+
if (['&&', '||'].includes(expr.op)) {
|
|
1728
|
+
// Logical operators need special handling
|
|
1729
|
+
if (expr.op === '&&') {
|
|
1730
|
+
// Short-circuit AND
|
|
1731
|
+
this.builder.emitAssign(dst, left);
|
|
1732
|
+
const rightVar = this.operandToVar(right);
|
|
1733
|
+
// dst = dst && right → if dst != 0 then dst = right
|
|
1734
|
+
this.builder.emitRaw(`execute if score ${dst} ${exports.LOWERING_OBJ} matches 1.. run scoreboard players operation ${dst} ${exports.LOWERING_OBJ} = ${rightVar} ${exports.LOWERING_OBJ}`);
|
|
1735
|
+
}
|
|
1736
|
+
else {
|
|
1737
|
+
// Short-circuit OR
|
|
1738
|
+
this.builder.emitAssign(dst, left);
|
|
1739
|
+
const rightVar = this.operandToVar(right);
|
|
1740
|
+
// dst = dst || right → if dst == 0 then dst = right
|
|
1741
|
+
this.builder.emitRaw(`execute if score ${dst} ${exports.LOWERING_OBJ} matches ..0 run scoreboard players operation ${dst} ${exports.LOWERING_OBJ} = ${rightVar} ${exports.LOWERING_OBJ}`);
|
|
1742
|
+
}
|
|
1743
|
+
return { kind: 'var', name: dst };
|
|
1744
|
+
}
|
|
1745
|
+
if (['==', '!=', '<', '<=', '>', '>='].includes(expr.op)) {
|
|
1746
|
+
this.builder.emitCmp(dst, left, expr.op, right);
|
|
1747
|
+
}
|
|
1748
|
+
else {
|
|
1749
|
+
// Check if this is float arithmetic
|
|
1750
|
+
const isFloatOp = this.isFloatExpr(expr.left) || this.isFloatExpr(expr.right);
|
|
1751
|
+
if (isFloatOp && (expr.op === '*' || expr.op === '/')) {
|
|
1752
|
+
// Float multiplication: a * b / 1000
|
|
1753
|
+
// Float division: a * 1000 / b
|
|
1754
|
+
if (expr.op === '*') {
|
|
1755
|
+
this.builder.emitBinop(dst, left, '*', right);
|
|
1756
|
+
// Divide by 1000 to correct for double scaling
|
|
1757
|
+
const constDiv = this.builder.freshTemp();
|
|
1758
|
+
this.builder.emitAssign(constDiv, { kind: 'const', value: 1000 });
|
|
1759
|
+
this.builder.emitRaw(`scoreboard players operation ${dst} ${exports.LOWERING_OBJ} /= ${constDiv} ${exports.LOWERING_OBJ}`);
|
|
1760
|
+
}
|
|
1761
|
+
else {
|
|
1762
|
+
// Division: a * 1000 / b
|
|
1763
|
+
const constMul = this.builder.freshTemp();
|
|
1764
|
+
this.builder.emitAssign(constMul, { kind: 'const', value: 1000 });
|
|
1765
|
+
this.builder.emitAssign(dst, left);
|
|
1766
|
+
this.builder.emitRaw(`scoreboard players operation ${dst} ${exports.LOWERING_OBJ} *= ${constMul} ${exports.LOWERING_OBJ}`);
|
|
1767
|
+
const rightVar = this.operandToVar(right);
|
|
1768
|
+
this.builder.emitRaw(`scoreboard players operation ${dst} ${exports.LOWERING_OBJ} /= ${rightVar} ${exports.LOWERING_OBJ}`);
|
|
1769
|
+
}
|
|
1770
|
+
return { kind: 'var', name: dst };
|
|
1771
|
+
}
|
|
1772
|
+
this.builder.emitBinop(dst, left, expr.op, right);
|
|
1773
|
+
}
|
|
1774
|
+
return { kind: 'var', name: dst };
|
|
1775
|
+
}
|
|
1776
|
+
isFloatExpr(expr) {
|
|
1777
|
+
if (expr.kind === 'float_lit')
|
|
1778
|
+
return true;
|
|
1779
|
+
if (expr.kind === 'ident') {
|
|
1780
|
+
return this.floatVars.has(expr.name);
|
|
1781
|
+
}
|
|
1782
|
+
if (expr.kind === 'binary') {
|
|
1783
|
+
return this.isFloatExpr(expr.left) || this.isFloatExpr(expr.right);
|
|
1784
|
+
}
|
|
1785
|
+
return false;
|
|
1786
|
+
}
|
|
1787
|
+
lowerUnaryExpr(expr) {
|
|
1788
|
+
const operand = this.lowerExpr(expr.operand);
|
|
1789
|
+
const dst = this.builder.freshTemp();
|
|
1790
|
+
if (expr.op === '!') {
|
|
1791
|
+
// Logical NOT: dst = (operand == 0) ? 1 : 0
|
|
1792
|
+
this.builder.emitCmp(dst, operand, '==', { kind: 'const', value: 0 });
|
|
1793
|
+
}
|
|
1794
|
+
else if (expr.op === '-') {
|
|
1795
|
+
// Negation: dst = 0 - operand
|
|
1796
|
+
this.builder.emitBinop(dst, { kind: 'const', value: 0 }, '-', operand);
|
|
1797
|
+
}
|
|
1798
|
+
return { kind: 'var', name: dst };
|
|
1799
|
+
}
|
|
1800
|
+
lowerAssignExpr(expr) {
|
|
1801
|
+
// Check for const reassignment (both compile-time consts and immutable globals)
|
|
1802
|
+
if (this.constValues.has(expr.target)) {
|
|
1803
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', `Cannot assign to constant '${expr.target}'`, getSpan(expr) ?? { line: 1, col: 1 });
|
|
1804
|
+
}
|
|
1805
|
+
const globalInfo = this.globalNames.get(expr.target);
|
|
1806
|
+
if (globalInfo && !globalInfo.mutable) {
|
|
1807
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', `Cannot assign to constant '${expr.target}'`, getSpan(expr) ?? { line: 1, col: 1 });
|
|
1808
|
+
}
|
|
1809
|
+
const blockPosValue = this.resolveBlockPosExpr(expr.value);
|
|
1810
|
+
if (blockPosValue) {
|
|
1811
|
+
this.blockPosVars.set(expr.target, blockPosValue);
|
|
1812
|
+
return { kind: 'const', value: 0 };
|
|
1813
|
+
}
|
|
1814
|
+
this.blockPosVars.delete(expr.target);
|
|
1815
|
+
const targetType = this.varTypes.get(expr.target);
|
|
1816
|
+
if (targetType?.kind === 'named' && targetType.name === 'string' && this.storeStringValue(expr.target, expr.value)) {
|
|
1817
|
+
return { kind: 'const', value: 0 };
|
|
1818
|
+
}
|
|
1819
|
+
const varName = this.varMap.get(expr.target) ?? `$${expr.target}`;
|
|
1820
|
+
const value = this.lowerExpr(expr.value);
|
|
1821
|
+
if (expr.op === '=') {
|
|
1822
|
+
this.builder.emitAssign(varName, value);
|
|
1823
|
+
}
|
|
1824
|
+
else {
|
|
1825
|
+
// Compound assignment
|
|
1826
|
+
const binOp = expr.op.slice(0, -1); // Remove '='
|
|
1827
|
+
const dst = this.builder.freshTemp();
|
|
1828
|
+
this.builder.emitBinop(dst, { kind: 'var', name: varName }, binOp, value);
|
|
1829
|
+
this.builder.emitAssign(varName, { kind: 'var', name: dst });
|
|
1830
|
+
}
|
|
1831
|
+
return { kind: 'var', name: varName };
|
|
1832
|
+
}
|
|
1833
|
+
lowerCallExpr(expr) {
|
|
1834
|
+
if (expr.fn === 'str_len') {
|
|
1835
|
+
const storagePath = this.getStringStoragePath(expr.args[0]);
|
|
1836
|
+
if (storagePath) {
|
|
1837
|
+
const dst = this.builder.freshTemp();
|
|
1838
|
+
this.builder.emitRaw(`execute store result score ${dst} ${exports.LOWERING_OBJ} run data get storage ${storagePath}`);
|
|
1839
|
+
return { kind: 'var', name: dst };
|
|
1840
|
+
}
|
|
1841
|
+
const staticString = this.resolveStaticString(expr.args[0]);
|
|
1842
|
+
if (staticString !== null) {
|
|
1843
|
+
return { kind: 'const', value: Array.from(staticString).length };
|
|
1844
|
+
}
|
|
1845
|
+
else {
|
|
1846
|
+
const dst = this.builder.freshTemp();
|
|
1847
|
+
this.builder.emitAssign(dst, { kind: 'const', value: 0 });
|
|
1848
|
+
return { kind: 'var', name: dst };
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
// Check for builtin
|
|
1852
|
+
if (expr.fn in BUILTINS) {
|
|
1853
|
+
return this.lowerBuiltinCall(expr.fn, expr.args, getSpan(expr));
|
|
1854
|
+
}
|
|
1855
|
+
// Handle entity methods: __entity_tag, __entity_untag, __entity_has_tag
|
|
1856
|
+
if (expr.fn === '__entity_tag') {
|
|
1857
|
+
const entity = this.exprToString(expr.args[0]);
|
|
1858
|
+
const tagName = this.exprToString(expr.args[1]);
|
|
1859
|
+
this.builder.emitRaw(`tag ${entity} add ${tagName}`);
|
|
1860
|
+
return { kind: 'const', value: 0 };
|
|
1861
|
+
}
|
|
1862
|
+
if (expr.fn === '__entity_untag') {
|
|
1863
|
+
const entity = this.exprToString(expr.args[0]);
|
|
1864
|
+
const tagName = this.exprToString(expr.args[1]);
|
|
1865
|
+
this.builder.emitRaw(`tag ${entity} remove ${tagName}`);
|
|
1866
|
+
return { kind: 'const', value: 0 };
|
|
1867
|
+
}
|
|
1868
|
+
if (expr.fn === '__entity_has_tag') {
|
|
1869
|
+
const entity = this.exprToString(expr.args[0]);
|
|
1870
|
+
const tagName = this.exprToString(expr.args[1]);
|
|
1871
|
+
const dst = this.builder.freshTemp();
|
|
1872
|
+
this.builder.emitRaw(`execute store result score ${dst} ${exports.LOWERING_OBJ} if entity ${entity}[tag=${tagName}]`);
|
|
1873
|
+
return { kind: 'var', name: dst };
|
|
1874
|
+
}
|
|
1875
|
+
// Handle array push
|
|
1876
|
+
if (expr.fn === '__array_push') {
|
|
1877
|
+
const arrExpr = expr.args[0];
|
|
1878
|
+
const valueExpr = expr.args[1];
|
|
1879
|
+
const arrName = this.getArrayStorageName(arrExpr);
|
|
1880
|
+
if (arrName) {
|
|
1881
|
+
const value = this.lowerExpr(valueExpr);
|
|
1882
|
+
if (value.kind === 'const') {
|
|
1883
|
+
this.builder.emitRaw(`data modify storage rs:heap ${arrName} append value ${value.value}`);
|
|
1884
|
+
}
|
|
1885
|
+
else if (value.kind === 'var') {
|
|
1886
|
+
this.builder.emitRaw(`data modify storage rs:heap ${arrName} append value 0`);
|
|
1887
|
+
this.builder.emitRaw(`execute store result storage rs:heap ${arrName}[-1] int 1 run scoreboard players get ${value.name} ${exports.LOWERING_OBJ}`);
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
return { kind: 'const', value: 0 };
|
|
1891
|
+
}
|
|
1892
|
+
if (expr.fn === '__array_pop') {
|
|
1893
|
+
const arrName = this.getArrayStorageName(expr.args[0]);
|
|
1894
|
+
const dst = this.builder.freshTemp();
|
|
1895
|
+
if (arrName) {
|
|
1896
|
+
this.builder.emitRaw(`execute store result score ${dst} ${exports.LOWERING_OBJ} run data get storage rs:heap ${arrName}[-1]`);
|
|
1897
|
+
this.builder.emitRaw(`data remove storage rs:heap ${arrName}[-1]`);
|
|
1898
|
+
}
|
|
1899
|
+
else {
|
|
1900
|
+
this.builder.emitAssign(dst, { kind: 'const', value: 0 });
|
|
1901
|
+
}
|
|
1902
|
+
return { kind: 'var', name: dst };
|
|
1903
|
+
}
|
|
1904
|
+
// Handle spawn_object - creates world object (invisible armor stand)
|
|
1905
|
+
if (expr.fn === 'spawn_object') {
|
|
1906
|
+
const x = this.exprToString(expr.args[0]);
|
|
1907
|
+
const y = this.exprToString(expr.args[1]);
|
|
1908
|
+
const z = this.exprToString(expr.args[2]);
|
|
1909
|
+
const tag = `__rs_obj_${this.worldObjCounter++}`;
|
|
1910
|
+
this.builder.emitRaw(`summon minecraft:armor_stand ${x} ${y} ${z} {Invisible:1b,Marker:1b,NoGravity:1b,Tags:["${tag}"]}`);
|
|
1911
|
+
// Return a selector pointing to this entity
|
|
1912
|
+
const selector = `@e[tag=${tag},limit=1]`;
|
|
1913
|
+
return { kind: 'var', name: selector };
|
|
1914
|
+
}
|
|
1915
|
+
// Handle kill for world objects
|
|
1916
|
+
if (expr.fn === 'kill' && expr.args.length === 1 && expr.args[0].kind === 'ident') {
|
|
1917
|
+
const mapped = this.varMap.get(expr.args[0].name);
|
|
1918
|
+
if (mapped && mapped.startsWith('@e[tag=__rs_obj_')) {
|
|
1919
|
+
this.builder.emitRaw(`kill ${mapped}`);
|
|
1920
|
+
return { kind: 'const', value: 0 };
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
const callbackTarget = this.resolveFunctionRefByName(expr.fn);
|
|
1924
|
+
if (callbackTarget) {
|
|
1925
|
+
return this.emitDirectFunctionCall(callbackTarget, expr.args);
|
|
1926
|
+
}
|
|
1927
|
+
const implMethod = this.resolveInstanceMethod(expr);
|
|
1928
|
+
if (implMethod) {
|
|
1929
|
+
// Copy struct fields from instance to 'self' storage before calling
|
|
1930
|
+
const receiver = expr.args[0];
|
|
1931
|
+
if (receiver?.kind === 'ident') {
|
|
1932
|
+
const receiverType = this.inferExprType(receiver);
|
|
1933
|
+
if (receiverType?.kind === 'struct') {
|
|
1934
|
+
const structDecl = this.structDecls.get(receiverType.name);
|
|
1935
|
+
const structName = receiverType.name.toLowerCase();
|
|
1936
|
+
if (structDecl) {
|
|
1937
|
+
for (const field of structDecl.fields) {
|
|
1938
|
+
const srcPath = `rs:heap ${structName}_${receiver.name}.${field.name}`;
|
|
1939
|
+
const dstPath = `rs:heap ${structName}_self.${field.name}`;
|
|
1940
|
+
this.builder.emitRaw(`data modify storage ${dstPath} set from storage ${srcPath}`);
|
|
1941
|
+
}
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1945
|
+
return this.emitMethodCall(implMethod.loweredName, implMethod.fn, expr.args);
|
|
1946
|
+
}
|
|
1947
|
+
// Regular function call
|
|
1948
|
+
const fnDecl = this.fnDecls.get(expr.fn);
|
|
1949
|
+
const defaultArgs = this.functionDefaults.get(expr.fn) ?? [];
|
|
1950
|
+
const fullArgs = [...expr.args];
|
|
1951
|
+
for (let i = fullArgs.length; i < defaultArgs.length; i++) {
|
|
1952
|
+
const defaultExpr = defaultArgs[i];
|
|
1953
|
+
if (!defaultExpr) {
|
|
1954
|
+
break;
|
|
1955
|
+
}
|
|
1956
|
+
fullArgs.push(defaultExpr);
|
|
1957
|
+
}
|
|
1958
|
+
if (fnDecl) {
|
|
1959
|
+
const callbackBindings = new Map();
|
|
1960
|
+
const runtimeArgs = [];
|
|
1961
|
+
for (let i = 0; i < fullArgs.length; i++) {
|
|
1962
|
+
const param = fnDecl.params[i];
|
|
1963
|
+
if (param && this.normalizeType(param.type).kind === 'function_type') {
|
|
1964
|
+
const functionRef = this.resolveFunctionRefExpr(fullArgs[i]);
|
|
1965
|
+
if (!functionRef) {
|
|
1966
|
+
throw new Error(`Cannot lower callback argument for parameter '${param.name}'`);
|
|
1967
|
+
}
|
|
1968
|
+
callbackBindings.set(param.name, functionRef);
|
|
1969
|
+
continue;
|
|
1970
|
+
}
|
|
1971
|
+
runtimeArgs.push(fullArgs[i]);
|
|
1972
|
+
}
|
|
1973
|
+
const stdlibCallSite = this.getStdlibCallSiteContext(fnDecl, getSpan(expr));
|
|
1974
|
+
const targetFn = callbackBindings.size > 0 || stdlibCallSite
|
|
1975
|
+
? this.ensureSpecializedFunctionWithContext(fnDecl, callbackBindings, stdlibCallSite)
|
|
1976
|
+
: expr.fn;
|
|
1977
|
+
// Check if this is a call to a known macro function
|
|
1978
|
+
const macroParams = this.macroFunctionInfo.get(targetFn);
|
|
1979
|
+
if (macroParams && macroParams.length > 0) {
|
|
1980
|
+
return this.emitMacroFunctionCall(targetFn, runtimeArgs, macroParams, fnDecl);
|
|
1981
|
+
}
|
|
1982
|
+
return this.emitDirectFunctionCall(targetFn, runtimeArgs);
|
|
1983
|
+
}
|
|
1984
|
+
// Check for macro function (forward-declared or external)
|
|
1985
|
+
const macroParamsForUnknown = this.macroFunctionInfo.get(expr.fn);
|
|
1986
|
+
if (macroParamsForUnknown && macroParamsForUnknown.length > 0) {
|
|
1987
|
+
return this.emitMacroFunctionCall(expr.fn, fullArgs, macroParamsForUnknown, undefined);
|
|
1988
|
+
}
|
|
1989
|
+
return this.emitDirectFunctionCall(expr.fn, fullArgs);
|
|
1990
|
+
}
|
|
1991
|
+
lowerStaticCallExpr(expr) {
|
|
1992
|
+
const method = this.implMethods.get(expr.type)?.get(expr.method);
|
|
1993
|
+
const targetFn = method?.loweredName ?? `${expr.type}_${expr.method}`;
|
|
1994
|
+
return this.emitMethodCall(targetFn, method?.fn, expr.args);
|
|
1995
|
+
}
|
|
1996
|
+
lowerInvokeExpr(expr) {
|
|
1997
|
+
if (expr.callee.kind === 'lambda') {
|
|
1998
|
+
if (!Array.isArray(expr.callee.body)) {
|
|
1999
|
+
return this.inlineLambdaInvoke(expr.callee, expr.args);
|
|
2000
|
+
}
|
|
2001
|
+
const lambdaName = this.lowerLambdaExpr(expr.callee);
|
|
2002
|
+
return this.emitDirectFunctionCall(lambdaName, expr.args);
|
|
2003
|
+
}
|
|
2004
|
+
const functionRef = this.resolveFunctionRefExpr(expr.callee);
|
|
2005
|
+
if (!functionRef) {
|
|
2006
|
+
throw new Error('Cannot invoke a non-function value');
|
|
2007
|
+
}
|
|
2008
|
+
return this.emitDirectFunctionCall(functionRef, expr.args);
|
|
2009
|
+
}
|
|
2010
|
+
inlineLambdaInvoke(expr, args) {
|
|
2011
|
+
const savedVarMap = new Map(this.varMap);
|
|
2012
|
+
const savedVarTypes = new Map(this.varTypes);
|
|
2013
|
+
const savedLambdaBindings = new Map(this.lambdaBindings);
|
|
2014
|
+
const savedBlockPosVars = new Map(this.blockPosVars);
|
|
2015
|
+
for (let i = 0; i < expr.params.length; i++) {
|
|
2016
|
+
const param = expr.params[i];
|
|
2017
|
+
const temp = this.builder.freshTemp();
|
|
2018
|
+
const arg = args[i];
|
|
2019
|
+
this.builder.emitAssign(temp, arg ? this.lowerExpr(arg) : { kind: 'const', value: 0 });
|
|
2020
|
+
this.varMap.set(param.name, temp);
|
|
2021
|
+
if (param.type) {
|
|
2022
|
+
this.varTypes.set(param.name, this.normalizeType(param.type));
|
|
2023
|
+
}
|
|
2024
|
+
this.lambdaBindings.delete(param.name);
|
|
2025
|
+
this.blockPosVars.delete(param.name);
|
|
2026
|
+
}
|
|
2027
|
+
const result = this.lowerExpr(expr.body);
|
|
2028
|
+
this.varMap = savedVarMap;
|
|
2029
|
+
this.varTypes = savedVarTypes;
|
|
2030
|
+
this.lambdaBindings = savedLambdaBindings;
|
|
2031
|
+
this.blockPosVars = savedBlockPosVars;
|
|
2032
|
+
return result;
|
|
2033
|
+
}
|
|
2034
|
+
emitDirectFunctionCall(fn, args) {
|
|
2035
|
+
const loweredArgs = args.map(arg => this.lowerExpr(arg));
|
|
2036
|
+
const dst = this.builder.freshTemp();
|
|
2037
|
+
this.builder.emitCall(fn, loweredArgs, dst);
|
|
2038
|
+
return { kind: 'var', name: dst };
|
|
2039
|
+
}
|
|
2040
|
+
emitMethodCall(fn, fnDecl, args) {
|
|
2041
|
+
const defaultArgs = this.functionDefaults.get(fn) ?? fnDecl?.params.map(param => param.default) ?? [];
|
|
2042
|
+
const fullArgs = [...args];
|
|
2043
|
+
for (let i = fullArgs.length; i < defaultArgs.length; i++) {
|
|
2044
|
+
const defaultExpr = defaultArgs[i];
|
|
2045
|
+
if (!defaultExpr) {
|
|
2046
|
+
break;
|
|
2047
|
+
}
|
|
2048
|
+
fullArgs.push(defaultExpr);
|
|
2049
|
+
}
|
|
2050
|
+
return this.emitDirectFunctionCall(fn, fullArgs);
|
|
2051
|
+
}
|
|
2052
|
+
resolveFunctionRefExpr(expr) {
|
|
2053
|
+
if (expr.kind === 'lambda') {
|
|
2054
|
+
return this.lowerLambdaExpr(expr);
|
|
2055
|
+
}
|
|
2056
|
+
if (expr.kind === 'ident') {
|
|
2057
|
+
return this.resolveFunctionRefByName(expr.name) ?? (this.fnDecls.has(expr.name) ? expr.name : null);
|
|
2058
|
+
}
|
|
2059
|
+
return null;
|
|
2060
|
+
}
|
|
2061
|
+
resolveFunctionRefByName(name) {
|
|
2062
|
+
return this.lambdaBindings.get(name) ?? this.currentCallbackBindings.get(name) ?? null;
|
|
2063
|
+
}
|
|
2064
|
+
ensureSpecializedFunction(fn, callbackBindings) {
|
|
2065
|
+
return this.ensureSpecializedFunctionWithContext(fn, callbackBindings);
|
|
2066
|
+
}
|
|
2067
|
+
ensureSpecializedFunctionWithContext(fn, callbackBindings, stdlibCallSite) {
|
|
2068
|
+
const parts = [...callbackBindings.entries()]
|
|
2069
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
2070
|
+
.map(([param, target]) => `${param}_${target.replace(/[^a-zA-Z0-9_]/g, '_')}`);
|
|
2071
|
+
const callSiteHash = stdlibCallSite ? this.shortHash(this.serializeCallSite(stdlibCallSite)) : null;
|
|
2072
|
+
if (callSiteHash) {
|
|
2073
|
+
parts.push(`callsite_${callSiteHash}`);
|
|
2074
|
+
}
|
|
2075
|
+
const key = `${fn.name}::${parts.join('::')}`;
|
|
2076
|
+
const cached = this.specializedFunctions.get(key);
|
|
2077
|
+
if (cached) {
|
|
2078
|
+
return cached;
|
|
2079
|
+
}
|
|
2080
|
+
const specializedName = `${fn.name}__${parts.join('__')}`;
|
|
2081
|
+
this.specializedFunctions.set(key, specializedName);
|
|
2082
|
+
this.withSavedFunctionState(() => {
|
|
2083
|
+
this.lowerFn(fn, { name: specializedName, callbackBindings, stdlibCallSite });
|
|
2084
|
+
});
|
|
2085
|
+
return specializedName;
|
|
2086
|
+
}
|
|
2087
|
+
lowerLambdaExpr(expr) {
|
|
2088
|
+
const lambdaName = `__lambda_${this.lambdaCounter++}`;
|
|
2089
|
+
const lambdaFn = {
|
|
2090
|
+
name: lambdaName,
|
|
2091
|
+
params: expr.params.map(param => ({
|
|
2092
|
+
name: param.name,
|
|
2093
|
+
type: param.type ?? { kind: 'named', name: 'int' },
|
|
2094
|
+
})),
|
|
2095
|
+
returnType: expr.returnType ?? this.inferLambdaReturnType(expr),
|
|
2096
|
+
decorators: [],
|
|
2097
|
+
body: Array.isArray(expr.body) ? expr.body : [{ kind: 'return', value: expr.body }],
|
|
2098
|
+
};
|
|
2099
|
+
this.withSavedFunctionState(() => {
|
|
2100
|
+
this.lowerFn(lambdaFn);
|
|
2101
|
+
});
|
|
2102
|
+
return lambdaName;
|
|
2103
|
+
}
|
|
2104
|
+
withSavedFunctionState(callback) {
|
|
2105
|
+
const savedCurrentFn = this.currentFn;
|
|
2106
|
+
const savedStdlibCallSite = this.currentStdlibCallSite;
|
|
2107
|
+
const savedForeachCounter = this.foreachCounter;
|
|
2108
|
+
const savedBuilder = this.builder;
|
|
2109
|
+
const savedVarMap = new Map(this.varMap);
|
|
2110
|
+
const savedLambdaBindings = new Map(this.lambdaBindings);
|
|
2111
|
+
const savedIntervalBindings = new Map(this.intervalBindings);
|
|
2112
|
+
const savedCallbackBindings = new Map(this.currentCallbackBindings);
|
|
2113
|
+
const savedContext = this.currentContext;
|
|
2114
|
+
const savedBlockPosVars = new Map(this.blockPosVars);
|
|
2115
|
+
const savedStringValues = new Map(this.stringValues);
|
|
2116
|
+
const savedVarTypes = new Map(this.varTypes);
|
|
2117
|
+
// Macro tracking state
|
|
2118
|
+
const savedCurrentFnParamNames = new Set(this.currentFnParamNames);
|
|
2119
|
+
const savedCurrentFnMacroParams = new Set(this.currentFnMacroParams);
|
|
2120
|
+
try {
|
|
2121
|
+
return callback();
|
|
2122
|
+
}
|
|
2123
|
+
finally {
|
|
2124
|
+
this.currentFn = savedCurrentFn;
|
|
2125
|
+
this.currentStdlibCallSite = savedStdlibCallSite;
|
|
2126
|
+
this.foreachCounter = savedForeachCounter;
|
|
2127
|
+
this.builder = savedBuilder;
|
|
2128
|
+
this.varMap = savedVarMap;
|
|
2129
|
+
this.lambdaBindings = savedLambdaBindings;
|
|
2130
|
+
this.intervalBindings = savedIntervalBindings;
|
|
2131
|
+
this.currentCallbackBindings = savedCallbackBindings;
|
|
2132
|
+
this.currentContext = savedContext;
|
|
2133
|
+
this.blockPosVars = savedBlockPosVars;
|
|
2134
|
+
this.stringValues = savedStringValues;
|
|
2135
|
+
this.varTypes = savedVarTypes;
|
|
2136
|
+
this.currentFnParamNames = savedCurrentFnParamNames;
|
|
2137
|
+
this.currentFnMacroParams = savedCurrentFnMacroParams;
|
|
2138
|
+
}
|
|
2139
|
+
}
|
|
2140
|
+
lowerBuiltinCall(name, args, callSpan) {
|
|
2141
|
+
const richTextCommand = this.lowerRichTextBuiltin(name, args);
|
|
2142
|
+
if (richTextCommand) {
|
|
2143
|
+
this.builder.emitRaw(richTextCommand);
|
|
2144
|
+
return { kind: 'const', value: 0 };
|
|
2145
|
+
}
|
|
2146
|
+
if (name === 'setTimeout') {
|
|
2147
|
+
return this.lowerSetTimeout(args);
|
|
2148
|
+
}
|
|
2149
|
+
if (name === 'setInterval') {
|
|
2150
|
+
return this.lowerSetInterval(args);
|
|
2151
|
+
}
|
|
2152
|
+
if (name === 'clearInterval') {
|
|
2153
|
+
return this.lowerClearInterval(args, callSpan);
|
|
2154
|
+
}
|
|
2155
|
+
// Special case: random - legacy scoreboard RNG for pre-1.20.3 compatibility
|
|
2156
|
+
if (name === 'random') {
|
|
2157
|
+
const dst = this.builder.freshTemp();
|
|
2158
|
+
const min = args[0] ? this.exprToLiteral(args[0]) : '0';
|
|
2159
|
+
const max = args[1] ? this.exprToLiteral(args[1]) : '100';
|
|
2160
|
+
this.builder.emitRaw(`scoreboard players random ${dst} ${exports.LOWERING_OBJ} ${min} ${max}`);
|
|
2161
|
+
return { kind: 'var', name: dst };
|
|
2162
|
+
}
|
|
2163
|
+
// Special case: random_native - /random value (MC 1.20.3+)
|
|
2164
|
+
if (name === 'random_native') {
|
|
2165
|
+
const dst = this.builder.freshTemp();
|
|
2166
|
+
const min = args[0] ? this.exprToLiteral(args[0]) : '0';
|
|
2167
|
+
const max = args[1] ? this.exprToLiteral(args[1]) : '100';
|
|
2168
|
+
this.builder.emitRaw(`execute store result score ${dst} ${exports.LOWERING_OBJ} run random value ${min} ${max}`);
|
|
2169
|
+
return { kind: 'var', name: dst };
|
|
2170
|
+
}
|
|
2171
|
+
// Special case: random_sequence - /random reset (MC 1.20.3+)
|
|
2172
|
+
if (name === 'random_sequence') {
|
|
2173
|
+
const sequence = this.exprToString(args[0]);
|
|
2174
|
+
const seed = args[1] ? this.exprToLiteral(args[1]) : '0';
|
|
2175
|
+
this.builder.emitRaw(`random reset ${sequence} ${seed}`);
|
|
2176
|
+
return { kind: 'const', value: 0 };
|
|
2177
|
+
}
|
|
2178
|
+
// Special case: scoreboard_get / score — read from vanilla MC scoreboard
|
|
2179
|
+
if (name === 'scoreboard_get' || name === 'score') {
|
|
2180
|
+
const dst = this.builder.freshTemp();
|
|
2181
|
+
const player = this.exprToTargetString(args[0]);
|
|
2182
|
+
const objective = this.resolveScoreboardObjective(args[0], args[1], callSpan);
|
|
2183
|
+
this.builder.emitRaw(`execute store result score ${dst} ${exports.LOWERING_OBJ} run scoreboard players get ${player} ${objective}`);
|
|
2184
|
+
return { kind: 'var', name: dst };
|
|
2185
|
+
}
|
|
2186
|
+
// Special case: scoreboard_set — write to vanilla MC scoreboard
|
|
2187
|
+
if (name === 'scoreboard_set') {
|
|
2188
|
+
const player = this.exprToTargetString(args[0]);
|
|
2189
|
+
const objective = this.resolveScoreboardObjective(args[0], args[1], callSpan);
|
|
2190
|
+
const value = this.lowerExpr(args[2]);
|
|
2191
|
+
if (value.kind === 'const') {
|
|
2192
|
+
this.builder.emitRaw(`scoreboard players set ${player} ${objective} ${value.value}`);
|
|
2193
|
+
}
|
|
2194
|
+
else if (value.kind === 'var') {
|
|
2195
|
+
// Read directly from the computed scoreboard temp. Routing through a fresh
|
|
2196
|
+
// temp here breaks once optimization removes the apparently-dead assign.
|
|
2197
|
+
this.builder.emitRaw(`execute store result score ${player} ${objective} run scoreboard players get ${value.name} ${exports.LOWERING_OBJ}`);
|
|
2198
|
+
}
|
|
2199
|
+
return { kind: 'const', value: 0 };
|
|
2200
|
+
}
|
|
2201
|
+
if (name === 'scoreboard_display') {
|
|
2202
|
+
const slot = this.exprToString(args[0]);
|
|
2203
|
+
const objective = this.resolveScoreboardObjective(undefined, args[1], callSpan);
|
|
2204
|
+
this.builder.emitRaw(`scoreboard objectives setdisplay ${slot} ${objective}`);
|
|
2205
|
+
return { kind: 'const', value: 0 };
|
|
2206
|
+
}
|
|
2207
|
+
if (name === 'scoreboard_hide') {
|
|
2208
|
+
const slot = this.exprToString(args[0]);
|
|
2209
|
+
this.builder.emitRaw(`scoreboard objectives setdisplay ${slot}`);
|
|
2210
|
+
return { kind: 'const', value: 0 };
|
|
2211
|
+
}
|
|
2212
|
+
if (name === 'scoreboard_add_objective') {
|
|
2213
|
+
const objective = this.resolveScoreboardObjective(undefined, args[0], callSpan);
|
|
2214
|
+
const criteria = this.exprToString(args[1]);
|
|
2215
|
+
const displayName = args[2] ? ` ${this.exprToQuotedString(args[2])}` : '';
|
|
2216
|
+
this.builder.emitRaw(`scoreboard objectives add ${objective} ${criteria}${displayName}`);
|
|
2217
|
+
return { kind: 'const', value: 0 };
|
|
2218
|
+
}
|
|
2219
|
+
if (name === 'scoreboard_remove_objective') {
|
|
2220
|
+
const objective = this.resolveScoreboardObjective(undefined, args[0], callSpan);
|
|
2221
|
+
this.builder.emitRaw(`scoreboard objectives remove ${objective}`);
|
|
2222
|
+
return { kind: 'const', value: 0 };
|
|
2223
|
+
}
|
|
2224
|
+
if (name === 'bossbar_add') {
|
|
2225
|
+
const id = this.exprToString(args[0]);
|
|
2226
|
+
const title = this.exprToTextComponent(args[1]);
|
|
2227
|
+
this.builder.emitRaw(`bossbar add ${id} ${title}`);
|
|
2228
|
+
return { kind: 'const', value: 0 };
|
|
2229
|
+
}
|
|
2230
|
+
if (name === 'bossbar_set_value') {
|
|
2231
|
+
this.builder.emitRaw(`bossbar set ${this.exprToString(args[0])} value ${this.exprToString(args[1])}`);
|
|
2232
|
+
return { kind: 'const', value: 0 };
|
|
2233
|
+
}
|
|
2234
|
+
if (name === 'bossbar_set_max') {
|
|
2235
|
+
this.builder.emitRaw(`bossbar set ${this.exprToString(args[0])} max ${this.exprToString(args[1])}`);
|
|
2236
|
+
return { kind: 'const', value: 0 };
|
|
2237
|
+
}
|
|
2238
|
+
if (name === 'bossbar_set_color') {
|
|
2239
|
+
this.builder.emitRaw(`bossbar set ${this.exprToString(args[0])} color ${this.exprToString(args[1])}`);
|
|
2240
|
+
return { kind: 'const', value: 0 };
|
|
2241
|
+
}
|
|
2242
|
+
if (name === 'bossbar_set_style') {
|
|
2243
|
+
this.builder.emitRaw(`bossbar set ${this.exprToString(args[0])} style ${this.exprToString(args[1])}`);
|
|
2244
|
+
return { kind: 'const', value: 0 };
|
|
2245
|
+
}
|
|
2246
|
+
if (name === 'bossbar_set_visible') {
|
|
2247
|
+
this.builder.emitRaw(`bossbar set ${this.exprToString(args[0])} visible ${this.exprToBoolString(args[1])}`);
|
|
2248
|
+
return { kind: 'const', value: 0 };
|
|
2249
|
+
}
|
|
2250
|
+
if (name === 'bossbar_set_players') {
|
|
2251
|
+
this.builder.emitRaw(`bossbar set ${this.exprToString(args[0])} players ${this.exprToTargetString(args[1])}`);
|
|
2252
|
+
return { kind: 'const', value: 0 };
|
|
2253
|
+
}
|
|
2254
|
+
if (name === 'bossbar_remove') {
|
|
2255
|
+
this.builder.emitRaw(`bossbar remove ${this.exprToString(args[0])}`);
|
|
2256
|
+
return { kind: 'const', value: 0 };
|
|
2257
|
+
}
|
|
2258
|
+
if (name === 'bossbar_get_value') {
|
|
2259
|
+
const dst = this.builder.freshTemp();
|
|
2260
|
+
this.builder.emitRaw(`execute store result score ${dst} ${exports.LOWERING_OBJ} run bossbar get ${this.exprToString(args[0])} value`);
|
|
2261
|
+
return { kind: 'var', name: dst };
|
|
2262
|
+
}
|
|
2263
|
+
if (name === 'team_add') {
|
|
2264
|
+
const team = this.exprToString(args[0]);
|
|
2265
|
+
const displayName = args[1] ? ` ${this.exprToTextComponent(args[1])}` : '';
|
|
2266
|
+
this.builder.emitRaw(`team add ${team}${displayName}`);
|
|
2267
|
+
return { kind: 'const', value: 0 };
|
|
2268
|
+
}
|
|
2269
|
+
if (name === 'team_remove') {
|
|
2270
|
+
this.builder.emitRaw(`team remove ${this.exprToString(args[0])}`);
|
|
2271
|
+
return { kind: 'const', value: 0 };
|
|
2272
|
+
}
|
|
2273
|
+
if (name === 'team_join') {
|
|
2274
|
+
this.builder.emitRaw(`team join ${this.exprToString(args[0])} ${this.exprToTargetString(args[1])}`);
|
|
2275
|
+
return { kind: 'const', value: 0 };
|
|
2276
|
+
}
|
|
2277
|
+
if (name === 'team_leave') {
|
|
2278
|
+
this.builder.emitRaw(`team leave ${this.exprToTargetString(args[0])}`);
|
|
2279
|
+
return { kind: 'const', value: 0 };
|
|
2280
|
+
}
|
|
2281
|
+
if (name === 'team_option') {
|
|
2282
|
+
const team = this.exprToString(args[0]);
|
|
2283
|
+
const option = this.exprToString(args[1]);
|
|
2284
|
+
const value = this.isTeamTextOption(option)
|
|
2285
|
+
? this.exprToTextComponent(args[2])
|
|
2286
|
+
: this.exprToString(args[2]);
|
|
2287
|
+
this.builder.emitRaw(`team modify ${team} ${option} ${value}`);
|
|
2288
|
+
return { kind: 'const', value: 0 };
|
|
2289
|
+
}
|
|
2290
|
+
// Special case: data_get — read NBT data into a variable
|
|
2291
|
+
// data_get(target_type, target, path, scale?)
|
|
2292
|
+
// target_type: "entity", "block", "storage"
|
|
2293
|
+
if (name === 'data_get') {
|
|
2294
|
+
const dst = this.builder.freshTemp();
|
|
2295
|
+
const targetType = this.exprToString(args[0]);
|
|
2296
|
+
const target = targetType === 'entity'
|
|
2297
|
+
? this.exprToTargetString(args[1])
|
|
2298
|
+
: this.exprToString(args[1]);
|
|
2299
|
+
const path = this.exprToString(args[2]);
|
|
2300
|
+
const scale = args[3] ? this.exprToString(args[3]) : '1';
|
|
2301
|
+
this.builder.emitRaw(`execute store result score ${dst} ${exports.LOWERING_OBJ} run data get ${targetType} ${target} ${path} ${scale}`);
|
|
2302
|
+
return { kind: 'var', name: dst };
|
|
2303
|
+
}
|
|
2304
|
+
// storage_get_int(storage_ns, array_key, index) -> int
|
|
2305
|
+
// Reads one element from an NBT int-array stored in data storage.
|
|
2306
|
+
// storage_ns : e.g. "math:tables"
|
|
2307
|
+
// array_key : e.g. "sin"
|
|
2308
|
+
// index : integer index (const or runtime)
|
|
2309
|
+
//
|
|
2310
|
+
// Const index: execute store result score $dst ${LOWERING_OBJ} run data get storage math:tables sin[N] 1
|
|
2311
|
+
// Runtime index: macro sub-function via rs:heap, mirrors readArrayElement.
|
|
2312
|
+
if (name === 'storage_get_int') {
|
|
2313
|
+
const storageNs = this.exprToString(args[0]); // "math:tables"
|
|
2314
|
+
const arrayKey = this.exprToString(args[1]); // "sin"
|
|
2315
|
+
const indexOperand = this.lowerExpr(args[2]);
|
|
2316
|
+
const dst = this.builder.freshTemp();
|
|
2317
|
+
if (indexOperand.kind === 'const') {
|
|
2318
|
+
this.builder.emitRaw(`execute store result score ${dst} ${exports.LOWERING_OBJ} run data get storage ${storageNs} ${arrayKey}[${indexOperand.value}] 1`);
|
|
2319
|
+
}
|
|
2320
|
+
else {
|
|
2321
|
+
// Runtime index: store the index into rs:heap under a unique key,
|
|
2322
|
+
// then call a macro sub-function that uses $(key) to index the array.
|
|
2323
|
+
const macroKey = `__sgi_${this.foreachCounter++}`;
|
|
2324
|
+
const subFnName = `${this.currentFn}/__sgi_${this.foreachCounter++}`;
|
|
2325
|
+
const indexVar = indexOperand.kind === 'var'
|
|
2326
|
+
? indexOperand.name
|
|
2327
|
+
: this.operandToVar(indexOperand);
|
|
2328
|
+
this.builder.emitRaw(`execute store result storage rs:heap ${macroKey} int 1 run scoreboard players get ${indexVar} ${exports.LOWERING_OBJ}`);
|
|
2329
|
+
this.builder.emitRaw(`function ${this.namespace}:${subFnName} with storage rs:heap`);
|
|
2330
|
+
// Prefix \x01 is a sentinel for the MC macro '$' line-start marker.
|
|
2331
|
+
// We avoid using literal '$execute' here so the pre-alloc pass
|
|
2332
|
+
// doesn't mistakenly register 'execute' as a scoreboard variable.
|
|
2333
|
+
// Codegen replaces \x01 → '$' when emitting the mc function file.
|
|
2334
|
+
this.emitRawSubFunction(subFnName, `\x01execute store result score ${dst} ${exports.LOWERING_OBJ} run data get storage ${storageNs} ${arrayKey}[$(${macroKey})] 1`);
|
|
2335
|
+
}
|
|
2336
|
+
return { kind: 'var', name: dst };
|
|
2337
|
+
}
|
|
2338
|
+
// storage_set_array(storage_ns, array_key, nbt_array_literal)
|
|
2339
|
+
// Writes a literal NBT int array to data storage (used in @load for tables).
|
|
2340
|
+
// storage_set_array("math:tables", "sin", "[0, 17, 35, ...]")
|
|
2341
|
+
if (name === 'storage_set_array') {
|
|
2342
|
+
const storageNs = this.exprToString(args[0]);
|
|
2343
|
+
const arrayKey = this.exprToString(args[1]);
|
|
2344
|
+
const nbtLiteral = this.exprToString(args[2]);
|
|
2345
|
+
this.builder.emitRaw(`data modify storage ${storageNs} ${arrayKey} set value ${nbtLiteral}`);
|
|
2346
|
+
return { kind: 'const', value: 0 };
|
|
2347
|
+
}
|
|
2348
|
+
// storage_set_int(storage_ns, array_key, index, value) -> void
|
|
2349
|
+
// Writes one integer element into an NBT int-array stored in data storage.
|
|
2350
|
+
// storage_ns : e.g. "rs:bigint"
|
|
2351
|
+
// array_key : e.g. "a"
|
|
2352
|
+
// index : element index (const or runtime)
|
|
2353
|
+
// value : integer value to write (const or runtime)
|
|
2354
|
+
//
|
|
2355
|
+
// Const index + const value:
|
|
2356
|
+
// execute store result storage <ns> <key>[N] int 1 run scoreboard players set $const_V ${LOWERING_OBJ} V
|
|
2357
|
+
// Runtime index or value: macro sub-function via rs:heap
|
|
2358
|
+
if (name === 'storage_set_int') {
|
|
2359
|
+
const storageNs = this.exprToString(args[0]);
|
|
2360
|
+
const arrayKey = this.exprToString(args[1]);
|
|
2361
|
+
const indexOperand = this.lowerExpr(args[2]);
|
|
2362
|
+
const valueOperand = this.lowerExpr(args[3]);
|
|
2363
|
+
if (indexOperand.kind === 'const') {
|
|
2364
|
+
// Static index — use execute store result to write to the fixed slot
|
|
2365
|
+
const valVar = valueOperand.kind === 'var'
|
|
2366
|
+
? valueOperand.name
|
|
2367
|
+
: this.operandToVar(valueOperand);
|
|
2368
|
+
this.builder.emitRaw(`execute store result storage ${storageNs} ${arrayKey}[${indexOperand.value}] int 1 run scoreboard players get ${valVar} ${exports.LOWERING_OBJ}`);
|
|
2369
|
+
}
|
|
2370
|
+
else {
|
|
2371
|
+
// Runtime index: we need a macro sub-function.
|
|
2372
|
+
// Store index + value into rs:heap, call macro that does:
|
|
2373
|
+
// $data modify storage <ns> <key>[$(idx_key)] set value $(val_key)
|
|
2374
|
+
const macroIdxKey = `__ssi_i_${this.foreachCounter++}`;
|
|
2375
|
+
const macroValKey = `__ssi_v_${this.foreachCounter++}`; // kept to pin valVar in optimizer
|
|
2376
|
+
const subFnName = `${this.currentFn}/__ssi_${this.foreachCounter++}`;
|
|
2377
|
+
const indexVar = indexOperand.kind === 'var'
|
|
2378
|
+
? indexOperand.name
|
|
2379
|
+
: this.operandToVar(indexOperand);
|
|
2380
|
+
const valVar = valueOperand.kind === 'var'
|
|
2381
|
+
? valueOperand.name
|
|
2382
|
+
: this.operandToVar(valueOperand);
|
|
2383
|
+
this.builder.emitRaw(`execute store result storage rs:heap ${macroIdxKey} int 1 run scoreboard players get ${indexVar} ${exports.LOWERING_OBJ}`);
|
|
2384
|
+
// Pin valVar in the optimizer's read-set so the assignment is not dead-code-eliminated.
|
|
2385
|
+
// The value is stored to rs:heap but NOT used by the macro (the macro reads the scoreboard
|
|
2386
|
+
// slot directly to avoid the MC 'data modify set value $(n)' macro substitution bug).
|
|
2387
|
+
this.builder.emitRaw(`execute store result storage rs:heap ${macroValKey} int 1 run scoreboard players get ${valVar} ${exports.LOWERING_OBJ}`);
|
|
2388
|
+
this.builder.emitRaw(`function ${this.namespace}:${subFnName} with storage rs:heap`);
|
|
2389
|
+
// Use execute store result (not 'data modify set value $(val)') to avoid MC macro
|
|
2390
|
+
// substitution bugs with numeric values. The scoreboard slot ${valVar} is hardcoded
|
|
2391
|
+
// into the macro sub-function at compile time — only the array index is macro-substituted.
|
|
2392
|
+
this.emitRawSubFunction(subFnName, `\x01execute store result storage ${storageNs} ${arrayKey}[$(${macroIdxKey})] int 1 run scoreboard players get ${valVar} ${exports.LOWERING_OBJ}`);
|
|
2393
|
+
}
|
|
2394
|
+
return { kind: 'const', value: 0 };
|
|
2395
|
+
}
|
|
2396
|
+
// data_merge(target, nbt) — merge NBT into entity/block/storage
|
|
2397
|
+
// data_merge(@s, { Invisible: 1b, Silent: 1b })
|
|
2398
|
+
if (name === 'data_merge') {
|
|
2399
|
+
const target = args[0];
|
|
2400
|
+
const nbt = args[1];
|
|
2401
|
+
const nbtStr = this.exprToSnbt ? this.exprToSnbt(nbt) : this.exprToString(nbt);
|
|
2402
|
+
// Check if target is a selector (entity) or string (block/storage)
|
|
2403
|
+
if (target.kind === 'selector') {
|
|
2404
|
+
const sel = this.exprToTargetString(target);
|
|
2405
|
+
this.builder.emitRaw(`data merge entity ${sel} ${nbtStr}`);
|
|
2406
|
+
}
|
|
2407
|
+
else {
|
|
2408
|
+
// Assume block position or storage
|
|
2409
|
+
const targetStr = this.exprToString(target);
|
|
2410
|
+
// If it looks like coordinates, use block; otherwise storage
|
|
2411
|
+
if (targetStr.match(/^~|^\d|^\^/)) {
|
|
2412
|
+
this.builder.emitRaw(`data merge block ${targetStr} ${nbtStr}`);
|
|
2413
|
+
}
|
|
2414
|
+
else {
|
|
2415
|
+
this.builder.emitRaw(`data merge storage ${targetStr} ${nbtStr}`);
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
return { kind: 'const', value: 0 };
|
|
2419
|
+
}
|
|
2420
|
+
// Set data structure operations — unique collections via NBT storage
|
|
2421
|
+
// set_new is primarily handled in lowerLetStmt for proper string tracking.
|
|
2422
|
+
// This fallback handles standalone set_new() calls without assignment.
|
|
2423
|
+
if (name === 'set_new') {
|
|
2424
|
+
const setId = `__set_${this.foreachCounter++}`;
|
|
2425
|
+
this.builder.emitRaw(`data modify storage rs:sets ${setId} set value []`);
|
|
2426
|
+
return { kind: 'const', value: 0 };
|
|
2427
|
+
}
|
|
2428
|
+
if (name === 'set_add') {
|
|
2429
|
+
const setId = this.exprToString(args[0]);
|
|
2430
|
+
const value = this.exprToString(args[1]);
|
|
2431
|
+
this.builder.emitRaw(`execute unless data storage rs:sets ${setId}[{value:${value}}] run data modify storage rs:sets ${setId} append value {value:${value}}`);
|
|
2432
|
+
return { kind: 'const', value: 0 };
|
|
2433
|
+
}
|
|
2434
|
+
if (name === 'set_contains') {
|
|
2435
|
+
const dst = this.builder.freshTemp();
|
|
2436
|
+
const setId = this.exprToString(args[0]);
|
|
2437
|
+
const value = this.exprToString(args[1]);
|
|
2438
|
+
this.builder.emitRaw(`execute store result score ${dst} ${exports.LOWERING_OBJ} if data storage rs:sets ${setId}[{value:${value}}]`);
|
|
2439
|
+
return { kind: 'var', name: dst };
|
|
2440
|
+
}
|
|
2441
|
+
if (name === 'set_remove') {
|
|
2442
|
+
const setId = this.exprToString(args[0]);
|
|
2443
|
+
const value = this.exprToString(args[1]);
|
|
2444
|
+
this.builder.emitRaw(`data remove storage rs:sets ${setId}[{value:${value}}]`);
|
|
2445
|
+
return { kind: 'const', value: 0 };
|
|
2446
|
+
}
|
|
2447
|
+
if (name === 'set_clear') {
|
|
2448
|
+
const setId = this.exprToString(args[0]);
|
|
2449
|
+
this.builder.emitRaw(`data modify storage rs:sets ${setId} set value []`);
|
|
2450
|
+
return { kind: 'const', value: 0 };
|
|
2451
|
+
}
|
|
2452
|
+
const coordCommand = this.lowerCoordinateBuiltin(name, args);
|
|
2453
|
+
if (coordCommand) {
|
|
2454
|
+
this.builder.emitRaw(coordCommand);
|
|
2455
|
+
return { kind: 'const', value: 0 };
|
|
2456
|
+
}
|
|
2457
|
+
if (name === 'tp_to') {
|
|
2458
|
+
this.warnings.push({
|
|
2459
|
+
message: 'tp_to is deprecated; use tp instead',
|
|
2460
|
+
code: 'W_DEPRECATED',
|
|
2461
|
+
...(callSpan ? { line: callSpan.line, col: callSpan.col } : {}),
|
|
2462
|
+
});
|
|
2463
|
+
const tpResult = this.lowerTpCommandMacroAware(args);
|
|
2464
|
+
if (tpResult) {
|
|
2465
|
+
this.builder.emitRaw(tpResult.cmd);
|
|
2466
|
+
}
|
|
2467
|
+
return { kind: 'const', value: 0 };
|
|
2468
|
+
}
|
|
2469
|
+
if (name === 'tp') {
|
|
2470
|
+
const tpResult = this.lowerTpCommandMacroAware(args);
|
|
2471
|
+
if (tpResult) {
|
|
2472
|
+
this.builder.emitRaw(tpResult.cmd);
|
|
2473
|
+
}
|
|
2474
|
+
return { kind: 'const', value: 0 };
|
|
2475
|
+
}
|
|
2476
|
+
// All builtins support macro params - check if any arg is a param needing macro treatment
|
|
2477
|
+
const argResults = args.map(arg => this.exprToBuiltinArg(arg));
|
|
2478
|
+
const hasMacroArg = argResults.some(r => r.macroParam !== undefined);
|
|
2479
|
+
if (hasMacroArg) {
|
|
2480
|
+
argResults.forEach(r => { if (r.macroParam)
|
|
2481
|
+
this.currentFnMacroParams.add(r.macroParam); });
|
|
2482
|
+
}
|
|
2483
|
+
const strArgs = argResults.map(r => r.str);
|
|
2484
|
+
const cmd = BUILTINS[name]?.(strArgs);
|
|
2485
|
+
if (cmd) {
|
|
2486
|
+
// Use \x01 sentinel (not literal '$') so resolveRaw doesn't treat the
|
|
2487
|
+
// MC macro prefix as a variable reference and allocate a fresh temp for it.
|
|
2488
|
+
this.builder.emitRaw(hasMacroArg ? `\x01${cmd}` : cmd);
|
|
2489
|
+
}
|
|
2490
|
+
return { kind: 'const', value: 0 };
|
|
2491
|
+
}
|
|
2492
|
+
lowerSetTimeout(args) {
|
|
2493
|
+
const delay = this.exprToLiteral(args[0]);
|
|
2494
|
+
const callback = args[1];
|
|
2495
|
+
if (!callback || callback.kind !== 'lambda') {
|
|
2496
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', 'setTimeout requires a lambda callback', getSpan(callback) ?? { line: 1, col: 1 });
|
|
2497
|
+
}
|
|
2498
|
+
const fnName = `__timeout_${this.timeoutCounter++}`;
|
|
2499
|
+
this.lowerNamedLambdaFunction(fnName, callback);
|
|
2500
|
+
this.builder.emitRaw(`schedule function ${this.namespace}:${fnName} ${delay}t`);
|
|
2501
|
+
return { kind: 'const', value: 0 };
|
|
2502
|
+
}
|
|
2503
|
+
lowerSetInterval(args) {
|
|
2504
|
+
const delay = this.exprToLiteral(args[0]);
|
|
2505
|
+
const callback = args[1];
|
|
2506
|
+
if (!callback || callback.kind !== 'lambda') {
|
|
2507
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', 'setInterval requires a lambda callback', getSpan(callback) ?? { line: 1, col: 1 });
|
|
2508
|
+
}
|
|
2509
|
+
const id = this.intervalCounter++;
|
|
2510
|
+
const bodyName = `__interval_body_${id}`;
|
|
2511
|
+
const fnName = `__interval_${id}`;
|
|
2512
|
+
this.lowerNamedLambdaFunction(bodyName, callback);
|
|
2513
|
+
this.lowerIntervalWrapperFunction(fnName, bodyName, delay);
|
|
2514
|
+
this.intervalFunctions.set(id, fnName);
|
|
2515
|
+
this.builder.emitRaw(`schedule function ${this.namespace}:${fnName} ${delay}t`);
|
|
2516
|
+
return { kind: 'const', value: id };
|
|
2517
|
+
}
|
|
2518
|
+
lowerClearInterval(args, callSpan) {
|
|
2519
|
+
const fnName = this.resolveIntervalFunctionName(args[0]);
|
|
2520
|
+
if (!fnName) {
|
|
2521
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', 'clearInterval requires an interval ID returned from setInterval', callSpan ?? getSpan(args[0]) ?? { line: 1, col: 1 });
|
|
2522
|
+
}
|
|
2523
|
+
this.builder.emitRaw(`schedule clear ${this.namespace}:${fnName}`);
|
|
2524
|
+
return { kind: 'const', value: 0 };
|
|
2525
|
+
}
|
|
2526
|
+
lowerNamedLambdaFunction(name, expr) {
|
|
2527
|
+
const lambdaFn = {
|
|
2528
|
+
name,
|
|
2529
|
+
params: expr.params.map(param => ({
|
|
2530
|
+
name: param.name,
|
|
2531
|
+
type: param.type ?? { kind: 'named', name: 'int' },
|
|
2532
|
+
})),
|
|
2533
|
+
returnType: expr.returnType ?? this.inferLambdaReturnType(expr),
|
|
2534
|
+
decorators: [],
|
|
2535
|
+
body: Array.isArray(expr.body) ? expr.body : [{ kind: 'return', value: expr.body }],
|
|
2536
|
+
};
|
|
2537
|
+
this.withSavedFunctionState(() => {
|
|
2538
|
+
this.lowerFn(lambdaFn);
|
|
2539
|
+
});
|
|
2540
|
+
}
|
|
2541
|
+
lowerIntervalWrapperFunction(name, bodyName, delay) {
|
|
2542
|
+
const intervalFn = {
|
|
2543
|
+
name,
|
|
2544
|
+
params: [],
|
|
2545
|
+
returnType: { kind: 'named', name: 'void' },
|
|
2546
|
+
decorators: [],
|
|
2547
|
+
body: [
|
|
2548
|
+
{ kind: 'raw', cmd: `function ${this.namespace}:${bodyName}` },
|
|
2549
|
+
{ kind: 'raw', cmd: `schedule function ${this.namespace}:${name} ${delay}t` },
|
|
2550
|
+
],
|
|
2551
|
+
};
|
|
2552
|
+
this.withSavedFunctionState(() => {
|
|
2553
|
+
this.lowerFn(intervalFn);
|
|
2554
|
+
});
|
|
2555
|
+
}
|
|
2556
|
+
resolveIntervalFunctionName(expr) {
|
|
2557
|
+
if (!expr) {
|
|
2558
|
+
return null;
|
|
2559
|
+
}
|
|
2560
|
+
if (expr.kind === 'ident') {
|
|
2561
|
+
const boundInterval = this.intervalBindings.get(expr.name);
|
|
2562
|
+
if (boundInterval) {
|
|
2563
|
+
return boundInterval;
|
|
2564
|
+
}
|
|
2565
|
+
const constValue = this.constValues.get(expr.name);
|
|
2566
|
+
if (constValue?.kind === 'int_lit') {
|
|
2567
|
+
return this.intervalFunctions.get(constValue.value) ?? null;
|
|
2568
|
+
}
|
|
2569
|
+
return null;
|
|
2570
|
+
}
|
|
2571
|
+
if (expr.kind === 'int_lit') {
|
|
2572
|
+
return this.intervalFunctions.get(expr.value) ?? null;
|
|
2573
|
+
}
|
|
2574
|
+
return null;
|
|
2575
|
+
}
|
|
2576
|
+
lowerRichTextBuiltin(name, args) {
|
|
2577
|
+
const messageArgIndex = this.getRichTextArgIndex(name);
|
|
2578
|
+
if (messageArgIndex === null) {
|
|
2579
|
+
return null;
|
|
2580
|
+
}
|
|
2581
|
+
const messageExpr = args[messageArgIndex];
|
|
2582
|
+
if (!messageExpr || (messageExpr.kind !== 'str_interp' && messageExpr.kind !== 'f_string')) {
|
|
2583
|
+
return null;
|
|
2584
|
+
}
|
|
2585
|
+
const json = this.buildRichTextJson(messageExpr);
|
|
2586
|
+
switch (name) {
|
|
2587
|
+
case 'say':
|
|
2588
|
+
case 'announce':
|
|
2589
|
+
return `tellraw @a ${json}`;
|
|
2590
|
+
case 'tell':
|
|
2591
|
+
case 'tellraw':
|
|
2592
|
+
return `tellraw ${this.exprToString(args[0])} ${json}`;
|
|
2593
|
+
case 'title':
|
|
2594
|
+
return `title ${this.exprToString(args[0])} title ${json}`;
|
|
2595
|
+
case 'actionbar':
|
|
2596
|
+
return `title ${this.exprToString(args[0])} actionbar ${json}`;
|
|
2597
|
+
case 'subtitle':
|
|
2598
|
+
return `title ${this.exprToString(args[0])} subtitle ${json}`;
|
|
2599
|
+
default:
|
|
2600
|
+
return null;
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
getRichTextArgIndex(name) {
|
|
2604
|
+
switch (name) {
|
|
2605
|
+
case 'say':
|
|
2606
|
+
case 'announce':
|
|
2607
|
+
return 0;
|
|
2608
|
+
case 'tell':
|
|
2609
|
+
case 'tellraw':
|
|
2610
|
+
case 'title':
|
|
2611
|
+
case 'actionbar':
|
|
2612
|
+
case 'subtitle':
|
|
2613
|
+
return 1;
|
|
2614
|
+
default:
|
|
2615
|
+
return null;
|
|
2616
|
+
}
|
|
2617
|
+
}
|
|
2618
|
+
buildRichTextJson(expr) {
|
|
2619
|
+
const components = [''];
|
|
2620
|
+
if (expr.kind === 'f_string') {
|
|
2621
|
+
for (const part of expr.parts) {
|
|
2622
|
+
if (part.kind === 'text') {
|
|
2623
|
+
if (part.value.length > 0) {
|
|
2624
|
+
components.push({ text: part.value });
|
|
2625
|
+
}
|
|
2626
|
+
continue;
|
|
2627
|
+
}
|
|
2628
|
+
this.appendRichTextExpr(components, part.expr);
|
|
2629
|
+
}
|
|
2630
|
+
return JSON.stringify(components);
|
|
2631
|
+
}
|
|
2632
|
+
for (const part of expr.parts) {
|
|
2633
|
+
if (typeof part === 'string') {
|
|
2634
|
+
if (part.length > 0) {
|
|
2635
|
+
components.push({ text: part });
|
|
2636
|
+
}
|
|
2637
|
+
continue;
|
|
2638
|
+
}
|
|
2639
|
+
this.appendRichTextExpr(components, part);
|
|
2640
|
+
}
|
|
2641
|
+
return JSON.stringify(components);
|
|
2642
|
+
}
|
|
2643
|
+
appendRichTextExpr(components, expr) {
|
|
2644
|
+
if (expr.kind === 'ident') {
|
|
2645
|
+
const constValue = this.constValues.get(expr.name);
|
|
2646
|
+
if (constValue) {
|
|
2647
|
+
this.appendRichTextExpr(components, constValue);
|
|
2648
|
+
return;
|
|
2649
|
+
}
|
|
2650
|
+
const stringValue = this.stringValues.get(expr.name);
|
|
2651
|
+
if (stringValue !== undefined) {
|
|
2652
|
+
components.push({ text: stringValue });
|
|
2653
|
+
return;
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
if (expr.kind === 'str_lit') {
|
|
2657
|
+
if (expr.value.length > 0) {
|
|
2658
|
+
components.push({ text: expr.value });
|
|
2659
|
+
}
|
|
2660
|
+
return;
|
|
2661
|
+
}
|
|
2662
|
+
if (expr.kind === 'str_interp') {
|
|
2663
|
+
for (const part of expr.parts) {
|
|
2664
|
+
if (typeof part === 'string') {
|
|
2665
|
+
if (part.length > 0) {
|
|
2666
|
+
components.push({ text: part });
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
else {
|
|
2670
|
+
this.appendRichTextExpr(components, part);
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
return;
|
|
2674
|
+
}
|
|
2675
|
+
if (expr.kind === 'f_string') {
|
|
2676
|
+
for (const part of expr.parts) {
|
|
2677
|
+
if (part.kind === 'text') {
|
|
2678
|
+
if (part.value.length > 0) {
|
|
2679
|
+
components.push({ text: part.value });
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
else {
|
|
2683
|
+
this.appendRichTextExpr(components, part.expr);
|
|
2684
|
+
}
|
|
2685
|
+
}
|
|
2686
|
+
return;
|
|
2687
|
+
}
|
|
2688
|
+
if (expr.kind === 'bool_lit') {
|
|
2689
|
+
components.push({ text: expr.value ? 'true' : 'false' });
|
|
2690
|
+
return;
|
|
2691
|
+
}
|
|
2692
|
+
if (expr.kind === 'int_lit') {
|
|
2693
|
+
components.push({ text: expr.value.toString() });
|
|
2694
|
+
return;
|
|
2695
|
+
}
|
|
2696
|
+
if (expr.kind === 'float_lit') {
|
|
2697
|
+
components.push({ text: expr.value.toString() });
|
|
2698
|
+
return;
|
|
2699
|
+
}
|
|
2700
|
+
const operand = this.lowerExpr(expr);
|
|
2701
|
+
if (operand.kind === 'const') {
|
|
2702
|
+
components.push({ text: operand.value.toString() });
|
|
2703
|
+
return;
|
|
2704
|
+
}
|
|
2705
|
+
components.push({ score: { name: this.operandToVar(operand), objective: exports.LOWERING_OBJ } });
|
|
2706
|
+
}
|
|
2707
|
+
exprToString(expr) {
|
|
2708
|
+
switch (expr.kind) {
|
|
2709
|
+
case 'int_lit':
|
|
2710
|
+
return expr.value.toString();
|
|
2711
|
+
case 'float_lit':
|
|
2712
|
+
return Math.trunc(expr.value).toString();
|
|
2713
|
+
case 'byte_lit':
|
|
2714
|
+
return `${expr.value}b`;
|
|
2715
|
+
case 'short_lit':
|
|
2716
|
+
return `${expr.value}s`;
|
|
2717
|
+
case 'long_lit':
|
|
2718
|
+
return `${expr.value}L`;
|
|
2719
|
+
case 'double_lit':
|
|
2720
|
+
return `${expr.value}d`;
|
|
2721
|
+
case 'rel_coord':
|
|
2722
|
+
return expr.value; // ~ or ~5 or ~-3 - output as-is for MC commands
|
|
2723
|
+
case 'local_coord':
|
|
2724
|
+
return expr.value; // ^ or ^5 or ^-3 - output as-is for MC commands
|
|
2725
|
+
case 'bool_lit':
|
|
2726
|
+
return expr.value ? '1' : '0';
|
|
2727
|
+
case 'str_lit':
|
|
2728
|
+
return expr.value;
|
|
2729
|
+
case 'mc_name':
|
|
2730
|
+
return expr.value; // #health → "health" (no quotes, used as bare MC name)
|
|
2731
|
+
case 'str_interp':
|
|
2732
|
+
case 'f_string':
|
|
2733
|
+
return this.buildRichTextJson(expr);
|
|
2734
|
+
case 'blockpos':
|
|
2735
|
+
return emitBlockPos(expr);
|
|
2736
|
+
case 'ident': {
|
|
2737
|
+
const constValue = this.constValues.get(expr.name);
|
|
2738
|
+
if (constValue) {
|
|
2739
|
+
return this.exprToString(constValue);
|
|
2740
|
+
}
|
|
2741
|
+
const stringValue = this.stringValues.get(expr.name);
|
|
2742
|
+
if (stringValue !== undefined) {
|
|
2743
|
+
return stringValue;
|
|
2744
|
+
}
|
|
2745
|
+
const mapped = this.varMap.get(expr.name);
|
|
2746
|
+
return mapped ?? `$${expr.name}`;
|
|
2747
|
+
}
|
|
2748
|
+
case 'selector':
|
|
2749
|
+
return this.selectorToString(expr.sel);
|
|
2750
|
+
case 'unary':
|
|
2751
|
+
// Handle unary minus on literals directly
|
|
2752
|
+
if (expr.op === '-' && expr.operand.kind === 'int_lit') {
|
|
2753
|
+
return (-expr.operand.value).toString();
|
|
2754
|
+
}
|
|
2755
|
+
if (expr.op === '-' && expr.operand.kind === 'float_lit') {
|
|
2756
|
+
return Math.trunc(-expr.operand.value).toString();
|
|
2757
|
+
}
|
|
2758
|
+
// Fall through to default for complex cases
|
|
2759
|
+
const unaryOp = this.lowerExpr(expr);
|
|
2760
|
+
return this.operandToVar(unaryOp);
|
|
2761
|
+
default:
|
|
2762
|
+
// Complex expression - lower and return var name
|
|
2763
|
+
const op = this.lowerExpr(expr);
|
|
2764
|
+
return this.operandToVar(op);
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
exprToEntitySelector(expr) {
|
|
2768
|
+
if (expr.kind === 'selector') {
|
|
2769
|
+
return this.selectorToString(expr.sel);
|
|
2770
|
+
}
|
|
2771
|
+
if (expr.kind === 'ident') {
|
|
2772
|
+
const constValue = this.constValues.get(expr.name);
|
|
2773
|
+
if (constValue) {
|
|
2774
|
+
return this.exprToEntitySelector(constValue);
|
|
2775
|
+
}
|
|
2776
|
+
const mapped = this.varMap.get(expr.name);
|
|
2777
|
+
if (mapped?.startsWith('@')) {
|
|
2778
|
+
return mapped;
|
|
2779
|
+
}
|
|
2780
|
+
}
|
|
2781
|
+
return null;
|
|
2782
|
+
}
|
|
2783
|
+
appendTypeFilter(selector, mcType) {
|
|
2784
|
+
if (selector.endsWith(']')) {
|
|
2785
|
+
return `${selector.slice(0, -1)},type=${mcType}]`;
|
|
2786
|
+
}
|
|
2787
|
+
return `${selector}[type=${mcType}]`;
|
|
2788
|
+
}
|
|
2789
|
+
exprToSnbt(expr) {
|
|
2790
|
+
switch (expr.kind) {
|
|
2791
|
+
case 'struct_lit': {
|
|
2792
|
+
const entries = expr.fields.map(f => `${f.name}:${this.exprToSnbt(f.value)}`);
|
|
2793
|
+
return `{${entries.join(',')}}`;
|
|
2794
|
+
}
|
|
2795
|
+
case 'array_lit': {
|
|
2796
|
+
const items = expr.elements.map(e => this.exprToSnbt(e));
|
|
2797
|
+
return `[${items.join(',')}]`;
|
|
2798
|
+
}
|
|
2799
|
+
case 'str_lit':
|
|
2800
|
+
return `"${expr.value}"`;
|
|
2801
|
+
case 'int_lit':
|
|
2802
|
+
return String(expr.value);
|
|
2803
|
+
case 'float_lit':
|
|
2804
|
+
return String(expr.value);
|
|
2805
|
+
case 'byte_lit':
|
|
2806
|
+
return `${expr.value}b`;
|
|
2807
|
+
case 'short_lit':
|
|
2808
|
+
return `${expr.value}s`;
|
|
2809
|
+
case 'long_lit':
|
|
2810
|
+
return `${expr.value}L`;
|
|
2811
|
+
case 'double_lit':
|
|
2812
|
+
return `${expr.value}d`;
|
|
2813
|
+
case 'bool_lit':
|
|
2814
|
+
return expr.value ? '1b' : '0b';
|
|
2815
|
+
default:
|
|
2816
|
+
return this.exprToString(expr);
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
2819
|
+
exprToTargetString(expr) {
|
|
2820
|
+
if (expr.kind === 'selector') {
|
|
2821
|
+
return this.selectorToString(expr.sel);
|
|
2822
|
+
}
|
|
2823
|
+
if (expr.kind === 'str_lit' && expr.value.startsWith('@')) {
|
|
2824
|
+
const span = getSpan(expr);
|
|
2825
|
+
this.warnings.push({
|
|
2826
|
+
message: `Quoted selector "${expr.value}" is deprecated; pass ${expr.value} without quotes`,
|
|
2827
|
+
code: 'W_QUOTED_SELECTOR',
|
|
2828
|
+
...(span ? { line: span.line, col: span.col } : {}),
|
|
2829
|
+
});
|
|
2830
|
+
return expr.value;
|
|
2831
|
+
}
|
|
2832
|
+
return this.exprToString(expr);
|
|
2833
|
+
}
|
|
2834
|
+
exprToLiteral(expr) {
|
|
2835
|
+
if (expr.kind === 'int_lit')
|
|
2836
|
+
return expr.value.toString();
|
|
2837
|
+
if (expr.kind === 'float_lit')
|
|
2838
|
+
return Math.trunc(expr.value).toString();
|
|
2839
|
+
return '0';
|
|
2840
|
+
}
|
|
2841
|
+
exprToQuotedString(expr) {
|
|
2842
|
+
return JSON.stringify(this.exprToString(expr));
|
|
2843
|
+
}
|
|
2844
|
+
exprToTextComponent(expr) {
|
|
2845
|
+
return JSON.stringify({ text: this.exprToString(expr) });
|
|
2846
|
+
}
|
|
2847
|
+
exprToBoolString(expr) {
|
|
2848
|
+
if (expr.kind === 'bool_lit') {
|
|
2849
|
+
return expr.value ? 'true' : 'false';
|
|
2850
|
+
}
|
|
2851
|
+
return this.exprToString(expr);
|
|
2852
|
+
}
|
|
2853
|
+
isTeamTextOption(option) {
|
|
2854
|
+
return option === 'displayName' || option === 'prefix' || option === 'suffix';
|
|
2855
|
+
}
|
|
2856
|
+
exprToScoreboardObjective(expr, span) {
|
|
2857
|
+
if (expr.kind === 'mc_name') {
|
|
2858
|
+
// 'rs' is the canonical token for the current RS scoreboard objective.
|
|
2859
|
+
// Resolve to LOWERING_OBJ so it respects --scoreboard / namespace default.
|
|
2860
|
+
return expr.value === 'rs' ? exports.LOWERING_OBJ : expr.value;
|
|
2861
|
+
}
|
|
2862
|
+
const objective = this.exprToString(expr);
|
|
2863
|
+
if (objective.startsWith('#') || objective.includes('.')) {
|
|
2864
|
+
if (objective.startsWith('#')) {
|
|
2865
|
+
const name = objective.slice(1);
|
|
2866
|
+
// '#rs' is the canonical way to reference the current RS scoreboard objective.
|
|
2867
|
+
// Resolve to LOWERING_OBJ so it tracks the --scoreboard option.
|
|
2868
|
+
return name === 'rs' ? exports.LOWERING_OBJ : name;
|
|
2869
|
+
}
|
|
2870
|
+
return objective;
|
|
2871
|
+
}
|
|
2872
|
+
return `${this.getObjectiveNamespace(span)}.${objective}`;
|
|
2873
|
+
}
|
|
2874
|
+
resolveScoreboardObjective(playerExpr, objectiveExpr, span) {
|
|
2875
|
+
const stdlibInternalObjective = this.tryGetStdlibInternalObjective(playerExpr, objectiveExpr, span);
|
|
2876
|
+
if (stdlibInternalObjective) {
|
|
2877
|
+
return stdlibInternalObjective;
|
|
2878
|
+
}
|
|
2879
|
+
return this.exprToScoreboardObjective(objectiveExpr, span);
|
|
2880
|
+
}
|
|
2881
|
+
getObjectiveNamespace(span) {
|
|
2882
|
+
const filePath = this.filePathForSpan(span);
|
|
2883
|
+
if (!filePath) {
|
|
2884
|
+
return this.namespace;
|
|
2885
|
+
}
|
|
2886
|
+
return this.isStdlibFile(filePath) ? exports.LOWERING_OBJ : this.namespace;
|
|
2887
|
+
}
|
|
2888
|
+
tryGetStdlibInternalObjective(playerExpr, objectiveExpr, span) {
|
|
2889
|
+
if (!span || !this.currentStdlibCallSite || objectiveExpr.kind !== 'mc_name' || objectiveExpr.value !== 'rs') {
|
|
2890
|
+
return null;
|
|
2891
|
+
}
|
|
2892
|
+
const filePath = this.filePathForSpan(span);
|
|
2893
|
+
if (!filePath || !this.isStdlibFile(filePath)) {
|
|
2894
|
+
return null;
|
|
2895
|
+
}
|
|
2896
|
+
const resourceBase = this.getStdlibInternalResourceBase(playerExpr);
|
|
2897
|
+
if (!resourceBase) {
|
|
2898
|
+
return null;
|
|
2899
|
+
}
|
|
2900
|
+
const hash = this.shortHash(this.serializeCallSite(this.currentStdlibCallSite));
|
|
2901
|
+
return `${exports.LOWERING_OBJ}._${resourceBase}_${hash}`;
|
|
2902
|
+
}
|
|
2903
|
+
getStdlibInternalResourceBase(playerExpr) {
|
|
2904
|
+
if (!playerExpr || playerExpr.kind !== 'str_lit') {
|
|
2905
|
+
return null;
|
|
2906
|
+
}
|
|
2907
|
+
const match = playerExpr.value.match(/^([a-z0-9]+)_/);
|
|
2908
|
+
return match?.[1] ?? null;
|
|
2909
|
+
}
|
|
2910
|
+
getStdlibCallSiteContext(fn, exprSpan) {
|
|
2911
|
+
const fnFilePath = this.filePathForSpan(getSpan(fn));
|
|
2912
|
+
if (!fnFilePath || !this.isStdlibFile(fnFilePath)) {
|
|
2913
|
+
return undefined;
|
|
2914
|
+
}
|
|
2915
|
+
if (this.currentStdlibCallSite) {
|
|
2916
|
+
return this.currentStdlibCallSite;
|
|
2917
|
+
}
|
|
2918
|
+
if (!exprSpan) {
|
|
2919
|
+
return undefined;
|
|
2920
|
+
}
|
|
2921
|
+
return {
|
|
2922
|
+
filePath: this.filePathForSpan(exprSpan),
|
|
2923
|
+
line: exprSpan.line,
|
|
2924
|
+
col: exprSpan.col,
|
|
2925
|
+
};
|
|
2926
|
+
}
|
|
2927
|
+
serializeCallSite(callSite) {
|
|
2928
|
+
return `${callSite.filePath ?? '<memory>'}:${callSite.line}:${callSite.col}`;
|
|
2929
|
+
}
|
|
2930
|
+
shortHash(input) {
|
|
2931
|
+
let hash = 2166136261;
|
|
2932
|
+
for (let i = 0; i < input.length; i++) {
|
|
2933
|
+
hash ^= input.charCodeAt(i);
|
|
2934
|
+
hash = Math.imul(hash, 16777619);
|
|
2935
|
+
}
|
|
2936
|
+
return (hash >>> 0).toString(16).padStart(8, '0').slice(0, 4);
|
|
2937
|
+
}
|
|
2938
|
+
isStdlibFile(filePath) {
|
|
2939
|
+
const normalized = path.normalize(filePath);
|
|
2940
|
+
const stdlibSegment = `${path.sep}src${path.sep}stdlib${path.sep}`;
|
|
2941
|
+
return normalized.includes(stdlibSegment);
|
|
2942
|
+
}
|
|
2943
|
+
filePathForSpan(span) {
|
|
2944
|
+
if (!span) {
|
|
2945
|
+
return undefined;
|
|
2946
|
+
}
|
|
2947
|
+
const line = span.line;
|
|
2948
|
+
return this.sourceRanges.find(range => line >= range.startLine && line <= range.endLine)?.filePath;
|
|
2949
|
+
}
|
|
2950
|
+
lowerCoordinateBuiltin(name, args) {
|
|
2951
|
+
const pos0 = args[0] ? this.resolveBlockPosExpr(args[0]) : null;
|
|
2952
|
+
const pos1 = args[1] ? this.resolveBlockPosExpr(args[1]) : null;
|
|
2953
|
+
const pos2 = args[2] ? this.resolveBlockPosExpr(args[2]) : null;
|
|
2954
|
+
if (name === 'setblock') {
|
|
2955
|
+
if (args.length === 2 && pos0) {
|
|
2956
|
+
return `setblock ${emitBlockPos(pos0)} ${this.exprToString(args[1])}`;
|
|
2957
|
+
}
|
|
2958
|
+
return null;
|
|
2959
|
+
}
|
|
2960
|
+
if (name === 'fill') {
|
|
2961
|
+
if (args.length === 3 && pos0 && pos1) {
|
|
2962
|
+
return `fill ${emitBlockPos(pos0)} ${emitBlockPos(pos1)} ${this.exprToString(args[2])}`;
|
|
2963
|
+
}
|
|
2964
|
+
return null;
|
|
2965
|
+
}
|
|
2966
|
+
if (name === 'clone') {
|
|
2967
|
+
if (args.length === 3 && pos0 && pos1 && pos2) {
|
|
2968
|
+
return `clone ${emitBlockPos(pos0)} ${emitBlockPos(pos1)} ${emitBlockPos(pos2)}`;
|
|
2969
|
+
}
|
|
2970
|
+
return null;
|
|
2971
|
+
}
|
|
2972
|
+
if (name === 'summon') {
|
|
2973
|
+
if (args.length >= 2 && pos1) {
|
|
2974
|
+
const nbt = args[2] ? ` ${this.exprToString(args[2])}` : '';
|
|
2975
|
+
return `summon ${this.exprToString(args[0])} ${emitBlockPos(pos1)}${nbt}`;
|
|
2976
|
+
}
|
|
2977
|
+
return null;
|
|
2978
|
+
}
|
|
2979
|
+
return null;
|
|
2980
|
+
}
|
|
2981
|
+
lowerTpCommand(args) {
|
|
2982
|
+
const pos0 = args[0] ? this.resolveBlockPosExpr(args[0]) : null;
|
|
2983
|
+
const pos1 = args[1] ? this.resolveBlockPosExpr(args[1]) : null;
|
|
2984
|
+
if (args.length === 1 && pos0) {
|
|
2985
|
+
return `tp ${emitBlockPos(pos0)}`;
|
|
2986
|
+
}
|
|
2987
|
+
if (args.length === 2) {
|
|
2988
|
+
if (pos1) {
|
|
2989
|
+
return `tp ${this.exprToString(args[0])} ${emitBlockPos(pos1)}`;
|
|
2990
|
+
}
|
|
2991
|
+
return `tp ${this.exprToString(args[0])} ${this.exprToString(args[1])}`;
|
|
2992
|
+
}
|
|
2993
|
+
if (args.length === 4) {
|
|
2994
|
+
return `tp ${this.exprToString(args[0])} ${this.exprToString(args[1])} ${this.exprToString(args[2])} ${this.exprToString(args[3])}`;
|
|
2995
|
+
}
|
|
2996
|
+
return null;
|
|
2997
|
+
}
|
|
2998
|
+
lowerTpCommandMacroAware(args) {
|
|
2999
|
+
const pos0 = args[0] ? this.resolveBlockPosExpr(args[0]) : null;
|
|
3000
|
+
const pos1 = args[1] ? this.resolveBlockPosExpr(args[1]) : null;
|
|
3001
|
+
// If blockpos args are used, no macro needed (coords are already resolved)
|
|
3002
|
+
if (args.length === 1 && pos0) {
|
|
3003
|
+
return { cmd: `tp ${emitBlockPos(pos0)}` };
|
|
3004
|
+
}
|
|
3005
|
+
if (args.length === 2 && pos1) {
|
|
3006
|
+
return { cmd: `tp ${this.exprToString(args[0])} ${emitBlockPos(pos1)}` };
|
|
3007
|
+
}
|
|
3008
|
+
// Check for macro args (int params used as coordinates)
|
|
3009
|
+
if (args.length >= 2) {
|
|
3010
|
+
const argResults = args.map(a => this.exprToBuiltinArg(a));
|
|
3011
|
+
const hasMacro = argResults.some(r => r.macroParam !== undefined);
|
|
3012
|
+
if (hasMacro) {
|
|
3013
|
+
argResults.forEach(r => { if (r.macroParam)
|
|
3014
|
+
this.currentFnMacroParams.add(r.macroParam); });
|
|
3015
|
+
const strs = argResults.map(r => r.str);
|
|
3016
|
+
if (args.length === 2) {
|
|
3017
|
+
return { cmd: `\x01tp ${strs[0]} ${strs[1]}` };
|
|
3018
|
+
}
|
|
3019
|
+
if (args.length === 4) {
|
|
3020
|
+
return { cmd: `\x01tp ${strs[0]} ${strs[1]} ${strs[2]} ${strs[3]}` };
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
// Fallback to non-macro
|
|
3025
|
+
const plain = this.lowerTpCommand(args);
|
|
3026
|
+
return plain ? { cmd: plain } : null;
|
|
3027
|
+
}
|
|
3028
|
+
resolveBlockPosExpr(expr) {
|
|
3029
|
+
if (expr.kind === 'blockpos') {
|
|
3030
|
+
return expr;
|
|
3031
|
+
}
|
|
3032
|
+
if (expr.kind === 'ident') {
|
|
3033
|
+
return this.blockPosVars.get(expr.name) ?? null;
|
|
3034
|
+
}
|
|
3035
|
+
return null;
|
|
3036
|
+
}
|
|
3037
|
+
getArrayStorageName(expr) {
|
|
3038
|
+
if (expr.kind === 'ident') {
|
|
3039
|
+
return expr.name;
|
|
3040
|
+
}
|
|
3041
|
+
return null;
|
|
3042
|
+
}
|
|
3043
|
+
inferLambdaReturnType(expr) {
|
|
3044
|
+
if (expr.returnType) {
|
|
3045
|
+
return this.normalizeType(expr.returnType);
|
|
3046
|
+
}
|
|
3047
|
+
if (Array.isArray(expr.body)) {
|
|
3048
|
+
return { kind: 'named', name: 'void' };
|
|
3049
|
+
}
|
|
3050
|
+
return this.inferExprType(expr.body) ?? { kind: 'named', name: 'void' };
|
|
3051
|
+
}
|
|
3052
|
+
inferExprType(expr) {
|
|
3053
|
+
if (expr.kind === 'int_lit')
|
|
3054
|
+
return { kind: 'named', name: 'int' };
|
|
3055
|
+
if (expr.kind === 'float_lit')
|
|
3056
|
+
return { kind: 'named', name: 'float' };
|
|
3057
|
+
if (expr.kind === 'bool_lit')
|
|
3058
|
+
return { kind: 'named', name: 'bool' };
|
|
3059
|
+
if (expr.kind === 'str_lit' || expr.kind === 'str_interp')
|
|
3060
|
+
return { kind: 'named', name: 'string' };
|
|
3061
|
+
if (expr.kind === 'f_string')
|
|
3062
|
+
return { kind: 'named', name: 'format_string' };
|
|
3063
|
+
if (expr.kind === 'blockpos')
|
|
3064
|
+
return { kind: 'named', name: 'BlockPos' };
|
|
3065
|
+
if (expr.kind === 'ident') {
|
|
3066
|
+
const constValue = this.constValues.get(expr.name);
|
|
3067
|
+
if (constValue) {
|
|
3068
|
+
switch (constValue.kind) {
|
|
3069
|
+
case 'int_lit':
|
|
3070
|
+
return { kind: 'named', name: 'int' };
|
|
3071
|
+
case 'float_lit':
|
|
3072
|
+
return { kind: 'named', name: 'float' };
|
|
3073
|
+
case 'bool_lit':
|
|
3074
|
+
return { kind: 'named', name: 'bool' };
|
|
3075
|
+
case 'str_lit':
|
|
3076
|
+
return { kind: 'named', name: 'string' };
|
|
3077
|
+
}
|
|
3078
|
+
}
|
|
3079
|
+
return this.varTypes.get(expr.name);
|
|
3080
|
+
}
|
|
3081
|
+
if (expr.kind === 'lambda') {
|
|
3082
|
+
return {
|
|
3083
|
+
kind: 'function_type',
|
|
3084
|
+
params: expr.params.map(param => this.normalizeType(param.type ?? { kind: 'named', name: 'int' })),
|
|
3085
|
+
return: this.inferLambdaReturnType(expr),
|
|
3086
|
+
};
|
|
3087
|
+
}
|
|
3088
|
+
if (expr.kind === 'call') {
|
|
3089
|
+
const resolved = this.resolveFunctionRefByName(expr.fn) ?? this.resolveInstanceMethod(expr)?.loweredName ?? expr.fn;
|
|
3090
|
+
return this.fnDecls.get(resolved)?.returnType;
|
|
3091
|
+
}
|
|
3092
|
+
if (expr.kind === 'static_call') {
|
|
3093
|
+
return this.implMethods.get(expr.type)?.get(expr.method)?.fn.returnType;
|
|
3094
|
+
}
|
|
3095
|
+
if (expr.kind === 'invoke') {
|
|
3096
|
+
const calleeType = this.inferExprType(expr.callee);
|
|
3097
|
+
if (calleeType?.kind === 'function_type') {
|
|
3098
|
+
return calleeType.return;
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
if (expr.kind === 'binary') {
|
|
3102
|
+
if (['==', '!=', '<', '<=', '>', '>=', '&&', '||'].includes(expr.op)) {
|
|
3103
|
+
return { kind: 'named', name: 'bool' };
|
|
3104
|
+
}
|
|
3105
|
+
return this.inferExprType(expr.left);
|
|
3106
|
+
}
|
|
3107
|
+
if (expr.kind === 'unary') {
|
|
3108
|
+
return expr.op === '!' ? { kind: 'named', name: 'bool' } : this.inferExprType(expr.operand);
|
|
3109
|
+
}
|
|
3110
|
+
if (expr.kind === 'array_lit') {
|
|
3111
|
+
return {
|
|
3112
|
+
kind: 'array',
|
|
3113
|
+
elem: expr.elements[0] ? (this.inferExprType(expr.elements[0]) ?? { kind: 'named', name: 'int' }) : { kind: 'named', name: 'int' },
|
|
3114
|
+
};
|
|
3115
|
+
}
|
|
3116
|
+
if (expr.kind === 'member' && expr.obj.kind === 'ident' && this.enumDefs.has(expr.obj.name)) {
|
|
3117
|
+
return { kind: 'enum', name: expr.obj.name };
|
|
3118
|
+
}
|
|
3119
|
+
return undefined;
|
|
3120
|
+
}
|
|
3121
|
+
/**
|
|
3122
|
+
* Checks a raw() command string for `${...}` interpolation containing runtime variables.
|
|
3123
|
+
* - If the interpolated expression is a numeric literal → OK (MC macro syntax).
|
|
3124
|
+
* - If the interpolated name is a compile-time constant (in constValues) → OK.
|
|
3125
|
+
* - If the interpolated name is a known runtime variable (in varMap) → DiagnosticError.
|
|
3126
|
+
* - Unknown names → OK (could be MC macro params or external constants).
|
|
3127
|
+
*
|
|
3128
|
+
* This catches the common mistake of writing raw("say ${score}") expecting interpolation,
|
|
3129
|
+
* which would silently emit a literal `${score}` in the MC command.
|
|
3130
|
+
*/
|
|
3131
|
+
checkRawCommandInterpolation(cmd, span) {
|
|
3132
|
+
const interpRe = /\$\{([^}]+)\}/g;
|
|
3133
|
+
let match;
|
|
3134
|
+
while ((match = interpRe.exec(cmd)) !== null) {
|
|
3135
|
+
const name = match[1].trim();
|
|
3136
|
+
// Numeric/boolean literals are fine (intentional MC macro syntax)
|
|
3137
|
+
if (/^\d+(\.\d+)?$/.test(name) || name === 'true' || name === 'false') {
|
|
3138
|
+
continue;
|
|
3139
|
+
}
|
|
3140
|
+
// Compile-time constants are fine
|
|
3141
|
+
if (this.constValues.has(name)) {
|
|
3142
|
+
continue;
|
|
3143
|
+
}
|
|
3144
|
+
// Only error if it's a known runtime variable (in varMap or function params)
|
|
3145
|
+
// Unknown identifiers are left alone (could be MC macro params the user intends)
|
|
3146
|
+
if (this.varMap.has(name) || this.currentFnParamNames.has(name)) {
|
|
3147
|
+
const loc = span ?? { line: 1, col: 1 };
|
|
3148
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', `raw() command contains runtime variable interpolation '\${${name}}'. ` +
|
|
3149
|
+
`Variables cannot be interpolated into raw commands at compile time. ` +
|
|
3150
|
+
`Use f-string messages (say/tell/announce) or MC macro syntax '$(${name})' for MC 1.20.2+ commands.`, loc);
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
}
|
|
3154
|
+
resolveInstanceMethod(expr) {
|
|
3155
|
+
const receiver = expr.args[0];
|
|
3156
|
+
if (!receiver) {
|
|
3157
|
+
return null;
|
|
3158
|
+
}
|
|
3159
|
+
const receiverType = this.inferExprType(receiver);
|
|
3160
|
+
if (receiverType?.kind !== 'struct') {
|
|
3161
|
+
return null;
|
|
3162
|
+
}
|
|
3163
|
+
const method = this.implMethods.get(receiverType.name)?.get(expr.fn);
|
|
3164
|
+
if (!method || method.fn.params[0]?.name !== 'self') {
|
|
3165
|
+
return null;
|
|
3166
|
+
}
|
|
3167
|
+
return method;
|
|
3168
|
+
}
|
|
3169
|
+
normalizeType(type) {
|
|
3170
|
+
if (type.kind === 'array') {
|
|
3171
|
+
return { kind: 'array', elem: this.normalizeType(type.elem) };
|
|
3172
|
+
}
|
|
3173
|
+
if (type.kind === 'function_type') {
|
|
3174
|
+
return {
|
|
3175
|
+
kind: 'function_type',
|
|
3176
|
+
params: type.params.map(param => this.normalizeType(param)),
|
|
3177
|
+
return: this.normalizeType(type.return),
|
|
3178
|
+
};
|
|
3179
|
+
}
|
|
3180
|
+
if ((type.kind === 'struct' || type.kind === 'enum') && this.enumDefs.has(type.name)) {
|
|
3181
|
+
return { kind: 'enum', name: type.name };
|
|
3182
|
+
}
|
|
3183
|
+
return type;
|
|
3184
|
+
}
|
|
3185
|
+
readArrayElement(arrayName, index) {
|
|
3186
|
+
const dst = this.builder.freshTemp();
|
|
3187
|
+
if (index.kind === 'const') {
|
|
3188
|
+
this.builder.emitRaw(`execute store result score ${dst} ${exports.LOWERING_OBJ} run data get storage rs:heap ${arrayName}[${index.value}]`);
|
|
3189
|
+
return { kind: 'var', name: dst };
|
|
3190
|
+
}
|
|
3191
|
+
const macroKey = `__rs_index_${this.foreachCounter++}`;
|
|
3192
|
+
const subFnName = `${this.currentFn}/array_get_${this.foreachCounter++}`;
|
|
3193
|
+
const indexVar = index.kind === 'var' ? index.name : this.operandToVar(index);
|
|
3194
|
+
this.builder.emitRaw(`execute store result storage rs:heap ${macroKey} int 1 run scoreboard players get ${indexVar} ${exports.LOWERING_OBJ}`);
|
|
3195
|
+
this.builder.emitRaw(`function ${this.namespace}:${subFnName} with storage rs:heap`);
|
|
3196
|
+
this.emitRawSubFunction(subFnName, `\x01execute store result score ${dst} ${exports.LOWERING_OBJ} run data get storage rs:heap ${arrayName}[$(${macroKey})]`);
|
|
3197
|
+
return { kind: 'var', name: dst };
|
|
3198
|
+
}
|
|
3199
|
+
emitRawSubFunction(name, ...commands) {
|
|
3200
|
+
const builder = new LoweringBuilder();
|
|
3201
|
+
builder.startBlock('entry');
|
|
3202
|
+
for (const cmd of commands) {
|
|
3203
|
+
builder.emitRaw(cmd);
|
|
3204
|
+
}
|
|
3205
|
+
builder.emitReturn();
|
|
3206
|
+
this.functions.push(builder.build(name, [], false));
|
|
3207
|
+
}
|
|
3208
|
+
// -------------------------------------------------------------------------
|
|
3209
|
+
// Helpers
|
|
3210
|
+
// -------------------------------------------------------------------------
|
|
3211
|
+
storeStringValue(name, expr) {
|
|
3212
|
+
const value = this.resolveStaticString(expr);
|
|
3213
|
+
if (value === null) {
|
|
3214
|
+
this.stringValues.delete(name);
|
|
3215
|
+
return false;
|
|
3216
|
+
}
|
|
3217
|
+
this.stringValues.set(name, value);
|
|
3218
|
+
this.builder.emitRaw(`data modify storage rs:strings ${name} set value ${JSON.stringify(value)}`);
|
|
3219
|
+
return true;
|
|
3220
|
+
}
|
|
3221
|
+
resolveStaticString(expr) {
|
|
3222
|
+
if (!expr) {
|
|
3223
|
+
return null;
|
|
3224
|
+
}
|
|
3225
|
+
if (expr.kind === 'str_lit') {
|
|
3226
|
+
return expr.value;
|
|
3227
|
+
}
|
|
3228
|
+
if (expr.kind === 'ident') {
|
|
3229
|
+
const constValue = this.constValues.get(expr.name);
|
|
3230
|
+
if (constValue?.kind === 'str_lit') {
|
|
3231
|
+
return constValue.value;
|
|
3232
|
+
}
|
|
3233
|
+
return this.stringValues.get(expr.name) ?? null;
|
|
3234
|
+
}
|
|
3235
|
+
return null;
|
|
3236
|
+
}
|
|
3237
|
+
getStringStoragePath(expr) {
|
|
3238
|
+
if (!expr || expr.kind !== 'ident') {
|
|
3239
|
+
return null;
|
|
3240
|
+
}
|
|
3241
|
+
if (this.stringValues.has(expr.name)) {
|
|
3242
|
+
return `rs:strings ${expr.name}`;
|
|
3243
|
+
}
|
|
3244
|
+
return null;
|
|
3245
|
+
}
|
|
3246
|
+
lowerConstLiteral(expr) {
|
|
3247
|
+
switch (expr.kind) {
|
|
3248
|
+
case 'int_lit':
|
|
3249
|
+
return { kind: 'const', value: expr.value };
|
|
3250
|
+
case 'float_lit':
|
|
3251
|
+
return { kind: 'const', value: Math.round(expr.value * 1000) };
|
|
3252
|
+
case 'bool_lit':
|
|
3253
|
+
return { kind: 'const', value: expr.value ? 1 : 0 };
|
|
3254
|
+
case 'str_lit':
|
|
3255
|
+
return { kind: 'const', value: 0 };
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
operandToVar(op) {
|
|
3259
|
+
if (op.kind === 'var')
|
|
3260
|
+
return op.name;
|
|
3261
|
+
// Constant needs to be stored in a temp
|
|
3262
|
+
const dst = this.builder.freshTemp();
|
|
3263
|
+
this.builder.emitAssign(dst, op);
|
|
3264
|
+
return dst;
|
|
3265
|
+
}
|
|
3266
|
+
selectorToString(sel) {
|
|
3267
|
+
const { kind, filters } = sel;
|
|
3268
|
+
if (!filters)
|
|
3269
|
+
return this.finalizeSelector(kind);
|
|
3270
|
+
const parts = [];
|
|
3271
|
+
if (filters.type)
|
|
3272
|
+
parts.push(`type=${filters.type}`);
|
|
3273
|
+
if (filters.distance)
|
|
3274
|
+
parts.push(`distance=${this.rangeToString(filters.distance)}`);
|
|
3275
|
+
if (filters.tag)
|
|
3276
|
+
filters.tag.forEach(t => parts.push(`tag=${t}`));
|
|
3277
|
+
if (filters.notTag)
|
|
3278
|
+
filters.notTag.forEach(t => parts.push(`tag=!${t}`));
|
|
3279
|
+
if (filters.limit !== undefined)
|
|
3280
|
+
parts.push(`limit=${filters.limit}`);
|
|
3281
|
+
if (filters.sort)
|
|
3282
|
+
parts.push(`sort=${filters.sort}`);
|
|
3283
|
+
if (filters.scores) {
|
|
3284
|
+
const scoreStr = Object.entries(filters.scores)
|
|
3285
|
+
.map(([k, v]) => `${k}=${this.rangeToString(v)}`).join(',');
|
|
3286
|
+
parts.push(`scores={${scoreStr}}`);
|
|
3287
|
+
}
|
|
3288
|
+
if (filters.nbt)
|
|
3289
|
+
parts.push(`nbt=${filters.nbt}`);
|
|
3290
|
+
if (filters.gamemode)
|
|
3291
|
+
parts.push(`gamemode=${filters.gamemode}`);
|
|
3292
|
+
// Position filters
|
|
3293
|
+
if (filters.x)
|
|
3294
|
+
parts.push(`x=${this.rangeToString(filters.x)}`);
|
|
3295
|
+
if (filters.y)
|
|
3296
|
+
parts.push(`y=${this.rangeToString(filters.y)}`);
|
|
3297
|
+
if (filters.z)
|
|
3298
|
+
parts.push(`z=${this.rangeToString(filters.z)}`);
|
|
3299
|
+
// Rotation filters
|
|
3300
|
+
if (filters.x_rotation)
|
|
3301
|
+
parts.push(`x_rotation=${this.rangeToString(filters.x_rotation)}`);
|
|
3302
|
+
if (filters.y_rotation)
|
|
3303
|
+
parts.push(`y_rotation=${this.rangeToString(filters.y_rotation)}`);
|
|
3304
|
+
return this.finalizeSelector(parts.length ? `${kind}[${parts.join(',')}]` : kind);
|
|
3305
|
+
}
|
|
3306
|
+
finalizeSelector(selector) {
|
|
3307
|
+
return normalizeSelector(selector, this.warnings);
|
|
3308
|
+
}
|
|
3309
|
+
rangeToString(r) {
|
|
3310
|
+
if (r.min !== undefined && r.max !== undefined) {
|
|
3311
|
+
if (r.min === r.max)
|
|
3312
|
+
return `${r.min}`;
|
|
3313
|
+
return `${r.min}..${r.max}`;
|
|
3314
|
+
}
|
|
3315
|
+
if (r.min !== undefined)
|
|
3316
|
+
return `${r.min}..`;
|
|
3317
|
+
if (r.max !== undefined)
|
|
3318
|
+
return `..${r.max}`;
|
|
3319
|
+
return '..';
|
|
3320
|
+
}
|
|
3321
|
+
}
|
|
3322
|
+
exports.Lowering = Lowering;
|
|
3323
|
+
// ---------------------------------------------------------------------------
|
|
3324
|
+
// LoweringBuilder - Wrapper around IR construction
|
|
3325
|
+
// ---------------------------------------------------------------------------
|
|
3326
|
+
class LoweringBuilder {
|
|
3327
|
+
constructor() {
|
|
3328
|
+
this.labelCount = 0;
|
|
3329
|
+
this.blocks = [];
|
|
3330
|
+
this.currentBlock = null;
|
|
3331
|
+
this.locals = new Set();
|
|
3332
|
+
}
|
|
3333
|
+
/** Reset the global temp counter (call between compilations). */
|
|
3334
|
+
static resetTempCounter() {
|
|
3335
|
+
LoweringBuilder.globalTempId = 0;
|
|
3336
|
+
}
|
|
3337
|
+
freshTemp() {
|
|
3338
|
+
const name = `$_${LoweringBuilder.globalTempId++}`;
|
|
3339
|
+
this.locals.add(name);
|
|
3340
|
+
return name;
|
|
3341
|
+
}
|
|
3342
|
+
freshLabel(hint = 'L') {
|
|
3343
|
+
return `${hint}_${this.labelCount++}`;
|
|
3344
|
+
}
|
|
3345
|
+
startBlock(label) {
|
|
3346
|
+
this.currentBlock = { label, instrs: [], term: null };
|
|
3347
|
+
}
|
|
3348
|
+
isBlockSealed() {
|
|
3349
|
+
return this.currentBlock === null || this.currentBlock.term !== null;
|
|
3350
|
+
}
|
|
3351
|
+
sealBlock(term) {
|
|
3352
|
+
if (this.currentBlock) {
|
|
3353
|
+
this.currentBlock.term = term;
|
|
3354
|
+
this.blocks.push(this.currentBlock);
|
|
3355
|
+
this.currentBlock = null;
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
emitAssign(dst, src) {
|
|
3359
|
+
if (!dst.startsWith('$') && !dst.startsWith('@')) {
|
|
3360
|
+
dst = '$' + dst;
|
|
3361
|
+
}
|
|
3362
|
+
this.locals.add(dst);
|
|
3363
|
+
this.currentBlock?.instrs.push({ op: 'assign', dst, src });
|
|
3364
|
+
}
|
|
3365
|
+
emitBinop(dst, lhs, bop, rhs) {
|
|
3366
|
+
this.locals.add(dst);
|
|
3367
|
+
this.currentBlock?.instrs.push({ op: 'binop', dst, lhs, bop, rhs });
|
|
3368
|
+
}
|
|
3369
|
+
emitCmp(dst, lhs, cop, rhs) {
|
|
3370
|
+
this.locals.add(dst);
|
|
3371
|
+
this.currentBlock?.instrs.push({ op: 'cmp', dst, lhs, cop, rhs });
|
|
3372
|
+
}
|
|
3373
|
+
emitCall(fn, args, dst) {
|
|
3374
|
+
if (dst)
|
|
3375
|
+
this.locals.add(dst);
|
|
3376
|
+
this.currentBlock?.instrs.push({ op: 'call', fn, args, dst });
|
|
3377
|
+
}
|
|
3378
|
+
emitRaw(cmd) {
|
|
3379
|
+
this.currentBlock?.instrs.push({ op: 'raw', cmd });
|
|
3380
|
+
}
|
|
3381
|
+
emitJump(target) {
|
|
3382
|
+
this.sealBlock({ op: 'jump', target });
|
|
3383
|
+
}
|
|
3384
|
+
emitJumpIf(cond, then, else_) {
|
|
3385
|
+
this.sealBlock({ op: 'jump_if', cond, then, else_ });
|
|
3386
|
+
}
|
|
3387
|
+
emitReturn(value) {
|
|
3388
|
+
this.sealBlock({ op: 'return', value });
|
|
3389
|
+
}
|
|
3390
|
+
build(name, params, isTickLoop = false) {
|
|
3391
|
+
// Ensure current block is sealed
|
|
3392
|
+
if (this.currentBlock && !this.currentBlock.term) {
|
|
3393
|
+
this.sealBlock({ op: 'return' });
|
|
3394
|
+
}
|
|
3395
|
+
return {
|
|
3396
|
+
name,
|
|
3397
|
+
params,
|
|
3398
|
+
locals: Array.from(this.locals),
|
|
3399
|
+
blocks: this.blocks,
|
|
3400
|
+
isTickLoop,
|
|
3401
|
+
};
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
LoweringBuilder.globalTempId = 0;
|
|
3405
|
+
//# sourceMappingURL=index.js.map
|