redscript-mc 1.1.0 → 1.2.1

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.
Files changed (83) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/README.md +53 -10
  3. package/README.zh.md +53 -10
  4. package/dist/__tests__/cli.test.js +138 -0
  5. package/dist/__tests__/codegen.test.js +25 -0
  6. package/dist/__tests__/dce.test.d.ts +1 -0
  7. package/dist/__tests__/dce.test.js +137 -0
  8. package/dist/__tests__/e2e.test.js +190 -12
  9. package/dist/__tests__/lexer.test.js +31 -4
  10. package/dist/__tests__/lowering.test.js +172 -9
  11. package/dist/__tests__/mc-integration.test.js +145 -51
  12. package/dist/__tests__/mc-syntax.test.js +12 -0
  13. package/dist/__tests__/optimizer-advanced.test.js +3 -3
  14. package/dist/__tests__/parser.test.js +90 -0
  15. package/dist/__tests__/runtime.test.js +21 -8
  16. package/dist/__tests__/typechecker.test.js +188 -0
  17. package/dist/ast/types.d.ts +42 -3
  18. package/dist/cli.js +15 -10
  19. package/dist/codegen/mcfunction/index.js +30 -1
  20. package/dist/codegen/structure/index.d.ts +4 -1
  21. package/dist/codegen/structure/index.js +29 -2
  22. package/dist/compile.d.ts +11 -0
  23. package/dist/compile.js +40 -6
  24. package/dist/events/types.d.ts +35 -0
  25. package/dist/events/types.js +59 -0
  26. package/dist/index.d.ts +1 -0
  27. package/dist/index.js +7 -3
  28. package/dist/ir/types.d.ts +4 -0
  29. package/dist/lexer/index.d.ts +2 -1
  30. package/dist/lexer/index.js +91 -1
  31. package/dist/lowering/index.d.ts +32 -1
  32. package/dist/lowering/index.js +476 -16
  33. package/dist/optimizer/dce.d.ts +23 -0
  34. package/dist/optimizer/dce.js +591 -0
  35. package/dist/parser/index.d.ts +4 -0
  36. package/dist/parser/index.js +160 -26
  37. package/dist/typechecker/index.d.ts +19 -0
  38. package/dist/typechecker/index.js +392 -17
  39. package/docs/ARCHITECTURE.zh.md +1088 -0
  40. package/docs/ENTITY_TYPE_SYSTEM.md +242 -0
  41. package/editors/vscode/.vscodeignore +3 -0
  42. package/editors/vscode/CHANGELOG.md +9 -0
  43. package/editors/vscode/icon.png +0 -0
  44. package/editors/vscode/out/extension.js +1144 -72
  45. package/editors/vscode/package-lock.json +2 -2
  46. package/editors/vscode/package.json +1 -1
  47. package/editors/vscode/syntaxes/redscript.tmLanguage.json +6 -2
  48. package/examples/spiral.mcrs +79 -0
  49. package/logo.png +0 -0
  50. package/package.json +1 -1
  51. package/src/__tests__/cli.test.ts +166 -0
  52. package/src/__tests__/codegen.test.ts +27 -0
  53. package/src/__tests__/dce.test.ts +129 -0
  54. package/src/__tests__/e2e.test.ts +201 -12
  55. package/src/__tests__/fixtures/event-test.mcrs +13 -0
  56. package/src/__tests__/fixtures/impl-test.mcrs +46 -0
  57. package/src/__tests__/fixtures/interval-test.mcrs +11 -0
  58. package/src/__tests__/fixtures/is-check-test.mcrs +20 -0
  59. package/src/__tests__/fixtures/timeout-test.mcrs +7 -0
  60. package/src/__tests__/lexer.test.ts +35 -4
  61. package/src/__tests__/lowering.test.ts +187 -9
  62. package/src/__tests__/mc-integration.test.ts +166 -51
  63. package/src/__tests__/mc-syntax.test.ts +14 -0
  64. package/src/__tests__/optimizer-advanced.test.ts +3 -3
  65. package/src/__tests__/parser.test.ts +102 -5
  66. package/src/__tests__/runtime.test.ts +24 -8
  67. package/src/__tests__/typechecker.test.ts +204 -0
  68. package/src/ast/types.ts +39 -2
  69. package/src/cli.ts +24 -10
  70. package/src/codegen/mcfunction/index.ts +31 -1
  71. package/src/codegen/structure/index.ts +40 -2
  72. package/src/compile.ts +59 -7
  73. package/src/events/types.ts +69 -0
  74. package/src/index.ts +9 -4
  75. package/src/ir/types.ts +4 -0
  76. package/src/lexer/index.ts +105 -2
  77. package/src/lowering/index.ts +566 -18
  78. package/src/optimizer/dce.ts +618 -0
  79. package/src/parser/index.ts +187 -29
  80. package/src/stdlib/README.md +34 -4
  81. package/src/stdlib/tags.mcrs +951 -0
  82. package/src/stdlib/timer.mcrs +54 -33
  83. package/src/typechecker/index.ts +469 -18
