redscript-mc 1.1.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/CHANGELOG.md +54 -0
- package/dist/__tests__/cli.test.js +138 -0
- package/dist/__tests__/codegen.test.js +25 -0
- package/dist/__tests__/e2e.test.js +190 -12
- package/dist/__tests__/lexer.test.js +12 -2
- package/dist/__tests__/lowering.test.js +164 -9
- package/dist/__tests__/mc-integration.test.js +145 -51
- package/dist/__tests__/optimizer-advanced.test.js +3 -3
- package/dist/__tests__/parser.test.js +80 -0
- package/dist/__tests__/runtime.test.js +8 -8
- package/dist/__tests__/typechecker.test.js +158 -0
- package/dist/ast/types.d.ts +20 -1
- package/dist/codegen/mcfunction/index.js +30 -1
- package/dist/codegen/structure/index.js +25 -0
- 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/index.js +3 -2
- package/dist/ir/types.d.ts +4 -0
- package/dist/lexer/index.d.ts +1 -1
- package/dist/lexer/index.js +2 -0
- package/dist/lowering/index.d.ts +32 -1
- package/dist/lowering/index.js +439 -15
- package/dist/parser/index.d.ts +2 -0
- package/dist/parser/index.js +79 -10
- package/dist/typechecker/index.d.ts +17 -0
- package/dist/typechecker/index.js +343 -17
- package/docs/ENTITY_TYPE_SYSTEM.md +242 -0
- package/editors/vscode/CHANGELOG.md +9 -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/package.json +1 -1
- package/src/__tests__/cli.test.ts +166 -0
- package/src/__tests__/codegen.test.ts +27 -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 +14 -2
- package/src/__tests__/lowering.test.ts +178 -9
- package/src/__tests__/mc-integration.test.ts +166 -51
- package/src/__tests__/optimizer-advanced.test.ts +3 -3
- package/src/__tests__/parser.test.ts +91 -5
- package/src/__tests__/runtime.test.ts +8 -8
- package/src/__tests__/typechecker.test.ts +171 -0
- package/src/ast/types.ts +25 -1
- package/src/codegen/mcfunction/index.ts +31 -1
- package/src/codegen/structure/index.ts +27 -0
- package/src/compile.ts +54 -6
- package/src/events/types.ts +69 -0
- package/src/index.ts +4 -3
- package/src/ir/types.ts +4 -0
- package/src/lexer/index.ts +3 -1
- package/src/lowering/index.ts +528 -16
- package/src/parser/index.ts +90 -12
- 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 +404 -18
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
|
// ---------------------------------------------------------------------------
|
|
@@ -80,12 +115,31 @@ const BUILTINS = {
|
|
|
80
115
|
set_contains: () => null, // Special handling (returns 1/0)
|
|
81
116
|
set_remove: () => null, // Special handling
|
|
82
117
|
set_clear: () => null, // Special handling
|
|
118
|
+
setTimeout: () => null, // Special handling
|
|
119
|
+
setInterval: () => null, // Special handling
|
|
120
|
+
clearInterval: () => null, // Special handling
|
|
83
121
|
};
|
|
84
122
|
function getSpan(node) {
|
|
85
123
|
return node?.span;
|
|
86
124
|
}
|
|
87
125
|
const NAMESPACED_ENTITY_TYPE_RE = /^[a-z0-9_.-]+:[a-z0-9_./-]+$/;
|
|
88
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
|
+
};
|
|
89
143
|
function normalizeSelector(selector, warnings) {
|
|
90
144
|
return selector.replace(/type=([^,\]]+)/g, (match, entityType) => {
|
|
91
145
|
const trimmed = entityType.trim();
|
|
@@ -122,18 +176,23 @@ function emitBlockPos(pos) {
|
|
|
122
176
|
// Lowering Class
|
|
123
177
|
// ---------------------------------------------------------------------------
|
|
124
178
|
class Lowering {
|
|
125
|
-
constructor(namespace) {
|
|
179
|
+
constructor(namespace, sourceRanges = []) {
|
|
126
180
|
this.functions = [];
|
|
127
181
|
this.globals = [];
|
|
128
182
|
this.globalNames = new Map();
|
|
129
183
|
this.fnDecls = new Map();
|
|
184
|
+
this.implMethods = new Map();
|
|
130
185
|
this.specializedFunctions = new Map();
|
|
131
186
|
this.currentFn = '';
|
|
132
187
|
this.foreachCounter = 0;
|
|
133
188
|
this.lambdaCounter = 0;
|
|
189
|
+
this.timeoutCounter = 0;
|
|
190
|
+
this.intervalCounter = 0;
|
|
134
191
|
this.warnings = [];
|
|
135
192
|
this.varMap = new Map();
|
|
136
193
|
this.lambdaBindings = new Map();
|
|
194
|
+
this.intervalBindings = new Map();
|
|
195
|
+
this.intervalFunctions = new Map();
|
|
137
196
|
this.currentCallbackBindings = new Map();
|
|
138
197
|
this.currentContext = {};
|
|
139
198
|
this.blockPosVars = new Map();
|
|
@@ -150,6 +209,7 @@ class Lowering {
|
|
|
150
209
|
// World object counter for unique tags
|
|
151
210
|
this.worldObjCounter = 0;
|
|
152
211
|
this.namespace = namespace;
|
|
212
|
+
this.sourceRanges = sourceRanges;
|
|
153
213
|
LoweringBuilder.resetTempCounter();
|
|
154
214
|
}
|
|
155
215
|
lower(program) {
|
|
@@ -184,9 +244,27 @@ class Lowering {
|
|
|
184
244
|
this.fnDecls.set(fn.name, fn);
|
|
185
245
|
this.functionDefaults.set(fn.name, fn.params.map(param => param.default));
|
|
186
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
|
+
}
|
|
187
260
|
for (const fn of program.declarations) {
|
|
188
261
|
this.lowerFn(fn);
|
|
189
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
|
+
}
|
|
190
268
|
return (0, builder_1.buildModule)(this.namespace, this.functions, this.globals);
|
|
191
269
|
}
|
|
192
270
|
// -------------------------------------------------------------------------
|
|
@@ -195,21 +273,48 @@ class Lowering {
|
|
|
195
273
|
lowerFn(fn, options = {}) {
|
|
196
274
|
const loweredName = options.name ?? fn.name;
|
|
197
275
|
const callbackBindings = options.callbackBindings ?? new Map();
|
|
198
|
-
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));
|
|
199
283
|
this.currentFn = loweredName;
|
|
284
|
+
this.currentStdlibCallSite = stdlibCallSite;
|
|
200
285
|
this.foreachCounter = 0;
|
|
201
286
|
this.varMap = new Map();
|
|
202
287
|
this.lambdaBindings = new Map();
|
|
288
|
+
this.intervalBindings = new Map();
|
|
203
289
|
this.currentCallbackBindings = new Map(callbackBindings);
|
|
204
290
|
this.currentContext = {};
|
|
205
291
|
this.blockPosVars = new Map();
|
|
206
292
|
this.stringValues = new Map();
|
|
207
293
|
this.builder = new LoweringBuilder();
|
|
208
294
|
// Map parameters
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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
|
+
}
|
|
213
318
|
}
|
|
214
319
|
for (const param of fn.params) {
|
|
215
320
|
if (callbackBindings.has(param.name)) {
|
|
@@ -224,6 +329,15 @@ class Lowering {
|
|
|
224
329
|
const varName = `$${paramName}`;
|
|
225
330
|
this.builder.emitAssign(varName, { kind: 'var', name: `$p${i}` });
|
|
226
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
|
+
}
|
|
227
341
|
// Lower body
|
|
228
342
|
this.lowerBlock(fn.body);
|
|
229
343
|
// If no explicit return, add void return
|
|
@@ -267,6 +381,12 @@ class Lowering {
|
|
|
267
381
|
break;
|
|
268
382
|
}
|
|
269
383
|
}
|
|
384
|
+
if (eventType && (0, types_1.isEventTypeName)(eventType)) {
|
|
385
|
+
irFn.eventHandler = {
|
|
386
|
+
eventType,
|
|
387
|
+
tag: types_1.EVENT_TYPES[eventType].tag,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
270
390
|
// Check for @load decorator
|
|
271
391
|
if (fn.decorators.some(d => d.name === 'load')) {
|
|
272
392
|
irFn.isLoadInit = true;
|
|
@@ -397,6 +517,15 @@ class Lowering {
|
|
|
397
517
|
this.lambdaBindings.set(stmt.name, lambdaName);
|
|
398
518
|
return;
|
|
399
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
|
+
}
|
|
400
529
|
// Handle struct literal initialization
|
|
401
530
|
if (stmt.init.kind === 'struct_lit' && stmt.type?.kind === 'struct') {
|
|
402
531
|
const structName = stmt.type.name.toLowerCase();
|
|
@@ -470,6 +599,10 @@ class Lowering {
|
|
|
470
599
|
}
|
|
471
600
|
}
|
|
472
601
|
lowerIfStmt(stmt) {
|
|
602
|
+
if (stmt.cond.kind === 'is_check') {
|
|
603
|
+
this.lowerIsCheckIfStmt(stmt);
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
473
606
|
const condVar = this.lowerExpr(stmt.cond);
|
|
474
607
|
const condName = this.operandToVar(condVar);
|
|
475
608
|
const thenLabel = this.builder.freshLabel('then');
|
|
@@ -493,6 +626,40 @@ class Lowering {
|
|
|
493
626
|
// Merge block
|
|
494
627
|
this.builder.startBlock(mergeLabel);
|
|
495
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
|
+
}
|
|
496
663
|
lowerWhileStmt(stmt) {
|
|
497
664
|
const checkLabel = this.builder.freshLabel('loop_check');
|
|
498
665
|
const bodyLabel = this.builder.freshLabel('loop_body');
|
|
@@ -899,12 +1066,16 @@ class Lowering {
|
|
|
899
1066
|
return { kind: 'var', name: this.selectorToString(expr.sel) };
|
|
900
1067
|
case 'binary':
|
|
901
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 });
|
|
902
1071
|
case 'unary':
|
|
903
1072
|
return this.lowerUnaryExpr(expr);
|
|
904
1073
|
case 'assign':
|
|
905
1074
|
return this.lowerAssignExpr(expr);
|
|
906
1075
|
case 'call':
|
|
907
1076
|
return this.lowerCallExpr(expr);
|
|
1077
|
+
case 'static_call':
|
|
1078
|
+
return this.lowerStaticCallExpr(expr);
|
|
908
1079
|
case 'invoke':
|
|
909
1080
|
return this.lowerInvokeExpr(expr);
|
|
910
1081
|
case 'member_assign':
|
|
@@ -1222,6 +1393,10 @@ class Lowering {
|
|
|
1222
1393
|
if (callbackTarget) {
|
|
1223
1394
|
return this.emitDirectFunctionCall(callbackTarget, expr.args);
|
|
1224
1395
|
}
|
|
1396
|
+
const implMethod = this.resolveInstanceMethod(expr);
|
|
1397
|
+
if (implMethod) {
|
|
1398
|
+
return this.emitMethodCall(implMethod.loweredName, implMethod.fn, expr.args);
|
|
1399
|
+
}
|
|
1225
1400
|
// Regular function call
|
|
1226
1401
|
const fnDecl = this.fnDecls.get(expr.fn);
|
|
1227
1402
|
const defaultArgs = this.functionDefaults.get(expr.fn) ?? [];
|
|
@@ -1248,13 +1423,19 @@ class Lowering {
|
|
|
1248
1423
|
}
|
|
1249
1424
|
runtimeArgs.push(fullArgs[i]);
|
|
1250
1425
|
}
|
|
1251
|
-
const
|
|
1252
|
-
|
|
1426
|
+
const stdlibCallSite = this.getStdlibCallSiteContext(fnDecl, getSpan(expr));
|
|
1427
|
+
const targetFn = callbackBindings.size > 0 || stdlibCallSite
|
|
1428
|
+
? this.ensureSpecializedFunctionWithContext(fnDecl, callbackBindings, stdlibCallSite)
|
|
1253
1429
|
: expr.fn;
|
|
1254
1430
|
return this.emitDirectFunctionCall(targetFn, runtimeArgs);
|
|
1255
1431
|
}
|
|
1256
1432
|
return this.emitDirectFunctionCall(expr.fn, fullArgs);
|
|
1257
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
|
+
}
|
|
1258
1439
|
lowerInvokeExpr(expr) {
|
|
1259
1440
|
if (expr.callee.kind === 'lambda') {
|
|
1260
1441
|
if (!Array.isArray(expr.callee.body)) {
|
|
@@ -1299,6 +1480,18 @@ class Lowering {
|
|
|
1299
1480
|
this.builder.emitCall(fn, loweredArgs, dst);
|
|
1300
1481
|
return { kind: 'var', name: dst };
|
|
1301
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
|
+
}
|
|
1302
1495
|
resolveFunctionRefExpr(expr) {
|
|
1303
1496
|
if (expr.kind === 'lambda') {
|
|
1304
1497
|
return this.lowerLambdaExpr(expr);
|
|
@@ -1312,9 +1505,16 @@ class Lowering {
|
|
|
1312
1505
|
return this.lambdaBindings.get(name) ?? this.currentCallbackBindings.get(name) ?? null;
|
|
1313
1506
|
}
|
|
1314
1507
|
ensureSpecializedFunction(fn, callbackBindings) {
|
|
1508
|
+
return this.ensureSpecializedFunctionWithContext(fn, callbackBindings);
|
|
1509
|
+
}
|
|
1510
|
+
ensureSpecializedFunctionWithContext(fn, callbackBindings, stdlibCallSite) {
|
|
1315
1511
|
const parts = [...callbackBindings.entries()]
|
|
1316
1512
|
.sort(([left], [right]) => left.localeCompare(right))
|
|
1317
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
|
+
}
|
|
1318
1518
|
const key = `${fn.name}::${parts.join('::')}`;
|
|
1319
1519
|
const cached = this.specializedFunctions.get(key);
|
|
1320
1520
|
if (cached) {
|
|
@@ -1323,7 +1523,7 @@ class Lowering {
|
|
|
1323
1523
|
const specializedName = `${fn.name}__${parts.join('__')}`;
|
|
1324
1524
|
this.specializedFunctions.set(key, specializedName);
|
|
1325
1525
|
this.withSavedFunctionState(() => {
|
|
1326
|
-
this.lowerFn(fn, { name: specializedName, callbackBindings });
|
|
1526
|
+
this.lowerFn(fn, { name: specializedName, callbackBindings, stdlibCallSite });
|
|
1327
1527
|
});
|
|
1328
1528
|
return specializedName;
|
|
1329
1529
|
}
|
|
@@ -1346,10 +1546,12 @@ class Lowering {
|
|
|
1346
1546
|
}
|
|
1347
1547
|
withSavedFunctionState(callback) {
|
|
1348
1548
|
const savedCurrentFn = this.currentFn;
|
|
1549
|
+
const savedStdlibCallSite = this.currentStdlibCallSite;
|
|
1349
1550
|
const savedForeachCounter = this.foreachCounter;
|
|
1350
1551
|
const savedBuilder = this.builder;
|
|
1351
1552
|
const savedVarMap = new Map(this.varMap);
|
|
1352
1553
|
const savedLambdaBindings = new Map(this.lambdaBindings);
|
|
1554
|
+
const savedIntervalBindings = new Map(this.intervalBindings);
|
|
1353
1555
|
const savedCallbackBindings = new Map(this.currentCallbackBindings);
|
|
1354
1556
|
const savedContext = this.currentContext;
|
|
1355
1557
|
const savedBlockPosVars = new Map(this.blockPosVars);
|
|
@@ -1360,10 +1562,12 @@ class Lowering {
|
|
|
1360
1562
|
}
|
|
1361
1563
|
finally {
|
|
1362
1564
|
this.currentFn = savedCurrentFn;
|
|
1565
|
+
this.currentStdlibCallSite = savedStdlibCallSite;
|
|
1363
1566
|
this.foreachCounter = savedForeachCounter;
|
|
1364
1567
|
this.builder = savedBuilder;
|
|
1365
1568
|
this.varMap = savedVarMap;
|
|
1366
1569
|
this.lambdaBindings = savedLambdaBindings;
|
|
1570
|
+
this.intervalBindings = savedIntervalBindings;
|
|
1367
1571
|
this.currentCallbackBindings = savedCallbackBindings;
|
|
1368
1572
|
this.currentContext = savedContext;
|
|
1369
1573
|
this.blockPosVars = savedBlockPosVars;
|
|
@@ -1377,6 +1581,15 @@ class Lowering {
|
|
|
1377
1581
|
this.builder.emitRaw(richTextCommand);
|
|
1378
1582
|
return { kind: 'const', value: 0 };
|
|
1379
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
|
+
}
|
|
1380
1593
|
// Special case: random - legacy scoreboard RNG for pre-1.20.3 compatibility
|
|
1381
1594
|
if (name === 'random') {
|
|
1382
1595
|
const dst = this.builder.freshTemp();
|
|
@@ -1404,14 +1617,14 @@ class Lowering {
|
|
|
1404
1617
|
if (name === 'scoreboard_get' || name === 'score') {
|
|
1405
1618
|
const dst = this.builder.freshTemp();
|
|
1406
1619
|
const player = this.exprToTargetString(args[0]);
|
|
1407
|
-
const objective = this.
|
|
1620
|
+
const objective = this.resolveScoreboardObjective(args[0], args[1], callSpan);
|
|
1408
1621
|
this.builder.emitRaw(`execute store result score ${dst} rs run scoreboard players get ${player} ${objective}`);
|
|
1409
1622
|
return { kind: 'var', name: dst };
|
|
1410
1623
|
}
|
|
1411
1624
|
// Special case: scoreboard_set — write to vanilla MC scoreboard
|
|
1412
1625
|
if (name === 'scoreboard_set') {
|
|
1413
1626
|
const player = this.exprToTargetString(args[0]);
|
|
1414
|
-
const objective = this.
|
|
1627
|
+
const objective = this.resolveScoreboardObjective(args[0], args[1], callSpan);
|
|
1415
1628
|
const value = this.lowerExpr(args[2]);
|
|
1416
1629
|
if (value.kind === 'const') {
|
|
1417
1630
|
this.builder.emitRaw(`scoreboard players set ${player} ${objective} ${value.value}`);
|
|
@@ -1425,7 +1638,7 @@ class Lowering {
|
|
|
1425
1638
|
}
|
|
1426
1639
|
if (name === 'scoreboard_display') {
|
|
1427
1640
|
const slot = this.exprToString(args[0]);
|
|
1428
|
-
const objective = this.
|
|
1641
|
+
const objective = this.resolveScoreboardObjective(undefined, args[1], callSpan);
|
|
1429
1642
|
this.builder.emitRaw(`scoreboard objectives setdisplay ${slot} ${objective}`);
|
|
1430
1643
|
return { kind: 'const', value: 0 };
|
|
1431
1644
|
}
|
|
@@ -1435,14 +1648,14 @@ class Lowering {
|
|
|
1435
1648
|
return { kind: 'const', value: 0 };
|
|
1436
1649
|
}
|
|
1437
1650
|
if (name === 'scoreboard_add_objective') {
|
|
1438
|
-
const objective = this.
|
|
1651
|
+
const objective = this.resolveScoreboardObjective(undefined, args[0], callSpan);
|
|
1439
1652
|
const criteria = this.exprToString(args[1]);
|
|
1440
1653
|
const displayName = args[2] ? ` ${this.exprToQuotedString(args[2])}` : '';
|
|
1441
1654
|
this.builder.emitRaw(`scoreboard objectives add ${objective} ${criteria}${displayName}`);
|
|
1442
1655
|
return { kind: 'const', value: 0 };
|
|
1443
1656
|
}
|
|
1444
1657
|
if (name === 'scoreboard_remove_objective') {
|
|
1445
|
-
const objective = this.
|
|
1658
|
+
const objective = this.resolveScoreboardObjective(undefined, args[0], callSpan);
|
|
1446
1659
|
this.builder.emitRaw(`scoreboard objectives remove ${objective}`);
|
|
1447
1660
|
return { kind: 'const', value: 0 };
|
|
1448
1661
|
}
|
|
@@ -1616,6 +1829,90 @@ class Lowering {
|
|
|
1616
1829
|
}
|
|
1617
1830
|
return { kind: 'const', value: 0 };
|
|
1618
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
|
+
}
|
|
1619
1916
|
lowerRichTextBuiltin(name, args) {
|
|
1620
1917
|
const messageArgIndex = this.getRichTextArgIndex(name);
|
|
1621
1918
|
if (messageArgIndex === null) {
|
|
@@ -1775,6 +2072,28 @@ class Lowering {
|
|
|
1775
2072
|
return this.operandToVar(op);
|
|
1776
2073
|
}
|
|
1777
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
|
+
}
|
|
1778
2097
|
exprToSnbt(expr) {
|
|
1779
2098
|
switch (expr.kind) {
|
|
1780
2099
|
case 'struct_lit': {
|
|
@@ -1842,6 +2161,92 @@ class Lowering {
|
|
|
1842
2161
|
isTeamTextOption(option) {
|
|
1843
2162
|
return option === 'displayName' || option === 'prefix' || option === 'suffix';
|
|
1844
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
|
+
}
|
|
1845
2250
|
lowerCoordinateBuiltin(name, args) {
|
|
1846
2251
|
const pos0 = args[0] ? this.resolveBlockPosExpr(args[0]) : null;
|
|
1847
2252
|
const pos1 = args[1] ? this.resolveBlockPosExpr(args[1]) : null;
|
|
@@ -1949,7 +2354,11 @@ class Lowering {
|
|
|
1949
2354
|
};
|
|
1950
2355
|
}
|
|
1951
2356
|
if (expr.kind === 'call') {
|
|
1952
|
-
|
|
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;
|
|
1953
2362
|
}
|
|
1954
2363
|
if (expr.kind === 'invoke') {
|
|
1955
2364
|
const calleeType = this.inferExprType(expr.callee);
|
|
@@ -1977,6 +2386,21 @@ class Lowering {
|
|
|
1977
2386
|
}
|
|
1978
2387
|
return undefined;
|
|
1979
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
|
+
}
|
|
1980
2404
|
normalizeType(type) {
|
|
1981
2405
|
if (type.kind === 'array') {
|
|
1982
2406
|
return { kind: 'array', elem: this.normalizeType(type.elem) };
|