redscript-mc 1.2.10 → 1.2.12
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/dist/__tests__/dce.test.js +4 -3
- package/dist/__tests__/e2e.test.js +2 -2
- package/dist/__tests__/lowering.test.js +3 -3
- package/dist/__tests__/mc-integration.test.js +101 -16
- package/dist/ast/types.d.ts +78 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lowering/index.d.ts +1 -0
- package/dist/lowering/index.js +123 -18
- package/dist/optimizer/dce.js +5 -2
- package/dist/parser/index.d.ts +4 -0
- package/dist/parser/index.js +159 -18
- package/package.json +1 -1
- package/src/__tests__/dce.test.ts +4 -3
- package/src/__tests__/e2e.test.ts +2 -2
- package/src/__tests__/fixtures/array-test.mcrs +30 -0
- package/src/__tests__/fixtures/break-continue-test.mcrs +46 -0
- package/src/__tests__/fixtures/enum-test.mcrs +37 -0
- package/src/__tests__/fixtures/foreach-at-test.mcrs +33 -0
- package/src/__tests__/fixtures/is-check-test.mcrs +20 -13
- package/src/__tests__/fixtures/match-range-test.mcrs +45 -0
- package/src/__tests__/fixtures/struct-test.mcrs +34 -0
- package/src/__tests__/lowering.test.ts +3 -3
- package/src/__tests__/mc-integration.test.ts +114 -16
- package/src/ast/types.ts +22 -1
- package/src/index.ts +1 -1
- package/src/lowering/index.ts +123 -18
- package/src/optimizer/dce.ts +5 -2
- package/src/parser/index.ts +145 -18
package/dist/parser/index.js
CHANGED
|
@@ -546,11 +546,11 @@ class Parser {
|
|
|
546
546
|
this.expect('in');
|
|
547
547
|
const iterable = this.parseExpr();
|
|
548
548
|
this.expect(')');
|
|
549
|
-
// Parse optional execute context modifiers (at, positioned, rotated, facing, etc.)
|
|
549
|
+
// Parse optional execute context modifiers (as, at, positioned, rotated, facing, etc.)
|
|
550
550
|
let executeContext;
|
|
551
|
-
// Check for
|
|
552
|
-
const execIdentKeywords = ['positioned', 'rotated', 'facing', 'anchored', 'align'];
|
|
553
|
-
if (this.check('at') || this.check('in') || (this.check('ident') && execIdentKeywords.includes(this.peek().value))) {
|
|
551
|
+
// Check for execute subcommand keywords
|
|
552
|
+
const execIdentKeywords = ['positioned', 'rotated', 'facing', 'anchored', 'align', 'on', 'summon'];
|
|
553
|
+
if (this.check('as') || this.check('at') || this.check('in') || (this.check('ident') && execIdentKeywords.includes(this.peek().value))) {
|
|
554
554
|
// Collect everything until we hit '{'
|
|
555
555
|
let context = '';
|
|
556
556
|
while (!this.check('{') && !this.check('eof')) {
|
|
@@ -615,34 +615,175 @@ class Parser {
|
|
|
615
615
|
const selector = this.parseSelector();
|
|
616
616
|
subcommands.push({ kind: 'at', selector });
|
|
617
617
|
}
|
|
618
|
-
else if (this.
|
|
619
|
-
|
|
620
|
-
if (this.
|
|
621
|
-
this.
|
|
618
|
+
else if (this.checkIdent('positioned')) {
|
|
619
|
+
this.advance();
|
|
620
|
+
if (this.match('as')) {
|
|
621
|
+
const selector = this.parseSelector();
|
|
622
|
+
subcommands.push({ kind: 'positioned_as', selector });
|
|
623
|
+
}
|
|
624
|
+
else {
|
|
625
|
+
const x = this.parseCoordToken();
|
|
626
|
+
const y = this.parseCoordToken();
|
|
627
|
+
const z = this.parseCoordToken();
|
|
628
|
+
subcommands.push({ kind: 'positioned', x, y, z });
|
|
622
629
|
}
|
|
623
|
-
const selectorOrVar = this.parseSelectorOrVarSelector();
|
|
624
|
-
subcommands.push({ kind: 'if_entity', ...selectorOrVar });
|
|
625
630
|
}
|
|
626
|
-
else if (this.
|
|
627
|
-
|
|
628
|
-
if (this.
|
|
629
|
-
this.
|
|
631
|
+
else if (this.checkIdent('rotated')) {
|
|
632
|
+
this.advance();
|
|
633
|
+
if (this.match('as')) {
|
|
634
|
+
const selector = this.parseSelector();
|
|
635
|
+
subcommands.push({ kind: 'rotated_as', selector });
|
|
636
|
+
}
|
|
637
|
+
else {
|
|
638
|
+
const yaw = this.parseCoordToken();
|
|
639
|
+
const pitch = this.parseCoordToken();
|
|
640
|
+
subcommands.push({ kind: 'rotated', yaw, pitch });
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
else if (this.checkIdent('facing')) {
|
|
644
|
+
this.advance();
|
|
645
|
+
if (this.checkIdent('entity')) {
|
|
646
|
+
this.advance();
|
|
647
|
+
const selector = this.parseSelector();
|
|
648
|
+
const anchor = this.checkIdent('eyes') || this.checkIdent('feet') ? this.advance().value : 'feet';
|
|
649
|
+
subcommands.push({ kind: 'facing_entity', selector, anchor });
|
|
650
|
+
}
|
|
651
|
+
else {
|
|
652
|
+
const x = this.parseCoordToken();
|
|
653
|
+
const y = this.parseCoordToken();
|
|
654
|
+
const z = this.parseCoordToken();
|
|
655
|
+
subcommands.push({ kind: 'facing', x, y, z });
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
else if (this.checkIdent('anchored')) {
|
|
659
|
+
this.advance();
|
|
660
|
+
const anchor = this.advance().value;
|
|
661
|
+
subcommands.push({ kind: 'anchored', anchor });
|
|
662
|
+
}
|
|
663
|
+
else if (this.checkIdent('align')) {
|
|
664
|
+
this.advance();
|
|
665
|
+
const axes = this.advance().value;
|
|
666
|
+
subcommands.push({ kind: 'align', axes });
|
|
667
|
+
}
|
|
668
|
+
else if (this.checkIdent('on')) {
|
|
669
|
+
this.advance();
|
|
670
|
+
const relation = this.advance().value;
|
|
671
|
+
subcommands.push({ kind: 'on', relation });
|
|
672
|
+
}
|
|
673
|
+
else if (this.checkIdent('summon')) {
|
|
674
|
+
this.advance();
|
|
675
|
+
const entity = this.advance().value;
|
|
676
|
+
subcommands.push({ kind: 'summon', entity });
|
|
677
|
+
}
|
|
678
|
+
else if (this.checkIdent('store')) {
|
|
679
|
+
this.advance();
|
|
680
|
+
const storeType = this.advance().value; // 'result' or 'success'
|
|
681
|
+
if (this.checkIdent('score')) {
|
|
682
|
+
this.advance();
|
|
683
|
+
const target = this.advance().value;
|
|
684
|
+
const targetObj = this.advance().value;
|
|
685
|
+
if (storeType === 'result') {
|
|
686
|
+
subcommands.push({ kind: 'store_result', target, targetObj });
|
|
687
|
+
}
|
|
688
|
+
else {
|
|
689
|
+
subcommands.push({ kind: 'store_success', target, targetObj });
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
else {
|
|
693
|
+
this.error('store currently only supports score target');
|
|
630
694
|
}
|
|
631
|
-
|
|
632
|
-
|
|
695
|
+
}
|
|
696
|
+
else if (this.match('if')) {
|
|
697
|
+
this.parseExecuteCondition(subcommands, 'if');
|
|
698
|
+
}
|
|
699
|
+
else if (this.match('unless')) {
|
|
700
|
+
this.parseExecuteCondition(subcommands, 'unless');
|
|
633
701
|
}
|
|
634
702
|
else if (this.match('in')) {
|
|
635
|
-
|
|
703
|
+
// Dimension can be namespaced: minecraft:the_nether
|
|
704
|
+
let dim = this.advance().value;
|
|
705
|
+
if (this.match(':')) {
|
|
706
|
+
dim += ':' + this.advance().value;
|
|
707
|
+
}
|
|
636
708
|
subcommands.push({ kind: 'in', dimension: dim });
|
|
637
709
|
}
|
|
638
710
|
else {
|
|
639
|
-
this.error(`Unexpected token in execute statement: ${this.peek().kind}`);
|
|
711
|
+
this.error(`Unexpected token in execute statement: ${this.peek().kind} (${this.peek().value})`);
|
|
640
712
|
}
|
|
641
713
|
}
|
|
642
714
|
this.expect('run');
|
|
643
715
|
const body = this.parseBlock();
|
|
644
716
|
return this.withLoc({ kind: 'execute', subcommands, body }, executeToken);
|
|
645
717
|
}
|
|
718
|
+
parseExecuteCondition(subcommands, type) {
|
|
719
|
+
if (this.checkIdent('entity') || this.check('selector')) {
|
|
720
|
+
if (this.checkIdent('entity'))
|
|
721
|
+
this.advance();
|
|
722
|
+
const selectorOrVar = this.parseSelectorOrVarSelector();
|
|
723
|
+
subcommands.push({ kind: type === 'if' ? 'if_entity' : 'unless_entity', ...selectorOrVar });
|
|
724
|
+
}
|
|
725
|
+
else if (this.checkIdent('block')) {
|
|
726
|
+
this.advance();
|
|
727
|
+
const x = this.parseCoordToken();
|
|
728
|
+
const y = this.parseCoordToken();
|
|
729
|
+
const z = this.parseCoordToken();
|
|
730
|
+
const block = this.parseBlockId();
|
|
731
|
+
subcommands.push({ kind: type === 'if' ? 'if_block' : 'unless_block', pos: [x, y, z], block });
|
|
732
|
+
}
|
|
733
|
+
else if (this.checkIdent('score')) {
|
|
734
|
+
this.advance();
|
|
735
|
+
const target = this.advance().value;
|
|
736
|
+
const targetObj = this.advance().value;
|
|
737
|
+
// Check for range or comparison
|
|
738
|
+
if (this.checkIdent('matches')) {
|
|
739
|
+
this.advance();
|
|
740
|
+
const range = this.advance().value;
|
|
741
|
+
subcommands.push({ kind: type === 'if' ? 'if_score_range' : 'unless_score_range', target, targetObj, range });
|
|
742
|
+
}
|
|
743
|
+
else {
|
|
744
|
+
const op = this.advance().value; // <, <=, =, >=, >
|
|
745
|
+
const source = this.advance().value;
|
|
746
|
+
const sourceObj = this.advance().value;
|
|
747
|
+
subcommands.push({
|
|
748
|
+
kind: type === 'if' ? 'if_score' : 'unless_score',
|
|
749
|
+
target, targetObj, op, source, sourceObj
|
|
750
|
+
});
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
else {
|
|
754
|
+
this.error(`Unknown condition type after ${type}`);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
parseCoordToken() {
|
|
758
|
+
// Handle ~, ^, numbers, relative coords like ~5, ^-3
|
|
759
|
+
const token = this.peek();
|
|
760
|
+
if (token.kind === 'rel_coord' || token.kind === 'local_coord' ||
|
|
761
|
+
token.kind === 'int_lit' || token.kind === 'float_lit' ||
|
|
762
|
+
token.kind === '-' || token.kind === 'ident') {
|
|
763
|
+
return this.advance().value;
|
|
764
|
+
}
|
|
765
|
+
this.error(`Expected coordinate, got ${token.kind}`);
|
|
766
|
+
return '~';
|
|
767
|
+
}
|
|
768
|
+
parseBlockId() {
|
|
769
|
+
// Parse block ID like minecraft:stone or stone
|
|
770
|
+
let id = this.advance().value;
|
|
771
|
+
if (this.match(':')) {
|
|
772
|
+
id += ':' + this.advance().value;
|
|
773
|
+
}
|
|
774
|
+
// Handle block states [facing=north]
|
|
775
|
+
if (this.check('[')) {
|
|
776
|
+
id += this.advance().value; // [
|
|
777
|
+
while (!this.check(']') && !this.check('eof')) {
|
|
778
|
+
id += this.advance().value;
|
|
779
|
+
}
|
|
780
|
+
id += this.advance().value; // ]
|
|
781
|
+
}
|
|
782
|
+
return id;
|
|
783
|
+
}
|
|
784
|
+
checkIdent(value) {
|
|
785
|
+
return this.check('ident') && this.peek().value === value;
|
|
786
|
+
}
|
|
646
787
|
parseExprStmt() {
|
|
647
788
|
const expr = this.parseExpr();
|
|
648
789
|
this.expect(';');
|
package/package.json
CHANGED
|
@@ -14,17 +14,18 @@ function getFileContent(files: ReturnType<typeof compile>['files'], suffix: stri
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
describe('AST dead code elimination', () => {
|
|
17
|
-
it('removes unused functions
|
|
17
|
+
it('removes private unused functions (prefixed with _)', () => {
|
|
18
18
|
const source = `
|
|
19
|
-
fn
|
|
19
|
+
fn _unused() { say("never called"); }
|
|
20
20
|
fn used() { say("called"); }
|
|
21
21
|
@tick fn main() { used(); }
|
|
22
22
|
`
|
|
23
23
|
|
|
24
24
|
const result = compile(source, { namespace: 'test' })
|
|
25
25
|
|
|
26
|
+
// _unused is removed because it starts with _ (private) and is not called
|
|
26
27
|
expect(result.ast.declarations.map(fn => fn.name)).toEqual(['used', 'main'])
|
|
27
|
-
expect(result.ir.functions.some(fn => fn.name === '
|
|
28
|
+
expect(result.ir.functions.some(fn => fn.name === '_unused')).toBe(false)
|
|
28
29
|
})
|
|
29
30
|
|
|
30
31
|
it('removes unused local variables from the AST body', () => {
|
|
@@ -196,8 +196,8 @@ fn main() {
|
|
|
196
196
|
const thenFiles = files.filter(file => file.path.includes('/main/then_') && file.content.includes('kill @s'))
|
|
197
197
|
|
|
198
198
|
expect(mainFn).toContain('execute as @e run function test:main/foreach_0')
|
|
199
|
-
expect(foreachFn).toContain('execute if entity @s[type=player] run function test:main/then_')
|
|
200
|
-
expect(foreachFn).toContain('execute if entity @s[type=zombie] run function test:main/then_')
|
|
199
|
+
expect(foreachFn).toContain('execute if entity @s[type=minecraft:player] run function test:main/then_')
|
|
200
|
+
expect(foreachFn).toContain('execute if entity @s[type=minecraft:zombie] run function test:main/then_')
|
|
201
201
|
expect(thenFiles).toHaveLength(2)
|
|
202
202
|
})
|
|
203
203
|
})
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Array operations test
|
|
2
|
+
|
|
3
|
+
@tick fn test_array() {
|
|
4
|
+
// Array initialization
|
|
5
|
+
let nums: int[] = [10, 20, 30, 40, 50];
|
|
6
|
+
|
|
7
|
+
// Array access
|
|
8
|
+
scoreboard_set("#arr_0", #rs, nums[0]);
|
|
9
|
+
scoreboard_set("#arr_2", #rs, nums[2]);
|
|
10
|
+
scoreboard_set("#arr_4", #rs, nums[4]);
|
|
11
|
+
|
|
12
|
+
// Array length via .len property
|
|
13
|
+
scoreboard_set("#arr_len", #rs, nums.len);
|
|
14
|
+
|
|
15
|
+
// Sum via foreach
|
|
16
|
+
let sum: int = 0;
|
|
17
|
+
foreach (n in nums) {
|
|
18
|
+
sum = sum + n;
|
|
19
|
+
}
|
|
20
|
+
scoreboard_set("#arr_sum", #rs, sum);
|
|
21
|
+
|
|
22
|
+
// Push operation
|
|
23
|
+
let arr2: int[] = [1, 2, 3];
|
|
24
|
+
arr2.push(4);
|
|
25
|
+
scoreboard_set("#arr_push", #rs, arr2.len);
|
|
26
|
+
|
|
27
|
+
// Pop operation
|
|
28
|
+
let popped: int = arr2.pop();
|
|
29
|
+
scoreboard_set("#arr_pop", #rs, popped);
|
|
30
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Break and continue statements test
|
|
2
|
+
|
|
3
|
+
fn test_break_continue() {
|
|
4
|
+
// Test break - should stop at i=5
|
|
5
|
+
let break_at: int = -1;
|
|
6
|
+
for (let i: int = 0; i < 10; i = i + 1) {
|
|
7
|
+
if (i == 5) {
|
|
8
|
+
break_at = i;
|
|
9
|
+
break;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
scoreboard_set("#break_at", #rs, break_at);
|
|
13
|
+
|
|
14
|
+
// Test continue - sum only even numbers
|
|
15
|
+
let sum_evens: int = 0;
|
|
16
|
+
for (let i: int = 0; i < 10; i = i + 1) {
|
|
17
|
+
if (i % 2 != 0) {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
sum_evens = sum_evens + i;
|
|
21
|
+
}
|
|
22
|
+
// 0+2+4+6+8 = 20
|
|
23
|
+
scoreboard_set("#sum_evens", #rs, sum_evens);
|
|
24
|
+
|
|
25
|
+
// While with break
|
|
26
|
+
let count: int = 0;
|
|
27
|
+
while (true) {
|
|
28
|
+
count = count + 1;
|
|
29
|
+
if (count >= 7) {
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
scoreboard_set("#while_break", #rs, count);
|
|
34
|
+
|
|
35
|
+
// Nested loop break (breaks inner only)
|
|
36
|
+
let outer_count: int = 0;
|
|
37
|
+
for (let a: int = 0; a < 3; a = a + 1) {
|
|
38
|
+
for (let b: int = 0; b < 10; b = b + 1) {
|
|
39
|
+
if (b == 2) {
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
outer_count = outer_count + 1;
|
|
44
|
+
}
|
|
45
|
+
scoreboard_set("#nested_break", #rs, outer_count);
|
|
46
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Enum definition and matching test
|
|
2
|
+
|
|
3
|
+
enum GamePhase {
|
|
4
|
+
Lobby, // 0
|
|
5
|
+
Starting, // 1
|
|
6
|
+
Playing, // 2
|
|
7
|
+
Ended // 3
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
enum Rank {
|
|
11
|
+
Bronze = 1,
|
|
12
|
+
Silver = 2,
|
|
13
|
+
Gold = 3,
|
|
14
|
+
Diamond = 10
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
fn test_enum() {
|
|
18
|
+
// Basic enum value (use . not ::)
|
|
19
|
+
let phase: int = GamePhase.Playing;
|
|
20
|
+
scoreboard_set("#enum_phase", #rs, phase);
|
|
21
|
+
|
|
22
|
+
// Match on enum
|
|
23
|
+
match (phase) {
|
|
24
|
+
GamePhase.Lobby => { scoreboard_set("#enum_match", #rs, 0); }
|
|
25
|
+
GamePhase.Playing => { scoreboard_set("#enum_match", #rs, 2); }
|
|
26
|
+
_ => { scoreboard_set("#enum_match", #rs, -1); }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Custom values
|
|
30
|
+
let rank: int = Rank.Diamond;
|
|
31
|
+
scoreboard_set("#enum_rank", #rs, rank);
|
|
32
|
+
|
|
33
|
+
// Comparison
|
|
34
|
+
if (rank > Rank.Gold) {
|
|
35
|
+
scoreboard_set("#enum_high", #rs, 1);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// Foreach with execute context modifiers test
|
|
2
|
+
|
|
3
|
+
@load fn setup() {
|
|
4
|
+
scoreboard_add("rs");
|
|
5
|
+
scoreboard_set("#foreach_count", #rs, 0);
|
|
6
|
+
scoreboard_set("#foreach_at_count", #rs, 0);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
fn test_foreach_at() {
|
|
10
|
+
// Spawn test entities
|
|
11
|
+
raw("summon minecraft:armor_stand ~ ~ ~ {Tags:[\"test_foreach\"],NoGravity:1b}");
|
|
12
|
+
raw("summon minecraft:armor_stand ~2 ~ ~ {Tags:[\"test_foreach\"],NoGravity:1b}");
|
|
13
|
+
raw("summon minecraft:armor_stand ~4 ~ ~ {Tags:[\"test_foreach\"],NoGravity:1b}");
|
|
14
|
+
|
|
15
|
+
// Basic foreach
|
|
16
|
+
let count: int = 0;
|
|
17
|
+
foreach (e in @e[type=armor_stand,tag=test_foreach]) {
|
|
18
|
+
count = count + 1;
|
|
19
|
+
}
|
|
20
|
+
scoreboard_set("#foreach_count", #rs, count);
|
|
21
|
+
|
|
22
|
+
// Foreach with at @s (execute at entity position)
|
|
23
|
+
let at_count: int = 0;
|
|
24
|
+
foreach (e in @e[type=armor_stand,tag=test_foreach]) at @s {
|
|
25
|
+
// This runs at each entity's position
|
|
26
|
+
at_count = at_count + 1;
|
|
27
|
+
raw("particle minecraft:heart ~ ~1 ~ 0 0 0 0 1");
|
|
28
|
+
}
|
|
29
|
+
scoreboard_set("#foreach_at_count", #rs, at_count);
|
|
30
|
+
|
|
31
|
+
// Cleanup
|
|
32
|
+
raw("kill @e[type=armor_stand,tag=test_foreach]");
|
|
33
|
+
}
|
|
@@ -1,20 +1,27 @@
|
|
|
1
|
+
@load fn setup() {
|
|
2
|
+
scoreboard_add("armor_stands");
|
|
3
|
+
scoreboard_add("items");
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
// Test is-check type narrowing using armor_stands (don't despawn without players)
|
|
1
7
|
fn check_types() {
|
|
2
|
-
scoreboard_set("#is_check", #
|
|
3
|
-
scoreboard_set("#is_check", #
|
|
8
|
+
scoreboard_set("#is_check", #armor_stands, 0);
|
|
9
|
+
scoreboard_set("#is_check", #items, 0);
|
|
4
10
|
|
|
5
|
-
foreach
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
11
|
+
// Test foreach with is-check on armor_stands
|
|
12
|
+
foreach (e in @e[tag=is_check_target]) {
|
|
13
|
+
if (e is ArmorStand) {
|
|
14
|
+
let count: int = scoreboard_get("#is_check", #armor_stands);
|
|
15
|
+
scoreboard_set("#is_check", #armor_stands, count + 1);
|
|
9
16
|
}
|
|
10
17
|
|
|
11
|
-
if (e is
|
|
12
|
-
let
|
|
13
|
-
scoreboard_set("#is_check", #
|
|
18
|
+
if (e is Item) {
|
|
19
|
+
let count: int = scoreboard_get("#is_check", #items);
|
|
20
|
+
scoreboard_set("#is_check", #items, count + 1);
|
|
14
21
|
}
|
|
15
|
-
|
|
16
|
-
let zombies: int = scoreboard_get("#is_check", #zombies);
|
|
17
|
-
scoreboard_set("#is_check", #zombies, zombies + 1);
|
|
18
|
-
kill(e);
|
|
19
22
|
}
|
|
20
23
|
}
|
|
24
|
+
|
|
25
|
+
fn cleanup() {
|
|
26
|
+
raw("kill @e[tag=is_check_target]");
|
|
27
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Match with range patterns test
|
|
2
|
+
|
|
3
|
+
fn test_match_range() {
|
|
4
|
+
// Test score grading
|
|
5
|
+
let score: int = 85;
|
|
6
|
+
let grade: int = 0;
|
|
7
|
+
|
|
8
|
+
match (score) {
|
|
9
|
+
0..59 => { grade = 1; } // F
|
|
10
|
+
60..69 => { grade = 2; } // D
|
|
11
|
+
70..79 => { grade = 3; } // C
|
|
12
|
+
80..89 => { grade = 4; } // B
|
|
13
|
+
90..100 => { grade = 5; } // A
|
|
14
|
+
_ => { grade = 0; }
|
|
15
|
+
}
|
|
16
|
+
scoreboard_set("#grade", #rs, grade);
|
|
17
|
+
|
|
18
|
+
// Test boundary values
|
|
19
|
+
let val1: int = 59;
|
|
20
|
+
let result1: int = 0;
|
|
21
|
+
match (val1) {
|
|
22
|
+
0..59 => { result1 = 1; }
|
|
23
|
+
60..100 => { result1 = 2; }
|
|
24
|
+
_ => { result1 = 0; }
|
|
25
|
+
}
|
|
26
|
+
scoreboard_set("#boundary_59", #rs, result1);
|
|
27
|
+
|
|
28
|
+
let val2: int = 60;
|
|
29
|
+
let result2: int = 0;
|
|
30
|
+
match (val2) {
|
|
31
|
+
0..59 => { result2 = 1; }
|
|
32
|
+
60..100 => { result2 = 2; }
|
|
33
|
+
_ => { result2 = 0; }
|
|
34
|
+
}
|
|
35
|
+
scoreboard_set("#boundary_60", #rs, result2);
|
|
36
|
+
|
|
37
|
+
// Open-ended ranges
|
|
38
|
+
let neg: int = -5;
|
|
39
|
+
let neg_result: int = 0;
|
|
40
|
+
match (neg) {
|
|
41
|
+
..0 => { neg_result = 1; }
|
|
42
|
+
0.. => { neg_result = 2; }
|
|
43
|
+
}
|
|
44
|
+
scoreboard_set("#neg_range", #rs, neg_result);
|
|
45
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Struct instantiation and field access test
|
|
2
|
+
|
|
3
|
+
struct Point {
|
|
4
|
+
x: int,
|
|
5
|
+
y: int,
|
|
6
|
+
z: int
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
struct Player {
|
|
10
|
+
score: int,
|
|
11
|
+
alive: bool
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
fn test_struct() {
|
|
15
|
+
// Create struct instance
|
|
16
|
+
let p: Point = { x: 10, y: 64, z: -5 };
|
|
17
|
+
|
|
18
|
+
// Access fields
|
|
19
|
+
scoreboard_set("#struct_x", #rs, p.x);
|
|
20
|
+
scoreboard_set("#struct_y", #rs, p.y);
|
|
21
|
+
scoreboard_set("#struct_z", #rs, p.z);
|
|
22
|
+
|
|
23
|
+
// Modify via new instance
|
|
24
|
+
let p2: Point = { x: p.x + 5, y: p.y, z: p.z * 2 };
|
|
25
|
+
scoreboard_set("#struct_x2", #rs, p2.x);
|
|
26
|
+
scoreboard_set("#struct_z2", #rs, p2.z);
|
|
27
|
+
|
|
28
|
+
// Bool field
|
|
29
|
+
let player: Player = { score: 100, alive: true };
|
|
30
|
+
if (player.alive) {
|
|
31
|
+
scoreboard_set("#struct_alive", #rs, 1);
|
|
32
|
+
}
|
|
33
|
+
scoreboard_set("#struct_score", #rs, player.score);
|
|
34
|
+
}
|
|
@@ -285,7 +285,7 @@ fn scan() {
|
|
|
285
285
|
`)
|
|
286
286
|
const foreachFn = ir.functions.find(fn => fn.name.includes('scan/foreach'))!
|
|
287
287
|
const rawCmds = getRawCommands(foreachFn)
|
|
288
|
-
const isCheckCmd = rawCmds.find(cmd => cmd.startsWith('execute if entity @s[type=player] run function test:scan/then_'))
|
|
288
|
+
const isCheckCmd = rawCmds.find(cmd => cmd.startsWith('execute if entity @s[type=minecraft:player] run function test:scan/then_'))
|
|
289
289
|
expect(isCheckCmd).toBeDefined()
|
|
290
290
|
|
|
291
291
|
const thenFn = ir.functions.find(fn => fn.name.startsWith('scan/then_'))!
|
|
@@ -360,8 +360,8 @@ fn test() {
|
|
|
360
360
|
|
|
361
361
|
expect(getRawCommands(mainFn)).toContain('execute as @e run function test:test/foreach_0')
|
|
362
362
|
expect(thenFns).toHaveLength(2)
|
|
363
|
-
expect(rawCmds).toContain(`execute if entity @s[type=player] run function test:${playerThenFn.name}`)
|
|
364
|
-
expect(rawCmds).toContain(`execute if entity @s[type=zombie] run function test:${zombieThenFn.name}`)
|
|
363
|
+
expect(rawCmds).toContain(`execute if entity @s[type=minecraft:player] run function test:${playerThenFn.name}`)
|
|
364
|
+
expect(rawCmds).toContain(`execute if entity @s[type=minecraft:zombie] run function test:${zombieThenFn.name}`)
|
|
365
365
|
expect(getRawCommands(playerThenFn).some(cmd => cmd.includes('give @s diamond 1'))).toBe(true)
|
|
366
366
|
expect(getRawCommands(zombieThenFn)).toContain('kill @s')
|
|
367
367
|
})
|