@@ -0,0 +1,35 @@
1
+ import type { TypeNode } from '../ast/types';
2
+ export declare const EVENT_TYPES: {
3
+ readonly PlayerDeath: {
4
+ readonly tag: "rs.just_died";
5
+ readonly params: readonly ["player: Player"];
6
+ readonly detection: "scoreboard";
7
+ };
8
+ readonly PlayerJoin: {
9
+ readonly tag: "rs.just_joined";
10
+ readonly params: readonly ["player: Player"];
11
+ readonly detection: "tag";
12
+ };
13
+ readonly BlockBreak: {
14
+ readonly tag: "rs.just_broke_block";
15
+ readonly params: readonly ["player: Player", "block: string"];
16
+ readonly detection: "advancement";
17
+ };
18
+ readonly EntityKill: {
19
+ readonly tag: "rs.just_killed";
20
+ readonly params: readonly ["player: Player"];
21
+ readonly detection: "scoreboard";
22
+ };
23
+ readonly ItemUse: {
24
+ readonly tag: "rs.just_used_item";
25
+ readonly params: readonly ["player: Player"];
26
+ readonly detection: "scoreboard";
27
+ };
28
+ };
29
+ export type EventTypeName = keyof typeof EVENT_TYPES;
30
+ export interface EventParamSpec {
31
+ name: string;
32
+ type: TypeNode;
33
+ }
34
+ export declare function isEventTypeName(value: string): value is EventTypeName;
35
+ export declare function getEventParamSpecs(eventType: EventTypeName): EventParamSpec[];
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EVENT_TYPES = void 0;
4
+ exports.isEventTypeName = isEventTypeName;
5
+ exports.getEventParamSpecs = getEventParamSpecs;
6
+ exports.EVENT_TYPES = {
7
+ PlayerDeath: {
8
+ tag: 'rs.just_died',
9
+ params: ['player: Player'],
10
+ detection: 'scoreboard',
11
+ },
12
+ PlayerJoin: {
13
+ tag: 'rs.just_joined',
14
+ params: ['player: Player'],
15
+ detection: 'tag',
16
+ },
17
+ BlockBreak: {
18
+ tag: 'rs.just_broke_block',
19
+ params: ['player: Player', 'block: string'],
20
+ detection: 'advancement',
21
+ },
22
+ EntityKill: {
23
+ tag: 'rs.just_killed',
24
+ params: ['player: Player'],
25
+ detection: 'scoreboard',
26
+ },
27
+ ItemUse: {
28
+ tag: 'rs.just_used_item',
29
+ params: ['player: Player'],
30
+ detection: 'scoreboard',
31
+ },
32
+ };
33
+ function isEventTypeName(value) {
34
+ return value in exports.EVENT_TYPES;
35
+ }
36
+ function getEventParamSpecs(eventType) {
37
+ return exports.EVENT_TYPES[eventType].params.map(parseEventParam);
38
+ }
39
+ function parseEventParam(spec) {
40
+ const match = spec.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*:\s*([A-Za-z_][A-Za-z0-9_]*)$/);
41
+ if (!match) {
42
+ throw new Error(`Invalid event parameter spec: ${spec}`);
43
+ }
44
+ const [, name, typeName] = match;
45
+ return {
46
+ name,
47
+ type: toTypeNode(typeName),
48
+ };
49
+ }
50
+ function toTypeNode(typeName) {
51
+ if (typeName === 'Player') {
52
+ return { kind: 'entity', entityType: 'Player' };
53
+ }
54
+ if (typeName === 'string' || typeName === 'int' || typeName === 'bool' || typeName === 'float' || typeName === 'void' || typeName === 'BlockPos' || typeName === 'byte' || typeName === 'short' || typeName === 'long' || typeName === 'double') {
55
+ return { kind: 'named', name: typeName };
56
+ }
57
+ return { kind: 'struct', name: typeName };
58
+ }
59
+ //# sourceMappingURL=types.js.map
package/dist/index.d.ts CHANGED
@@ -14,6 +14,7 @@ export interface CompileOptions {
14
14
  optimize?: boolean;
15
15
  typeCheck?: boolean;
16
16
  filePath?: string;
17
+ dce?: boolean;
17
18
  }
