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.
- package/builtins.d.mcrs +42 -107
- package/dist/__tests__/compile-all.test.d.ts +9 -0
- package/dist/__tests__/compile-all.test.js +90 -0
- package/dist/builtins/metadata.js +8 -16
- package/dist/lexer/index.d.ts +1 -1
- package/dist/lexer/index.js +10 -0
- package/dist/lowering/index.d.ts +1 -0
- package/dist/lowering/index.js +41 -0
- package/dist/parser/index.d.ts +2 -0
- package/dist/parser/index.js +35 -3
- package/editors/vscode/builtins.d.mcrs +42 -107
- package/editors/vscode/package-lock.json +2 -2
- package/editors/vscode/package.json +1 -1
- package/editors/vscode/syntaxes/redscript.tmLanguage.json +1 -1
- package/package.json +1 -1
- package/src/__tests__/compile-all.test.ts +60 -0
- package/src/builtins/metadata.ts +8 -13
- package/src/lexer/index.ts +11 -1
- package/src/lowering/index.ts +39 -0
- package/src/parser/index.ts +35 -3
|
@@ -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
|
|
940
|
-
lines.push(`/// @param ${p.name}
|
|
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 (
|
|
974
|
-
type =
|
|
975
|
-
|
|
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};`);
|
package/dist/lexer/index.d.ts
CHANGED
|
@@ -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;
|
package/dist/lexer/index.js
CHANGED
|
@@ -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
|
}
|
package/dist/lowering/index.d.ts
CHANGED
|
@@ -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.
|
package/dist/lowering/index.js
CHANGED
|
@@ -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
|
}
|
package/dist/parser/index.d.ts
CHANGED
|
@@ -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;
|
package/dist/parser/index.js
CHANGED
|
@@ -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
|
-
|
|
217
|
-
|
|
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
|
-
|
|
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')) {
|