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.
- package/.github/ISSUE_TEMPLATE/bug_report.yml +72 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +57 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +17 -25
- package/CHANGELOG.md +58 -0
- package/CONTRIBUTING.md +140 -0
- package/README.md +28 -19
- package/README.zh.md +28 -19
- package/dist/__tests__/cli.test.js +10 -10
- package/dist/__tests__/codegen.test.js +1 -1
- package/dist/__tests__/diagnostics.test.js +5 -5
- package/dist/__tests__/e2e.test.js +146 -5
- package/dist/__tests__/formatter.test.d.ts +1 -0
- package/dist/__tests__/formatter.test.js +40 -0
- package/dist/__tests__/lowering.test.js +36 -3
- package/dist/__tests__/mc-integration.test.js +255 -10
- package/dist/__tests__/mc-syntax.test.js +3 -3
- package/dist/__tests__/nbt.test.js +2 -2
- package/dist/__tests__/optimizer-advanced.test.js +3 -3
- package/dist/__tests__/runtime.test.js +1 -1
- package/dist/ast/types.d.ts +21 -3
- package/dist/cli.js +25 -7
- package/dist/codegen/mcfunction/index.d.ts +1 -1
- package/dist/codegen/mcfunction/index.js +8 -2
- package/dist/codegen/structure/index.js +7 -1
- package/dist/formatter/index.d.ts +1 -0
- package/dist/formatter/index.js +26 -0
- package/dist/ir/builder.d.ts +2 -1
- package/dist/ir/types.d.ts +7 -2
- package/dist/ir/types.js +1 -1
- package/dist/lowering/index.d.ts +2 -0
- package/dist/lowering/index.js +183 -8
- package/dist/mc-test/runner.d.ts +2 -2
- package/dist/mc-test/runner.js +3 -3
- package/dist/mc-test/setup.js +2 -2
- package/dist/parser/index.d.ts +2 -0
- package/dist/parser/index.js +75 -7
- package/docs/COMPILATION_STATS.md +24 -24
- package/docs/IMPLEMENTATION_GUIDE.md +1 -1
- package/docs/STRUCTURE_TARGET.md +1 -1
- package/editors/vscode/.vscodeignore +1 -0
- package/editors/vscode/icons/mcrs.svg +7 -0
- package/editors/vscode/icons/redscript-icons.json +10 -0
- package/editors/vscode/out/extension.js +152 -9
- package/editors/vscode/package.json +10 -3
- package/editors/vscode/src/hover.ts +55 -2
- package/editors/vscode/src/symbols.ts +42 -0
- package/package.json +1 -1
- package/src/__tests__/cli.test.ts +10 -10
- package/src/__tests__/codegen.test.ts +1 -1
- package/src/__tests__/diagnostics.test.ts +5 -5
- package/src/__tests__/e2e.test.ts +134 -5
- package/src/__tests__/lowering.test.ts +48 -3
- package/src/__tests__/mc-integration.test.ts +285 -10
- package/src/__tests__/mc-syntax.test.ts +3 -3
- package/src/__tests__/nbt.test.ts +2 -2
- package/src/__tests__/optimizer-advanced.test.ts +3 -3
- package/src/__tests__/runtime.test.ts +1 -1
- package/src/ast/types.ts +20 -3
- package/src/cli.ts +10 -10
- package/src/codegen/mcfunction/index.ts +9 -2
- package/src/codegen/structure/index.ts +8 -1
- package/src/examples/capture_the_flag.mcrs +208 -0
- package/src/examples/{counter.rs → counter.mcrs} +1 -1
- package/src/examples/hunger_games.mcrs +301 -0
- package/src/examples/new_features_demo.mcrs +193 -0
- package/src/examples/parkour_race.mcrs +233 -0
- package/src/examples/rpg.mcrs +13 -0
- package/src/examples/{shop.rs → shop.mcrs} +1 -1
- package/src/examples/{showcase_game.rs → showcase_game.mcrs} +3 -3
- package/src/examples/{turret.rs → turret.mcrs} +1 -1
- package/src/examples/zombie_survival.mcrs +314 -0
- package/src/ir/builder.ts +3 -1
- package/src/ir/types.ts +8 -2
- package/src/lowering/index.ts +156 -8
- package/src/mc-test/runner.ts +3 -3
- package/src/mc-test/setup.ts +2 -2
- package/src/parser/index.ts +81 -8
- package/src/stdlib/README.md +155 -147
- package/src/stdlib/bossbar.mcrs +68 -0
- package/src/stdlib/{cooldown.rs → cooldown.mcrs} +1 -1
- package/src/stdlib/effects.mcrs +64 -0
- package/src/stdlib/interactions.mcrs +195 -0
- package/src/stdlib/inventory.mcrs +38 -0
- package/src/stdlib/mobs.mcrs +99 -0
- package/src/stdlib/particles.mcrs +52 -0
- package/src/stdlib/sets.mcrs +20 -0
- package/src/stdlib/spawn.mcrs +41 -0
- package/src/stdlib/teams.mcrs +68 -0
- package/src/stdlib/world.mcrs +92 -0
- package/src/examples/rpg.rs +0 -13
- package/src/stdlib/mobs.rs +0 -99
- /package/src/examples/{arena.rs → arena.mcrs} +0 -0
- /package/src/examples/{pvp_arena.rs → pvp_arena.mcrs} +0 -0
- /package/src/examples/{quiz.rs → quiz.mcrs} +0 -0
- /package/src/examples/{stdlib_demo.rs → stdlib_demo.mcrs} +0 -0
- /package/src/examples/{world_manager.rs → world_manager.mcrs} +0 -0
- /package/src/stdlib/{combat.rs → combat.mcrs} +0 -0
- /package/src/stdlib/{math.rs → math.mcrs} +0 -0
- /package/src/stdlib/{player.rs → player.mcrs} +0 -0
- /package/src/stdlib/{strings.rs → strings.mcrs} +0 -0
- /package/src/stdlib/{timer.rs → timer.mcrs} +0 -0
- /package/src/templates/{combat.rs → combat.mcrs} +0 -0
- /package/src/templates/{economy.rs → economy.mcrs} +0 -0
- /package/src/templates/{mini-game-framework.rs → mini-game-framework.mcrs} +0 -0
- /package/src/templates/{quest.rs → quest.mcrs} +0 -0
- /package/src/test_programs/{zombie_game.rs → zombie_game.mcrs} +0 -0
package/dist/mc-test/runner.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* RedScript MC Integration Test Runner
|
|
3
3
|
*
|
|
4
|
-
* Compiles a .
|
|
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.
|
|
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
|
package/dist/mc-test/runner.js
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* RedScript MC Integration Test Runner
|
|
4
4
|
*
|
|
5
|
-
* Compiles a .
|
|
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.
|
|
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.
|
|
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)
|
package/dist/mc-test/setup.js
CHANGED
|
@@ -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}.
|
|
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}.
|
|
74
|
+
console.log(` ⚠ ${ns}.mcrs not found, skipping`);
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
// Inline test fixtures
|
package/dist/parser/index.d.ts
CHANGED
|
@@ -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;
|
package/dist/parser/index.js
CHANGED
|
@@ -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('
|
|
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
|
|
538
|
-
subcommands.push({ kind: 'if_entity',
|
|
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
|
|
546
|
-
subcommands.push({ kind: 'unless_entity',
|
|
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
|
-
|
|
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/*.
|
|
7
|
-
name=$(basename "$example" .
|
|
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.
|
|
17
|
-
| `counter.
|
|
18
|
-
| `pvp_arena.
|
|
19
|
-
| `quiz.
|
|
20
|
-
| `rpg.
|
|
21
|
-
| `shop.
|
|
22
|
-
| `showcase_game.
|
|
23
|
-
| `stdlib_demo.
|
|
24
|
-
| `turret.
|
|
25
|
-
| `world_manager.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
142
|
-
- `turret.
|
|
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`.
|
package/docs/STRUCTURE_TARGET.md
CHANGED
|
@@ -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.
|
|
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:
|
|
@@ -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>
|
|
@@ -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
|
|
1189
|
+
const args3 = this.parseArgs();
|
|
1186
1190
|
this.expect(")");
|
|
1187
|
-
expr = this.withLoc({ kind: "call", fn: internalFn, args: [expr.obj, ...
|
|
1191
|
+
expr = this.withLoc({ kind: "call", fn: internalFn, args: [expr.obj, ...args3] }, this.getLocToken(expr) ?? openParenToken);
|
|
1188
1192
|
continue;
|
|
1189
1193
|
}
|
|
1190
|
-
this.
|
|
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_\\[\\]]*))
|
|
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
|
-
|
|
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
|
+
"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
|
-
".
|
|
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",
|