redscript-mc 1.2.13 → 1.2.15
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/dist/__tests__/macro.test.d.ts +8 -0
- package/dist/__tests__/macro.test.js +305 -0
- package/dist/ir/types.d.ts +2 -0
- package/dist/lowering/index.d.ts +23 -0
- package/dist/lowering/index.js +261 -12
- package/package.json +1 -1
- package/src/__tests__/fixtures/macro-test.mcrs +35 -0
- package/src/__tests__/macro.test.ts +343 -0
- package/src/ir/types.ts +3 -0
- package/src/lowering/index.ts +278 -14
package/dist/lowering/index.js
CHANGED
|
@@ -45,6 +45,14 @@ const diagnostics_1 = require("../diagnostics");
|
|
|
45
45
|
const path = __importStar(require("path"));
|
|
46
46
|
const types_1 = require("../events/types");
|
|
47
47
|
// ---------------------------------------------------------------------------
|
|
48
|
+
// Macro-aware builtins (MC 1.20.2+)
|
|
49
|
+
// These builtins generate commands where parameter variables cannot appear
|
|
50
|
+
// as literal values (coordinates, entity types, block types), so they
|
|
51
|
+
// require MC macro syntax when called with runtime variables.
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// All builtins support macro parameters - any arg that's a function param
|
|
54
|
+
// will automatically use MC 1.20.2+ macro syntax when needed
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
48
56
|
// Builtin Functions
|
|
49
57
|
// ---------------------------------------------------------------------------
|
|
50
58
|
const BUILTINS = {
|
|
@@ -213,12 +221,194 @@ class Lowering {
|
|
|
213
221
|
this.worldObjCounter = 0;
|
|
214
222
|
// Loop context stack for break/continue
|
|
215
223
|
this.loopStack = [];
|
|
224
|
+
// MC 1.20.2+ macro function support
|
|
225
|
+
// Names of params in the current function being lowered
|
|
226
|
+
this.currentFnParamNames = new Set();
|
|
227
|
+
// Params in the current function that need macro treatment (used in literal positions)
|
|
228
|
+
this.currentFnMacroParams = new Set();
|
|
229
|
+
// Global registry: fnName → macroParamNames (populated by pre-scan + lowering)
|
|
230
|
+
this.macroFunctionInfo = new Map();
|
|
216
231
|
this.namespace = namespace;
|
|
217
232
|
this.sourceRanges = sourceRanges;
|
|
218
233
|
LoweringBuilder.resetTempCounter();
|
|
219
234
|
}
|
|
235
|
+
// ---------------------------------------------------------------------------
|
|
236
|
+
// MC Macro pre-scan: identify which function params need macro treatment
|
|
237
|
+
// ---------------------------------------------------------------------------
|
|
238
|
+
preScanMacroFunctions(program) {
|
|
239
|
+
for (const fn of program.declarations) {
|
|
240
|
+
const paramNames = new Set(fn.params.map(p => p.name));
|
|
241
|
+
const macroParams = new Set();
|
|
242
|
+
this.preScanStmts(fn.body, paramNames, macroParams);
|
|
243
|
+
if (macroParams.size > 0) {
|
|
244
|
+
this.macroFunctionInfo.set(fn.name, [...macroParams]);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
for (const implBlock of program.implBlocks ?? []) {
|
|
248
|
+
for (const method of implBlock.methods) {
|
|
249
|
+
const paramNames = new Set(method.params.map(p => p.name));
|
|
250
|
+
const macroParams = new Set();
|
|
251
|
+
this.preScanStmts(method.body, paramNames, macroParams);
|
|
252
|
+
if (macroParams.size > 0) {
|
|
253
|
+
this.macroFunctionInfo.set(`${implBlock.typeName}_${method.name}`, [...macroParams]);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
preScanStmts(stmts, paramNames, macroParams) {
|
|
259
|
+
for (const stmt of stmts) {
|
|
260
|
+
this.preScanStmt(stmt, paramNames, macroParams);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
preScanStmt(stmt, paramNames, macroParams) {
|
|
264
|
+
switch (stmt.kind) {
|
|
265
|
+
case 'expr':
|
|
266
|
+
this.preScanExpr(stmt.expr, paramNames, macroParams);
|
|
267
|
+
break;
|
|
268
|
+
case 'let':
|
|
269
|
+
this.preScanExpr(stmt.init, paramNames, macroParams);
|
|
270
|
+
break;
|
|
271
|
+
case 'return':
|
|
272
|
+
if (stmt.value)
|
|
273
|
+
this.preScanExpr(stmt.value, paramNames, macroParams);
|
|
274
|
+
break;
|
|
275
|
+
case 'if':
|
|
276
|
+
this.preScanExpr(stmt.cond, paramNames, macroParams);
|
|
277
|
+
this.preScanStmts(stmt.then, paramNames, macroParams);
|
|
278
|
+
if (stmt.else_)
|
|
279
|
+
this.preScanStmts(stmt.else_, paramNames, macroParams);
|
|
280
|
+
break;
|
|
281
|
+
case 'while':
|
|
282
|
+
this.preScanExpr(stmt.cond, paramNames, macroParams);
|
|
283
|
+
this.preScanStmts(stmt.body, paramNames, macroParams);
|
|
284
|
+
break;
|
|
285
|
+
case 'for':
|
|
286
|
+
if (stmt.init)
|
|
287
|
+
this.preScanStmt(stmt.init, paramNames, macroParams);
|
|
288
|
+
this.preScanExpr(stmt.cond, paramNames, macroParams);
|
|
289
|
+
this.preScanStmts(stmt.body, paramNames, macroParams);
|
|
290
|
+
break;
|
|
291
|
+
case 'for_range':
|
|
292
|
+
this.preScanStmts(stmt.body, paramNames, macroParams);
|
|
293
|
+
break;
|
|
294
|
+
case 'foreach':
|
|
295
|
+
this.preScanStmts(stmt.body, paramNames, macroParams);
|
|
296
|
+
break;
|
|
297
|
+
case 'match':
|
|
298
|
+
this.preScanExpr(stmt.expr, paramNames, macroParams);
|
|
299
|
+
for (const arm of stmt.arms) {
|
|
300
|
+
this.preScanStmts(arm.body, paramNames, macroParams);
|
|
301
|
+
}
|
|
302
|
+
break;
|
|
303
|
+
case 'as_block':
|
|
304
|
+
case 'at_block':
|
|
305
|
+
this.preScanStmts(stmt.body, paramNames, macroParams);
|
|
306
|
+
break;
|
|
307
|
+
case 'execute':
|
|
308
|
+
this.preScanStmts(stmt.body, paramNames, macroParams);
|
|
309
|
+
break;
|
|
310
|
+
// raw, break, continue have no nested exprs of interest
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
preScanExpr(expr, paramNames, macroParams) {
|
|
314
|
+
if (expr.kind === 'call' && BUILTINS[expr.fn] !== undefined) {
|
|
315
|
+
// All ident args to macro-aware builtins that are params → macro params
|
|
316
|
+
for (const arg of expr.args) {
|
|
317
|
+
if (arg.kind === 'ident' && paramNames.has(arg.name)) {
|
|
318
|
+
macroParams.add(arg.name);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
// Recurse into sub-expressions for other call types
|
|
324
|
+
if (expr.kind === 'call') {
|
|
325
|
+
for (const arg of expr.args)
|
|
326
|
+
this.preScanExpr(arg, paramNames, macroParams);
|
|
327
|
+
}
|
|
328
|
+
else if (expr.kind === 'binary') {
|
|
329
|
+
this.preScanExpr(expr.left, paramNames, macroParams);
|
|
330
|
+
this.preScanExpr(expr.right, paramNames, macroParams);
|
|
331
|
+
}
|
|
332
|
+
else if (expr.kind === 'unary') {
|
|
333
|
+
this.preScanExpr(expr.operand, paramNames, macroParams);
|
|
334
|
+
}
|
|
335
|
+
else if (expr.kind === 'assign') {
|
|
336
|
+
this.preScanExpr(expr.value, paramNames, macroParams);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
// ---------------------------------------------------------------------------
|
|
340
|
+
// Macro helpers
|
|
341
|
+
// ---------------------------------------------------------------------------
|
|
342
|
+
/**
|
|
343
|
+
* If `expr` is a function parameter that needs macro treatment (runtime value
|
|
344
|
+
* used in a literal position), returns the param name; otherwise null.
|
|
345
|
+
*/
|
|
346
|
+
tryGetMacroParam(expr) {
|
|
347
|
+
if (expr.kind !== 'ident')
|
|
348
|
+
return null;
|
|
349
|
+
if (!this.currentFnParamNames.has(expr.name))
|
|
350
|
+
return null;
|
|
351
|
+
if (this.constValues.has(expr.name))
|
|
352
|
+
return null;
|
|
353
|
+
if (this.stringValues.has(expr.name))
|
|
354
|
+
return null;
|
|
355
|
+
return expr.name;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Converts an expression to a string for use as a builtin arg.
|
|
359
|
+
* If the expression is a macro param, returns `$(name)` and sets macroParam.
|
|
360
|
+
*/
|
|
361
|
+
exprToBuiltinArg(expr) {
|
|
362
|
+
const macroParam = this.tryGetMacroParam(expr);
|
|
363
|
+
if (macroParam) {
|
|
364
|
+
return { str: `$(${macroParam})`, macroParam };
|
|
365
|
+
}
|
|
366
|
+
if (expr.kind === 'struct_lit' || expr.kind === 'array_lit') {
|
|
367
|
+
return { str: this.exprToSnbt(expr) };
|
|
368
|
+
}
|
|
369
|
+
return { str: this.exprToString(expr) };
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Emits a call to a macro function, setting up both scoreboard params
|
|
373
|
+
* (for arithmetic use) and NBT macro args (for coordinate/literal use).
|
|
374
|
+
*/
|
|
375
|
+
emitMacroFunctionCall(fnName, args, macroParamNames, fnDecl) {
|
|
376
|
+
const params = fnDecl?.params ?? [];
|
|
377
|
+
const loweredArgs = args.map(arg => this.lowerExpr(arg));
|
|
378
|
+
// Set up regular scoreboard params (for arithmetic within the function)
|
|
379
|
+
for (let i = 0; i < loweredArgs.length; i++) {
|
|
380
|
+
const operand = loweredArgs[i];
|
|
381
|
+
if (operand.kind === 'const') {
|
|
382
|
+
this.builder.emitRaw(`scoreboard players set $p${i} rs ${operand.value}`);
|
|
383
|
+
}
|
|
384
|
+
else if (operand.kind === 'var') {
|
|
385
|
+
this.builder.emitRaw(`scoreboard players operation $p${i} rs = ${operand.name} rs`);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// Set up NBT storage for each macro param
|
|
389
|
+
for (const macroParam of macroParamNames) {
|
|
390
|
+
const paramIdx = params.findIndex(p => p.name === macroParam);
|
|
391
|
+
if (paramIdx < 0 || paramIdx >= loweredArgs.length)
|
|
392
|
+
continue;
|
|
393
|
+
const operand = loweredArgs[paramIdx];
|
|
394
|
+
if (operand.kind === 'const') {
|
|
395
|
+
this.builder.emitRaw(`data modify storage rs:macro_args ${macroParam} set value ${operand.value}`);
|
|
396
|
+
}
|
|
397
|
+
else if (operand.kind === 'var') {
|
|
398
|
+
this.builder.emitRaw(`execute store result storage rs:macro_args ${macroParam} int 1 run scoreboard players get ${operand.name} rs`);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
// Call with macro storage
|
|
402
|
+
this.builder.emitRaw(`function ${this.namespace}:${fnName} with storage rs:macro_args`);
|
|
403
|
+
// Copy return value (callers may use it)
|
|
404
|
+
const dst = this.builder.freshTemp();
|
|
405
|
+
this.builder.emitRaw(`scoreboard players operation ${dst} rs = $ret rs`);
|
|
406
|
+
return { kind: 'var', name: dst };
|
|
407
|
+
}
|
|
220
408
|
lower(program) {
|
|
221
409
|
this.namespace = program.namespace;
|
|
410
|
+
// Pre-scan for macro functions before main lowering (so call sites can detect them)
|
|
411
|
+
this.preScanMacroFunctions(program);
|
|
222
412
|
// Load struct definitions
|
|
223
413
|
for (const struct of program.structs ?? []) {
|
|
224
414
|
const fields = new Map();
|
|
@@ -297,6 +487,9 @@ class Lowering {
|
|
|
297
487
|
this.blockPosVars = new Map();
|
|
298
488
|
this.stringValues = new Map();
|
|
299
489
|
this.builder = new LoweringBuilder();
|
|
490
|
+
// Initialize macro tracking for this function
|
|
491
|
+
this.currentFnParamNames = new Set(runtimeParams.map(p => p.name));
|
|
492
|
+
this.currentFnMacroParams = new Set();
|
|
300
493
|
// Map parameters
|
|
301
494
|
if (staticEventDec) {
|
|
302
495
|
for (let i = 0; i < fn.params.length; i++) {
|
|
@@ -401,6 +594,13 @@ class Lowering {
|
|
|
401
594
|
if (tickRate && tickRate > 1) {
|
|
402
595
|
this.wrapWithTickRate(irFn, tickRate);
|
|
403
596
|
}
|
|
597
|
+
// Set macro metadata if this function uses MC macro syntax
|
|
598
|
+
if (this.currentFnMacroParams.size > 0) {
|
|
599
|
+
irFn.isMacroFunction = true;
|
|
600
|
+
irFn.macroParamNames = [...this.currentFnMacroParams];
|
|
601
|
+
// Update registry (may refine the pre-scan result)
|
|
602
|
+
this.macroFunctionInfo.set(loweredName, irFn.macroParamNames);
|
|
603
|
+
}
|
|
404
604
|
this.functions.push(irFn);
|
|
405
605
|
}
|
|
406
606
|
getTickRate(decorators) {
|
|
@@ -1588,8 +1788,18 @@ class Lowering {
|
|
|
1588
1788
|
const targetFn = callbackBindings.size > 0 || stdlibCallSite
|
|
1589
1789
|
? this.ensureSpecializedFunctionWithContext(fnDecl, callbackBindings, stdlibCallSite)
|
|
1590
1790
|
: expr.fn;
|
|
1791
|
+
// Check if this is a call to a known macro function
|
|
1792
|
+
const macroParams = this.macroFunctionInfo.get(targetFn);
|
|
1793
|
+
if (macroParams && macroParams.length > 0) {
|
|
1794
|
+
return this.emitMacroFunctionCall(targetFn, runtimeArgs, macroParams, fnDecl);
|
|
1795
|
+
}
|
|
1591
1796
|
return this.emitDirectFunctionCall(targetFn, runtimeArgs);
|
|
1592
1797
|
}
|
|
1798
|
+
// Check for macro function (forward-declared or external)
|
|
1799
|
+
const macroParamsForUnknown = this.macroFunctionInfo.get(expr.fn);
|
|
1800
|
+
if (macroParamsForUnknown && macroParamsForUnknown.length > 0) {
|
|
1801
|
+
return this.emitMacroFunctionCall(expr.fn, fullArgs, macroParamsForUnknown, undefined);
|
|
1802
|
+
}
|
|
1593
1803
|
return this.emitDirectFunctionCall(expr.fn, fullArgs);
|
|
1594
1804
|
}
|
|
1595
1805
|
lowerStaticCallExpr(expr) {
|
|
@@ -1718,6 +1928,9 @@ class Lowering {
|
|
|
1718
1928
|
const savedBlockPosVars = new Map(this.blockPosVars);
|
|
1719
1929
|
const savedStringValues = new Map(this.stringValues);
|
|
1720
1930
|
const savedVarTypes = new Map(this.varTypes);
|
|
1931
|
+
// Macro tracking state
|
|
1932
|
+
const savedCurrentFnParamNames = new Set(this.currentFnParamNames);
|
|
1933
|
+
const savedCurrentFnMacroParams = new Set(this.currentFnMacroParams);
|
|
1721
1934
|
try {
|
|
1722
1935
|
return callback();
|
|
1723
1936
|
}
|
|
@@ -1734,6 +1947,8 @@ class Lowering {
|
|
|
1734
1947
|
this.blockPosVars = savedBlockPosVars;
|
|
1735
1948
|
this.stringValues = savedStringValues;
|
|
1736
1949
|
this.varTypes = savedVarTypes;
|
|
1950
|
+
this.currentFnParamNames = savedCurrentFnParamNames;
|
|
1951
|
+
this.currentFnMacroParams = savedCurrentFnMacroParams;
|
|
1737
1952
|
}
|
|
1738
1953
|
}
|
|
1739
1954
|
lowerBuiltinCall(name, args, callSpan) {
|
|
@@ -1967,26 +2182,30 @@ class Lowering {
|
|
|
1967
2182
|
code: 'W_DEPRECATED',
|
|
1968
2183
|
...(callSpan ? { line: callSpan.line, col: callSpan.col } : {}),
|
|
1969
2184
|
});
|
|
1970
|
-
const
|
|
1971
|
-
if (
|
|
1972
|
-
this.builder.emitRaw(
|
|
2185
|
+
const tpResult = this.lowerTpCommandMacroAware(args);
|
|
2186
|
+
if (tpResult) {
|
|
2187
|
+
this.builder.emitRaw(tpResult.cmd);
|
|
1973
2188
|
}
|
|
1974
2189
|
return { kind: 'const', value: 0 };
|
|
1975
2190
|
}
|
|
1976
2191
|
if (name === 'tp') {
|
|
1977
|
-
const
|
|
1978
|
-
if (
|
|
1979
|
-
this.builder.emitRaw(
|
|
2192
|
+
const tpResult = this.lowerTpCommandMacroAware(args);
|
|
2193
|
+
if (tpResult) {
|
|
2194
|
+
this.builder.emitRaw(tpResult.cmd);
|
|
1980
2195
|
}
|
|
1981
2196
|
return { kind: 'const', value: 0 };
|
|
1982
2197
|
}
|
|
1983
|
-
//
|
|
1984
|
-
const
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
2198
|
+
// All builtins support macro params - check if any arg is a param needing macro treatment
|
|
2199
|
+
const argResults = args.map(arg => this.exprToBuiltinArg(arg));
|
|
2200
|
+
const hasMacroArg = argResults.some(r => r.macroParam !== undefined);
|
|
2201
|
+
if (hasMacroArg) {
|
|
2202
|
+
argResults.forEach(r => { if (r.macroParam)
|
|
2203
|
+
this.currentFnMacroParams.add(r.macroParam); });
|
|
2204
|
+
}
|
|
2205
|
+
const strArgs = argResults.map(r => r.str);
|
|
2206
|
+
const cmd = BUILTINS[name]?.(strArgs);
|
|
1988
2207
|
if (cmd) {
|
|
1989
|
-
this.builder.emitRaw(cmd);
|
|
2208
|
+
this.builder.emitRaw(hasMacroArg ? `$${cmd}` : cmd);
|
|
1990
2209
|
}
|
|
1991
2210
|
return { kind: 'const', value: 0 };
|
|
1992
2211
|
}
|
|
@@ -2488,6 +2707,36 @@ class Lowering {
|
|
|
2488
2707
|
}
|
|
2489
2708
|
return null;
|
|
2490
2709
|
}
|
|
2710
|
+
lowerTpCommandMacroAware(args) {
|
|
2711
|
+
const pos0 = args[0] ? this.resolveBlockPosExpr(args[0]) : null;
|
|
2712
|
+
const pos1 = args[1] ? this.resolveBlockPosExpr(args[1]) : null;
|
|
2713
|
+
// If blockpos args are used, no macro needed (coords are already resolved)
|
|
2714
|
+
if (args.length === 1 && pos0) {
|
|
2715
|
+
return { cmd: `tp ${emitBlockPos(pos0)}` };
|
|
2716
|
+
}
|
|
2717
|
+
if (args.length === 2 && pos1) {
|
|
2718
|
+
return { cmd: `tp ${this.exprToString(args[0])} ${emitBlockPos(pos1)}` };
|
|
2719
|
+
}
|
|
2720
|
+
// Check for macro args (int params used as coordinates)
|
|
2721
|
+
if (args.length >= 2) {
|
|
2722
|
+
const argResults = args.map(a => this.exprToBuiltinArg(a));
|
|
2723
|
+
const hasMacro = argResults.some(r => r.macroParam !== undefined);
|
|
2724
|
+
if (hasMacro) {
|
|
2725
|
+
argResults.forEach(r => { if (r.macroParam)
|
|
2726
|
+
this.currentFnMacroParams.add(r.macroParam); });
|
|
2727
|
+
const strs = argResults.map(r => r.str);
|
|
2728
|
+
if (args.length === 2) {
|
|
2729
|
+
return { cmd: `$tp ${strs[0]} ${strs[1]}` };
|
|
2730
|
+
}
|
|
2731
|
+
if (args.length === 4) {
|
|
2732
|
+
return { cmd: `$tp ${strs[0]} ${strs[1]} ${strs[2]} ${strs[3]}` };
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
// Fallback to non-macro
|
|
2737
|
+
const plain = this.lowerTpCommand(args);
|
|
2738
|
+
return plain ? { cmd: plain } : null;
|
|
2739
|
+
}
|
|
2491
2740
|
resolveBlockPosExpr(expr) {
|
|
2492
2741
|
if (expr.kind === 'blockpos') {
|
|
2493
2742
|
return expr;
|
package/package.json
CHANGED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// MC 1.20.2+ macro function test
|
|
2
|
+
// Tests that functions using parameters in coordinate/type positions
|
|
3
|
+
// generate macro syntax ($-prefixed commands with $(param) substitution)
|
|
4
|
+
//
|
|
5
|
+
// Compilation: redscript compile macro-test.mcrs -o output/ -n test
|
|
6
|
+
|
|
7
|
+
// Basic macro function: parameters used as summon coordinates
|
|
8
|
+
fn spawn_zombie(x: int, y: int, z: int) {
|
|
9
|
+
summon("minecraft:zombie", x, y, z);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Macro function: particle effect at coordinates
|
|
13
|
+
fn show_particle(x: int, y: int, z: int) {
|
|
14
|
+
particle("minecraft:flame", x, y, z);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Macro function: setblock at variable position
|
|
18
|
+
fn place_block(x: int, y: int, z: int) {
|
|
19
|
+
setblock(x, y, z, "minecraft:stone");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Non-macro function: all args are compile-time constants in the body
|
|
23
|
+
fn summon_fixed() {
|
|
24
|
+
summon("minecraft:zombie", 100, 64, 200);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Caller with constant args (calls macro function with inline NBT data)
|
|
28
|
+
fn call_with_constants() {
|
|
29
|
+
spawn_zombie(100, 64, 200);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Caller with variable args (calls macro function via 'with storage rs:macro_args')
|
|
33
|
+
fn call_with_vars(px: int, pz: int) {
|
|
34
|
+
spawn_zombie(px, 64, pz);
|
|
35
|
+
}
|