redscript-mc 1.2.15 → 1.2.17

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/cli.js CHANGED
@@ -48,6 +48,7 @@ const cmdblock_1 = require("./codegen/cmdblock");
48
48
  const structure_1 = require("./codegen/structure");
49
49
  const diagnostics_1 = require("./diagnostics");
50
50
  const repl_1 = require("./repl");
51
+ const metadata_1 = require("./builtins/metadata");
51
52
  const fs = __importStar(require("fs"));
52
53
  const path = __importStar(require("path"));
53
54
  // Parse command line arguments
@@ -61,16 +62,18 @@ Usage:
61
62
  redscript watch <dir> [-o <outdir>] [--namespace <ns>] [--hot-reload <url>]
62
63
  redscript check <file>
63
64
  redscript fmt <file.mcrs> [file2.mcrs ...]
65
+ redscript generate-dts [-o <file>]
64
66
  redscript repl
65
67
  redscript version
66
68
 
67
69
  Commands:
68
- compile Compile a RedScript file to a Minecraft datapack
69
- watch Watch a directory for .mcrs file changes, recompile, and hot reload
70
- check Check a RedScript file for errors without generating output
71
- fmt Auto-format RedScript source files
72
- repl Start an interactive RedScript REPL
73
- version Print the RedScript version
70
+ compile Compile a RedScript file to a Minecraft datapack
71
+ watch Watch a directory for .mcrs file changes, recompile, and hot reload
72
+ check Check a RedScript file for errors without generating output
73
+ fmt Auto-format RedScript source files
74
+ generate-dts Generate builtin function declaration file (builtins.d.mcrs)
75
+ repl Start an interactive RedScript REPL
76
+ version Print the RedScript version
74
77
 
75
78
  Options:
76
79
  -o, --output <path> Output directory or file path, depending on target
@@ -414,6 +417,13 @@ async function main() {
414
417
  }
415
418
  break;
416
419
  }
420
+ case 'generate-dts': {
421
+ const output = parsed.output ?? 'builtins.d.mcrs';
422
+ const dtsContent = (0, metadata_1.generateDts)();
423
+ fs.writeFileSync(output, dtsContent, 'utf-8');
424
+ console.log(`Generated ${output}`);
425
+ break;
426
+ }
417
427
  case 'repl':
418
428
  await (0, repl_1.startRepl)(parsed.namespace ?? 'repl');
419
429
  break;
@@ -222,6 +222,15 @@ class Lexer {
222
222
  value += this.advance();
223
223
  }
224
224
  }
225
+ // Check for ident (e.g. ~height → macro variable offset)
226
+ if (/[a-zA-Z_]/.test(this.peek())) {
227
+ let ident = '';
228
+ while (/[a-zA-Z0-9_]/.test(this.peek())) {
229
+ ident += this.advance();
230
+ }
231
+ // Store as rel_coord with embedded ident: ~height
232
+ value += ident;
233
+ }
225
234
  this.addToken('rel_coord', value, startLine, startCol);
226
235
  return;
227
236
  }
@@ -60,6 +60,7 @@ export declare class Lowering {
60
60
  * used in a literal position), returns the param name; otherwise null.
61
61
  */
62
62
  private tryGetMacroParam;
63
+ private tryGetMacroParamByName;
63
64
  /**
64
65
  * Converts an expression to a string for use as a builtin arg.
65
66
  * If the expression is a macro param, returns `$(name)` and sets macroParam.
@@ -151,6 +152,17 @@ export declare class Lowering {
151
152
  private getArrayStorageName;
152
153
  private inferLambdaReturnType;
153
154
  private inferExprType;
155
+ /**
156
+ * Checks a raw() command string for `${...}` interpolation containing runtime variables.
157
+ * - If the interpolated expression is a numeric literal → OK (MC macro syntax).
158
+ * - If the interpolated name is a compile-time constant (in constValues) → OK.
159
+ * - If the interpolated name is a known runtime variable (in varMap) → DiagnosticError.
160
+ * - Unknown names → OK (could be MC macro params or external constants).
161
+ *
162
+ * This catches the common mistake of writing raw("say ${score}") expecting interpolation,
163
+ * which would silently emit a literal `${score}` in the MC command.
164
+ */
165
+ private checkRawCommandInterpolation;
154
166
  private resolveInstanceMethod;
155
167
  private normalizeType;
156
168
  private readArrayElement;
@@ -354,6 +354,15 @@ class Lowering {
354
354
  return null;
355
355
  return expr.name;
356
356
  }
