redscript-mc 1.0.0 → 1.2.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/.github/ISSUE_TEMPLATE/bug_report.yml +72 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +57 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +17 -25
- package/CHANGELOG.md +112 -0
- package/CONTRIBUTING.md +140 -0
- package/README.md +28 -19
- package/README.zh.md +28 -19
- package/dist/__tests__/cli.test.js +148 -10
- package/dist/__tests__/codegen.test.js +26 -1
- package/dist/__tests__/diagnostics.test.js +5 -5
- package/dist/__tests__/e2e.test.js +336 -17
- package/dist/__tests__/formatter.test.d.ts +1 -0
- package/dist/__tests__/formatter.test.js +40 -0
- package/dist/__tests__/lexer.test.js +12 -2
- package/dist/__tests__/lowering.test.js +200 -12
- package/dist/__tests__/mc-integration.test.js +370 -31
- package/dist/__tests__/mc-syntax.test.js +3 -3
- package/dist/__tests__/nbt.test.js +2 -2
- package/dist/__tests__/optimizer-advanced.test.js +5 -5
- package/dist/__tests__/parser.test.js +80 -0
- package/dist/__tests__/runtime.test.js +9 -9
- package/dist/__tests__/typechecker.test.js +158 -0
- package/dist/ast/types.d.ts +40 -3
- package/dist/cli.js +25 -7
- package/dist/codegen/mcfunction/index.d.ts +1 -1
- package/dist/codegen/mcfunction/index.js +38 -3
- package/dist/codegen/structure/index.js +32 -1
- package/dist/compile.d.ts +10 -0
- package/dist/compile.js +36 -5
- package/dist/events/types.d.ts +35 -0
- package/dist/events/types.js +59 -0
- package/dist/formatter/index.d.ts +1 -0
- package/dist/formatter/index.js +26 -0
- package/dist/index.js +3 -2
- package/dist/ir/builder.d.ts +2 -1
- package/dist/ir/types.d.ts +11 -2
- package/dist/ir/types.js +1 -1
- package/dist/lexer/index.d.ts +1 -1
- package/dist/lexer/index.js +2 -0
- package/dist/lowering/index.d.ts +34 -1
- package/dist/lowering/index.js +622 -23
- package/dist/mc-test/runner.d.ts +2 -2
- package/dist/mc-test/runner.js +3 -3
- package/dist/mc-test/setup.js +2 -2
- package/dist/parser/index.d.ts +4 -0
- package/dist/parser/index.js +153 -16
- package/dist/typechecker/index.d.ts +17 -0
- package/dist/typechecker/index.js +343 -17
- package/docs/COMPILATION_STATS.md +24 -24
- package/docs/ENTITY_TYPE_SYSTEM.md +242 -0
- package/docs/IMPLEMENTATION_GUIDE.md +1 -1
- package/docs/STRUCTURE_TARGET.md +1 -1
- package/editors/vscode/.vscodeignore +1 -0
- package/editors/vscode/CHANGELOG.md +9 -0
- package/editors/vscode/icons/mcrs.svg +7 -0
- package/editors/vscode/icons/redscript-icons.json +10 -0
- package/editors/vscode/out/extension.js +1295 -80
- package/editors/vscode/package-lock.json +2 -2
- package/editors/vscode/package.json +10 -3
- package/editors/vscode/src/hover.ts +55 -2
- package/editors/vscode/src/symbols.ts +42 -0
- package/package.json +1 -1
- package/src/__tests__/cli.test.ts +176 -10
- package/src/__tests__/codegen.test.ts +28 -1
- package/src/__tests__/diagnostics.test.ts +5 -5
- package/src/__tests__/e2e.test.ts +335 -17
- package/src/__tests__/fixtures/event-test.mcrs +13 -0
- package/src/__tests__/fixtures/impl-test.mcrs +46 -0
- package/src/__tests__/fixtures/interval-test.mcrs +11 -0
- package/src/__tests__/fixtures/is-check-test.mcrs +20 -0
- package/src/__tests__/fixtures/timeout-test.mcrs +7 -0
- package/src/__tests__/lexer.test.ts +14 -2
- package/src/__tests__/lowering.test.ts +226 -12
- package/src/__tests__/mc-integration.test.ts +421 -31
- package/src/__tests__/mc-syntax.test.ts +3 -3
- package/src/__tests__/nbt.test.ts +2 -2
- package/src/__tests__/optimizer-advanced.test.ts +5 -5
- package/src/__tests__/parser.test.ts +91 -5
- package/src/__tests__/runtime.test.ts +9 -9
- package/src/__tests__/typechecker.test.ts +171 -0
- package/src/ast/types.ts +44 -3
- package/src/cli.ts +10 -10
- package/src/codegen/mcfunction/index.ts +40 -3
- package/src/codegen/structure/index.ts +35 -1
- package/src/compile.ts +54 -6
- package/src/events/types.ts +69 -0
- package/src/examples/capture_the_flag.mcrs +208 -0
- package/src/examples/{counter.rs → counter.mcrs} +1 -1
- package/src/examples/hunger_games.mcrs +301 -0
- package/src/examples/new_features_demo.mcrs +193 -0
- package/src/examples/parkour_race.mcrs +233 -0
- package/src/examples/rpg.mcrs +13 -0
- package/src/examples/{shop.rs → shop.mcrs} +1 -1
- package/src/examples/{showcase_game.rs → showcase_game.mcrs} +3 -3
- package/src/examples/{turret.rs → turret.mcrs} +1 -1
- package/src/examples/zombie_survival.mcrs +314 -0
- package/src/index.ts +4 -3
- package/src/ir/builder.ts +3 -1
- package/src/ir/types.ts +12 -2
- package/src/lexer/index.ts +3 -1
- package/src/lowering/index.ts +684 -24
- package/src/mc-test/runner.ts +3 -3
- package/src/mc-test/setup.ts +2 -2
- package/src/parser/index.ts +170 -19
- package/src/stdlib/README.md +178 -140
- package/src/stdlib/bossbar.mcrs +68 -0
- package/src/stdlib/{cooldown.rs → cooldown.mcrs} +1 -1
- package/src/stdlib/effects.mcrs +64 -0
- package/src/stdlib/interactions.mcrs +195 -0
- package/src/stdlib/inventory.mcrs +38 -0
- package/src/stdlib/mobs.mcrs +99 -0
- package/src/stdlib/particles.mcrs +52 -0
- package/src/stdlib/sets.mcrs +20 -0
- package/src/stdlib/spawn.mcrs +41 -0
- package/src/stdlib/tags.mcrs +951 -0
- package/src/stdlib/teams.mcrs +68 -0
- package/src/stdlib/timer.mcrs +72 -0
- package/src/stdlib/world.mcrs +92 -0
- package/src/typechecker/index.ts +404 -18
- package/src/examples/rpg.rs +0 -13
- package/src/stdlib/mobs.rs +0 -99
- package/src/stdlib/timer.rs +0 -51
- /package/src/examples/{arena.rs → arena.mcrs} +0 -0
- /package/src/examples/{pvp_arena.rs → pvp_arena.mcrs} +0 -0
- /package/src/examples/{quiz.rs → quiz.mcrs} +0 -0
- /package/src/examples/{stdlib_demo.rs → stdlib_demo.mcrs} +0 -0
- /package/src/examples/{world_manager.rs → world_manager.mcrs} +0 -0
- /package/src/stdlib/{combat.rs → combat.mcrs} +0 -0
- /package/src/stdlib/{math.rs → math.mcrs} +0 -0
- /package/src/stdlib/{player.rs → player.mcrs} +0 -0
- /package/src/stdlib/{strings.rs → strings.mcrs} +0 -0
- /package/src/templates/{combat.rs → combat.mcrs} +0 -0
- /package/src/templates/{economy.rs → economy.mcrs} +0 -0
- /package/src/templates/{mini-game-framework.rs → mini-game-framework.mcrs} +0 -0
- /package/src/templates/{quest.rs → quest.mcrs} +0 -0
- /package/src/test_programs/{zombie_game.rs → zombie_game.mcrs} +0 -0
package/dist/lowering/index.js
CHANGED
|
@@ -5,10 +5,45 @@
|
|
|
5
5
|
* Transforms AST into IR (Three-Address Code).
|
|
6
6
|
* Handles control flow, function extraction for foreach, and builtin calls.
|
|
7
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
|
+
})();
|
|
8
41
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
42
|
exports.Lowering = void 0;
|
|
10
43
|
const builder_1 = require("../ir/builder");
|
|
11
44
|
const diagnostics_1 = require("../diagnostics");
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const types_1 = require("../events/types");
|
|
12
47
|
// ---------------------------------------------------------------------------
|
|
13
48
|
// Builtin Functions
|
|
14
49
|
// ---------------------------------------------------------------------------
|
|
@@ -20,9 +55,10 @@ const BUILTINS = {
|
|
|
20
55
|
subtitle: ([sel, msg]) => `title ${sel} subtitle {"text":"${msg}"}`,
|
|
21
56
|
title_times: ([sel, fadeIn, stay, fadeOut]) => `title ${sel} times ${fadeIn} ${stay} ${fadeOut}`,
|
|
22
57
|
announce: ([msg]) => `tellraw @a {"text":"${msg}"}`,
|
|
23
|
-
give: ([sel, item, count]) => `give ${sel} ${item} ${count ?? '1'}`,
|
|
58
|
+
give: ([sel, item, count, nbt]) => nbt ? `give ${sel} ${item}${nbt} ${count ?? '1'}` : `give ${sel} ${item} ${count ?? '1'}`,
|
|
24
59
|
kill: ([sel]) => `kill ${sel ?? '@s'}`,
|
|
25
60
|
effect: ([sel, eff, dur, amp]) => `effect give ${sel} ${eff} ${dur ?? '30'} ${amp ?? '0'}`,
|
|
61
|
+
effect_clear: ([sel, eff]) => eff ? `effect clear ${sel} ${eff}` : `effect clear ${sel}`,
|
|
26
62
|
summon: ([type, x, y, z, nbt]) => {
|
|
27
63
|
const pos = [x ?? '~', y ?? '~', z ?? '~'].join(' ');
|
|
28
64
|
return nbt ? `summon ${type} ${pos} ${nbt}` : `summon ${type} ${pos}`;
|
|
@@ -73,12 +109,37 @@ const BUILTINS = {
|
|
|
73
109
|
team_leave: () => null, // Special handling
|
|
74
110
|
team_option: () => null, // Special handling
|
|
75
111
|
data_get: () => null, // Special handling (returns value from NBT)
|
|
112
|
+
data_merge: () => null, // Special handling (merge NBT)
|
|
113
|
+
set_new: () => null, // Special handling (returns set ID)
|
|
114
|
+
set_add: () => null, // Special handling
|
|
115
|
+
set_contains: () => null, // Special handling (returns 1/0)
|
|
116
|
+
set_remove: () => null, // Special handling
|
|
117
|
+
set_clear: () => null, // Special handling
|
|
118
|
+
setTimeout: () => null, // Special handling
|
|
119
|
+
setInterval: () => null, // Special handling
|
|
120
|
+
clearInterval: () => null, // Special handling
|
|
76
121
|
};
|
|
77
122
|
function getSpan(node) {
|
|
78
123
|
return node?.span;
|
|
79
124
|
}
|
|
80
125
|
const NAMESPACED_ENTITY_TYPE_RE = /^[a-z0-9_.-]+:[a-z0-9_./-]+$/;
|
|
81
126
|
const BARE_ENTITY_TYPE_RE = /^[a-z0-9_./-]+$/;
|
|
127
|
+
const ENTITY_TO_MC_TYPE = {
|
|
128
|
+
Player: 'player',
|
|
129
|
+
Zombie: 'zombie',
|
|
130
|
+
Skeleton: 'skeleton',
|
|
131
|
+
Creeper: 'creeper',
|
|
132
|
+
Spider: 'spider',
|
|
133
|
+
Enderman: 'enderman',
|
|
134
|
+
Pig: 'pig',
|
|
135
|
+
Cow: 'cow',
|
|
136
|
+
Sheep: 'sheep',
|
|
137
|
+
Chicken: 'chicken',
|
|
138
|
+
Villager: 'villager',
|
|
139
|
+
ArmorStand: 'armor_stand',
|
|
140
|
+
Item: 'item',
|
|
141
|
+
Arrow: 'arrow',
|
|
142
|
+
};
|
|
82
143
|
function normalizeSelector(selector, warnings) {
|
|
83
144
|
return selector.replace(/type=([^,\]]+)/g, (match, entityType) => {
|
|
84
145
|
const trimmed = entityType.trim();
|
|
@@ -115,17 +176,23 @@ function emitBlockPos(pos) {
|
|
|
115
176
|
// Lowering Class
|
|
116
177
|
// ---------------------------------------------------------------------------
|
|
117
178
|
class Lowering {
|
|
118
|
-
constructor(namespace) {
|
|
179
|
+
constructor(namespace, sourceRanges = []) {
|
|
119
180
|
this.functions = [];
|
|
120
181
|
this.globals = [];
|
|
182
|
+
this.globalNames = new Map();
|
|
121
183
|
this.fnDecls = new Map();
|
|
184
|
+
this.implMethods = new Map();
|
|
122
185
|
this.specializedFunctions = new Map();
|
|
123
186
|
this.currentFn = '';
|
|
124
187
|
this.foreachCounter = 0;
|
|
125
188
|
this.lambdaCounter = 0;
|
|
189
|
+
this.timeoutCounter = 0;
|
|
190
|
+
this.intervalCounter = 0;
|
|
126
191
|
this.warnings = [];
|
|
127
192
|
this.varMap = new Map();
|
|
128
193
|
this.lambdaBindings = new Map();
|
|
194
|
+
this.intervalBindings = new Map();
|
|
195
|
+
this.intervalFunctions = new Map();
|
|
129
196
|
this.currentCallbackBindings = new Map();
|
|
130
197
|
this.currentContext = {};
|
|
131
198
|
this.blockPosVars = new Map();
|
|
@@ -142,6 +209,8 @@ class Lowering {
|
|
|
142
209
|
// World object counter for unique tags
|
|
143
210
|
this.worldObjCounter = 0;
|
|
144
211
|
this.namespace = namespace;
|
|
212
|
+
this.sourceRanges = sourceRanges;
|
|
213
|
+
LoweringBuilder.resetTempCounter();
|
|
145
214
|
}
|
|
146
215
|
lower(program) {
|
|
147
216
|
this.namespace = program.namespace;
|
|
@@ -164,13 +233,38 @@ class Lowering {
|
|
|
164
233
|
this.constValues.set(constDecl.name, constDecl.value);
|
|
165
234
|
this.varTypes.set(constDecl.name, this.normalizeType(constDecl.type));
|
|
166
235
|
}
|
|
236
|
+
// Process global variable declarations (top-level let)
|
|
237
|
+
for (const g of program.globals ?? []) {
|
|
238
|
+
this.globalNames.set(g.name, { mutable: g.mutable });
|
|
239
|
+
this.varTypes.set(g.name, this.normalizeType(g.type));
|
|
240
|
+
const initValue = g.init.kind === 'int_lit' ? g.init.value : 0;
|
|
241
|
+
this.globals.push({ name: `$${g.name}`, init: initValue });
|
|
242
|
+
}
|
|
167
243
|
for (const fn of program.declarations) {
|
|
168
244
|
this.fnDecls.set(fn.name, fn);
|
|
169
245
|
this.functionDefaults.set(fn.name, fn.params.map(param => param.default));
|
|
170
246
|
}
|
|
247
|
+
for (const implBlock of program.implBlocks ?? []) {
|
|
248
|
+
let methods = this.implMethods.get(implBlock.typeName);
|
|
249
|
+
if (!methods) {
|
|
250
|
+
methods = new Map();
|
|
251
|
+
this.implMethods.set(implBlock.typeName, methods);
|
|
252
|
+
}
|
|
253
|
+
for (const method of implBlock.methods) {
|
|
254
|
+
const loweredName = `${implBlock.typeName}_${method.name}`;
|
|
255
|
+
methods.set(method.name, { fn: method, loweredName });
|
|
256
|
+
this.fnDecls.set(loweredName, method);
|
|
257
|
+
this.functionDefaults.set(loweredName, method.params.map(param => param.default));
|
|
258
|
+
}
|
|
259
|
+
}
|
|
171
260
|
for (const fn of program.declarations) {
|
|
172
261
|
this.lowerFn(fn);
|
|
173
262
|
}
|
|
263
|
+
for (const implBlock of program.implBlocks ?? []) {
|
|
264
|
+
for (const method of implBlock.methods) {
|
|
265
|
+
this.lowerFn(method, { name: `${implBlock.typeName}_${method.name}` });
|
|
266
|
+
}
|
|
267
|
+
}
|
|
174
268
|
return (0, builder_1.buildModule)(this.namespace, this.functions, this.globals);
|
|
175
269
|
}
|
|
176
270
|
// -------------------------------------------------------------------------
|
|
@@ -179,21 +273,48 @@ class Lowering {
|
|
|
179
273
|
lowerFn(fn, options = {}) {
|
|
180
274
|
const loweredName = options.name ?? fn.name;
|
|
181
275
|
const callbackBindings = options.callbackBindings ?? new Map();
|
|
182
|
-
const
|
|
276
|
+
const stdlibCallSite = options.stdlibCallSite;
|
|
277
|
+
const staticEventDec = fn.decorators.find(d => d.name === 'on');
|
|
278
|
+
const eventType = staticEventDec?.args?.eventType;
|
|
279
|
+
const eventParamSpecs = eventType && (0, types_1.isEventTypeName)(eventType) ? (0, types_1.getEventParamSpecs)(eventType) : [];
|
|
280
|
+
const runtimeParams = staticEventDec
|
|
281
|
+
? []
|
|
282
|
+
: fn.params.filter(param => !callbackBindings.has(param.name));
|
|
183
283
|
this.currentFn = loweredName;
|
|
284
|
+
this.currentStdlibCallSite = stdlibCallSite;
|
|
184
285
|
this.foreachCounter = 0;
|
|
185
286
|
this.varMap = new Map();
|
|
186
287
|
this.lambdaBindings = new Map();
|
|
288
|
+
this.intervalBindings = new Map();
|
|
187
289
|
this.currentCallbackBindings = new Map(callbackBindings);
|
|
188
290
|
this.currentContext = {};
|
|
189
291
|
this.blockPosVars = new Map();
|
|
190
292
|
this.stringValues = new Map();
|
|
191
293
|
this.builder = new LoweringBuilder();
|
|
192
294
|
// Map parameters
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
295
|
+
if (staticEventDec) {
|
|
296
|
+
for (let i = 0; i < fn.params.length; i++) {
|
|
297
|
+
const param = fn.params[i];
|
|
298
|
+
const expected = eventParamSpecs[i];
|
|
299
|
+
const normalizedType = this.normalizeType(param.type);
|
|
300
|
+
this.varTypes.set(param.name, normalizedType);
|
|
301
|
+
if (expected?.type.kind === 'entity') {
|
|
302
|
+
this.varMap.set(param.name, '@s');
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (expected?.type.kind === 'named' && expected.type.name === 'string') {
|
|
306
|
+
this.stringValues.set(param.name, '');
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
this.varMap.set(param.name, `$${param.name}`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
for (const param of runtimeParams) {
|
|
314
|
+
const paramName = param.name;
|
|
315
|
+
this.varMap.set(paramName, `$${paramName}`);
|
|
316
|
+
this.varTypes.set(paramName, this.normalizeType(param.type));
|
|
317
|
+
}
|
|
197
318
|
}
|
|
198
319
|
for (const param of fn.params) {
|
|
199
320
|
if (callbackBindings.has(param.name)) {
|
|
@@ -208,6 +329,15 @@ class Lowering {
|
|
|
208
329
|
const varName = `$${paramName}`;
|
|
209
330
|
this.builder.emitAssign(varName, { kind: 'var', name: `$p${i}` });
|
|
210
331
|
}
|
|
332
|
+
if (staticEventDec) {
|
|
333
|
+
for (let i = 0; i < fn.params.length; i++) {
|
|
334
|
+
const param = fn.params[i];
|
|
335
|
+
const expected = eventParamSpecs[i];
|
|
336
|
+
if (expected?.type.kind === 'named' && expected.type.name !== 'string') {
|
|
337
|
+
this.builder.emitAssign(`$${param.name}`, { kind: 'const', value: 0 });
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
211
341
|
// Lower body
|
|
212
342
|
this.lowerBlock(fn.body);
|
|
213
343
|
// If no explicit return, add void return
|
|
@@ -251,6 +381,16 @@ class Lowering {
|
|
|
251
381
|
break;
|
|
252
382
|
}
|
|
253
383
|
}
|
|
384
|
+
if (eventType && (0, types_1.isEventTypeName)(eventType)) {
|
|
385
|
+
irFn.eventHandler = {
|
|
386
|
+
eventType,
|
|
387
|
+
tag: types_1.EVENT_TYPES[eventType].tag,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
// Check for @load decorator
|
|
391
|
+
if (fn.decorators.some(d => d.name === 'load')) {
|
|
392
|
+
irFn.isLoadInit = true;
|
|
393
|
+
}
|
|
254
394
|
// Handle tick rate counter if needed
|
|
255
395
|
if (tickRate && tickRate > 1) {
|
|
256
396
|
this.wrapWithTickRate(irFn, tickRate);
|
|
@@ -264,7 +404,7 @@ class Lowering {
|
|
|
264
404
|
wrapWithTickRate(fn, rate) {
|
|
265
405
|
// Add tick counter logic to entry block
|
|
266
406
|
const counterVar = `$__tick_${fn.name}`;
|
|
267
|
-
this.globals.push(counterVar);
|
|
407
|
+
this.globals.push({ name: counterVar, init: 0 });
|
|
268
408
|
// Prepend counter logic to entry block
|
|
269
409
|
const entry = fn.blocks[0];
|
|
270
410
|
const originalInstrs = [...entry.instrs];
|
|
@@ -357,6 +497,10 @@ class Lowering {
|
|
|
357
497
|
}
|
|
358
498
|
}
|
|
359
499
|
lowerLetStmt(stmt) {
|
|
500
|
+
// Check for duplicate declaration of foreach binding
|
|
501
|
+
if (this.currentContext.binding === stmt.name) {
|
|
502
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', `Cannot redeclare foreach binding '${stmt.name}'`, stmt.span ?? { line: 0, col: 0 });
|
|
503
|
+
}
|
|
360
504
|
const varName = `$${stmt.name}`;
|
|
361
505
|
this.varMap.set(stmt.name, varName);
|
|
362
506
|
// Track variable type
|
|
@@ -373,6 +517,15 @@ class Lowering {
|
|
|
373
517
|
this.lambdaBindings.set(stmt.name, lambdaName);
|
|
374
518
|
return;
|
|
375
519
|
}
|
|
520
|
+
if (stmt.init.kind === 'call' && stmt.init.fn === 'setInterval') {
|
|
521
|
+
const value = this.lowerExpr(stmt.init);
|
|
522
|
+
const intervalFn = this.intervalFunctions.get(value.kind === 'const' ? value.value : NaN);
|
|
523
|
+
if (intervalFn) {
|
|
524
|
+
this.intervalBindings.set(stmt.name, intervalFn);
|
|
525
|
+
}
|
|
526
|
+
this.builder.emitAssign(varName, value);
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
376
529
|
// Handle struct literal initialization
|
|
377
530
|
if (stmt.init.kind === 'struct_lit' && stmt.type?.kind === 'struct') {
|
|
378
531
|
const structName = stmt.type.name.toLowerCase();
|
|
@@ -406,6 +559,13 @@ class Lowering {
|
|
|
406
559
|
}
|
|
407
560
|
return;
|
|
408
561
|
}
|
|
562
|
+
// Handle set_new returning a set ID string
|
|
563
|
+
if (stmt.init.kind === 'call' && stmt.init.fn === 'set_new') {
|
|
564
|
+
const setId = `__set_${this.foreachCounter++}`;
|
|
565
|
+
this.builder.emitRaw(`data modify storage rs:sets ${setId} set value []`);
|
|
566
|
+
this.stringValues.set(stmt.name, setId);
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
409
569
|
// Handle spawn_object returning entity handle
|
|
410
570
|
if (stmt.init.kind === 'call' && stmt.init.fn === 'spawn_object') {
|
|
411
571
|
const value = this.lowerExpr(stmt.init);
|
|
@@ -439,6 +599,10 @@ class Lowering {
|
|
|
439
599
|
}
|
|
440
600
|
}
|
|
441
601
|
lowerIfStmt(stmt) {
|
|
602
|
+
if (stmt.cond.kind === 'is_check') {
|
|
603
|
+
this.lowerIsCheckIfStmt(stmt);
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
442
606
|
const condVar = this.lowerExpr(stmt.cond);
|
|
443
607
|
const condName = this.operandToVar(condVar);
|
|
444
608
|
const thenLabel = this.builder.freshLabel('then');
|
|
@@ -462,6 +626,40 @@ class Lowering {
|
|
|
462
626
|
// Merge block
|
|
463
627
|
this.builder.startBlock(mergeLabel);
|
|
464
628
|
}
|
|
629
|
+
lowerIsCheckIfStmt(stmt) {
|
|
630
|
+
const cond = stmt.cond;
|
|
631
|
+
if (cond.kind !== 'is_check') {
|
|
632
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', "Internal error: expected 'is' check condition", stmt.span ?? { line: 0, col: 0 });
|
|
633
|
+
}
|
|
634
|
+
if (stmt.else_) {
|
|
635
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', "'is' checks with else branches are not yet supported", cond.span ?? stmt.span ?? { line: 0, col: 0 });
|
|
636
|
+
}
|
|
637
|
+
const selector = this.exprToEntitySelector(cond.expr);
|
|
638
|
+
if (!selector) {
|
|
639
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', "'is' checks require an entity selector or entity binding", cond.span ?? stmt.span ?? { line: 0, col: 0 });
|
|
640
|
+
}
|
|
641
|
+
const mcType = ENTITY_TO_MC_TYPE[cond.entityType];
|
|
642
|
+
if (!mcType) {
|
|
643
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', `Cannot lower entity type check for '${cond.entityType}'`, cond.span ?? stmt.span ?? { line: 0, col: 0 });
|
|
644
|
+
}
|
|
645
|
+
const thenFnName = `${this.currentFn}/then_${this.foreachCounter++}`;
|
|
646
|
+
this.builder.emitRaw(`execute if entity ${this.appendTypeFilter(selector, mcType)} run function ${this.namespace}:${thenFnName}`);
|
|
647
|
+
const savedBuilder = this.builder;
|
|
648
|
+
const savedVarMap = new Map(this.varMap);
|
|
649
|
+
const savedBlockPosVars = new Map(this.blockPosVars);
|
|
650
|
+
this.builder = new LoweringBuilder();
|
|
651
|
+
this.varMap = new Map(savedVarMap);
|
|
652
|
+
this.blockPosVars = new Map(savedBlockPosVars);
|
|
653
|
+
this.builder.startBlock('entry');
|
|
654
|
+
this.lowerBlock(stmt.then);
|
|
655
|
+
if (!this.builder.isBlockSealed()) {
|
|
656
|
+
this.builder.emitReturn();
|
|
657
|
+
}
|
|
658
|
+
this.functions.push(this.builder.build(thenFnName, [], false));
|
|
659
|
+
this.builder = savedBuilder;
|
|
660
|
+
this.varMap = savedVarMap;
|
|
661
|
+
this.blockPosVars = savedBlockPosVars;
|
|
662
|
+
}
|
|
465
663
|
lowerWhileStmt(stmt) {
|
|
466
664
|
const checkLabel = this.builder.freshLabel('loop_check');
|
|
467
665
|
const bodyLabel = this.builder.freshLabel('loop_body');
|
|
@@ -760,10 +958,24 @@ class Lowering {
|
|
|
760
958
|
parts.push(`at ${this.selectorToString(sub.selector)}`);
|
|
761
959
|
break;
|
|
762
960
|
case 'if_entity':
|
|
763
|
-
|
|
961
|
+
if (sub.selector) {
|
|
962
|
+
parts.push(`if entity ${this.selectorToString(sub.selector)}`);
|
|
963
|
+
}
|
|
964
|
+
else if (sub.varName) {
|
|
965
|
+
// Variable with filters - substitute with @s and apply filters
|
|
966
|
+
const sel = { kind: '@s', filters: sub.filters };
|
|
967
|
+
parts.push(`if entity ${this.selectorToString(sel)}`);
|
|
968
|
+
}
|
|
764
969
|
break;
|
|
765
970
|
case 'unless_entity':
|
|
766
|
-
|
|
971
|
+
if (sub.selector) {
|
|
972
|
+
parts.push(`unless entity ${this.selectorToString(sub.selector)}`);
|
|
973
|
+
}
|
|
974
|
+
else if (sub.varName) {
|
|
975
|
+
// Variable with filters - substitute with @s and apply filters
|
|
976
|
+
const sel = { kind: '@s', filters: sub.filters };
|
|
977
|
+
parts.push(`unless entity ${this.selectorToString(sel)}`);
|
|
978
|
+
}
|
|
767
979
|
break;
|
|
768
980
|
case 'in':
|
|
769
981
|
parts.push(`in ${sub.dimension}`);
|
|
@@ -854,12 +1066,16 @@ class Lowering {
|
|
|
854
1066
|
return { kind: 'var', name: this.selectorToString(expr.sel) };
|
|
855
1067
|
case 'binary':
|
|
856
1068
|
return this.lowerBinaryExpr(expr);
|
|
1069
|
+
case 'is_check':
|
|
1070
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', "'is' checks are only supported as if conditions", expr.span ?? { line: 0, col: 0 });
|
|
857
1071
|
case 'unary':
|
|
858
1072
|
return this.lowerUnaryExpr(expr);
|
|
859
1073
|
case 'assign':
|
|
860
1074
|
return this.lowerAssignExpr(expr);
|
|
861
1075
|
case 'call':
|
|
862
1076
|
return this.lowerCallExpr(expr);
|
|
1077
|
+
case 'static_call':
|
|
1078
|
+
return this.lowerStaticCallExpr(expr);
|
|
863
1079
|
case 'invoke':
|
|
864
1080
|
return this.lowerInvokeExpr(expr);
|
|
865
1081
|
case 'member_assign':
|
|
@@ -1051,6 +1267,14 @@ class Lowering {
|
|
|
1051
1267
|
return { kind: 'var', name: dst };
|
|
1052
1268
|
}
|
|
1053
1269
|
lowerAssignExpr(expr) {
|
|
1270
|
+
// Check for const reassignment (both compile-time consts and immutable globals)
|
|
1271
|
+
if (this.constValues.has(expr.target)) {
|
|
1272
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', `Cannot assign to constant '${expr.target}'`, getSpan(expr) ?? { line: 1, col: 1 });
|
|
1273
|
+
}
|
|
1274
|
+
const globalInfo = this.globalNames.get(expr.target);
|
|
1275
|
+
if (globalInfo && !globalInfo.mutable) {
|
|
1276
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', `Cannot assign to constant '${expr.target}'`, getSpan(expr) ?? { line: 1, col: 1 });
|
|
1277
|
+
}
|
|
1054
1278
|
const blockPosValue = this.resolveBlockPosExpr(expr.value);
|
|
1055
1279
|
if (blockPosValue) {
|
|
1056
1280
|
this.blockPosVars.set(expr.target, blockPosValue);
|
|
@@ -1169,6 +1393,10 @@ class Lowering {
|
|
|
1169
1393
|
if (callbackTarget) {
|
|
1170
1394
|
return this.emitDirectFunctionCall(callbackTarget, expr.args);
|
|
1171
1395
|
}
|
|
1396
|
+
const implMethod = this.resolveInstanceMethod(expr);
|
|
1397
|
+
if (implMethod) {
|
|
1398
|
+
return this.emitMethodCall(implMethod.loweredName, implMethod.fn, expr.args);
|
|
1399
|
+
}
|
|
1172
1400
|
// Regular function call
|
|
1173
1401
|
const fnDecl = this.fnDecls.get(expr.fn);
|
|
1174
1402
|
const defaultArgs = this.functionDefaults.get(expr.fn) ?? [];
|
|
@@ -1195,13 +1423,19 @@ class Lowering {
|
|
|
1195
1423
|
}
|
|
1196
1424
|
runtimeArgs.push(fullArgs[i]);
|
|
1197
1425
|
}
|
|
1198
|
-
const
|
|
1199
|
-
|
|
1426
|
+
const stdlibCallSite = this.getStdlibCallSiteContext(fnDecl, getSpan(expr));
|
|
1427
|
+
const targetFn = callbackBindings.size > 0 || stdlibCallSite
|
|
1428
|
+
? this.ensureSpecializedFunctionWithContext(fnDecl, callbackBindings, stdlibCallSite)
|
|
1200
1429
|
: expr.fn;
|
|
1201
1430
|
return this.emitDirectFunctionCall(targetFn, runtimeArgs);
|
|
1202
1431
|
}
|
|
1203
1432
|
return this.emitDirectFunctionCall(expr.fn, fullArgs);
|
|
1204
1433
|
}
|
|
1434
|
+
lowerStaticCallExpr(expr) {
|
|
1435
|
+
const method = this.implMethods.get(expr.type)?.get(expr.method);
|
|
1436
|
+
const targetFn = method?.loweredName ?? `${expr.type}_${expr.method}`;
|
|
1437
|
+
return this.emitMethodCall(targetFn, method?.fn, expr.args);
|
|
1438
|
+
}
|
|
1205
1439
|
lowerInvokeExpr(expr) {
|
|
1206
1440
|
if (expr.callee.kind === 'lambda') {
|
|
1207
1441
|
if (!Array.isArray(expr.callee.body)) {
|
|
@@ -1246,6 +1480,18 @@ class Lowering {
|
|
|
1246
1480
|
this.builder.emitCall(fn, loweredArgs, dst);
|
|
1247
1481
|
return { kind: 'var', name: dst };
|
|
1248
1482
|
}
|
|
1483
|
+
emitMethodCall(fn, fnDecl, args) {
|
|
1484
|
+
const defaultArgs = this.functionDefaults.get(fn) ?? fnDecl?.params.map(param => param.default) ?? [];
|
|
1485
|
+
const fullArgs = [...args];
|
|
1486
|
+
for (let i = fullArgs.length; i < defaultArgs.length; i++) {
|
|
1487
|
+
const defaultExpr = defaultArgs[i];
|
|
1488
|
+
if (!defaultExpr) {
|
|
1489
|
+
break;
|
|
1490
|
+
}
|
|
1491
|
+
fullArgs.push(defaultExpr);
|
|
1492
|
+
}
|
|
1493
|
+
return this.emitDirectFunctionCall(fn, fullArgs);
|
|
1494
|
+
}
|
|
1249
1495
|
resolveFunctionRefExpr(expr) {
|
|
1250
1496
|
if (expr.kind === 'lambda') {
|
|
1251
1497
|
return this.lowerLambdaExpr(expr);
|
|
@@ -1259,9 +1505,16 @@ class Lowering {
|
|
|
1259
1505
|
return this.lambdaBindings.get(name) ?? this.currentCallbackBindings.get(name) ?? null;
|
|
1260
1506
|
}
|
|
1261
1507
|
ensureSpecializedFunction(fn, callbackBindings) {
|
|
1508
|
+
return this.ensureSpecializedFunctionWithContext(fn, callbackBindings);
|
|
1509
|
+
}
|
|
1510
|
+
ensureSpecializedFunctionWithContext(fn, callbackBindings, stdlibCallSite) {
|
|
1262
1511
|
const parts = [...callbackBindings.entries()]
|
|
1263
1512
|
.sort(([left], [right]) => left.localeCompare(right))
|
|
1264
1513
|
.map(([param, target]) => `${param}_${target.replace(/[^a-zA-Z0-9_]/g, '_')}`);
|
|
1514
|
+
const callSiteHash = stdlibCallSite ? this.shortHash(this.serializeCallSite(stdlibCallSite)) : null;
|
|
1515
|
+
if (callSiteHash) {
|
|
1516
|
+
parts.push(`callsite_${callSiteHash}`);
|
|
1517
|
+
}
|
|
1265
1518
|
const key = `${fn.name}::${parts.join('::')}`;
|
|
1266
1519
|
const cached = this.specializedFunctions.get(key);
|
|
1267
1520
|
if (cached) {
|
|
@@ -1270,7 +1523,7 @@ class Lowering {
|
|
|
1270
1523
|
const specializedName = `${fn.name}__${parts.join('__')}`;
|
|
1271
1524
|
this.specializedFunctions.set(key, specializedName);
|
|
1272
1525
|
this.withSavedFunctionState(() => {
|
|
1273
|
-
this.lowerFn(fn, { name: specializedName, callbackBindings });
|
|
1526
|
+
this.lowerFn(fn, { name: specializedName, callbackBindings, stdlibCallSite });
|
|
1274
1527
|
});
|
|
1275
1528
|
return specializedName;
|
|
1276
1529
|
}
|
|
@@ -1293,10 +1546,12 @@ class Lowering {
|
|
|
1293
1546
|
}
|
|
1294
1547
|
withSavedFunctionState(callback) {
|
|
1295
1548
|
const savedCurrentFn = this.currentFn;
|
|
1549
|
+
const savedStdlibCallSite = this.currentStdlibCallSite;
|
|
1296
1550
|
const savedForeachCounter = this.foreachCounter;
|
|
1297
1551
|
const savedBuilder = this.builder;
|
|
1298
1552
|
const savedVarMap = new Map(this.varMap);
|
|
1299
1553
|
const savedLambdaBindings = new Map(this.lambdaBindings);
|
|
1554
|
+
const savedIntervalBindings = new Map(this.intervalBindings);
|
|
1300
1555
|
const savedCallbackBindings = new Map(this.currentCallbackBindings);
|
|
1301
1556
|
const savedContext = this.currentContext;
|
|
1302
1557
|
const savedBlockPosVars = new Map(this.blockPosVars);
|
|
@@ -1307,10 +1562,12 @@ class Lowering {
|
|
|
1307
1562
|
}
|
|
1308
1563
|
finally {
|
|
1309
1564
|
this.currentFn = savedCurrentFn;
|
|
1565
|
+
this.currentStdlibCallSite = savedStdlibCallSite;
|
|
1310
1566
|
this.foreachCounter = savedForeachCounter;
|
|
1311
1567
|
this.builder = savedBuilder;
|
|
1312
1568
|
this.varMap = savedVarMap;
|
|
1313
1569
|
this.lambdaBindings = savedLambdaBindings;
|
|
1570
|
+
this.intervalBindings = savedIntervalBindings;
|
|
1314
1571
|
this.currentCallbackBindings = savedCallbackBindings;
|
|
1315
1572
|
this.currentContext = savedContext;
|
|
1316
1573
|
this.blockPosVars = savedBlockPosVars;
|
|
@@ -1324,6 +1581,15 @@ class Lowering {
|
|
|
1324
1581
|
this.builder.emitRaw(richTextCommand);
|
|
1325
1582
|
return { kind: 'const', value: 0 };
|
|
1326
1583
|
}
|
|
1584
|
+
if (name === 'setTimeout') {
|
|
1585
|
+
return this.lowerSetTimeout(args);
|
|
1586
|
+
}
|
|
1587
|
+
if (name === 'setInterval') {
|
|
1588
|
+
return this.lowerSetInterval(args);
|
|
1589
|
+
}
|
|
1590
|
+
if (name === 'clearInterval') {
|
|
1591
|
+
return this.lowerClearInterval(args, callSpan);
|
|
1592
|
+
}
|
|
1327
1593
|
// Special case: random - legacy scoreboard RNG for pre-1.20.3 compatibility
|
|
1328
1594
|
if (name === 'random') {
|
|
1329
1595
|
const dst = this.builder.freshTemp();
|
|
@@ -1351,14 +1617,14 @@ class Lowering {
|
|
|
1351
1617
|
if (name === 'scoreboard_get' || name === 'score') {
|
|
1352
1618
|
const dst = this.builder.freshTemp();
|
|
1353
1619
|
const player = this.exprToTargetString(args[0]);
|
|
1354
|
-
const objective = this.
|
|
1620
|
+
const objective = this.resolveScoreboardObjective(args[0], args[1], callSpan);
|
|
1355
1621
|
this.builder.emitRaw(`execute store result score ${dst} rs run scoreboard players get ${player} ${objective}`);
|
|
1356
1622
|
return { kind: 'var', name: dst };
|
|
1357
1623
|
}
|
|
1358
1624
|
// Special case: scoreboard_set — write to vanilla MC scoreboard
|
|
1359
1625
|
if (name === 'scoreboard_set') {
|
|
1360
1626
|
const player = this.exprToTargetString(args[0]);
|
|
1361
|
-
const objective = this.
|
|
1627
|
+
const objective = this.resolveScoreboardObjective(args[0], args[1], callSpan);
|
|
1362
1628
|
const value = this.lowerExpr(args[2]);
|
|
1363
1629
|
if (value.kind === 'const') {
|
|
1364
1630
|
this.builder.emitRaw(`scoreboard players set ${player} ${objective} ${value.value}`);
|
|
@@ -1372,7 +1638,7 @@ class Lowering {
|
|
|
1372
1638
|
}
|
|
1373
1639
|
if (name === 'scoreboard_display') {
|
|
1374
1640
|
const slot = this.exprToString(args[0]);
|
|
1375
|
-
const objective = this.
|
|
1641
|
+
const objective = this.resolveScoreboardObjective(undefined, args[1], callSpan);
|
|
1376
1642
|
this.builder.emitRaw(`scoreboard objectives setdisplay ${slot} ${objective}`);
|
|
1377
1643
|
return { kind: 'const', value: 0 };
|
|
1378
1644
|
}
|
|
@@ -1382,14 +1648,14 @@ class Lowering {
|
|
|
1382
1648
|
return { kind: 'const', value: 0 };
|
|
1383
1649
|
}
|
|
1384
1650
|
if (name === 'scoreboard_add_objective') {
|
|
1385
|
-
const objective = this.
|
|
1651
|
+
const objective = this.resolveScoreboardObjective(undefined, args[0], callSpan);
|
|
1386
1652
|
const criteria = this.exprToString(args[1]);
|
|
1387
1653
|
const displayName = args[2] ? ` ${this.exprToQuotedString(args[2])}` : '';
|
|
1388
1654
|
this.builder.emitRaw(`scoreboard objectives add ${objective} ${criteria}${displayName}`);
|
|
1389
1655
|
return { kind: 'const', value: 0 };
|
|
1390
1656
|
}
|
|
1391
1657
|
if (name === 'scoreboard_remove_objective') {
|
|
1392
|
-
const objective = this.
|
|
1658
|
+
const objective = this.resolveScoreboardObjective(undefined, args[0], callSpan);
|
|
1393
1659
|
this.builder.emitRaw(`scoreboard objectives remove ${objective}`);
|
|
1394
1660
|
return { kind: 'const', value: 0 };
|
|
1395
1661
|
}
|
|
@@ -1473,6 +1739,62 @@ class Lowering {
|
|
|
1473
1739
|
this.builder.emitRaw(`execute store result score ${dst} rs run data get ${targetType} ${target} ${path} ${scale}`);
|
|
1474
1740
|
return { kind: 'var', name: dst };
|
|
1475
1741
|
}
|
|
1742
|
+
// data_merge(target, nbt) — merge NBT into entity/block/storage
|
|
1743
|
+
// data_merge(@s, { Invisible: 1b, Silent: 1b })
|
|
1744
|
+
if (name === 'data_merge') {
|
|
1745
|
+
const target = args[0];
|
|
1746
|
+
const nbt = args[1];
|
|
1747
|
+
const nbtStr = this.exprToSnbt ? this.exprToSnbt(nbt) : this.exprToString(nbt);
|
|
1748
|
+
// Check if target is a selector (entity) or string (block/storage)
|
|
1749
|
+
if (target.kind === 'selector') {
|
|
1750
|
+
const sel = this.exprToTargetString(target);
|
|
1751
|
+
this.builder.emitRaw(`data merge entity ${sel} ${nbtStr}`);
|
|
1752
|
+
}
|
|
1753
|
+
else {
|
|
1754
|
+
// Assume block position or storage
|
|
1755
|
+
const targetStr = this.exprToString(target);
|
|
1756
|
+
// If it looks like coordinates, use block; otherwise storage
|
|
1757
|
+
if (targetStr.match(/^~|^\d|^\^/)) {
|
|
1758
|
+
this.builder.emitRaw(`data merge block ${targetStr} ${nbtStr}`);
|
|
1759
|
+
}
|
|
1760
|
+
else {
|
|
1761
|
+
this.builder.emitRaw(`data merge storage ${targetStr} ${nbtStr}`);
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
return { kind: 'const', value: 0 };
|
|
1765
|
+
}
|
|
1766
|
+
// Set data structure operations — unique collections via NBT storage
|
|
1767
|
+
// set_new is primarily handled in lowerLetStmt for proper string tracking.
|
|
1768
|
+
// This fallback handles standalone set_new() calls without assignment.
|
|
1769
|
+
if (name === 'set_new') {
|
|
1770
|
+
const setId = `__set_${this.foreachCounter++}`;
|
|
1771
|
+
this.builder.emitRaw(`data modify storage rs:sets ${setId} set value []`);
|
|
1772
|
+
return { kind: 'const', value: 0 };
|
|
1773
|
+
}
|
|
1774
|
+
if (name === 'set_add') {
|
|
1775
|
+
const setId = this.exprToString(args[0]);
|
|
1776
|
+
const value = this.exprToString(args[1]);
|
|
1777
|
+
this.builder.emitRaw(`execute unless data storage rs:sets ${setId}[{value:${value}}] run data modify storage rs:sets ${setId} append value {value:${value}}`);
|
|
1778
|
+
return { kind: 'const', value: 0 };
|
|
1779
|
+
}
|
|
1780
|
+
if (name === 'set_contains') {
|
|
1781
|
+
const dst = this.builder.freshTemp();
|
|
1782
|
+
const setId = this.exprToString(args[0]);
|
|
1783
|
+
const value = this.exprToString(args[1]);
|
|
1784
|
+
this.builder.emitRaw(`execute store result score ${dst} rs if data storage rs:sets ${setId}[{value:${value}}]`);
|
|
1785
|
+
return { kind: 'var', name: dst };
|
|
1786
|
+
}
|
|
1787
|
+
if (name === 'set_remove') {
|
|
1788
|
+
const setId = this.exprToString(args[0]);
|
|
1789
|
+
const value = this.exprToString(args[1]);
|
|
1790
|
+
this.builder.emitRaw(`data remove storage rs:sets ${setId}[{value:${value}}]`);
|
|
1791
|
+
return { kind: 'const', value: 0 };
|
|
1792
|
+
}
|
|
1793
|
+
if (name === 'set_clear') {
|
|
1794
|
+
const setId = this.exprToString(args[0]);
|
|
1795
|
+
this.builder.emitRaw(`data modify storage rs:sets ${setId} set value []`);
|
|
1796
|
+
return { kind: 'const', value: 0 };
|
|
1797
|
+
}
|
|
1476
1798
|
const coordCommand = this.lowerCoordinateBuiltin(name, args);
|
|
1477
1799
|
if (coordCommand) {
|
|
1478
1800
|
this.builder.emitRaw(coordCommand);
|
|
@@ -1497,14 +1819,100 @@ class Lowering {
|
|
|
1497
1819
|
}
|
|
1498
1820
|
return { kind: 'const', value: 0 };
|
|
1499
1821
|
}
|
|
1500
|
-
// Convert args to strings for builtin
|
|
1501
|
-
const strArgs = args.map(arg =>
|
|
1822
|
+
// Convert args to strings for builtin (use SNBT for struct/array literals)
|
|
1823
|
+
const strArgs = args.map(arg => arg.kind === 'struct_lit' || arg.kind === 'array_lit'
|
|
1824
|
+
? this.exprToSnbt(arg)
|
|
1825
|
+
: this.exprToString(arg));
|
|
1502
1826
|
const cmd = BUILTINS[name](strArgs);
|
|
1503
1827
|
if (cmd) {
|
|
1504
1828
|
this.builder.emitRaw(cmd);
|
|
1505
1829
|
}
|
|
1506
1830
|
return { kind: 'const', value: 0 };
|
|
1507
1831
|
}
|
|
1832
|
+
lowerSetTimeout(args) {
|
|
1833
|
+
const delay = this.exprToLiteral(args[0]);
|
|
1834
|
+
const callback = args[1];
|
|
1835
|
+
if (!callback || callback.kind !== 'lambda') {
|
|
1836
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', 'setTimeout requires a lambda callback', getSpan(callback) ?? { line: 1, col: 1 });
|
|
1837
|
+
}
|
|
1838
|
+
const fnName = `__timeout_${this.timeoutCounter++}`;
|
|
1839
|
+
this.lowerNamedLambdaFunction(fnName, callback);
|
|
1840
|
+
this.builder.emitRaw(`schedule function ${this.namespace}:${fnName} ${delay}t`);
|
|
1841
|
+
return { kind: 'const', value: 0 };
|
|
1842
|
+
}
|
|
1843
|
+
lowerSetInterval(args) {
|
|
1844
|
+
const delay = this.exprToLiteral(args[0]);
|
|
1845
|
+
const callback = args[1];
|
|
1846
|
+
if (!callback || callback.kind !== 'lambda') {
|
|
1847
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', 'setInterval requires a lambda callback', getSpan(callback) ?? { line: 1, col: 1 });
|
|
1848
|
+
}
|
|
1849
|
+
const id = this.intervalCounter++;
|
|
1850
|
+
const bodyName = `__interval_body_${id}`;
|
|
1851
|
+
const fnName = `__interval_${id}`;
|
|
1852
|
+
this.lowerNamedLambdaFunction(bodyName, callback);
|
|
1853
|
+
this.lowerIntervalWrapperFunction(fnName, bodyName, delay);
|
|
1854
|
+
this.intervalFunctions.set(id, fnName);
|
|
1855
|
+
this.builder.emitRaw(`schedule function ${this.namespace}:${fnName} ${delay}t`);
|
|
1856
|
+
return { kind: 'const', value: id };
|
|
1857
|
+
}
|
|
1858
|
+
lowerClearInterval(args, callSpan) {
|
|
1859
|
+
const fnName = this.resolveIntervalFunctionName(args[0]);
|
|
1860
|
+
if (!fnName) {
|
|
1861
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', 'clearInterval requires an interval ID returned from setInterval', callSpan ?? getSpan(args[0]) ?? { line: 1, col: 1 });
|
|
1862
|
+
}
|
|
1863
|
+
this.builder.emitRaw(`schedule clear ${this.namespace}:${fnName}`);
|
|
1864
|
+
return { kind: 'const', value: 0 };
|
|
1865
|
+
}
|
|
1866
|
+
lowerNamedLambdaFunction(name, expr) {
|
|
1867
|
+
const lambdaFn = {
|
|
1868
|
+
name,
|
|
1869
|
+
params: expr.params.map(param => ({
|
|
1870
|
+
name: param.name,
|
|
1871
|
+
type: param.type ?? { kind: 'named', name: 'int' },
|
|
1872
|
+
})),
|
|
1873
|
+
returnType: expr.returnType ?? this.inferLambdaReturnType(expr),
|
|
1874
|
+
decorators: [],
|
|
1875
|
+
body: Array.isArray(expr.body) ? expr.body : [{ kind: 'return', value: expr.body }],
|
|
1876
|
+
};
|
|
1877
|
+
this.withSavedFunctionState(() => {
|
|
1878
|
+
this.lowerFn(lambdaFn);
|
|
1879
|
+
});
|
|
1880
|
+
}
|
|
1881
|
+
lowerIntervalWrapperFunction(name, bodyName, delay) {
|
|
1882
|
+
const intervalFn = {
|
|
1883
|
+
name,
|
|
1884
|
+
params: [],
|
|
1885
|
+
returnType: { kind: 'named', name: 'void' },
|
|
1886
|
+
decorators: [],
|
|
1887
|
+
body: [
|
|
1888
|
+
{ kind: 'raw', cmd: `function ${this.namespace}:${bodyName}` },
|
|
1889
|
+
{ kind: 'raw', cmd: `schedule function ${this.namespace}:${name} ${delay}t` },
|
|
1890
|
+
],
|
|
1891
|
+
};
|
|
1892
|
+
this.withSavedFunctionState(() => {
|
|
1893
|
+
this.lowerFn(intervalFn);
|
|
1894
|
+
});
|
|
1895
|
+
}
|
|
1896
|
+
resolveIntervalFunctionName(expr) {
|
|
1897
|
+
if (!expr) {
|
|
1898
|
+
return null;
|
|
1899
|
+
}
|
|
1900
|
+
if (expr.kind === 'ident') {
|
|
1901
|
+
const boundInterval = this.intervalBindings.get(expr.name);
|
|
1902
|
+
if (boundInterval) {
|
|
1903
|
+
return boundInterval;
|
|
1904
|
+
}
|
|
1905
|
+
const constValue = this.constValues.get(expr.name);
|
|
1906
|
+
if (constValue?.kind === 'int_lit') {
|
|
1907
|
+
return this.intervalFunctions.get(constValue.value) ?? null;
|
|
1908
|
+
}
|
|
1909
|
+
return null;
|
|
1910
|
+
}
|
|
1911
|
+
if (expr.kind === 'int_lit') {
|
|
1912
|
+
return this.intervalFunctions.get(expr.value) ?? null;
|
|
1913
|
+
}
|
|
1914
|
+
return null;
|
|
1915
|
+
}
|
|
1508
1916
|
lowerRichTextBuiltin(name, args) {
|
|
1509
1917
|
const messageArgIndex = this.getRichTextArgIndex(name);
|
|
1510
1918
|
if (messageArgIndex === null) {
|
|
@@ -1647,12 +2055,75 @@ class Lowering {
|
|
|
1647
2055
|
}
|
|
1648
2056
|
case 'selector':
|
|
1649
2057
|
return this.selectorToString(expr.sel);
|
|
2058
|
+
case 'unary':
|
|
2059
|
+
// Handle unary minus on literals directly
|
|
2060
|
+
if (expr.op === '-' && expr.operand.kind === 'int_lit') {
|
|
2061
|
+
return (-expr.operand.value).toString();
|
|
2062
|
+
}
|
|
2063
|
+
if (expr.op === '-' && expr.operand.kind === 'float_lit') {
|
|
2064
|
+
return Math.trunc(-expr.operand.value).toString();
|
|
2065
|
+
}
|
|
2066
|
+
// Fall through to default for complex cases
|
|
2067
|
+
const unaryOp = this.lowerExpr(expr);
|
|
2068
|
+
return this.operandToVar(unaryOp);
|
|
1650
2069
|
default:
|
|
1651
2070
|
// Complex expression - lower and return var name
|
|
1652
2071
|
const op = this.lowerExpr(expr);
|
|
1653
2072
|
return this.operandToVar(op);
|
|
1654
2073
|
}
|
|
1655
2074
|
}
|
|
2075
|
+
exprToEntitySelector(expr) {
|
|
2076
|
+
if (expr.kind === 'selector') {
|
|
2077
|
+
return this.selectorToString(expr.sel);
|
|
2078
|
+
}
|
|
2079
|
+
if (expr.kind === 'ident') {
|
|
2080
|
+
const constValue = this.constValues.get(expr.name);
|
|
2081
|
+
if (constValue) {
|
|
2082
|
+
return this.exprToEntitySelector(constValue);
|
|
2083
|
+
}
|
|
2084
|
+
const mapped = this.varMap.get(expr.name);
|
|
2085
|
+
if (mapped?.startsWith('@')) {
|
|
2086
|
+
return mapped;
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
return null;
|
|
2090
|
+
}
|
|
2091
|
+
appendTypeFilter(selector, mcType) {
|
|
2092
|
+
if (selector.endsWith(']')) {
|
|
2093
|
+
return `${selector.slice(0, -1)},type=${mcType}]`;
|
|
2094
|
+
}
|
|
2095
|
+
return `${selector}[type=${mcType}]`;
|
|
2096
|
+
}
|
|
2097
|
+
exprToSnbt(expr) {
|
|
2098
|
+
switch (expr.kind) {
|
|
2099
|
+
case 'struct_lit': {
|
|
2100
|
+
const entries = expr.fields.map(f => `${f.name}:${this.exprToSnbt(f.value)}`);
|
|
2101
|
+
return `{${entries.join(',')}}`;
|
|
2102
|
+
}
|
|
2103
|
+
case 'array_lit': {
|
|
2104
|
+
const items = expr.elements.map(e => this.exprToSnbt(e));
|
|
2105
|
+
return `[${items.join(',')}]`;
|
|
2106
|
+
}
|
|
2107
|
+
case 'str_lit':
|
|
2108
|
+
return `"${expr.value}"`;
|
|
2109
|
+
case 'int_lit':
|
|
2110
|
+
return String(expr.value);
|
|
2111
|
+
case 'float_lit':
|
|
2112
|
+
return String(expr.value);
|
|
2113
|
+
case 'byte_lit':
|
|
2114
|
+
return `${expr.value}b`;
|
|
2115
|
+
case 'short_lit':
|
|
2116
|
+
return `${expr.value}s`;
|
|
2117
|
+
case 'long_lit':
|
|
2118
|
+
return `${expr.value}L`;
|
|
2119
|
+
case 'double_lit':
|
|
2120
|
+
return `${expr.value}d`;
|
|
2121
|
+
case 'bool_lit':
|
|
2122
|
+
return expr.value ? '1b' : '0b';
|
|
2123
|
+
default:
|
|
2124
|
+
return this.exprToString(expr);
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
1656
2127
|
exprToTargetString(expr) {
|
|
1657
2128
|
if (expr.kind === 'selector') {
|
|
1658
2129
|
return this.selectorToString(expr.sel);
|
|
@@ -1690,6 +2161,92 @@ class Lowering {
|
|
|
1690
2161
|
isTeamTextOption(option) {
|
|
1691
2162
|
return option === 'displayName' || option === 'prefix' || option === 'suffix';
|
|
1692
2163
|
}
|
|
2164
|
+
exprToScoreboardObjective(expr, span) {
|
|
2165
|
+
if (expr.kind === 'mc_name') {
|
|
2166
|
+
return expr.value;
|
|
2167
|
+
}
|
|
2168
|
+
const objective = this.exprToString(expr);
|
|
2169
|
+
if (objective.startsWith('#') || objective.includes('.')) {
|
|
2170
|
+
return objective.startsWith('#') ? objective.slice(1) : objective;
|
|
2171
|
+
}
|
|
2172
|
+
return `${this.getObjectiveNamespace(span)}.${objective}`;
|
|
2173
|
+
}
|
|
2174
|
+
resolveScoreboardObjective(playerExpr, objectiveExpr, span) {
|
|
2175
|
+
const stdlibInternalObjective = this.tryGetStdlibInternalObjective(playerExpr, objectiveExpr, span);
|
|
2176
|
+
if (stdlibInternalObjective) {
|
|
2177
|
+
return stdlibInternalObjective;
|
|
2178
|
+
}
|
|
2179
|
+
return this.exprToScoreboardObjective(objectiveExpr, span);
|
|
2180
|
+
}
|
|
2181
|
+
getObjectiveNamespace(span) {
|
|
2182
|
+
const filePath = this.filePathForSpan(span);
|
|
2183
|
+
if (!filePath) {
|
|
2184
|
+
return this.namespace;
|
|
2185
|
+
}
|
|
2186
|
+
return this.isStdlibFile(filePath) ? 'rs' : this.namespace;
|
|
2187
|
+
}
|
|
2188
|
+
tryGetStdlibInternalObjective(playerExpr, objectiveExpr, span) {
|
|
2189
|
+
if (!span || !this.currentStdlibCallSite || objectiveExpr.kind !== 'mc_name' || objectiveExpr.value !== 'rs') {
|
|
2190
|
+
return null;
|
|
2191
|
+
}
|
|
2192
|
+
const filePath = this.filePathForSpan(span);
|
|
2193
|
+
if (!filePath || !this.isStdlibFile(filePath)) {
|
|
2194
|
+
return null;
|
|
2195
|
+
}
|
|
2196
|
+
const resourceBase = this.getStdlibInternalResourceBase(playerExpr);
|
|
2197
|
+
if (!resourceBase) {
|
|
2198
|
+
return null;
|
|
2199
|
+
}
|
|
2200
|
+
const hash = this.shortHash(this.serializeCallSite(this.currentStdlibCallSite));
|
|
2201
|
+
return `rs._${resourceBase}_${hash}`;
|
|
2202
|
+
}
|
|
2203
|
+
getStdlibInternalResourceBase(playerExpr) {
|
|
2204
|
+
if (!playerExpr || playerExpr.kind !== 'str_lit') {
|
|
2205
|
+
return null;
|
|
2206
|
+
}
|
|
2207
|
+
const match = playerExpr.value.match(/^([a-z0-9]+)_/);
|
|
2208
|
+
return match?.[1] ?? null;
|
|
2209
|
+
}
|
|
2210
|
+
getStdlibCallSiteContext(fn, exprSpan) {
|
|
2211
|
+
const fnFilePath = this.filePathForSpan(getSpan(fn));
|
|
2212
|
+
if (!fnFilePath || !this.isStdlibFile(fnFilePath)) {
|
|
2213
|
+
return undefined;
|
|
2214
|
+
}
|
|
2215
|
+
if (this.currentStdlibCallSite) {
|
|
2216
|
+
return this.currentStdlibCallSite;
|
|
2217
|
+
}
|
|
2218
|
+
if (!exprSpan) {
|
|
2219
|
+
return undefined;
|
|
2220
|
+
}
|
|
2221
|
+
return {
|
|
2222
|
+
filePath: this.filePathForSpan(exprSpan),
|
|
2223
|
+
line: exprSpan.line,
|
|
2224
|
+
col: exprSpan.col,
|
|
2225
|
+
};
|
|
2226
|
+
}
|
|
2227
|
+
serializeCallSite(callSite) {
|
|
2228
|
+
return `${callSite.filePath ?? '<memory>'}:${callSite.line}:${callSite.col}`;
|
|
2229
|
+
}
|
|
2230
|
+
shortHash(input) {
|
|
2231
|
+
let hash = 2166136261;
|
|
2232
|
+
for (let i = 0; i < input.length; i++) {
|
|
2233
|
+
hash ^= input.charCodeAt(i);
|
|
2234
|
+
hash = Math.imul(hash, 16777619);
|
|
2235
|
+
}
|
|
2236
|
+
return (hash >>> 0).toString(16).padStart(8, '0').slice(0, 4);
|
|
2237
|
+
}
|
|
2238
|
+
isStdlibFile(filePath) {
|
|
2239
|
+
const normalized = path.normalize(filePath);
|
|
2240
|
+
const stdlibSegment = `${path.sep}src${path.sep}stdlib${path.sep}`;
|
|
2241
|
+
return normalized.includes(stdlibSegment);
|
|
2242
|
+
}
|
|
2243
|
+
filePathForSpan(span) {
|
|
2244
|
+
if (!span) {
|
|
2245
|
+
return undefined;
|
|
2246
|
+
}
|
|
2247
|
+
const line = span.line;
|
|
2248
|
+
return this.sourceRanges.find(range => line >= range.startLine && line <= range.endLine)?.filePath;
|
|
2249
|
+
}
|
|
1693
2250
|
lowerCoordinateBuiltin(name, args) {
|
|
1694
2251
|
const pos0 = args[0] ? this.resolveBlockPosExpr(args[0]) : null;
|
|
1695
2252
|
const pos1 = args[1] ? this.resolveBlockPosExpr(args[1]) : null;
|
|
@@ -1712,6 +2269,13 @@ class Lowering {
|
|
|
1712
2269
|
}
|
|
1713
2270
|
return null;
|
|
1714
2271
|
}
|
|
2272
|
+
if (name === 'summon') {
|
|
2273
|
+
if (args.length >= 2 && pos1) {
|
|
2274
|
+
const nbt = args[2] ? ` ${this.exprToString(args[2])}` : '';
|
|
2275
|
+
return `summon ${this.exprToString(args[0])} ${emitBlockPos(pos1)}${nbt}`;
|
|
2276
|
+
}
|
|
2277
|
+
return null;
|
|
2278
|
+
}
|
|
1715
2279
|
return null;
|
|
1716
2280
|
}
|
|
1717
2281
|
lowerTpCommand(args) {
|
|
@@ -1790,7 +2354,11 @@ class Lowering {
|
|
|
1790
2354
|
};
|
|
1791
2355
|
}
|
|
1792
2356
|
if (expr.kind === 'call') {
|
|
1793
|
-
|
|
2357
|
+
const resolved = this.resolveFunctionRefByName(expr.fn) ?? this.resolveInstanceMethod(expr)?.loweredName ?? expr.fn;
|
|
2358
|
+
return this.fnDecls.get(resolved)?.returnType;
|
|
2359
|
+
}
|
|
2360
|
+
if (expr.kind === 'static_call') {
|
|
2361
|
+
return this.implMethods.get(expr.type)?.get(expr.method)?.fn.returnType;
|
|
1794
2362
|
}
|
|
1795
2363
|
if (expr.kind === 'invoke') {
|
|
1796
2364
|
const calleeType = this.inferExprType(expr.callee);
|
|
@@ -1818,6 +2386,21 @@ class Lowering {
|
|
|
1818
2386
|
}
|
|
1819
2387
|
return undefined;
|
|
1820
2388
|
}
|
|
2389
|
+
resolveInstanceMethod(expr) {
|
|
2390
|
+
const receiver = expr.args[0];
|
|
2391
|
+
if (!receiver) {
|
|
2392
|
+
return null;
|
|
2393
|
+
}
|
|
2394
|
+
const receiverType = this.inferExprType(receiver);
|
|
2395
|
+
if (receiverType?.kind !== 'struct') {
|
|
2396
|
+
return null;
|
|
2397
|
+
}
|
|
2398
|
+
const method = this.implMethods.get(receiverType.name)?.get(expr.fn);
|
|
2399
|
+
if (!method || method.fn.params[0]?.name !== 'self') {
|
|
2400
|
+
return null;
|
|
2401
|
+
}
|
|
2402
|
+
return method;
|
|
2403
|
+
}
|
|
1821
2404
|
normalizeType(type) {
|
|
1822
2405
|
if (type.kind === 'array') {
|
|
1823
2406
|
return { kind: 'array', elem: this.normalizeType(type.elem) };
|
|
@@ -1941,6 +2524,18 @@ class Lowering {
|
|
|
1941
2524
|
parts.push(`nbt=${filters.nbt}`);
|
|
1942
2525
|
if (filters.gamemode)
|
|
1943
2526
|
parts.push(`gamemode=${filters.gamemode}`);
|
|
2527
|
+
// Position filters
|
|
2528
|
+
if (filters.x)
|
|
2529
|
+
parts.push(`x=${this.rangeToString(filters.x)}`);
|
|
2530
|
+
if (filters.y)
|
|
2531
|
+
parts.push(`y=${this.rangeToString(filters.y)}`);
|
|
2532
|
+
if (filters.z)
|
|
2533
|
+
parts.push(`z=${this.rangeToString(filters.z)}`);
|
|
2534
|
+
// Rotation filters
|
|
2535
|
+
if (filters.x_rotation)
|
|
2536
|
+
parts.push(`x_rotation=${this.rangeToString(filters.x_rotation)}`);
|
|
2537
|
+
if (filters.y_rotation)
|
|
2538
|
+
parts.push(`y_rotation=${this.rangeToString(filters.y_rotation)}`);
|
|
1944
2539
|
return this.finalizeSelector(parts.length ? `${kind}[${parts.join(',')}]` : kind);
|
|
1945
2540
|
}
|
|
1946
2541
|
finalizeSelector(selector) {
|
|
@@ -1965,14 +2560,17 @@ exports.Lowering = Lowering;
|
|
|
1965
2560
|
// ---------------------------------------------------------------------------
|
|
1966
2561
|
class LoweringBuilder {
|
|
1967
2562
|
constructor() {
|
|
1968
|
-
this.tempCount = 0;
|
|
1969
2563
|
this.labelCount = 0;
|
|
1970
2564
|
this.blocks = [];
|
|
1971
2565
|
this.currentBlock = null;
|
|
1972
2566
|
this.locals = new Set();
|
|
1973
2567
|
}
|
|
2568
|
+
/** Reset the global temp counter (call between compilations). */
|
|
2569
|
+
static resetTempCounter() {
|
|
2570
|
+
LoweringBuilder.globalTempId = 0;
|
|
2571
|
+
}
|
|
1974
2572
|
freshTemp() {
|
|
1975
|
-
const name = `$
|
|
2573
|
+
const name = `$_${LoweringBuilder.globalTempId++}`;
|
|
1976
2574
|
this.locals.add(name);
|
|
1977
2575
|
return name;
|
|
1978
2576
|
}
|
|
@@ -2038,4 +2636,5 @@ class LoweringBuilder {
|
|
|
2038
2636
|
};
|
|
2039
2637
|
}
|
|
2040
2638
|
}
|
|
2639
|
+
LoweringBuilder.globalTempId = 0;
|
|
2041
2640
|
//# sourceMappingURL=index.js.map
|