redscript-mc 1.1.0 → 1.2.1
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/CHANGELOG.md +59 -0
- package/README.md +53 -10
- package/README.zh.md +53 -10
- package/dist/__tests__/cli.test.js +138 -0
- package/dist/__tests__/codegen.test.js +25 -0
- package/dist/__tests__/dce.test.d.ts +1 -0
- package/dist/__tests__/dce.test.js +137 -0
- package/dist/__tests__/e2e.test.js +190 -12
- package/dist/__tests__/lexer.test.js +31 -4
- package/dist/__tests__/lowering.test.js +172 -9
- package/dist/__tests__/mc-integration.test.js +145 -51
- package/dist/__tests__/mc-syntax.test.js +12 -0
- package/dist/__tests__/optimizer-advanced.test.js +3 -3
- package/dist/__tests__/parser.test.js +90 -0
- package/dist/__tests__/runtime.test.js +21 -8
- package/dist/__tests__/typechecker.test.js +188 -0
- package/dist/ast/types.d.ts +42 -3
- package/dist/cli.js +15 -10
- package/dist/codegen/mcfunction/index.js +30 -1
- package/dist/codegen/structure/index.d.ts +4 -1
- package/dist/codegen/structure/index.js +29 -2
- package/dist/compile.d.ts +11 -0
- package/dist/compile.js +40 -6
- package/dist/events/types.d.ts +35 -0
- package/dist/events/types.js +59 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +7 -3
- package/dist/ir/types.d.ts +4 -0
- package/dist/lexer/index.d.ts +2 -1
- package/dist/lexer/index.js +91 -1
- package/dist/lowering/index.d.ts +32 -1
- package/dist/lowering/index.js +476 -16
- package/dist/optimizer/dce.d.ts +23 -0
- package/dist/optimizer/dce.js +591 -0
- package/dist/parser/index.d.ts +4 -0
- package/dist/parser/index.js +160 -26
- package/dist/typechecker/index.d.ts +19 -0
- package/dist/typechecker/index.js +392 -17
- package/docs/ARCHITECTURE.zh.md +1088 -0
- package/docs/ENTITY_TYPE_SYSTEM.md +242 -0
- package/editors/vscode/.vscodeignore +3 -0
- package/editors/vscode/CHANGELOG.md +9 -0
- package/editors/vscode/icon.png +0 -0
- package/editors/vscode/out/extension.js +1144 -72
- package/editors/vscode/package-lock.json +2 -2
- package/editors/vscode/package.json +1 -1
- package/editors/vscode/syntaxes/redscript.tmLanguage.json +6 -2
- package/examples/spiral.mcrs +79 -0
- package/logo.png +0 -0
- package/package.json +1 -1
- package/src/__tests__/cli.test.ts +166 -0
- package/src/__tests__/codegen.test.ts +27 -0
- package/src/__tests__/dce.test.ts +129 -0
- package/src/__tests__/e2e.test.ts +201 -12
- 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 +35 -4
- package/src/__tests__/lowering.test.ts +187 -9
- package/src/__tests__/mc-integration.test.ts +166 -51
- package/src/__tests__/mc-syntax.test.ts +14 -0
- package/src/__tests__/optimizer-advanced.test.ts +3 -3
- package/src/__tests__/parser.test.ts +102 -5
- package/src/__tests__/runtime.test.ts +24 -8
- package/src/__tests__/typechecker.test.ts +204 -0
- package/src/ast/types.ts +39 -2
- package/src/cli.ts +24 -10
- package/src/codegen/mcfunction/index.ts +31 -1
- package/src/codegen/structure/index.ts +40 -2
- package/src/compile.ts +59 -7
- package/src/events/types.ts +69 -0
- package/src/index.ts +9 -4
- package/src/ir/types.ts +4 -0
- package/src/lexer/index.ts +105 -2
- package/src/lowering/index.ts +566 -18
- package/src/optimizer/dce.ts +618 -0
- package/src/parser/index.ts +187 -29
- package/src/stdlib/README.md +34 -4
- package/src/stdlib/tags.mcrs +951 -0
- package/src/stdlib/timer.mcrs +54 -33
- package/src/typechecker/index.ts +469 -18
package/dist/lowering/index.js
CHANGED
|
@@ -5,16 +5,52 @@
|
|
|
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
|
// ---------------------------------------------------------------------------
|
|
15
50
|
const BUILTINS = {
|
|
16
51
|
say: ([msg]) => `say ${msg}`,
|
|
17
52
|
tell: ([sel, msg]) => `tellraw ${sel} {"text":"${msg}"}`,
|
|
53
|
+
tellraw: ([sel, msg]) => `tellraw ${sel} {"text":"${msg}"}`,
|
|
18
54
|
title: ([sel, msg]) => `title ${sel} title {"text":"${msg}"}`,
|
|
19
55
|
actionbar: ([sel, msg]) => `title ${sel} actionbar {"text":"${msg}"}`,
|
|
20
56
|
subtitle: ([sel, msg]) => `title ${sel} subtitle {"text":"${msg}"}`,
|
|
@@ -80,12 +116,31 @@ const BUILTINS = {
|
|
|
80
116
|
set_contains: () => null, // Special handling (returns 1/0)
|
|
81
117
|
set_remove: () => null, // Special handling
|
|
82
118
|
set_clear: () => null, // Special handling
|
|
119
|
+
setTimeout: () => null, // Special handling
|
|
120
|
+
setInterval: () => null, // Special handling
|
|
121
|
+
clearInterval: () => null, // Special handling
|
|
83
122
|
};
|
|
84
123
|
function getSpan(node) {
|
|
85
124
|
return node?.span;
|
|
86
125
|
}
|
|
87
126
|
const NAMESPACED_ENTITY_TYPE_RE = /^[a-z0-9_.-]+:[a-z0-9_./-]+$/;
|
|
88
127
|
const BARE_ENTITY_TYPE_RE = /^[a-z0-9_./-]+$/;
|
|
128
|
+
const ENTITY_TO_MC_TYPE = {
|
|
129
|
+
Player: 'player',
|
|
130
|
+
Zombie: 'zombie',
|
|
131
|
+
Skeleton: 'skeleton',
|
|
132
|
+
Creeper: 'creeper',
|
|
133
|
+
Spider: 'spider',
|
|
134
|
+
Enderman: 'enderman',
|
|
135
|
+
Pig: 'pig',
|
|
136
|
+
Cow: 'cow',
|
|
137
|
+
Sheep: 'sheep',
|
|
138
|
+
Chicken: 'chicken',
|
|
139
|
+
Villager: 'villager',
|
|
140
|
+
ArmorStand: 'armor_stand',
|
|
141
|
+
Item: 'item',
|
|
142
|
+
Arrow: 'arrow',
|
|
143
|
+
};
|
|
89
144
|
function normalizeSelector(selector, warnings) {
|
|
90
145
|
return selector.replace(/type=([^,\]]+)/g, (match, entityType) => {
|
|
91
146
|
const trimmed = entityType.trim();
|
|
@@ -122,18 +177,23 @@ function emitBlockPos(pos) {
|
|
|
122
177
|
// Lowering Class
|
|
123
178
|
// ---------------------------------------------------------------------------
|
|
124
179
|
class Lowering {
|
|
125
|
-
constructor(namespace) {
|
|
180
|
+
constructor(namespace, sourceRanges = []) {
|
|
126
181
|
this.functions = [];
|
|
127
182
|
this.globals = [];
|
|
128
183
|
this.globalNames = new Map();
|
|
129
184
|
this.fnDecls = new Map();
|
|
185
|
+
this.implMethods = new Map();
|
|
130
186
|
this.specializedFunctions = new Map();
|
|
131
187
|
this.currentFn = '';
|
|
132
188
|
this.foreachCounter = 0;
|
|
133
189
|
this.lambdaCounter = 0;
|
|
190
|
+
this.timeoutCounter = 0;
|
|
191
|
+
this.intervalCounter = 0;
|
|
134
192
|
this.warnings = [];
|
|
135
193
|
this.varMap = new Map();
|
|
136
194
|
this.lambdaBindings = new Map();
|
|
195
|
+
this.intervalBindings = new Map();
|
|
196
|
+
this.intervalFunctions = new Map();
|
|
137
197
|
this.currentCallbackBindings = new Map();
|
|
138
198
|
this.currentContext = {};
|
|
139
199
|
this.blockPosVars = new Map();
|
|
@@ -150,6 +210,7 @@ class Lowering {
|
|
|
150
210
|
// World object counter for unique tags
|
|
151
211
|
this.worldObjCounter = 0;
|
|
152
212
|
this.namespace = namespace;
|
|
213
|
+
this.sourceRanges = sourceRanges;
|
|
153
214
|
LoweringBuilder.resetTempCounter();
|
|
154
215
|
}
|
|
155
216
|
lower(program) {
|
|
@@ -184,9 +245,27 @@ class Lowering {
|
|
|
184
245
|
this.fnDecls.set(fn.name, fn);
|
|
185
246
|
this.functionDefaults.set(fn.name, fn.params.map(param => param.default));
|
|
186
247
|
}
|
|
248
|
+
for (const implBlock of program.implBlocks ?? []) {
|
|
249
|
+
let methods = this.implMethods.get(implBlock.typeName);
|
|
250
|
+
if (!methods) {
|
|
251
|
+
methods = new Map();
|
|
252
|
+
this.implMethods.set(implBlock.typeName, methods);
|
|
253
|
+
}
|
|
254
|
+
for (const method of implBlock.methods) {
|
|
255
|
+
const loweredName = `${implBlock.typeName}_${method.name}`;
|
|
256
|
+
methods.set(method.name, { fn: method, loweredName });
|
|
257
|
+
this.fnDecls.set(loweredName, method);
|
|
258
|
+
this.functionDefaults.set(loweredName, method.params.map(param => param.default));
|
|
259
|
+
}
|
|
260
|
+
}
|
|
187
261
|
for (const fn of program.declarations) {
|
|
188
262
|
this.lowerFn(fn);
|
|
189
263
|
}
|
|
264
|
+
for (const implBlock of program.implBlocks ?? []) {
|
|
265
|
+
for (const method of implBlock.methods) {
|
|
266
|
+
this.lowerFn(method, { name: `${implBlock.typeName}_${method.name}` });
|
|
267
|
+
}
|
|
268
|
+
}
|
|
190
269
|
return (0, builder_1.buildModule)(this.namespace, this.functions, this.globals);
|
|
191
270
|
}
|
|
192
271
|
// -------------------------------------------------------------------------
|
|
@@ -195,21 +274,48 @@ class Lowering {
|
|
|
195
274
|
lowerFn(fn, options = {}) {
|
|
196
275
|
const loweredName = options.name ?? fn.name;
|
|
197
276
|
const callbackBindings = options.callbackBindings ?? new Map();
|
|
198
|
-
const
|
|
277
|
+
const stdlibCallSite = options.stdlibCallSite;
|
|
278
|
+
const staticEventDec = fn.decorators.find(d => d.name === 'on');
|
|
279
|
+
const eventType = staticEventDec?.args?.eventType;
|
|
280
|
+
const eventParamSpecs = eventType && (0, types_1.isEventTypeName)(eventType) ? (0, types_1.getEventParamSpecs)(eventType) : [];
|
|
281
|
+
const runtimeParams = staticEventDec
|
|
282
|
+
? []
|
|
283
|
+
: fn.params.filter(param => !callbackBindings.has(param.name));
|
|
199
284
|
this.currentFn = loweredName;
|
|
285
|
+
this.currentStdlibCallSite = stdlibCallSite;
|
|
200
286
|
this.foreachCounter = 0;
|
|
201
287
|
this.varMap = new Map();
|
|
202
288
|
this.lambdaBindings = new Map();
|
|
289
|
+
this.intervalBindings = new Map();
|
|
203
290
|
this.currentCallbackBindings = new Map(callbackBindings);
|
|
204
291
|
this.currentContext = {};
|
|
205
292
|
this.blockPosVars = new Map();
|
|
206
293
|
this.stringValues = new Map();
|
|
207
294
|
this.builder = new LoweringBuilder();
|
|
208
295
|
// Map parameters
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
296
|
+
if (staticEventDec) {
|
|
297
|
+
for (let i = 0; i < fn.params.length; i++) {
|
|
298
|
+
const param = fn.params[i];
|
|
299
|
+
const expected = eventParamSpecs[i];
|
|
300
|
+
const normalizedType = this.normalizeType(param.type);
|
|
301
|
+
this.varTypes.set(param.name, normalizedType);
|
|
302
|
+
if (expected?.type.kind === 'entity') {
|
|
303
|
+
this.varMap.set(param.name, '@s');
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
if (expected?.type.kind === 'named' && expected.type.name === 'string') {
|
|
307
|
+
this.stringValues.set(param.name, '');
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
this.varMap.set(param.name, `$${param.name}`);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
for (const param of runtimeParams) {
|
|
315
|
+
const paramName = param.name;
|
|
316
|
+
this.varMap.set(paramName, `$${paramName}`);
|
|
317
|
+
this.varTypes.set(paramName, this.normalizeType(param.type));
|
|
318
|
+
}
|
|
213
319
|
}
|
|
214
320
|
for (const param of fn.params) {
|
|
215
321
|
if (callbackBindings.has(param.name)) {
|
|
@@ -224,6 +330,15 @@ class Lowering {
|
|
|
224
330
|
const varName = `$${paramName}`;
|
|
225
331
|
this.builder.emitAssign(varName, { kind: 'var', name: `$p${i}` });
|
|
226
332
|
}
|
|
333
|
+
if (staticEventDec) {
|
|
334
|
+
for (let i = 0; i < fn.params.length; i++) {
|
|
335
|
+
const param = fn.params[i];
|
|
336
|
+
const expected = eventParamSpecs[i];
|
|
337
|
+
if (expected?.type.kind === 'named' && expected.type.name !== 'string') {
|
|
338
|
+
this.builder.emitAssign(`$${param.name}`, { kind: 'const', value: 0 });
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
227
342
|
// Lower body
|
|
228
343
|
this.lowerBlock(fn.body);
|
|
229
344
|
// If no explicit return, add void return
|
|
@@ -267,6 +382,12 @@ class Lowering {
|
|
|
267
382
|
break;
|
|
268
383
|
}
|
|
269
384
|
}
|
|
385
|
+
if (eventType && (0, types_1.isEventTypeName)(eventType)) {
|
|
386
|
+
irFn.eventHandler = {
|
|
387
|
+
eventType,
|
|
388
|
+
tag: types_1.EVENT_TYPES[eventType].tag,
|
|
389
|
+
};
|
|
390
|
+
}
|
|
270
391
|
// Check for @load decorator
|
|
271
392
|
if (fn.decorators.some(d => d.name === 'load')) {
|
|
272
393
|
irFn.isLoadInit = true;
|
|
@@ -397,6 +518,15 @@ class Lowering {
|
|
|
397
518
|
this.lambdaBindings.set(stmt.name, lambdaName);
|
|
398
519
|
return;
|
|
399
520
|
}
|
|
521
|
+
if (stmt.init.kind === 'call' && stmt.init.fn === 'setInterval') {
|
|
522
|
+
const value = this.lowerExpr(stmt.init);
|
|
523
|
+
const intervalFn = this.intervalFunctions.get(value.kind === 'const' ? value.value : NaN);
|
|
524
|
+
if (intervalFn) {
|
|
525
|
+
this.intervalBindings.set(stmt.name, intervalFn);
|
|
526
|
+
}
|
|
527
|
+
this.builder.emitAssign(varName, value);
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
400
530
|
// Handle struct literal initialization
|
|
401
531
|
if (stmt.init.kind === 'struct_lit' && stmt.type?.kind === 'struct') {
|
|
402
532
|
const structName = stmt.type.name.toLowerCase();
|
|
@@ -470,6 +600,10 @@ class Lowering {
|
|
|
470
600
|
}
|
|
471
601
|
}
|
|
472
602
|
lowerIfStmt(stmt) {
|
|
603
|
+
if (stmt.cond.kind === 'is_check') {
|
|
604
|
+
this.lowerIsCheckIfStmt(stmt);
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
473
607
|
const condVar = this.lowerExpr(stmt.cond);
|
|
474
608
|
const condName = this.operandToVar(condVar);
|
|
475
609
|
const thenLabel = this.builder.freshLabel('then');
|
|
@@ -493,6 +627,40 @@ class Lowering {
|
|
|
493
627
|
// Merge block
|
|
494
628
|
this.builder.startBlock(mergeLabel);
|
|
495
629
|
}
|
|
630
|
+
lowerIsCheckIfStmt(stmt) {
|
|
631
|
+
const cond = stmt.cond;
|
|
632
|
+
if (cond.kind !== 'is_check') {
|
|
633
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', "Internal error: expected 'is' check condition", stmt.span ?? { line: 0, col: 0 });
|
|
634
|
+
}
|
|
635
|
+
if (stmt.else_) {
|
|
636
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', "'is' checks with else branches are not yet supported", cond.span ?? stmt.span ?? { line: 0, col: 0 });
|
|
637
|
+
}
|
|
638
|
+
const selector = this.exprToEntitySelector(cond.expr);
|
|
639
|
+
if (!selector) {
|
|
640
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', "'is' checks require an entity selector or entity binding", cond.span ?? stmt.span ?? { line: 0, col: 0 });
|
|
641
|
+
}
|
|
642
|
+
const mcType = ENTITY_TO_MC_TYPE[cond.entityType];
|
|
643
|
+
if (!mcType) {
|
|
644
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', `Cannot lower entity type check for '${cond.entityType}'`, cond.span ?? stmt.span ?? { line: 0, col: 0 });
|
|
645
|
+
}
|
|
646
|
+
const thenFnName = `${this.currentFn}/then_${this.foreachCounter++}`;
|
|
647
|
+
this.builder.emitRaw(`execute if entity ${this.appendTypeFilter(selector, mcType)} run function ${this.namespace}:${thenFnName}`);
|
|
648
|
+
const savedBuilder = this.builder;
|
|
649
|
+
const savedVarMap = new Map(this.varMap);
|
|
650
|
+
const savedBlockPosVars = new Map(this.blockPosVars);
|
|
651
|
+
this.builder = new LoweringBuilder();
|
|
652
|
+
this.varMap = new Map(savedVarMap);
|
|
653
|
+
this.blockPosVars = new Map(savedBlockPosVars);
|
|
654
|
+
this.builder.startBlock('entry');
|
|
655
|
+
this.lowerBlock(stmt.then);
|
|
656
|
+
if (!this.builder.isBlockSealed()) {
|
|
657
|
+
this.builder.emitReturn();
|
|
658
|
+
}
|
|
659
|
+
this.functions.push(this.builder.build(thenFnName, [], false));
|
|
660
|
+
this.builder = savedBuilder;
|
|
661
|
+
this.varMap = savedVarMap;
|
|
662
|
+
this.blockPosVars = savedBlockPosVars;
|
|
663
|
+
}
|
|
496
664
|
lowerWhileStmt(stmt) {
|
|
497
665
|
const checkLabel = this.builder.freshLabel('loop_check');
|
|
498
666
|
const bodyLabel = this.builder.freshLabel('loop_body');
|
|
@@ -862,6 +1030,7 @@ class Lowering {
|
|
|
862
1030
|
// MC names (#health, #red) treated as string constants
|
|
863
1031
|
return { kind: 'const', value: 0 }; // Handled inline in exprToString
|
|
864
1032
|
case 'str_interp':
|
|
1033
|
+
case 'f_string':
|
|
865
1034
|
// Interpolated strings are handled inline in message builtins.
|
|
866
1035
|
return { kind: 'const', value: 0 };
|
|
867
1036
|
case 'range_lit':
|
|
@@ -899,12 +1068,16 @@ class Lowering {
|
|
|
899
1068
|
return { kind: 'var', name: this.selectorToString(expr.sel) };
|
|
900
1069
|
case 'binary':
|
|
901
1070
|
return this.lowerBinaryExpr(expr);
|
|
1071
|
+
case 'is_check':
|
|
1072
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', "'is' checks are only supported as if conditions", expr.span ?? { line: 0, col: 0 });
|
|
902
1073
|
case 'unary':
|
|
903
1074
|
return this.lowerUnaryExpr(expr);
|
|
904
1075
|
case 'assign':
|
|
905
1076
|
return this.lowerAssignExpr(expr);
|
|
906
1077
|
case 'call':
|
|
907
1078
|
return this.lowerCallExpr(expr);
|
|
1079
|
+
case 'static_call':
|
|
1080
|
+
return this.lowerStaticCallExpr(expr);
|
|
908
1081
|
case 'invoke':
|
|
909
1082
|
return this.lowerInvokeExpr(expr);
|
|
910
1083
|
case 'member_assign':
|
|
@@ -1222,6 +1395,10 @@ class Lowering {
|
|
|
1222
1395
|
if (callbackTarget) {
|
|
1223
1396
|
return this.emitDirectFunctionCall(callbackTarget, expr.args);
|
|
1224
1397
|
}
|
|
1398
|
+
const implMethod = this.resolveInstanceMethod(expr);
|
|
1399
|
+
if (implMethod) {
|
|
1400
|
+
return this.emitMethodCall(implMethod.loweredName, implMethod.fn, expr.args);
|
|
1401
|
+
}
|
|
1225
1402
|
// Regular function call
|
|
1226
1403
|
const fnDecl = this.fnDecls.get(expr.fn);
|
|
1227
1404
|
const defaultArgs = this.functionDefaults.get(expr.fn) ?? [];
|
|
@@ -1248,13 +1425,19 @@ class Lowering {
|
|
|
1248
1425
|
}
|
|
1249
1426
|
runtimeArgs.push(fullArgs[i]);
|
|
1250
1427
|
}
|
|
1251
|
-
const
|
|
1252
|
-
|
|
1428
|
+
const stdlibCallSite = this.getStdlibCallSiteContext(fnDecl, getSpan(expr));
|
|
1429
|
+
const targetFn = callbackBindings.size > 0 || stdlibCallSite
|
|
1430
|
+
? this.ensureSpecializedFunctionWithContext(fnDecl, callbackBindings, stdlibCallSite)
|
|
1253
1431
|
: expr.fn;
|
|
1254
1432
|
return this.emitDirectFunctionCall(targetFn, runtimeArgs);
|
|
1255
1433
|
}
|
|
1256
1434
|
return this.emitDirectFunctionCall(expr.fn, fullArgs);
|
|
1257
1435
|
}
|
|
1436
|
+
lowerStaticCallExpr(expr) {
|
|
1437
|
+
const method = this.implMethods.get(expr.type)?.get(expr.method);
|
|
1438
|
+
const targetFn = method?.loweredName ?? `${expr.type}_${expr.method}`;
|
|
1439
|
+
return this.emitMethodCall(targetFn, method?.fn, expr.args);
|
|
1440
|
+
}
|
|
1258
1441
|
lowerInvokeExpr(expr) {
|
|
1259
1442
|
if (expr.callee.kind === 'lambda') {
|
|
1260
1443
|
if (!Array.isArray(expr.callee.body)) {
|
|
@@ -1299,6 +1482,18 @@ class Lowering {
|
|
|
1299
1482
|
this.builder.emitCall(fn, loweredArgs, dst);
|
|
1300
1483
|
return { kind: 'var', name: dst };
|
|
1301
1484
|
}
|
|
1485
|
+
emitMethodCall(fn, fnDecl, args) {
|
|
1486
|
+
const defaultArgs = this.functionDefaults.get(fn) ?? fnDecl?.params.map(param => param.default) ?? [];
|
|
1487
|
+
const fullArgs = [...args];
|
|
1488
|
+
for (let i = fullArgs.length; i < defaultArgs.length; i++) {
|
|
1489
|
+
const defaultExpr = defaultArgs[i];
|
|
1490
|
+
if (!defaultExpr) {
|
|
1491
|
+
break;
|
|
1492
|
+
}
|
|
1493
|
+
fullArgs.push(defaultExpr);
|
|
1494
|
+
}
|
|
1495
|
+
return this.emitDirectFunctionCall(fn, fullArgs);
|
|
1496
|
+
}
|
|
1302
1497
|
resolveFunctionRefExpr(expr) {
|
|
1303
1498
|
if (expr.kind === 'lambda') {
|
|
1304
1499
|
return this.lowerLambdaExpr(expr);
|
|
@@ -1312,9 +1507,16 @@ class Lowering {
|
|
|
1312
1507
|
return this.lambdaBindings.get(name) ?? this.currentCallbackBindings.get(name) ?? null;
|
|
1313
1508
|
}
|
|
1314
1509
|
ensureSpecializedFunction(fn, callbackBindings) {
|
|
1510
|
+
return this.ensureSpecializedFunctionWithContext(fn, callbackBindings);
|
|
1511
|
+
}
|
|
1512
|
+
ensureSpecializedFunctionWithContext(fn, callbackBindings, stdlibCallSite) {
|
|
1315
1513
|
const parts = [...callbackBindings.entries()]
|
|
1316
1514
|
.sort(([left], [right]) => left.localeCompare(right))
|
|
1317
1515
|
.map(([param, target]) => `${param}_${target.replace(/[^a-zA-Z0-9_]/g, '_')}`);
|
|
1516
|
+
const callSiteHash = stdlibCallSite ? this.shortHash(this.serializeCallSite(stdlibCallSite)) : null;
|
|
1517
|
+
if (callSiteHash) {
|
|
1518
|
+
parts.push(`callsite_${callSiteHash}`);
|
|
1519
|
+
}
|
|
1318
1520
|
const key = `${fn.name}::${parts.join('::')}`;
|
|
1319
1521
|
const cached = this.specializedFunctions.get(key);
|
|
1320
1522
|
if (cached) {
|
|
@@ -1323,7 +1525,7 @@ class Lowering {
|
|
|
1323
1525
|
const specializedName = `${fn.name}__${parts.join('__')}`;
|
|
1324
1526
|
this.specializedFunctions.set(key, specializedName);
|
|
1325
1527
|
this.withSavedFunctionState(() => {
|
|
1326
|
-
this.lowerFn(fn, { name: specializedName, callbackBindings });
|
|
1528
|
+
this.lowerFn(fn, { name: specializedName, callbackBindings, stdlibCallSite });
|
|
1327
1529
|
});
|
|
1328
1530
|
return specializedName;
|
|
1329
1531
|
}
|
|
@@ -1346,10 +1548,12 @@ class Lowering {
|
|
|
1346
1548
|
}
|
|
1347
1549
|
withSavedFunctionState(callback) {
|
|
1348
1550
|
const savedCurrentFn = this.currentFn;
|
|
1551
|
+
const savedStdlibCallSite = this.currentStdlibCallSite;
|
|
1349
1552
|
const savedForeachCounter = this.foreachCounter;
|
|
1350
1553
|
const savedBuilder = this.builder;
|
|
1351
1554
|
const savedVarMap = new Map(this.varMap);
|
|
1352
1555
|
const savedLambdaBindings = new Map(this.lambdaBindings);
|
|
1556
|
+
const savedIntervalBindings = new Map(this.intervalBindings);
|
|
1353
1557
|
const savedCallbackBindings = new Map(this.currentCallbackBindings);
|
|
1354
1558
|
const savedContext = this.currentContext;
|
|
1355
1559
|
const savedBlockPosVars = new Map(this.blockPosVars);
|
|
@@ -1360,10 +1564,12 @@ class Lowering {
|
|
|
1360
1564
|
}
|
|
1361
1565
|
finally {
|
|
1362
1566
|
this.currentFn = savedCurrentFn;
|
|
1567
|
+
this.currentStdlibCallSite = savedStdlibCallSite;
|
|
1363
1568
|
this.foreachCounter = savedForeachCounter;
|
|
1364
1569
|
this.builder = savedBuilder;
|
|
1365
1570
|
this.varMap = savedVarMap;
|
|
1366
1571
|
this.lambdaBindings = savedLambdaBindings;
|
|
1572
|
+
this.intervalBindings = savedIntervalBindings;
|
|
1367
1573
|
this.currentCallbackBindings = savedCallbackBindings;
|
|
1368
1574
|
this.currentContext = savedContext;
|
|
1369
1575
|
this.blockPosVars = savedBlockPosVars;
|
|
@@ -1377,6 +1583,15 @@ class Lowering {
|
|
|
1377
1583
|
this.builder.emitRaw(richTextCommand);
|
|
1378
1584
|
return { kind: 'const', value: 0 };
|
|
1379
1585
|
}
|
|
1586
|
+
if (name === 'setTimeout') {
|
|
1587
|
+
return this.lowerSetTimeout(args);
|
|
1588
|
+
}
|
|
1589
|
+
if (name === 'setInterval') {
|
|
1590
|
+
return this.lowerSetInterval(args);
|
|
1591
|
+
}
|
|
1592
|
+
if (name === 'clearInterval') {
|
|
1593
|
+
return this.lowerClearInterval(args, callSpan);
|
|
1594
|
+
}
|
|
1380
1595
|
// Special case: random - legacy scoreboard RNG for pre-1.20.3 compatibility
|
|
1381
1596
|
if (name === 'random') {
|
|
1382
1597
|
const dst = this.builder.freshTemp();
|
|
@@ -1404,14 +1619,14 @@ class Lowering {
|
|
|
1404
1619
|
if (name === 'scoreboard_get' || name === 'score') {
|
|
1405
1620
|
const dst = this.builder.freshTemp();
|
|
1406
1621
|
const player = this.exprToTargetString(args[0]);
|
|
1407
|
-
const objective = this.
|
|
1622
|
+
const objective = this.resolveScoreboardObjective(args[0], args[1], callSpan);
|
|
1408
1623
|
this.builder.emitRaw(`execute store result score ${dst} rs run scoreboard players get ${player} ${objective}`);
|
|
1409
1624
|
return { kind: 'var', name: dst };
|
|
1410
1625
|
}
|
|
1411
1626
|
// Special case: scoreboard_set — write to vanilla MC scoreboard
|
|
1412
1627
|
if (name === 'scoreboard_set') {
|
|
1413
1628
|
const player = this.exprToTargetString(args[0]);
|
|
1414
|
-
const objective = this.
|
|
1629
|
+
const objective = this.resolveScoreboardObjective(args[0], args[1], callSpan);
|
|
1415
1630
|
const value = this.lowerExpr(args[2]);
|
|
1416
1631
|
if (value.kind === 'const') {
|
|
1417
1632
|
this.builder.emitRaw(`scoreboard players set ${player} ${objective} ${value.value}`);
|
|
@@ -1425,7 +1640,7 @@ class Lowering {
|
|
|
1425
1640
|
}
|
|
1426
1641
|
if (name === 'scoreboard_display') {
|
|
1427
1642
|
const slot = this.exprToString(args[0]);
|
|
1428
|
-
const objective = this.
|
|
1643
|
+
const objective = this.resolveScoreboardObjective(undefined, args[1], callSpan);
|
|
1429
1644
|
this.builder.emitRaw(`scoreboard objectives setdisplay ${slot} ${objective}`);
|
|
1430
1645
|
return { kind: 'const', value: 0 };
|
|
1431
1646
|
}
|
|
@@ -1435,14 +1650,14 @@ class Lowering {
|
|
|
1435
1650
|
return { kind: 'const', value: 0 };
|
|
1436
1651
|
}
|
|
1437
1652
|
if (name === 'scoreboard_add_objective') {
|
|
1438
|
-
const objective = this.
|
|
1653
|
+
const objective = this.resolveScoreboardObjective(undefined, args[0], callSpan);
|
|
1439
1654
|
const criteria = this.exprToString(args[1]);
|
|
1440
1655
|
const displayName = args[2] ? ` ${this.exprToQuotedString(args[2])}` : '';
|
|
1441
1656
|
this.builder.emitRaw(`scoreboard objectives add ${objective} ${criteria}${displayName}`);
|
|
1442
1657
|
return { kind: 'const', value: 0 };
|
|
1443
1658
|
}
|
|
1444
1659
|
if (name === 'scoreboard_remove_objective') {
|
|
1445
|
-
const objective = this.
|
|
1660
|
+
const objective = this.resolveScoreboardObjective(undefined, args[0], callSpan);
|
|
1446
1661
|
this.builder.emitRaw(`scoreboard objectives remove ${objective}`);
|
|
1447
1662
|
return { kind: 'const', value: 0 };
|
|
1448
1663
|
}
|
|
@@ -1616,13 +1831,97 @@ class Lowering {
|
|
|
1616
1831
|
}
|
|
1617
1832
|
return { kind: 'const', value: 0 };
|
|
1618
1833
|
}
|
|
1834
|
+
lowerSetTimeout(args) {
|
|
1835
|
+
const delay = this.exprToLiteral(args[0]);
|
|
1836
|
+
const callback = args[1];
|
|
1837
|
+
if (!callback || callback.kind !== 'lambda') {
|
|
1838
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', 'setTimeout requires a lambda callback', getSpan(callback) ?? { line: 1, col: 1 });
|
|
1839
|
+
}
|
|
1840
|
+
const fnName = `__timeout_${this.timeoutCounter++}`;
|
|
1841
|
+
this.lowerNamedLambdaFunction(fnName, callback);
|
|
1842
|
+
this.builder.emitRaw(`schedule function ${this.namespace}:${fnName} ${delay}t`);
|
|
1843
|
+
return { kind: 'const', value: 0 };
|
|
1844
|
+
}
|
|
1845
|
+
lowerSetInterval(args) {
|
|
1846
|
+
const delay = this.exprToLiteral(args[0]);
|
|
1847
|
+
const callback = args[1];
|
|
1848
|
+
if (!callback || callback.kind !== 'lambda') {
|
|
1849
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', 'setInterval requires a lambda callback', getSpan(callback) ?? { line: 1, col: 1 });
|
|
1850
|
+
}
|
|
1851
|
+
const id = this.intervalCounter++;
|
|
1852
|
+
const bodyName = `__interval_body_${id}`;
|
|
1853
|
+
const fnName = `__interval_${id}`;
|
|
1854
|
+
this.lowerNamedLambdaFunction(bodyName, callback);
|
|
1855
|
+
this.lowerIntervalWrapperFunction(fnName, bodyName, delay);
|
|
1856
|
+
this.intervalFunctions.set(id, fnName);
|
|
1857
|
+
this.builder.emitRaw(`schedule function ${this.namespace}:${fnName} ${delay}t`);
|
|
1858
|
+
return { kind: 'const', value: id };
|
|
1859
|
+
}
|
|
1860
|
+
lowerClearInterval(args, callSpan) {
|
|
1861
|
+
const fnName = this.resolveIntervalFunctionName(args[0]);
|
|
1862
|
+
if (!fnName) {
|
|
1863
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', 'clearInterval requires an interval ID returned from setInterval', callSpan ?? getSpan(args[0]) ?? { line: 1, col: 1 });
|
|
1864
|
+
}
|
|
1865
|
+
this.builder.emitRaw(`schedule clear ${this.namespace}:${fnName}`);
|
|
1866
|
+
return { kind: 'const', value: 0 };
|
|
1867
|
+
}
|
|
1868
|
+
lowerNamedLambdaFunction(name, expr) {
|
|
1869
|
+
const lambdaFn = {
|
|
1870
|
+
name,
|
|
1871
|
+
params: expr.params.map(param => ({
|
|
1872
|
+
name: param.name,
|
|
1873
|
+
type: param.type ?? { kind: 'named', name: 'int' },
|
|
1874
|
+
})),
|
|
1875
|
+
returnType: expr.returnType ?? this.inferLambdaReturnType(expr),
|
|
1876
|
+
decorators: [],
|
|
1877
|
+
body: Array.isArray(expr.body) ? expr.body : [{ kind: 'return', value: expr.body }],
|
|
1878
|
+
};
|
|
1879
|
+
this.withSavedFunctionState(() => {
|
|
1880
|
+
this.lowerFn(lambdaFn);
|
|
1881
|
+
});
|
|
1882
|
+
}
|
|
1883
|
+
lowerIntervalWrapperFunction(name, bodyName, delay) {
|
|
1884
|
+
const intervalFn = {
|
|
1885
|
+
name,
|
|
1886
|
+
params: [],
|
|
1887
|
+
returnType: { kind: 'named', name: 'void' },
|
|
1888
|
+
decorators: [],
|
|
1889
|
+
body: [
|
|
1890
|
+
{ kind: 'raw', cmd: `function ${this.namespace}:${bodyName}` },
|
|
1891
|
+
{ kind: 'raw', cmd: `schedule function ${this.namespace}:${name} ${delay}t` },
|
|
1892
|
+
],
|
|
1893
|
+
};
|
|
1894
|
+
this.withSavedFunctionState(() => {
|
|
1895
|
+
this.lowerFn(intervalFn);
|
|
1896
|
+
});
|
|
1897
|
+
}
|
|
1898
|
+
resolveIntervalFunctionName(expr) {
|
|
1899
|
+
if (!expr) {
|
|
1900
|
+
return null;
|
|
1901
|
+
}
|
|
1902
|
+
if (expr.kind === 'ident') {
|
|
1903
|
+
const boundInterval = this.intervalBindings.get(expr.name);
|
|
1904
|
+
if (boundInterval) {
|
|
1905
|
+
return boundInterval;
|
|
1906
|
+
}
|
|
1907
|
+
const constValue = this.constValues.get(expr.name);
|
|
1908
|
+
if (constValue?.kind === 'int_lit') {
|
|
1909
|
+
return this.intervalFunctions.get(constValue.value) ?? null;
|
|
1910
|
+
}
|
|
1911
|
+
return null;
|
|
1912
|
+
}
|
|
1913
|
+
if (expr.kind === 'int_lit') {
|
|
1914
|
+
return this.intervalFunctions.get(expr.value) ?? null;
|
|
1915
|
+
}
|
|
1916
|
+
return null;
|
|
1917
|
+
}
|
|
1619
1918
|
lowerRichTextBuiltin(name, args) {
|
|
1620
1919
|
const messageArgIndex = this.getRichTextArgIndex(name);
|
|
1621
1920
|
if (messageArgIndex === null) {
|
|
1622
1921
|
return null;
|
|
1623
1922
|
}
|
|
1624
1923
|
const messageExpr = args[messageArgIndex];
|
|
1625
|
-
if (!messageExpr || messageExpr.kind !== 'str_interp') {
|
|
1924
|
+
if (!messageExpr || (messageExpr.kind !== 'str_interp' && messageExpr.kind !== 'f_string')) {
|
|
1626
1925
|
return null;
|
|
1627
1926
|
}
|
|
1628
1927
|
const json = this.buildRichTextJson(messageExpr);
|
|
@@ -1631,6 +1930,7 @@ class Lowering {
|
|
|
1631
1930
|
case 'announce':
|
|
1632
1931
|
return `tellraw @a ${json}`;
|
|
1633
1932
|
case 'tell':
|
|
1933
|
+
case 'tellraw':
|
|
1634
1934
|
return `tellraw ${this.exprToString(args[0])} ${json}`;
|
|
1635
1935
|
case 'title':
|
|
1636
1936
|
return `title ${this.exprToString(args[0])} title ${json}`;
|
|
@@ -1648,6 +1948,7 @@ class Lowering {
|
|
|
1648
1948
|
case 'announce':
|
|
1649
1949
|
return 0;
|
|
1650
1950
|
case 'tell':
|
|
1951
|
+
case 'tellraw':
|
|
1651
1952
|
case 'title':
|
|
1652
1953
|
case 'actionbar':
|
|
1653
1954
|
case 'subtitle':
|
|
@@ -1658,6 +1959,18 @@ class Lowering {
|
|
|
1658
1959
|
}
|
|
1659
1960
|
buildRichTextJson(expr) {
|
|
1660
1961
|
const components = [''];
|
|
1962
|
+
if (expr.kind === 'f_string') {
|
|
1963
|
+
for (const part of expr.parts) {
|
|
1964
|
+
if (part.kind === 'text') {
|
|
1965
|
+
if (part.value.length > 0) {
|
|
1966
|
+
components.push({ text: part.value });
|
|
1967
|
+
}
|
|
1968
|
+
continue;
|
|
1969
|
+
}
|
|
1970
|
+
this.appendRichTextExpr(components, part.expr);
|
|
1971
|
+
}
|
|
1972
|
+
return JSON.stringify(components);
|
|
1973
|
+
}
|
|
1661
1974
|
for (const part of expr.parts) {
|
|
1662
1975
|
if (typeof part === 'string') {
|
|
1663
1976
|
if (part.length > 0) {
|
|
@@ -1701,6 +2014,19 @@ class Lowering {
|
|
|
1701
2014
|
}
|
|
1702
2015
|
return;
|
|
1703
2016
|
}
|
|
2017
|
+
if (expr.kind === 'f_string') {
|
|
2018
|
+
for (const part of expr.parts) {
|
|
2019
|
+
if (part.kind === 'text') {
|
|
2020
|
+
if (part.value.length > 0) {
|
|
2021
|
+
components.push({ text: part.value });
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
else {
|
|
2025
|
+
this.appendRichTextExpr(components, part.expr);
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
return;
|
|
2029
|
+
}
|
|
1704
2030
|
if (expr.kind === 'bool_lit') {
|
|
1705
2031
|
components.push({ text: expr.value ? 'true' : 'false' });
|
|
1706
2032
|
return;
|
|
@@ -1734,6 +2060,10 @@ class Lowering {
|
|
|
1734
2060
|
return `${expr.value}L`;
|
|
1735
2061
|
case 'double_lit':
|
|
1736
2062
|
return `${expr.value}d`;
|
|
2063
|
+
case 'rel_coord':
|
|
2064
|
+
return expr.value; // ~ or ~5 or ~-3 - output as-is for MC commands
|
|
2065
|
+
case 'local_coord':
|
|
2066
|
+
return expr.value; // ^ or ^5 or ^-3 - output as-is for MC commands
|
|
1737
2067
|
case 'bool_lit':
|
|
1738
2068
|
return expr.value ? '1' : '0';
|
|
1739
2069
|
case 'str_lit':
|
|
@@ -1741,6 +2071,7 @@ class Lowering {
|
|
|
1741
2071
|
case 'mc_name':
|
|
1742
2072
|
return expr.value; // #health → "health" (no quotes, used as bare MC name)
|
|
1743
2073
|
case 'str_interp':
|
|
2074
|
+
case 'f_string':
|
|
1744
2075
|
return this.buildRichTextJson(expr);
|
|
1745
2076
|
case 'blockpos':
|
|
1746
2077
|
return emitBlockPos(expr);
|
|
@@ -1775,6 +2106,28 @@ class Lowering {
|
|
|
1775
2106
|
return this.operandToVar(op);
|
|
1776
2107
|
}
|
|
1777
2108
|
}
|
|
2109
|
+
exprToEntitySelector(expr) {
|
|
2110
|
+
if (expr.kind === 'selector') {
|
|
2111
|
+
return this.selectorToString(expr.sel);
|
|
2112
|
+
}
|
|
2113
|
+
if (expr.kind === 'ident') {
|
|
2114
|
+
const constValue = this.constValues.get(expr.name);
|
|
2115
|
+
if (constValue) {
|
|
2116
|
+
return this.exprToEntitySelector(constValue);
|
|
2117
|
+
}
|
|
2118
|
+
const mapped = this.varMap.get(expr.name);
|
|
2119
|
+
if (mapped?.startsWith('@')) {
|
|
2120
|
+
return mapped;
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
return null;
|
|
2124
|
+
}
|
|
2125
|
+
appendTypeFilter(selector, mcType) {
|
|
2126
|
+
if (selector.endsWith(']')) {
|
|
2127
|
+
return `${selector.slice(0, -1)},type=${mcType}]`;
|
|
2128
|
+
}
|
|
2129
|
+
return `${selector}[type=${mcType}]`;
|
|
2130
|
+
}
|
|
1778
2131
|
exprToSnbt(expr) {
|
|
1779
2132
|
switch (expr.kind) {
|
|
1780
2133
|
case 'struct_lit': {
|
|
@@ -1842,6 +2195,92 @@ class Lowering {
|
|
|
1842
2195
|
isTeamTextOption(option) {
|
|
1843
2196
|
return option === 'displayName' || option === 'prefix' || option === 'suffix';
|
|
1844
2197
|
}
|
|
2198
|
+
exprToScoreboardObjective(expr, span) {
|
|
2199
|
+
if (expr.kind === 'mc_name') {
|
|
2200
|
+
return expr.value;
|
|
2201
|
+
}
|
|
2202
|
+
const objective = this.exprToString(expr);
|
|
2203
|
+
if (objective.startsWith('#') || objective.includes('.')) {
|
|
2204
|
+
return objective.startsWith('#') ? objective.slice(1) : objective;
|
|
2205
|
+
}
|
|
2206
|
+
return `${this.getObjectiveNamespace(span)}.${objective}`;
|
|
2207
|
+
}
|
|
2208
|
+
resolveScoreboardObjective(playerExpr, objectiveExpr, span) {
|
|
2209
|
+
const stdlibInternalObjective = this.tryGetStdlibInternalObjective(playerExpr, objectiveExpr, span);
|
|
2210
|
+
if (stdlibInternalObjective) {
|
|
2211
|
+
return stdlibInternalObjective;
|
|
2212
|
+
}
|
|
2213
|
+
return this.exprToScoreboardObjective(objectiveExpr, span);
|
|
2214
|
+
}
|
|
2215
|
+
getObjectiveNamespace(span) {
|
|
2216
|
+
const filePath = this.filePathForSpan(span);
|
|
2217
|
+
if (!filePath) {
|
|
2218
|
+
return this.namespace;
|
|
2219
|
+
}
|
|
2220
|
+
return this.isStdlibFile(filePath) ? 'rs' : this.namespace;
|
|
2221
|
+
}
|
|
2222
|
+
tryGetStdlibInternalObjective(playerExpr, objectiveExpr, span) {
|
|
2223
|
+
if (!span || !this.currentStdlibCallSite || objectiveExpr.kind !== 'mc_name' || objectiveExpr.value !== 'rs') {
|
|
2224
|
+
return null;
|
|
2225
|
+
}
|
|
2226
|
+
const filePath = this.filePathForSpan(span);
|
|
2227
|
+
if (!filePath || !this.isStdlibFile(filePath)) {
|
|
2228
|
+
return null;
|
|
2229
|
+
}
|
|
2230
|
+
const resourceBase = this.getStdlibInternalResourceBase(playerExpr);
|
|
2231
|
+
if (!resourceBase) {
|
|
2232
|
+
return null;
|
|
2233
|
+
}
|
|
2234
|
+
const hash = this.shortHash(this.serializeCallSite(this.currentStdlibCallSite));
|
|
2235
|
+
return `rs._${resourceBase}_${hash}`;
|
|
2236
|
+
}
|
|
2237
|
+
getStdlibInternalResourceBase(playerExpr) {
|
|
2238
|
+
if (!playerExpr || playerExpr.kind !== 'str_lit') {
|
|
2239
|
+
return null;
|
|
2240
|
+
}
|
|
2241
|
+
const match = playerExpr.value.match(/^([a-z0-9]+)_/);
|
|
2242
|
+
return match?.[1] ?? null;
|
|
2243
|
+
}
|
|
2244
|
+
getStdlibCallSiteContext(fn, exprSpan) {
|
|
2245
|
+
const fnFilePath = this.filePathForSpan(getSpan(fn));
|
|
2246
|
+
if (!fnFilePath || !this.isStdlibFile(fnFilePath)) {
|
|
2247
|
+
return undefined;
|
|
2248
|
+
}
|
|
2249
|
+
if (this.currentStdlibCallSite) {
|
|
2250
|
+
return this.currentStdlibCallSite;
|
|
2251
|
+
}
|
|
2252
|
+
if (!exprSpan) {
|
|
2253
|
+
return undefined;
|
|
2254
|
+
}
|
|
2255
|
+
return {
|
|
2256
|
+
filePath: this.filePathForSpan(exprSpan),
|
|
2257
|
+
line: exprSpan.line,
|
|
2258
|
+
col: exprSpan.col,
|
|
2259
|
+
};
|
|
2260
|
+
}
|
|
2261
|
+
serializeCallSite(callSite) {
|
|
2262
|
+
return `${callSite.filePath ?? '<memory>'}:${callSite.line}:${callSite.col}`;
|
|
2263
|
+
}
|
|
2264
|
+
shortHash(input) {
|
|
2265
|
+
let hash = 2166136261;
|
|
2266
|
+
for (let i = 0; i < input.length; i++) {
|
|
2267
|
+
hash ^= input.charCodeAt(i);
|
|
2268
|
+
hash = Math.imul(hash, 16777619);
|
|
2269
|
+
}
|
|
2270
|
+
return (hash >>> 0).toString(16).padStart(8, '0').slice(0, 4);
|
|
2271
|
+
}
|
|
2272
|
+
isStdlibFile(filePath) {
|
|
2273
|
+
const normalized = path.normalize(filePath);
|
|
2274
|
+
const stdlibSegment = `${path.sep}src${path.sep}stdlib${path.sep}`;
|
|
2275
|
+
return normalized.includes(stdlibSegment);
|
|
2276
|
+
}
|
|
2277
|
+
filePathForSpan(span) {
|
|
2278
|
+
if (!span) {
|
|
2279
|
+
return undefined;
|
|
2280
|
+
}
|
|
2281
|
+
const line = span.line;
|
|
2282
|
+
return this.sourceRanges.find(range => line >= range.startLine && line <= range.endLine)?.filePath;
|
|
2283
|
+
}
|
|
1845
2284
|
lowerCoordinateBuiltin(name, args) {
|
|
1846
2285
|
const pos0 = args[0] ? this.resolveBlockPosExpr(args[0]) : null;
|
|
1847
2286
|
const pos1 = args[1] ? this.resolveBlockPosExpr(args[1]) : null;
|
|
@@ -1923,6 +2362,8 @@ class Lowering {
|
|
|
1923
2362
|
return { kind: 'named', name: 'bool' };
|
|
1924
2363
|
if (expr.kind === 'str_lit' || expr.kind === 'str_interp')
|
|
1925
2364
|
return { kind: 'named', name: 'string' };
|
|
2365
|
+
if (expr.kind === 'f_string')
|
|
2366
|
+
return { kind: 'named', name: 'format_string' };
|
|
1926
2367
|
if (expr.kind === 'blockpos')
|
|
1927
2368
|
return { kind: 'named', name: 'BlockPos' };
|
|
1928
2369
|
if (expr.kind === 'ident') {
|
|
@@ -1949,7 +2390,11 @@ class Lowering {
|
|
|
1949
2390
|
};
|
|
1950
2391
|
}
|
|
1951
2392
|
if (expr.kind === 'call') {
|
|
1952
|
-
|
|
2393
|
+
const resolved = this.resolveFunctionRefByName(expr.fn) ?? this.resolveInstanceMethod(expr)?.loweredName ?? expr.fn;
|
|
2394
|
+
return this.fnDecls.get(resolved)?.returnType;
|
|
2395
|
+
}
|
|
2396
|
+
if (expr.kind === 'static_call') {
|
|
2397
|
+
return this.implMethods.get(expr.type)?.get(expr.method)?.fn.returnType;
|
|
1953
2398
|
}
|
|
1954
2399
|
if (expr.kind === 'invoke') {
|
|
1955
2400
|
const calleeType = this.inferExprType(expr.callee);
|
|
@@ -1977,6 +2422,21 @@ class Lowering {
|
|
|
1977
2422
|
}
|
|
1978
2423
|
return undefined;
|
|
1979
2424
|
}
|
|
2425
|
+
resolveInstanceMethod(expr) {
|
|
2426
|
+
const receiver = expr.args[0];
|
|
2427
|
+
if (!receiver) {
|
|
2428
|
+
return null;
|
|
2429
|
+
}
|
|
2430
|
+
const receiverType = this.inferExprType(receiver);
|
|
2431
|
+
if (receiverType?.kind !== 'struct') {
|
|
2432
|
+
return null;
|
|
2433
|
+
}
|
|
2434
|
+
const method = this.implMethods.get(receiverType.name)?.get(expr.fn);
|
|
2435
|
+
if (!method || method.fn.params[0]?.name !== 'self') {
|
|
2436
|
+
return null;
|
|
2437
|
+
}
|
|
2438
|
+
return method;
|
|
2439
|
+
}
|
|
1980
2440
|
normalizeType(type) {
|
|
1981
2441
|
if (type.kind === 'array') {
|
|
1982
2442
|
return { kind: 'array', elem: this.normalizeType(type.elem) };
|