357
+ tryGetMacroParamByName(name) {
358
+ if (!this.currentFnParamNames.has(name))
359
+ return null;
360
+ if (this.constValues.has(name))
361
+ return null;
362
+ if (this.stringValues.has(name))
363
+ return null;
364
+ return name;
365
+ }
357
366
  /**
358
367
  * Converts an expression to a string for use as a builtin arg.
359
368
  * If the expression is a macro param, returns `$(name)` and sets macroParam.
@@ -363,6 +372,19 @@ class Lowering {
363
372
  if (macroParam) {
364
373
  return { str: `$(${macroParam})`, macroParam };
365
374
  }
375
+ // Handle ~ident (e.g. ~height) - relative coord with variable offset
376
+ if (expr.kind === 'rel_coord' || expr.kind === 'local_coord') {
377
+ const val = expr.value; // e.g. "~height" or "^depth"
378
+ const prefix = val[0]; // ~ or ^
379
+ const rest = val.slice(1);
380
+ // If rest is an identifier (not a number), treat as macro param
381
+ if (rest && /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(rest)) {
382
+ const paramName = this.tryGetMacroParamByName(rest);
383
+ if (paramName) {
384
+ return { str: `${prefix}$(${paramName})`, macroParam: paramName };
385
+ }
386
+ }
387
+ }
366
388
  if (expr.kind === 'struct_lit' || expr.kind === 'array_lit') {
367
389
  return { str: this.exprToSnbt(expr) };
368
390
  }
@@ -704,6 +726,7 @@ class Lowering {
704
726
  this.lowerExecuteStmt(stmt);
705
727
  break;
706
728
  case 'raw':
729
+ this.checkRawCommandInterpolation(stmt.cmd, stmt.span);
707
730
  this.builder.emitRaw(stmt.cmd);
708
731
  break;
709
732
  }
@@ -2830,6 +2853,39 @@ class Lowering {
2830
2853
  }
2831
2854
  return undefined;
2832
2855
  }
2856
+ /**
2857
+ * Checks a raw() command string for `${...}` interpolation containing runtime variables.
2858
+ * - If the interpolated expression is a numeric literal → OK (MC macro syntax).
2859
+ * - If the interpolated name is a compile-time constant (in constValues) → OK.
2860
+ * - If the interpolated name is a known runtime variable (in varMap) → DiagnosticError.
2861
+ * - Unknown names → OK (could be MC macro params or external constants).
2862
+ *
2863
+ * This catches the common mistake of writing raw("say ${score}") expecting interpolation,
2864
+ * which would silently emit a literal `${score}` in the MC command.
2865
+ */
2866
+ checkRawCommandInterpolation(cmd, span) {
2867
+ const interpRe = /\$\{([^}]+)\}/g;
2868
+ let match;
2869
+ while ((match = interpRe.exec(cmd)) !== null) {
2870
+ const name = match[1].trim();
2871
+ // Numeric/boolean literals are fine (intentional MC macro syntax)
2872
+ if (/^\d+(\.\d+)?$/.test(name) || name === 'true' || name === 'false') {
2873
+ continue;
2874
+ }
2875
+ // Compile-time constants are fine
2876
+ if (this.constValues.has(name)) {
2877
+ continue;
2878
+ }
2879
+ // Only error if it's a known runtime variable (in varMap or function params)
2880
+ // Unknown identifiers are left alone (could be MC macro params the user intends)
2881
+ if (this.varMap.has(name) || this.currentFnParamNames.has(name)) {
2882
+ const loc = span ?? { line: 1, col: 1 };
2883
+ throw new diagnostics_1.DiagnosticError('LoweringError', `raw() command contains runtime variable interpolation '\${${name}}'. ` +
2884
+ `Variables cannot be interpolated into raw commands at compile time. ` +
2885
+ `Use f-string messages (say/tell/announce) or MC macro syntax '$(${name})' for MC 1.20.2+ commands.`, loc);
2886
+ }
2887
+ }
2888
+ }
2833
2889
  resolveInstanceMethod(expr) {
2834
2890
  const receiver = expr.args[0];
2835
2891
  if (!receiver) {
@@ -213,12 +213,19 @@ class Parser {
213
213
  parseConstDecl() {
214
214
  const constToken = this.expect('const');
215
215
  const name = this.expect('ident').value;
216
- this.expect(':');
217
- const type = this.parseType();
216
+ let type;
217
+ if (this.match(':')) {
218
+ type = this.parseType();
219
+ }
218
220
  this.expect('=');
219
221
  const value = this.parseLiteralExpr();
220
222
  this.match(';');
221
- return this.withLoc({ name, type, value }, constToken);
223
+ // Infer type from value if not provided
224
+ const inferredType = type ?? (value.kind === 'str_lit' ? { kind: 'named', name: 'string' } :
225
+ value.kind === 'bool_lit' ? { kind: 'named', name: 'bool' } :
226
+ value.kind === 'float_lit' ? { kind: 'named', name: 'float' } :
227
+ { kind: 'named', name: 'int' });
228
+ return this.withLoc({ name, type: inferredType, value }, constToken);
222
229
  }
223
230
  parseGlobalDecl(mutable) {
224
231
  const token = this.advance(); // consume 'let'