redscript-mc 1.0.0 → 1.1.0

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 (106) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.yml +72 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.yml +57 -0
  3. package/.github/PULL_REQUEST_TEMPLATE.md +17 -25
  4. package/CHANGELOG.md +58 -0
  5. package/CONTRIBUTING.md +140 -0
  6. package/README.md +28 -19
  7. package/README.zh.md +28 -19
  8. package/dist/__tests__/cli.test.js +10 -10
  9. package/dist/__tests__/codegen.test.js +1 -1
  10. package/dist/__tests__/diagnostics.test.js +5 -5
  11. package/dist/__tests__/e2e.test.js +146 -5
  12. package/dist/__tests__/formatter.test.d.ts +1 -0
  13. package/dist/__tests__/formatter.test.js +40 -0
  14. package/dist/__tests__/lowering.test.js +36 -3
  15. package/dist/__tests__/mc-integration.test.js +255 -10
  16. package/dist/__tests__/mc-syntax.test.js +3 -3
  17. package/dist/__tests__/nbt.test.js +2 -2
  18. package/dist/__tests__/optimizer-advanced.test.js +3 -3
  19. package/dist/__tests__/runtime.test.js +1 -1
  20. package/dist/ast/types.d.ts +21 -3
  21. package/dist/cli.js +25 -7
  22. package/dist/codegen/mcfunction/index.d.ts +1 -1
  23. package/dist/codegen/mcfunction/index.js +8 -2
  24. package/dist/codegen/structure/index.js +7 -1
  25. package/dist/formatter/index.d.ts +1 -0
  26. package/dist/formatter/index.js +26 -0
  27. package/dist/ir/builder.d.ts +2 -1
  28. package/dist/ir/types.d.ts +7 -2
  29. package/dist/ir/types.js +1 -1
  30. package/dist/lowering/index.d.ts +2 -0
  31. package/dist/lowering/index.js +183 -8
  32. package/dist/mc-test/runner.d.ts +2 -2
  33. package/dist/mc-test/runner.js +3 -3
  34. package/dist/mc-test/setup.js +2 -2
  35. package/dist/parser/index.d.ts +2 -0
  36. package/dist/parser/index.js +75 -7
  37. package/docs/COMPILATION_STATS.md +24 -24
  38. package/docs/IMPLEMENTATION_GUIDE.md +1 -1
  39. package/docs/STRUCTURE_TARGET.md +1 -1
  40. package/editors/vscode/.vscodeignore +1 -0
  41. package/editors/vscode/icons/mcrs.svg +7 -0
  42. package/editors/vscode/icons/redscript-icons.json +10 -0
  43. package/editors/vscode/out/extension.js +152 -9
  44. package/editors/vscode/package.json +10 -3
  45. package/editors/vscode/src/hover.ts +55 -2
  46. package/editors/vscode/src/symbols.ts +42 -0
  47. package/package.json +1 -1
  48. package/src/__tests__/cli.test.ts +10 -10
  49. package/src/__tests__/codegen.test.ts +1 -1
  50. package/src/__tests__/diagnostics.test.ts +5 -5
  51. package/src/__tests__/e2e.test.ts +134 -5
  52. package/src/__tests__/lowering.test.ts +48 -3
  53. package/src/__tests__/mc-integration.test.ts +285 -10
  54. package/src/__tests__/mc-syntax.test.ts +3 -3
  55. package/src/__tests__/nbt.test.ts +2 -2
  56. package/src/__tests__/optimizer-advanced.test.ts +3 -3
  57. package/src/__tests__/runtime.test.ts +1 -1
  58. package/src/ast/types.ts +20 -3
  59. package/src/cli.ts +10 -10
  60. package/src/codegen/mcfunction/index.ts +9 -2
  61. package/src/codegen/structure/index.ts +8 -1
  62. package/src/examples/capture_the_flag.mcrs +208 -0
  63. package/src/examples/{counter.rs → counter.mcrs} +1 -1
  64. package/src/examples/hunger_games.mcrs +301 -0
  65. package/src/examples/new_features_demo.mcrs +193 -0
  66. package/src/examples/parkour_race.mcrs +233 -0
  67. package/src/examples/rpg.mcrs +13 -0
  68. package/src/examples/{shop.rs → shop.mcrs} +1 -1
  69. package/src/examples/{showcase_game.rs → showcase_game.mcrs} +3 -3
  70. package/src/examples/{turret.rs → turret.mcrs} +1 -1
  71. package/src/examples/zombie_survival.mcrs +314 -0
  72. package/src/ir/builder.ts +3 -1
  73. package/src/ir/types.ts +8 -2
  74. package/src/lowering/index.ts +156 -8
  75. package/src/mc-test/runner.ts +3 -3
  76. package/src/mc-test/setup.ts +2 -2
  77. package/src/parser/index.ts +81 -8
  78. package/src/stdlib/README.md +155 -147
  79. package/src/stdlib/bossbar.mcrs +68 -0
  80. package/src/stdlib/{cooldown.rs → cooldown.mcrs} +1 -1
  81. package/src/stdlib/effects.mcrs +64 -0
  82. package/src/stdlib/interactions.mcrs +195 -0
  83. package/src/stdlib/inventory.mcrs +38 -0
  84. package/src/stdlib/mobs.mcrs +99 -0
  85. package/src/stdlib/particles.mcrs +52 -0
  86. package/src/stdlib/sets.mcrs +20 -0
  87. package/src/stdlib/spawn.mcrs +41 -0
  88. package/src/stdlib/teams.mcrs +68 -0
  89. package/src/stdlib/world.mcrs +92 -0
  90. package/src/examples/rpg.rs +0 -13
  91. package/src/stdlib/mobs.rs +0 -99
  92. /package/src/examples/{arena.rs → arena.mcrs} +0 -0
  93. /package/src/examples/{pvp_arena.rs → pvp_arena.mcrs} +0 -0
  94. /package/src/examples/{quiz.rs → quiz.mcrs} +0 -0
  95. /package/src/examples/{stdlib_demo.rs → stdlib_demo.mcrs} +0 -0
  96. /package/src/examples/{world_manager.rs → world_manager.mcrs} +0 -0
  97. /package/src/stdlib/{combat.rs → combat.mcrs} +0 -0
  98. /package/src/stdlib/{math.rs → math.mcrs} +0 -0
  99. /package/src/stdlib/{player.rs → player.mcrs} +0 -0
  100. /package/src/stdlib/{strings.rs → strings.mcrs} +0 -0
  101. /package/src/stdlib/{timer.rs → timer.mcrs} +0 -0
  102. /package/src/templates/{combat.rs → combat.mcrs} +0 -0
  103. /package/src/templates/{economy.rs → economy.mcrs} +0 -0
  104. /package/src/templates/{mini-game-framework.rs → mini-game-framework.mcrs} +0 -0
  105. /package/src/templates/{quest.rs → quest.mcrs} +0 -0
  106. /package/src/test_programs/{zombie_game.rs → zombie_game.mcrs} +0 -0
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * RedScript MC Integration Test Runner
3
3
  *