18
19
  export interface CompileResult {
19
20
  files: DatapackFile[];
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ const parser_1 = require("./parser");
13
13
  const typechecker_1 = require("./typechecker");
14
14
  const lowering_1 = require("./lowering");
15
15
  const passes_1 = require("./optimizer/passes");
16
+ const dce_1 = require("./optimizer/dce");
16
17
  const mcfunction_1 = require("./codegen/mcfunction");
17
18
  const compile_1 = require("./compile");
18
19
  const commands_1 = require("./optimizer/commands");
@@ -27,12 +28,15 @@ function compile(source, options = {}) {
27
28
  const namespace = options.namespace ?? 'redscript';
28
29
  const shouldOptimize = options.optimize ?? true;
29
30
  const shouldTypeCheck = options.typeCheck ?? true;
31
+ const shouldRunDce = options.dce ?? shouldOptimize;
30
32
  const filePath = options.filePath;
31
- const preprocessedSource = (0, compile_1.preprocessSource)(source, { filePath });
33
+ const preprocessed = (0, compile_1.preprocessSourceWithMetadata)(source, { filePath });
34
+ const preprocessedSource = preprocessed.source;
32
35
  // Lexing
33
36
  const tokens = new lexer_1.Lexer(preprocessedSource, filePath).tokenize();
34
37
  // Parsing
35
- const ast = new parser_1.Parser(tokens, preprocessedSource, filePath).parse(namespace);
38
+ const parsedAst = new parser_1.Parser(tokens, preprocessedSource, filePath).parse(namespace);
39
+ const ast = shouldRunDce ? (0, dce_1.eliminateDeadCode)(parsedAst) : parsedAst;
36
40
  // Type checking (warn mode - collect errors but don't block)
37
41
  let typeErrors;
38
42
  if (shouldTypeCheck) {
@@ -40,7 +44,7 @@ function compile(source, options = {}) {
40
44
  typeErrors = checker.check(ast);
41
45
  }
42
46
  // Lowering to IR
43
- const lowering = new lowering_1.Lowering(namespace);
47
+ const lowering = new lowering_1.Lowering(namespace, preprocessed.ranges);
44
48
  const ir = lowering.lower(ast);
45
49
  let optimizedIR = ir;
46
50
  let generated = (0, mcfunction_1.generateDatapackWithStats)(ir, { optimizeCommands: shouldOptimize });
@@ -110,6 +110,10 @@ export interface IRFunction {
110
110
  kind: 'advancement' | 'craft' | 'death' | 'login' | 'join_team';
111
111
  value?: string;
112
112
  };
113
+ eventHandler?: {
114
+ eventType: string;
115
+ tag: string;
116
+ };
113
117
  }
114
118
  export interface GlobalVar {
115
119
  name: string;
@@ -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' | 'as' | 'at' | 'in' | 'struct' | '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' | 'range_lit' | '+' | '-' | '*' | '/' | '%' | '~' | '^' | '==' | '!=' | '<' | '<=' | '>' | '>=' | '&&' | '||' | '!' | '=' | '+=' | '-=' | '*=' | '/=' | '%=' | '{' | '}' | '(' | ')' | '[' | ']' | ',' | ';' | ':' | '::' | '->' | '=>' | '.' | 'ident' | 'mc_name' | 'raw_cmd' | 'eof';
8
+ export type TokenKind = 'fn' | 'let' | 'const' | 'if' | 'else' | 'while' | 'for' | 'foreach' | 'match' | 'return' | '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';
9
9
  export interface Token {
10
10
  kind: TokenKind;
11
11
  value: string;
@@ -31,6 +31,7 @@ export declare class Lexer {
31
31
  private scanAtToken;
32
32
  private scanSelectorParams;
33
33
  private scanString;
34
+ private scanFString;
34
35
  private scanNumber;
35
36
  private scanIdentifier;
36
37
  }
@@ -26,7 +26,9 @@ const KEYWORDS = {
26
26
  as: 'as',
27
27
  at: 'at',
28
28
  in: 'in',
29
+ is: 'is',
29
30
  struct: 'struct',
31
+ impl: 'impl',
30
32
  enum: 'enum',
31
33
  trigger: 'trigger',
32
34
  namespace: 'namespace',
@@ -200,8 +202,50 @@ class Lexer {
200
202
  this.addToken('range_lit', value, startLine, startCol);
201
203
  return;
202
204
  }
205
+ // Relative coordinate: ~ or ~5 or ~-3 or ~0.5
206
+ if (char === '~') {
207
+ let value = '~';
208
+ // Check for optional sign
209
+ if (this.peek() === '-' || this.peek() === '+') {
210
+ value += this.advance();
211
+ }
212
+ // Check for number
213
+ while (/[0-9]/.test(this.peek())) {
214
+ value += this.advance();
215
+ }
216
+ // Check for decimal part
217
+ if (this.peek() === '.' && /[0-9]/.test(this.peek(1))) {
218
+ value += this.advance(); // .
219
+ while (/[0-9]/.test(this.peek())) {
220
+ value += this.advance();
221
+ }
222
+ }
223
+ this.addToken('rel_coord', value, startLine, startCol);
224
+ return;
225
+ }
226
+ // Local coordinate: ^ or ^5 or ^-3 or ^0.5
227
+ if (char === '^') {
228
+ let value = '^';
229
+ // Check for optional sign
230
+ if (this.peek() === '-' || this.peek() === '+') {
231
+ value += this.advance();
232
+ }
233
+ // Check for number
234
+ while (/[0-9]/.test(this.peek())) {
235
+ value += this.advance();
236
+ }
237
+ // Check for decimal part
238
+ if (this.peek() === '.' && /[0-9]/.test(this.peek(1))) {
239
+ value += this.advance(); // .
240
+ while (/[0-9]/.test(this.peek())) {
241
+ value += this.advance();
242
+ }
243
+ }
244
+ this.addToken('local_coord', value, startLine, startCol);
245
+ return;
246
+ }
203
247
  // Single-character operators and delimiters
204
- const singleChar = ['+', '-', '*', '/', '%', '~', '^', '<', '>', '!', '=',
248
+ const singleChar = ['+', '-', '*', '/', '%', '<', '>', '!', '=',
205
249
  '{', '}', '(', ')', '[', ']', ',', ';', ':', '.'];
206
250
  if (singleChar.includes(char)) {
207
251
  this.addToken(char, char, startLine, startCol);
@@ -212,6 +256,12 @@ class Lexer {
212
256
  this.scanAtToken(startLine, startCol);
213
257
  return;
214
258
  }
259
+ // f-string literal
260
+ if (char === 'f' && this.peek() === '"') {
261
+ this.advance();
262
+ this.scanFString(startLine, startCol);
263
+ return;
264
+ }
215
265
  // String literal
216
266
  if (char === '"') {
217
267
  this.scanString(startLine, startCol);
@@ -338,6 +388,46 @@ class Lexer {
338
388
  this.advance(); // closing quote
339
389
  this.addToken('string_lit', value, startLine, startCol);
340
390
  }
391
+ scanFString(startLine, startCol) {
392
+ let value = '';
393
+ let interpolationDepth = 0;
394
+ let interpolationString = false;
395
+ while (!this.isAtEnd()) {
396
+ if (interpolationDepth === 0 && this.peek() === '"') {
397
+ break;
398
+ }
399
+ if (this.peek() === '\\' && this.peek(1) === '"') {
400
+ this.advance();
401
+ value += this.advance();
402
+ continue;
403
+ }
404
+ if (interpolationDepth === 0 && this.peek() === '{') {
405
+ value += this.advance();
406
+ interpolationDepth = 1;
407
+ interpolationString = false;
408
+ continue;
409
+ }
410
+ const char = this.advance();
411
+ value += char;
412
+ if (interpolationDepth === 0)
413
+ continue;
414
+ if (char === '"' && this.source[this.pos - 2] !== '\\') {
415
+ interpolationString = !interpolationString;
416
+ continue;
417
+ }
418
+ if (interpolationString)
419
+ continue;
420
+ if (char === '{')
421
+ interpolationDepth++;
422
+ if (char === '}')
423
+ interpolationDepth--;
424
+ }
425
+ if (this.isAtEnd()) {
426
+ this.error('Unterminated f-string', startLine, startCol);
427
+ }
428
+ this.advance(); // closing quote
429
+ this.addToken('f_string', value, startLine, startCol);
430
+ }
341
431
  scanNumber(firstChar, startLine, startCol) {
342
432
  let value = firstChar;
343
433
  // Consume integer part
@@ -5,6 +5,7 @@
5
5
  * Handles control flow, function extraction for foreach, and builtin calls.
6
6
  */
7
7
  import type { IRModule } from '../ir/types';
8
+ import type { SourceRange } from '../compile';
8
9
  import type { Program } from '../ast/types';
9
10
  export interface Warning {
10
11
  message: string;
@@ -14,18 +15,25 @@ export interface Warning {
14
15
  }
15
16
  export declare class Lowering {
16
17
  private namespace;
18
+ private readonly sourceRanges;
17
19
  private functions;
18
20
  private globals;
19
21
  private globalNames;
20
22
  private fnDecls;
23
+ private implMethods;
21
24
  private specializedFunctions;
22
25
  private currentFn;
26
+ private currentStdlibCallSite?;
23
27
  private foreachCounter;
24
28
  private lambdaCounter;
29
+ private timeoutCounter;
30
+ private intervalCounter;
25
31
  readonly warnings: Warning[];
26
32
  private builder;
27
33
  private varMap;
28
34
  private lambdaBindings;
35
+ private intervalBindings;
36
+ private intervalFunctions;
29
37
  private currentCallbackBindings;
30
38
  private currentContext;
31
39
  private blockPosVars;
@@ -37,7 +45,7 @@ export declare class Lowering {
37
45
  private varTypes;
38
46
  private floatVars;
39
47
  private worldObjCounter;
40
- constructor(namespace: string);
48
+ constructor(namespace: string, sourceRanges?: SourceRange[]);
41
49
  lower(program: Program): IRModule;
42
50
  private lowerFn;
43
51
  private getTickRate;
@@ -47,6 +55,7 @@ export declare class Lowering {
47
55
  private lowerLetStmt;
48
56
  private lowerReturnStmt;
49
57
  private lowerIfStmt;
58
+ private lowerIsCheckIfStmt;
50
59
  private lowerWhileStmt;
51
60
  private lowerForStmt;
52
61
  private lowerForRangeStmt;
@@ -67,20 +76,31 @@ export declare class Lowering {
67
76
  private lowerUnaryExpr;
68
77
  private lowerAssignExpr;
69
78
  private lowerCallExpr;
79
+ private lowerStaticCallExpr;
70
80
  private lowerInvokeExpr;
71
81
  private inlineLambdaInvoke;
72
82
  private emitDirectFunctionCall;
83
+ private emitMethodCall;
73
84
  private resolveFunctionRefExpr;
74
85
  private resolveFunctionRefByName;
75
86
  private ensureSpecializedFunction;
87
+ private ensureSpecializedFunctionWithContext;
76
88
  private lowerLambdaExpr;
77
89
  private withSavedFunctionState;
78
90
  private lowerBuiltinCall;
91
+ private lowerSetTimeout;
92
+ private lowerSetInterval;
93
+ private lowerClearInterval;
94
+ private lowerNamedLambdaFunction;
95
+ private lowerIntervalWrapperFunction;
96
+ private resolveIntervalFunctionName;
79
97
  private lowerRichTextBuiltin;
80
98
  private getRichTextArgIndex;
81
99
  private buildRichTextJson;
82
100
  private appendRichTextExpr;
83
101
  private exprToString;
102
+ private exprToEntitySelector;
103
+ private appendTypeFilter;
84
104
  private exprToSnbt;
85
105
  private exprToTargetString;
86
106
  private exprToLiteral;
@@ -88,12 +108,23 @@ export declare class Lowering {
88
108
  private exprToTextComponent;
89
109
  private exprToBoolString;
90
110
  private isTeamTextOption;
111
+ private exprToScoreboardObjective;
112
+ private resolveScoreboardObjective;
113
+ private getObjectiveNamespace;
114
+ private tryGetStdlibInternalObjective;
115
+ private getStdlibInternalResourceBase;
116
+ private getStdlibCallSiteContext;
117
+ private serializeCallSite;
118
+ private shortHash;
119
+ private isStdlibFile;
120
+ private filePathForSpan;
91
121
  private lowerCoordinateBuiltin;
92
122
  private lowerTpCommand;
93
123
  private resolveBlockPosExpr;
94
124
  private getArrayStorageName;
95
125
  private inferLambdaReturnType;
96
126
  private inferExprType;
127
+ private resolveInstanceMethod;
97
128
  private normalizeType;
98
129
  private readArrayElement;
99
130
  private emitRawSubFunction;