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.
@@ -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 tpCommand = this.lowerTpCommand(args);
1971
- if (tpCommand) {
1972
- this.builder.emitRaw(tpCommand);
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 tpCommand = this.lowerTpCommand(args);
1978
- if (tpCommand) {
1979
- this.builder.emitRaw(tpCommand);
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
- // Convert args to strings for builtin (use SNBT for struct/array literals)
1984
- const strArgs = args.map(arg => arg.kind === 'struct_lit' || arg.kind === 'array_lit'
1985
- ? this.exprToSnbt(arg)
1986
- : this.exprToString(arg));
1987
- const cmd = BUILTINS[name](strArgs);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "redscript-mc",
3
- "version": "1.2.13",
3
+ "version": "1.2.15",
4
4
  "description": "A high-level programming language that compiles to Minecraft datapacks",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -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
+ }