4
- * Compiles a .rs file, installs it to a running Paper server,
4
+ * Compiles a .mcrs file, installs it to a running Paper server,
5
5
  * runs test scenarios, and reports results.
6
6
  *
7
7
  * Usage:
8
- * npx ts-node src/mc-test/runner.ts src/examples/counter.rs
8
+ * npx ts-node src/mc-test/runner.ts src/examples/counter.mcrs
9
9
  *
10
10
  * Requires:
11
11
  * - Paper server running with TestHarnessPlugin
@@ -2,11 +2,11 @@
2
2
  /**
3
3
  * RedScript MC Integration Test Runner
4
4
  *
5
- * Compiles a .rs file, installs it to a running Paper server,
5
+ * Compiles a .mcrs file, installs it to a running Paper server,
6
6
  * runs test scenarios, and reports results.
7
7
  *
8
8
  * Usage:
9
- * npx ts-node src/mc-test/runner.ts src/examples/counter.rs
9
+ * npx ts-node src/mc-test/runner.ts src/examples/counter.mcrs
10
10
  *
11
11
  * Requires:
12
12
  * - Paper server running with TestHarnessPlugin
@@ -115,7 +115,7 @@ async function runMCTests(sourceFile, tests, options = {}) {
115
115
  if (require.main === module) {
116
116
  const sourceFile = process.argv[2];
117
117
  if (!sourceFile) {
118
- console.error('Usage: ts-node runner.ts <source.rs>');
118
+ console.error('Usage: ts-node runner.ts <source.mcrs>');
119
119
  process.exit(1);
120
120
  }
121
121
  // Example test suite (replace with actual tests)
@@ -66,12 +66,12 @@ function main() {
66
66
  // Example files
67
67
  const exampleNamespaces = ['counter', 'world_manager'];
68
68
  for (const ns of exampleNamespaces) {
69
- const file = path.join(EXAMPLES_DIR, `${ns}.rs`);
69
+ const file = path.join(EXAMPLES_DIR, `${ns}.mcrs`);
70
70
  if (fs.existsSync(file)) {
71
71
  writeFixture(fs.readFileSync(file, 'utf-8'), ns);
72
72
  }
73
73
  else {
74
- console.log(` ⚠ ${ns}.rs not found, skipping`);
74
+ console.log(` ⚠ ${ns}.mcrs not found, skipping`);
75
75
  }
76
76
  }
77
77
  // Inline test fixtures
@@ -24,6 +24,7 @@ export declare class Parser {
24
24
  private parseStructDecl;
25
25
  private parseEnumDecl;
26
26
  private parseConstDecl;
27
+ private parseGlobalDecl;
27
28
  private parseFnDecl;
28
29
  private parseDecorators;
29
30
  private parseDecoratorValue;
@@ -68,6 +69,7 @@ export declare class Parser {
68
69
  private parseCoordComponent;
69
70
  private parseSignedCoordOffset;
70
71
  private parseSelector;
72
+ private parseSelectorOrVarSelector;
71
73
  private parseSelectorValue;
72
74
  private parseSelectorFilters;
73
75
  private splitSelectorParams;
@@ -99,6 +99,7 @@ class Parser {
99
99
  // -------------------------------------------------------------------------
100
100
  parse(defaultNamespace = 'redscript') {
101
101
  let namespace = defaultNamespace;
102
+ const globals = [];
102
103
  const declarations = [];
103
104
  const structs = [];
104
105
  const enums = [];
@@ -112,7 +113,10 @@ class Parser {
112
113
  }
113
114
  // Parse struct and function declarations
114
115
  while (!this.check('eof')) {
115
- if (this.check('struct')) {
116
+ if (this.check('let')) {
117
+ globals.push(this.parseGlobalDecl(true));
118
+ }
119
+ else if (this.check('struct')) {
116
120
  structs.push(this.parseStructDecl());
117
121
  }
118
122
  else if (this.check('enum')) {
@@ -125,7 +129,7 @@ class Parser {
125
129
  declarations.push(this.parseFnDecl());
126
130
  }
127
131
  }
128
- return { namespace, declarations, structs, enums, consts };
132
+ return { namespace, globals, declarations, structs, enums, consts };
129
133
  }
130
134
  // -------------------------------------------------------------------------
131
135
  // Struct Declaration
@@ -181,6 +185,16 @@ class Parser {
181
185
  this.match(';');
182
186
  return this.withLoc({ name, type, value }, constToken);
183
187
  }
188
+ parseGlobalDecl(mutable) {
189
+ const token = this.advance(); // consume 'let'
190
+ const name = this.expect('ident').value;
191
+ this.expect(':');
192
+ const type = this.parseType();
193
+ this.expect('=');
194
+ const init = this.parseExpr();
195
+ this.expect(';');
196
+ return this.withLoc({ kind: 'global', name, type, init, mutable }, token);
197
+ }
184
198
  // -------------------------------------------------------------------------
185
199
  // Function Declaration
186
200
  // -------------------------------------------------------------------------
@@ -534,16 +548,16 @@ class Parser {
534
548
  if (this.peek().kind === 'ident' && this.peek().value === 'entity') {
535
549
  this.advance(); // consume 'entity'
536
550
  }
537
- const selector = this.parseSelector();
538
- subcommands.push({ kind: 'if_entity', selector });
551
+ const selectorOrVar = this.parseSelectorOrVarSelector();
552
+ subcommands.push({ kind: 'if_entity', ...selectorOrVar });
539
553
  }
540
554
  else if (this.match('unless')) {
541
555
  // Expect 'entity' keyword (as ident) or just parse selector directly
542
556
  if (this.peek().kind === 'ident' && this.peek().value === 'entity') {
543
557
  this.advance(); // consume 'entity'
544
558
  }
545
- const selector = this.parseSelector();
546
- subcommands.push({ kind: 'unless_entity', selector });
559
+ const selectorOrVar = this.parseSelectorOrVarSelector();
560
+ subcommands.push({ kind: 'unless_entity', ...selectorOrVar });
547
561
  }
548
562
  else if (this.match('in')) {
549
563
  const dim = this.expect('ident').value;
@@ -645,6 +659,10 @@ class Parser {
645
659
  'has_tag': '__entity_has_tag',
646
660
  'push': '__array_push',
647
661
  'pop': '__array_pop',
662
+ 'add': 'set_add',
663
+ 'contains': 'set_contains',
664
+ 'remove': 'set_remove',
665
+ 'clear': 'set_clear',
648
666
  };
649
667
  const internalFn = methodMap[expr.field];
650
668
  if (internalFn) {
@@ -653,7 +671,11 @@ class Parser {
653
671
  expr = this.withLoc({ kind: 'call', fn: internalFn, args: [expr.obj, ...args] }, this.getLocToken(expr) ?? openParenToken);
654
672
  continue;
655
673
  }
656
- this.error(`Unknown method '${expr.field}'`);
674
+ // Generic method sugar: obj.method(args) method(obj, args)
675
+ const args = this.parseArgs();
676
+ this.expect(')');
677
+ expr = this.withLoc({ kind: 'call', fn: expr.field, args: [expr.obj, ...args] }, this.getLocToken(expr) ?? openParenToken);
678
+ continue;
657
679
  }
658
680
  const args = this.parseArgs();
659
681
  this.expect(')');
@@ -1077,6 +1099,37 @@ class Parser {
1077
1099
  const token = this.expect('selector');
1078
1100
  return this.parseSelectorValue(token.value);
1079
1101
  }
1102
+ // Parse either a selector (@a[...]) or a variable with filters (p[...])
1103
+ // Returns { selector } for selectors or { varName, filters } for variables
1104
+ parseSelectorOrVarSelector() {
1105
+ if (this.check('selector')) {
1106
+ return { selector: this.parseSelector() };
1107
+ }
1108
+ // Must be an identifier (variable) possibly with filters
1109
+ const varToken = this.expect('ident');
1110
+ const varName = varToken.value;
1111
+ // Check for optional filters [...]
1112
+ if (this.check('[')) {
1113
+ this.advance(); // consume '['
1114
+ // Collect everything until ']'
1115
+ let filterStr = '';
1116
+ let depth = 1;
1117
+ while (depth > 0 && !this.check('eof')) {
1118
+ if (this.check('['))
1119
+ depth++;
1120
+ else if (this.check(']'))
1121
+ depth--;
1122
+ if (depth > 0) {
1123
+ filterStr += this.peek().value ?? this.peek().kind;
1124
+ this.advance();
1125
+ }
1126
+ }
1127
+ this.expect(']');
1128
+ const filters = this.parseSelectorFilters(filterStr);
1129
+ return { varName, filters };
1130
+ }
1131
+ return { varName };
1132
+ }
1080
1133
  parseSelectorValue(value) {
1081
1134
  // Parse @e[type=zombie, distance=..5]
1082
1135
  const bracketIndex = value.indexOf('[');
@@ -1129,6 +1182,21 @@ class Parser {
1129
1182
  case 'scores':
1130
1183
  filters.scores = this.parseScoresFilter(val);
1131
1184
  break;
1185
+ case 'x':
1186
+ filters.x = this.parseRangeValue(val);
1187
+ break;
1188
+ case 'y':
1189
+ filters.y = this.parseRangeValue(val);
1190
+ break;
1191
+ case 'z':
1192
+ filters.z = this.parseRangeValue(val);
1193
+ break;
1194
+ case 'x_rotation':
1195
+ filters.x_rotation = this.parseRangeValue(val);
1196
+ break;
1197
+ case 'y_rotation':
1198
+ filters.y_rotation = this.parseRangeValue(val);
1199
+ break;
1132
1200
  }
1133
1201
  }
1134
1202
  return filters;
@@ -3,8 +3,8 @@
3
3
  Generated on 2026-03-12 with:
4
4
 
5
5
  ```bash
6
- for example in src/examples/*.rs; do
7
- name=$(basename "$example" .rs)
6
+ for example in src/examples/*.mcrs; do
7
+ name=$(basename "$example" .mcrs)
8
8
  TS_NODE_TRANSPILE_ONLY=1 npx ts-node src/cli.ts compile "$example" --stats -o /tmp/eval/$name 2>&1
9
9
  done
10
10
  ```
@@ -13,20 +13,20 @@ done
13
13
 
14
14
  | Example | Functions | Commands Before | Commands After | Savings |
15
15
  |:--|--:|--:|--:|--:|
16
- | `arena.rs` | 4 | 208 | 52 | 75% |
17
- | `counter.rs` | 1 | 76 | 19 | 75% |
18
- | `pvp_arena.rs` | 15 | 780 | 195 | 75% |
19
- | `quiz.rs` | 7 | 564 | 141 | 75% |
20
- | `rpg.rs` | 10 | 548 | 137 | 75% |
21
- | `shop.rs` | 2 | 160 | 40 | 75% |
22
- | `showcase_game.rs` | 89 | 3360 | 840 | 75% |
23
- | `stdlib_demo.rs` | 15 | 1104 | 276 | 75% |
24
- | `turret.rs` | 5 | 104 | 26 | 75% |
25
- | `world_manager.rs` | 3 | 80 | 20 | 75% |
16
+ | `arena.mcrs` | 4 | 208 | 52 | 75% |
17
+ | `counter.mcrs` | 1 | 76 | 19 | 75% |
18
+ | `pvp_arena.mcrs` | 15 | 780 | 195 | 75% |
19
+ | `quiz.mcrs` | 7 | 564 | 141 | 75% |
20
+ | `rpg.mcrs` | 10 | 548 | 137 | 75% |
21
+ | `shop.mcrs` | 2 | 160 | 40 | 75% |
22
+ | `showcase_game.mcrs` | 89 | 3360 | 840 | 75% |
23
+ | `stdlib_demo.mcrs` | 15 | 1104 | 276 | 75% |
24
+ | `turret.mcrs` | 5 | 104 | 26 | 75% |
25
+ | `world_manager.mcrs` | 3 | 80 | 20 | 75% |
26
26
 
27
27
  ## Per-example Optimizer Stats
28
28
 
29
- ### `arena.rs`
29
+ ### `arena.mcrs`
30
30
 
31
31
  - Functions: `4`
32
32
  - Commands: `208 -> 52`
@@ -37,7 +37,7 @@ done
37
37
  - Dead code removed: `2`
38
38
  - Constant folds: `0`
39
39
 
40
- ### `counter.rs`
40
+ ### `counter.mcrs`
41
41
 
42
42
  - Functions: `1`
43
43
  - Commands: `76 -> 19`
@@ -48,7 +48,7 @@ done
48
48
  - Dead code removed: `0`
49
49
  - Constant folds: `0`
50
50
 
51
- ### `pvp_arena.rs`
51
+ ### `pvp_arena.mcrs`
52
52
 
53
53
  - Functions: `15`
54
54
  - Commands: `780 -> 195`
@@ -59,7 +59,7 @@ done
59
59
  - Dead code removed: `4`
60
60
  - Constant folds: `0`
61
61
 
62
- ### `quiz.rs`
62
+ ### `quiz.mcrs`
63
63
 
64
64
  - Functions: `7`
65
65
  - Commands: `564 -> 141`
@@ -70,7 +70,7 @@ done
70
70
  - Dead code removed: `0`
71
71
  - Constant folds: `0`
72
72
 
73
- ### `rpg.rs`
73
+ ### `rpg.mcrs`
74
74
 
75
75
  - Functions: `10`
76
76
  - Commands: `548 -> 137`
@@ -81,7 +81,7 @@ done
81
81
  - Dead code removed: `3`
82
82
  - Constant folds: `1`
83
83
 
84
- ### `shop.rs`
84
+ ### `shop.mcrs`
85
85
 
86
86
  - Functions: `2`
87
87
  - Commands: `160 -> 40`
@@ -92,7 +92,7 @@ done
92
92
  - Dead code removed: `0`
93
93
  - Constant folds: `0`
94
94
 
95
- ### `showcase_game.rs`
95
+ ### `showcase_game.mcrs`
96
96
 
97
97
  - Functions: `89`
98
98
  - Commands: `3360 -> 840`
@@ -103,7 +103,7 @@ done
103
103
  - Dead code removed: `20`
104
104
  - Constant folds: `1`
105
105
 
106
- ### `stdlib_demo.rs`
106
+ ### `stdlib_demo.mcrs`
107
107
 
108
108
  - Functions: `15`
109
109
  - Commands: `1104 -> 276`
@@ -114,7 +114,7 @@ done
114
114
  - Dead code removed: `11`
115
115
  - Constant folds: `0`
116
116
 
117
- ### `turret.rs`
117
+ ### `turret.mcrs`
118
118
 
119
119
  - Functions: `5`
120
120
  - Commands: `104 -> 26`
@@ -125,7 +125,7 @@ done
125
125
  - Dead code removed: `0`
126
126
  - Constant folds: `0`
127
127
 
128
- ### `world_manager.rs`
128
+ ### `world_manager.mcrs`
129
129
 
130
130
  - Functions: `3`
131
131
  - Commands: `80 -> 20`
@@ -138,5 +138,5 @@ done
138
138
 
139
139
  ## Warnings Observed During Compilation
140
140
 
141
- - `showcase_game.rs`: warnings inherited from imported stdlib helpers using quoted selectors in `player.rs`, plus one auto-qualification warning for unnamespaced `zombie`.
142
- - `turret.rs`: auto-qualification warnings for unnamespaced `armor_stand` and `zombie`.
141
+ - `showcase_game.mcrs`: warnings inherited from imported stdlib helpers using quoted selectors in `player.mcrs`, plus one auto-qualification warning for unnamespaced `zombie`.
142
+ - `turret.mcrs`: auto-qualification warnings for unnamespaced `armor_stand` and `zombie`.
@@ -397,7 +397,7 @@ function rangeToString(r: RangeExpr): string {
397
397
 
398
398
  ### 测试 1:简单函数
399
399
  ```rs
400
- // input: src/test_programs/add.rs
400
+ // input: src/test_programs/add.mcrs
401
401
  fn add(a: int, b: int) -> int {
402
402
  return a + b;
403
403
  }
@@ -7,7 +7,7 @@
7
7
  Compile a source file into a raw structure NBT:
8
8
 
9
9
  ```bash
10
- redscript compile --target structure src/examples/arena.rs -o arena.nbt
10
+ redscript compile --target structure src/examples/arena.mcrs -o arena.nbt
11
11
  ```
12
12
 
13
13
  Then copy the generated file into your datapack's `structures/` folder, for example:
@@ -8,3 +8,4 @@ tsconfig.json
8
8
  package-lock.json
9
9
  *.vsix
10
10
  # out/ is included (bundled extension)
11
+ icons/
@@ -0,0 +1,7 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
2
+ <rect x="1" y="1" width="14" height="14" rx="2" fill="#C75450"/>
3
+ <rect x="3" y="3" width="10" height="10" rx="1" fill="#E57373"/>
4
+ <rect x="5" y="6" width="6" height="4" fill="#FFCDD2"/>
5
+ <rect x="6" y="4" width="4" height="2" fill="#FFCDD2"/>
6
+ <rect x="6" y="10" width="4" height="2" fill="#FFCDD2"/>
7
+ </svg>
@@ -0,0 +1,10 @@
1
+ {
2
+ "iconDefinitions": {
3
+ "_mcrs": {
4
+ "iconPath": "./mcrs.svg"
5
+ }
6
+ },
7
+ "fileExtensions": {
8
+ "mcrs": "_mcrs"
9
+ }
10
+ }
@@ -1178,16 +1178,23 @@ var require_parser = __commonJS({
1178
1178
  "untag": "__entity_untag",
1179
1179
  "has_tag": "__entity_has_tag",
1180
1180
  "push": "__array_push",
1181
- "pop": "__array_pop"
1181
+ "pop": "__array_pop",
1182
+ "add": "set_add",
1183
+ "contains": "set_contains",
1184
+ "remove": "set_remove",
1185
+ "clear": "set_clear"
1182
1186
  };
1183
1187
  const internalFn = methodMap[expr.field];
1184
1188
  if (internalFn) {
1185
- const args2 = this.parseArgs();
1189
+ const args3 = this.parseArgs();
1186
1190
  this.expect(")");
1187
- expr = this.withLoc({ kind: "call", fn: internalFn, args: [expr.obj, ...args2] }, this.getLocToken(expr) ?? openParenToken);
1191
+ expr = this.withLoc({ kind: "call", fn: internalFn, args: [expr.obj, ...args3] }, this.getLocToken(expr) ?? openParenToken);
1188
1192
  continue;
1189
1193
  }
1190
- this.error(`Unknown method '${expr.field}'`);
1194
+ const args2 = this.parseArgs();
1195
+ this.expect(")");
1196
+ expr = this.withLoc({ kind: "call", fn: expr.field, args: [expr.obj, ...args2] }, this.getLocToken(expr) ?? openParenToken);
1197
+ continue;
1191
1198
  }
1192
1199
  const args = this.parseArgs();
1193
1200
  this.expect(")");
@@ -2397,7 +2404,7 @@ var require_lowering = __commonJS({
2397
2404
  subtitle: ([sel, msg]) => `title ${sel} subtitle {"text":"${msg}"}`,
2398
2405
  title_times: ([sel, fadeIn, stay, fadeOut]) => `title ${sel} times ${fadeIn} ${stay} ${fadeOut}`,
2399
2406
  announce: ([msg]) => `tellraw @a {"text":"${msg}"}`,
2400
- give: ([sel, item, count]) => `give ${sel} ${item} ${count ?? "1"}`,
2407
+ give: ([sel, item, count, nbt]) => nbt ? `give ${sel} ${item}${nbt} ${count ?? "1"}` : `give ${sel} ${item} ${count ?? "1"}`,
2401
2408
  kill: ([sel]) => `kill ${sel ?? "@s"}`,
2402
2409
  effect: ([sel, eff, dur, amp]) => `effect give ${sel} ${eff} ${dur ?? "30"} ${amp ?? "0"}`,
2403
2410
  summon: ([type, x, y, z, nbt]) => {
@@ -2475,8 +2482,18 @@ var require_lowering = __commonJS({
2475
2482
  // Special handling
2476
2483
  team_option: () => null,
2477
2484
  // Special handling
2478
- data_get: () => null
2485
+ data_get: () => null,
2479
2486
  // Special handling (returns value from NBT)
2487
+ set_new: () => null,
2488
+ // Special handling (returns set ID)
2489
+ set_add: () => null,
2490
+ // Special handling
2491
+ set_contains: () => null,
2492
+ // Special handling (returns 1/0)
2493
+ set_remove: () => null,
2494
+ // Special handling
2495
+ set_clear: () => null
2496
+ // Special handling
2480
2497
  };
2481
2498
  function getSpan(node) {
2482
2499
  return node?.span;
@@ -2774,6 +2791,12 @@ var require_lowering = __commonJS({
2774
2791
  }
2775
2792
  return;
2776
2793
  }
2794
+ if (stmt.init.kind === "call" && stmt.init.fn === "set_new") {
2795
+ const setId = `__set_${this.foreachCounter++}`;
2796
+ this.builder.emitRaw(`data modify storage rs:sets ${setId} set value []`);
2797
+ this.stringValues.set(stmt.name, setId);
2798
+ return;
2799
+ }
2777
2800
  if (stmt.init.kind === "call" && stmt.init.fn === "spawn_object") {
2778
2801
  const value2 = this.lowerExpr(stmt.init);
2779
2802
  if (value2.kind === "var" && value2.name.startsWith("@e[tag=__rs_obj_")) {
@@ -3735,6 +3758,35 @@ var require_lowering = __commonJS({
3735
3758
  this.builder.emitRaw(`execute store result score ${dst} rs run data get ${targetType} ${target} ${path} ${scale}`);
3736
3759
  return { kind: "var", name: dst };
3737
3760
  }
3761
+ if (name === "set_new") {
3762
+ const setId = `__set_${this.foreachCounter++}`;
3763
+ this.builder.emitRaw(`data modify storage rs:sets ${setId} set value []`);
3764
+ return { kind: "const", value: 0 };
3765
+ }
3766
+ if (name === "set_add") {
3767
+ const setId = this.exprToString(args[0]);
3768
+ const value = this.exprToString(args[1]);
3769
+ this.builder.emitRaw(`execute unless data storage rs:sets ${setId}[{value:${value}}] run data modify storage rs:sets ${setId} append value {value:${value}}`);
3770
+ return { kind: "const", value: 0 };
3771
+ }
3772
+ if (name === "set_contains") {
3773
+ const dst = this.builder.freshTemp();
3774
+ const setId = this.exprToString(args[0]);
3775
+ const value = this.exprToString(args[1]);
3776
+ this.builder.emitRaw(`execute store result score ${dst} rs if data storage rs:sets ${setId}[{value:${value}}]`);
3777
+ return { kind: "var", name: dst };
3778
+ }
3779
+ if (name === "set_remove") {
3780
+ const setId = this.exprToString(args[0]);
3781
+ const value = this.exprToString(args[1]);
3782
+ this.builder.emitRaw(`data remove storage rs:sets ${setId}[{value:${value}}]`);
3783
+ return { kind: "const", value: 0 };
3784
+ }
3785
+ if (name === "set_clear") {
3786
+ const setId = this.exprToString(args[0]);
3787
+ this.builder.emitRaw(`data modify storage rs:sets ${setId} set value []`);
3788
+ return { kind: "const", value: 0 };
3789
+ }
3738
3790
  const coordCommand = this.lowerCoordinateBuiltin(name, args);
3739
3791
  if (coordCommand) {
3740
3792
  this.builder.emitRaw(coordCommand);
@@ -3759,7 +3811,7 @@ var require_lowering = __commonJS({
3759
3811
  }
3760
3812
  return { kind: "const", value: 0 };
3761
3813
  }
3762
- const strArgs = args.map((arg) => this.exprToString(arg));
3814
+ const strArgs = args.map((arg) => arg.kind === "struct_lit" || arg.kind === "array_lit" ? this.exprToSnbt(arg) : this.exprToString(arg));
3763
3815
  const cmd = BUILTINS2[name](strArgs);
3764
3816
  if (cmd) {
3765
3817
  this.builder.emitRaw(cmd);
@@ -3913,6 +3965,36 @@ var require_lowering = __commonJS({
3913
3965
  return this.operandToVar(op);
3914
3966
  }
3915
3967
  }
3968
+ exprToSnbt(expr) {
3969
+ switch (expr.kind) {
3970
+ case "struct_lit": {
3971
+ const entries = expr.fields.map((f) => `${f.name}:${this.exprToSnbt(f.value)}`);
3972
+ return `{${entries.join(",")}}`;
3973
+ }
3974
+ case "array_lit": {
3975
+ const items = expr.elements.map((e) => this.exprToSnbt(e));
3976
+ return `[${items.join(",")}]`;
3977
+ }
3978
+ case "str_lit":
3979
+ return `"${expr.value}"`;
3980
+ case "int_lit":
3981
+ return String(expr.value);
3982
+ case "float_lit":
3983
+ return String(expr.value);
3984
+ case "byte_lit":
3985
+ return `${expr.value}b`;
3986
+ case "short_lit":
3987
+ return `${expr.value}s`;
3988
+ case "long_lit":
3989
+ return `${expr.value}L`;
3990
+ case "double_lit":
3991
+ return `${expr.value}d`;
3992
+ case "bool_lit":
3993
+ return expr.value ? "1b" : "0b";
3994
+ default:
3995
+ return this.exprToString(expr);
3996
+ }
3997
+ }
3916
3998
  exprToTargetString(expr) {
3917
3999
  if (expr.kind === "selector") {
3918
4000
  return this.selectorToString(expr.sel);
@@ -6495,16 +6577,52 @@ function findFnDeclLine(document, name) {
6495
6577
  }
6496
6578
  function findFnSignature(document, name) {
6497
6579
  const text = document.getText();
6498
- const re = new RegExp(`\\bfn\\s+${escapeRe(name)}\\s*\\(([^)]*)\\)(?:\\s*->\\s*([A-Za-z_][A-Za-z0-9_\\[\\]]*))?`, "m");
6580
+ const re = new RegExp(`\\bfn\\s+${escapeRe(name)}\\s*\\(([^)]*)\\)(?:\\s*->\\s*([A-Za-z_][A-Za-z0-9_\\[\\]]*))?\\s*\\{`, "m");
6499
6581
  const match = re.exec(text);
6500
6582
  if (!match) return null;
6501
6583
  const params = match[1].trim();
6502
- const returnType = match[2];
6584
+ let returnType = match[2];
6585
+ if (!returnType) {
6586
+ returnType = inferReturnType(text, match.index + match[0].length);
6587
+ }
6503
6588
  if (returnType) {
6504
6589
  return `fn ${name}(${params}) -> ${returnType}`;
6505
6590
  }
6506
6591
  return `fn ${name}(${params})`;
6507
6592
  }
6593
+ function inferReturnType(text, bodyStart) {
6594
+ let braceCount = 1;
6595
+ let pos = bodyStart;
6596
+ while (pos < text.length && braceCount > 0) {
6597
+ if (text[pos] === "{") braceCount++;
6598
+ else if (text[pos] === "}") braceCount--;
6599
+ pos++;
6600
+ }
6601
+ const body = text.slice(bodyStart, pos - 1);
6602
+ const returnMatch = body.match(/\breturn\s+(.+?);/);
6603
+ if (!returnMatch) return null;
6604
+ const returnExpr = returnMatch[1].trim();
6605
+ if (/^\d+$/.test(returnExpr)) return "int";
6606
+ if (/^\d+\.\d+$/.test(returnExpr)) return "float";
6607
+ if (/^\d+[bB]$/.test(returnExpr)) return "byte";
6608
+ if (/^\d+[sS]$/.test(returnExpr)) return "short";
6609
+ if (/^\d+[lL]$/.test(returnExpr)) return "long";
6610
+ if (/^\d+(\.\d+)?[dD]$/.test(returnExpr)) return "double";
6611
+ if (/^".*"$/.test(returnExpr)) return "string";
6612
+ if (/^(true|false)$/.test(returnExpr)) return "bool";
6613
+ if (/^@[aeprs]/.test(returnExpr)) return "selector";
6614
+ if (/^\{/.test(returnExpr)) return "struct";
6615
+ if (/^\[/.test(returnExpr)) return "array";
6616
+ const callMatch = returnExpr.match(/^(\w+)\s*\(/);
6617
+ if (callMatch) {
6618
+ const fnName = callMatch[1];
6619
+ if (["scoreboard_get", "score", "random", "random_native", "str_len", "len", "data_get", "bossbar_get_value", "set_contains"].includes(fnName)) {
6620
+ return "int";
6621
+ }
6622
+ if (fnName === "set_new") return "string";
6623
+ }
6624
+ return null;
6625
+ }
6508
6626
  function escapeRe(s) {
6509
6627
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6510
6628
  }
@@ -6978,6 +7096,23 @@ function isStructLiteralField(doc, position, word) {
6978
7096
  if (fnMatch) return fnMatch[1];
6979
7097
  return null;
6980
7098
  }
7099
+ function isMemberAccessField(doc, position, word) {
7100
+ const line = doc.lineAt(position.line).text;
7101
+ const wordStart = position.character;
7102
+ const beforeWord = line.slice(0, wordStart);
7103
+ if (!beforeWord.endsWith(".")) return null;
7104
+ const varMatch = beforeWord.match(/(\w+)\s*\.$/);
7105
+ if (!varMatch) return null;
7106
+ const varName = varMatch[1];
7107
+ const text = doc.getText();
7108
+ const typeRe = new RegExp(`\\b(?:let|const)\\s+${varName}\\s*:\\s*(\\w+)`, "m");
7109
+ const typeMatch = text.match(typeRe);
7110
+ if (typeMatch) return typeMatch[1];
7111
+ const paramRe = new RegExp(`\\((?:[^)]*,\\s*)?${varName}\\s*:\\s*(\\w+)`, "m");
7112
+ const paramMatch = text.match(paramRe);
7113
+ if (paramMatch) return paramMatch[1];
7114
+ return null;
7115
+ }
6981
7116
  function findAllOccurrences(doc, word) {
6982
7117
  const text = doc.getText();
6983
7118
  const re = new RegExp(`\\b${escapeRegex(word)}\\b`, "g");
@@ -7010,6 +7145,14 @@ function registerSymbolProviders(context) {
7010
7145
  return new vscode4.Location(doc.uri, field.fieldRange);
7011
7146
  }
7012
7147
  }
7148
+ const memberAccess = isMemberAccessField(doc, position, word);
7149
+ if (memberAccess) {
7150
+ const structFields = findStructFields(doc);
7151
+ const field = structFields.find((f) => f.structName === memberAccess && f.fieldName === word);
7152
+ if (field) {
7153
+ return new vscode4.Location(doc.uri, field.fieldRange);
7154
+ }
7155
+ }
7013
7156
  const decls = findDeclarations(doc);
7014
7157
  const decl = decls.find((d) => d.name === word);
7015
7158
  if (!decl) return null;
@@ -2,7 +2,7 @@
2
2
  "name": "redscript-vscode",
3
3
  "displayName": "RedScript for Minecraft",
4
4
  "description": "Syntax highlighting, error diagnostics, and language support for RedScript — a compiler targeting Minecraft Java Edition",
5
- "version": "0.5.0",
5
+ "version": "0.8.2",
6
6
  "publisher": "bkmashiro",
7
7
  "icon": "icon.png",
8
8
  "license": "MIT",
@@ -36,7 +36,7 @@
36
36
  "redscript"
37
37
  ],
38
38
  "extensions": [
39
- ".rs"
39
+ ".mcrs"
40
40
  ],
41
41
  "configuration": "./redscript-language-configuration.json"
42
42
  },
@@ -116,7 +116,14 @@
116
116
  }
117
117
  ]
118
118
  }
119
- }
119
+ },
120
+ "iconThemes": [
121
+ {
122
+ "id": "redscript-icons",
123
+ "label": "RedScript Icons",
124
+ "path": "./icons/redscript-icons.json"
125
+ }
126
+ ]
120
127
  },
121
128
  "scripts": {
122
129
  "compile": "node build.mjs",