redscript-mc 1.2.16 → 1.2.18

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.
@@ -929,15 +929,12 @@ exports.BUILTIN_METADATA = {
929
929
  */
930
930
  function builtinToDeclaration(def) {
931
931
  const lines = [];
932
- // Doc comments
932
+ // Doc comments (English only)
933
933
  lines.push(`/// ${def.doc}`);
934
- if (def.docZh) {
935
- lines.push(`/// ${def.docZh}`);
936
- }
937
934
  // Param docs
938
935
  for (const p of def.params) {
939
- const opt = p.required ? '' : '?';
940
- lines.push(`/// @param ${p.name}${opt} ${p.doc}`);
936
+ const optTag = p.required ? '' : ' (optional)';
937
+ lines.push(`/// @param ${p.name} ${p.doc}${optTag}`);
941
938
  }
942
939
  // Returns
943
940
  if (def.returns !== 'void') {
@@ -947,15 +944,9 @@ function builtinToDeclaration(def) {
947
944
  for (const ex of def.examples) {
948
945
  lines.push(`/// @example ${ex.split('\n')[0]}`);
949
946
  }
950
- // Signature
947
+ // Signature - use default value syntax instead of ? for optional params
951
948
  const paramStrs = def.params.map(p => {
952
- const opt = p.required ? '' : '?';
953
949
  let type = p.type;
954
- // Map to .d.mcrs types
955
- if (type === 'selector')
956
- type = 'selector';
957
- if (type === 'BlockPos')
958
- type = 'BlockPos';
959
950
  if (type === 'effect')
960
951
  type = 'string';
961
952
  if (type === 'sound')
@@ -970,9 +961,10 @@ function builtinToDeclaration(def) {
970
961
  type = 'string';
971
962
  if (type === 'nbt')
972
963
  type = 'string';
973
- if (type === 'coord')
974
- type = 'coord';
975
- return `${p.name}${opt}: ${type}`;
964
+ if (!p.required && p.default !== undefined) {
965
+ return `${p.name}: ${type} = ${p.default}`;
966
+ }
967
+ return `${p.name}: ${type}`;
976
968
  });
977
969
  const retType = def.returns;
978
970
  lines.push(`declare fn ${def.name}(${paramStrs.join(', ')}): ${retType};`);
@@ -5,7 +5,7 @@
5
5
  * Handles special cases like entity selectors vs decorators,
6
6
  * range literals, and raw commands.
7
7
  */
8
- export type TokenKind = 'fn' | 'let' | 'const' | 'if' | 'else' | 'while' | 'for' | 'foreach' | 'match' | 'return' | 'break' | 'continue' | 'as' | 'at' | 'in' | 'is' | 'struct' | 'impl' | 'enum' | 'trigger' | 'namespace' | 'execute' | 'run' | 'unless' | 'int' | 'bool' | 'float' | 'string' | 'void' | 'BlockPos' | 'true' | 'false' | 'selector' | 'decorator' | 'int_lit' | 'float_lit' | 'byte_lit' | 'short_lit' | 'long_lit' | 'double_lit' | 'string_lit' | 'f_string' | 'range_lit' | 'rel_coord' | 'local_coord' | '+' | '-' | '*' | '/' | '%' | '~' | '^' | '==' | '!=' | '<' | '<=' | '>' | '>=' | '&&' | '||' | '!' | '=' | '+=' | '-=' | '*=' | '/=' | '%=' | '{' | '}' | '(' | ')' | '[' | ']' | ',' | ';' | ':' | '::' | '->' | '=>' | '.' | 'ident' | 'mc_name' | 'raw_cmd' | 'eof';
8
+ export type TokenKind = 'fn' | 'let' | 'const' | 'if' | 'else' | 'while' | 'for' | 'foreach' | 'match' | 'return' | 'break' | 'continue' | 'as' | 'at' | 'in' | 'is' | 'struct' | 'impl' | 'enum' | 'trigger' | 'namespace' | 'execute' | 'run' | 'unless' | 'declare' | 'int' | 'bool' | 'float' | 'string' | 'void' | 'BlockPos' | 'true' | 'false' | 'selector' | 'decorator' | 'int_lit' | 'float_lit' | 'byte_lit' | 'short_lit' | 'long_lit' | 'double_lit' | 'string_lit' | 'f_string' | 'range_lit' | 'rel_coord' | 'local_coord' | '+' | '-' | '*' | '/' | '%' | '~' | '^' | '==' | '!=' | '<' | '<=' | '>' | '>=' | '&&' | '||' | '!' | '=' | '+=' | '-=' | '*=' | '/=' | '%=' | '{' | '}' | '(' | ')' | '[' | ']' | ',' | ';' | ':' | '::' | '->' | '=>' | '.' | 'ident' | 'mc_name' | 'raw_cmd' | 'eof';
9
9
  export interface Token {
10
10
  kind: TokenKind;
11
11
  value: string;
@@ -37,6 +37,7 @@ const KEYWORDS = {
37
37
  execute: 'execute',
38
38
  run: 'run',
39
39
  unless: 'unless',
40
+ declare: 'declare',
40
41
  int: 'int',
41
42
  bool: 'bool',
42
43
  float: 'float',
@@ -222,6 +223,15 @@ class Lexer {
222
223
  value += this.advance();
223
224
  }
224
225
  }
226
+ // Check for ident (e.g. ~height → macro variable offset)
227
+ if (/[a-zA-Z_]/.test(this.peek())) {
228
+ let ident = '';
229
+ while (/[a-zA-Z0-9_]/.test(this.peek())) {
230
+ ident += this.advance();
231
+ }
232
+ // Store as rel_coord with embedded ident: ~height
233
+ value += ident;
234
+ }
225
235
  this.addToken('rel_coord', value, startLine, startCol);
226
236
  return;
227
237
  }
@@ -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.
@@ -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,38 @@ class Lowering {
363
372
  if (macroParam) {
364
373
  return { str: `$(${macroParam})`, macroParam };
365
374
  }
375
+ // Handle ~ident / ^ident syntax — relative/local coord with a VARIABLE offset.
376
+ //
377
+ // WHY macros are required here:
378
+ // Minecraft's ~N and ^N coordinate syntax requires N to be a compile-time
379
+ // literal number. There is no command that accepts a scoreboard value as a
380
+ // relative offset. Therefore `~height` (where height is a runtime int) can
381
+ // only be expressed at the MC level via the 1.20.2+ function macro system,
382
+ // which substitutes $(height) into the command text at call time.
383
+ //
384
+ // Contrast with absolute coords: `tp(target, x, y, z)` where x/y/z are
385
+ // plain ints — those become $(x) etc. as literal replacements, same mechanism,
386
+ // but the distinction matters to callers: ~$(height) means "relative by height
387
+ // blocks from current pos", not "teleport to absolute scoreboard value".
388
+ //
389
+ // Example:
390
+ // fn launch_up(target: selector, height: int) {
391
+ // tp(target, ~0, ~height, ~0); // "~height" parsed as rel_coord
392
+ // }
393
+ // Emits: $tp $(target) ~0 ~$(height) ~0
394
+ // Called: function ns:launch_up with storage rs:macro_args
395
+ if (expr.kind === 'rel_coord' || expr.kind === 'local_coord') {
396
+ const val = expr.value; // e.g. "~height" or "^depth"
397
+ const prefix = val[0]; // ~ or ^
398
+ const rest = val.slice(1);
399
+ // If rest is an identifier (not a number), treat as macro param
400
+ if (rest && /^[a-zA-Z_][a-zA-Z0-9_]*$/.test(rest)) {
401
+ const paramName = this.tryGetMacroParamByName(rest);
402
+ if (paramName) {
403
+ return { str: `${prefix}$(${paramName})`, macroParam: paramName };
404
+ }
405
+ }
406
+ }
366
407
  if (expr.kind === 'struct_lit' || expr.kind === 'array_lit') {
367
408
  return { str: this.exprToSnbt(expr) };
368
409
  }
@@ -27,6 +27,8 @@ export declare class Parser {
27
27
  private parseConstDecl;
28
28
  private parseGlobalDecl;
29
29
  private parseFnDecl;
30
+ /** Parse a `declare fn name(params): returnType;` stub — no body, just discard. */
31
+ private parseDeclareStub;
30
32
  private parseDecorators;
31
33
  private parseDecoratorValue;
32
34
  private parseParams;
@@ -149,6 +149,11 @@ class Parser {
149
149
  else if (this.check('const')) {
150
150
  consts.push(this.parseConstDecl());
151
151
  }
152
+ else if (this.check('declare')) {
153
+ // Declaration-only stub (e.g. from builtins.d.mcrs) — parse and discard
154
+ this.advance(); // consume 'declare'
155
+ this.parseDeclareStub();
156
+ }
152
157
  else {
153
158
  declarations.push(this.parseFnDecl());
154
159
  }
@@ -213,12 +218,19 @@ class Parser {
213
218
  parseConstDecl() {
214
219
  const constToken = this.expect('const');
215
220
  const name = this.expect('ident').value;
216
- this.expect(':');
217
- const type = this.parseType();
221
+ let type;
222
+ if (this.match(':')) {
223
+ type = this.parseType();
224
+ }
218
225
  this.expect('=');
219
226
  const value = this.parseLiteralExpr();
220
227
  this.match(';');
221
- return this.withLoc({ name, type, value }, constToken);
228
+ // Infer type from value if not provided
229
+ const inferredType = type ?? (value.kind === 'str_lit' ? { kind: 'named', name: 'string' } :
230
+ value.kind === 'bool_lit' ? { kind: 'named', name: 'bool' } :
231
+ value.kind === 'float_lit' ? { kind: 'named', name: 'float' } :
232
+ { kind: 'named', name: 'int' });
233
+ return this.withLoc({ name, type: inferredType, value }, constToken);
222
234
  }
223
235
  parseGlobalDecl(mutable) {
224
236
  const token = this.advance(); // consume 'let'
@@ -247,6 +259,26 @@ class Parser {
247
259
  const body = this.parseBlock();
248
260
  return this.withLoc({ name, params, returnType, decorators, body }, fnToken);
249
261
  }
262
+ /** Parse a `declare fn name(params): returnType;` stub — no body, just discard. */
263
+ parseDeclareStub() {
264
+ this.expect('fn');
265
+ this.expect('ident'); // name
266
+ this.expect('(');
267
+ // consume params until ')'
268
+ let depth = 1;
269
+ while (!this.check('eof') && depth > 0) {
270
+ const t = this.advance();
271
+ if (t.kind === '(')
272
+ depth++;
273
+ else if (t.kind === ')')
274
+ depth--;
275
+ }
276
+ // optional return type annotation `: type` or `-> type`
277
+ if (this.match(':') || this.match('->')) {
278
+ this.parseType();
279
+ }
280
+ this.match(';'); // consume trailing semicolon
281
+ }
250
282
  parseDecorators() {
251
283
  const decorators = [];
252
284
  while (this.check('decorator')) {