redscript-mc 1.0.0 → 1.2.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 +112 -0
- package/CONTRIBUTING.md +140 -0
- package/README.md +28 -19
- package/README.zh.md +28 -19
- package/dist/__tests__/cli.test.js +148 -10
- package/dist/__tests__/codegen.test.js +26 -1
- package/dist/__tests__/diagnostics.test.js +5 -5
- package/dist/__tests__/e2e.test.js +336 -17
- package/dist/__tests__/formatter.test.d.ts +1 -0
- package/dist/__tests__/formatter.test.js +40 -0
- package/dist/__tests__/lexer.test.js +12 -2
- package/dist/__tests__/lowering.test.js +200 -12
- package/dist/__tests__/mc-integration.test.js +370 -31
- package/dist/__tests__/mc-syntax.test.js +3 -3
- package/dist/__tests__/nbt.test.js +2 -2
- package/dist/__tests__/optimizer-advanced.test.js +5 -5
- package/dist/__tests__/parser.test.js +80 -0
- package/dist/__tests__/runtime.test.js +9 -9
- package/dist/__tests__/typechecker.test.js +158 -0
- package/dist/ast/types.d.ts +40 -3
- package/dist/cli.js +25 -7
- package/dist/codegen/mcfunction/index.d.ts +1 -1
- package/dist/codegen/mcfunction/index.js +38 -3
- package/dist/codegen/structure/index.js +32 -1
- package/dist/compile.d.ts +10 -0
- package/dist/compile.js +36 -5
- package/dist/events/types.d.ts +35 -0
- package/dist/events/types.js +59 -0
- package/dist/formatter/index.d.ts +1 -0
- package/dist/formatter/index.js +26 -0
- package/dist/index.js +3 -2
- package/dist/ir/builder.d.ts +2 -1
- package/dist/ir/types.d.ts +11 -2
- package/dist/ir/types.js +1 -1
- package/dist/lexer/index.d.ts +1 -1
- package/dist/lexer/index.js +2 -0
- package/dist/lowering/index.d.ts +34 -1
- package/dist/lowering/index.js +622 -23
- 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 +4 -0
- package/dist/parser/index.js +153 -16
- package/dist/typechecker/index.d.ts +17 -0
- package/dist/typechecker/index.js +343 -17
- package/docs/COMPILATION_STATS.md +24 -24
- package/docs/ENTITY_TYPE_SYSTEM.md +242 -0
- package/docs/IMPLEMENTATION_GUIDE.md +1 -1
- package/docs/STRUCTURE_TARGET.md +1 -1
- package/editors/vscode/.vscodeignore +1 -0
- package/editors/vscode/CHANGELOG.md +9 -0
- package/editors/vscode/icons/mcrs.svg +7 -0
- package/editors/vscode/icons/redscript-icons.json +10 -0
- package/editors/vscode/out/extension.js +1295 -80
- package/editors/vscode/package-lock.json +2 -2
- 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 +176 -10
- package/src/__tests__/codegen.test.ts +28 -1
- package/src/__tests__/diagnostics.test.ts +5 -5
- package/src/__tests__/e2e.test.ts +335 -17
- package/src/__tests__/fixtures/event-test.mcrs +13 -0
- package/src/__tests__/fixtures/impl-test.mcrs +46 -0
- package/src/__tests__/fixtures/interval-test.mcrs +11 -0
- package/src/__tests__/fixtures/is-check-test.mcrs +20 -0
- package/src/__tests__/fixtures/timeout-test.mcrs +7 -0
- package/src/__tests__/lexer.test.ts +14 -2
- package/src/__tests__/lowering.test.ts +226 -12
- package/src/__tests__/mc-integration.test.ts +421 -31
- package/src/__tests__/mc-syntax.test.ts +3 -3
- package/src/__tests__/nbt.test.ts +2 -2
- package/src/__tests__/optimizer-advanced.test.ts +5 -5
- package/src/__tests__/parser.test.ts +91 -5
- package/src/__tests__/runtime.test.ts +9 -9
- package/src/__tests__/typechecker.test.ts +171 -0
- package/src/ast/types.ts +44 -3
- package/src/cli.ts +10 -10
- package/src/codegen/mcfunction/index.ts +40 -3
- package/src/codegen/structure/index.ts +35 -1
- package/src/compile.ts +54 -6
- package/src/events/types.ts +69 -0
- 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/index.ts +4 -3
- package/src/ir/builder.ts +3 -1
- package/src/ir/types.ts +12 -2
- package/src/lexer/index.ts +3 -1
- package/src/lowering/index.ts +684 -24
- package/src/mc-test/runner.ts +3 -3
- package/src/mc-test/setup.ts +2 -2
- package/src/parser/index.ts +170 -19
- package/src/stdlib/README.md +178 -140
- 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/tags.mcrs +951 -0
- package/src/stdlib/teams.mcrs +68 -0
- package/src/stdlib/timer.mcrs +72 -0
- package/src/stdlib/world.mcrs +92 -0
- package/src/typechecker/index.ts +404 -18
- package/src/examples/rpg.rs +0 -13
- package/src/stdlib/mobs.rs +0 -99
- package/src/stdlib/timer.rs +0 -51
- /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/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
|
@@ -77,6 +77,41 @@ fn test() -> int {
|
|
|
77
77
|
const call = getInstructions(specialized).find(i => i.op === 'call');
|
|
78
78
|
expect(call.fn).toBe('__lambda_0');
|
|
79
79
|
});
|
|
80
|
+
it('lowers impl methods to prefixed function names', () => {
|
|
81
|
+
const ir = compile(`
|
|
82
|
+
struct Timer { duration: int }
|
|
83
|
+
|
|
84
|
+
impl Timer {
|
|
85
|
+
fn elapsed(self) -> int {
|
|
86
|
+
return self.duration;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
`);
|
|
90
|
+
expect(getFunction(ir, 'Timer_elapsed')).toBeDefined();
|
|
91
|
+
});
|
|
92
|
+
it('lowers impl instance and static method calls', () => {
|
|
93
|
+
const ir = compile(`
|
|
94
|
+
struct Timer { duration: int }
|
|
95
|
+
|
|
96
|
+
impl Timer {
|
|
97
|
+
fn new(duration: int) -> Timer {
|
|
98
|
+
return { duration: duration };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
fn elapsed(self) -> int {
|
|
102
|
+
return self.duration;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
fn test() -> int {
|
|
107
|
+
let timer: Timer = Timer::new(10);
|
|
108
|
+
return timer.elapsed();
|
|
109
|
+
}
|
|
110
|
+
`);
|
|
111
|
+
const fn = getFunction(ir, 'test');
|
|
112
|
+
const calls = getInstructions(fn).filter((instr) => instr.op === 'call');
|
|
113
|
+
expect(calls.map(call => call.fn)).toEqual(['Timer_new', 'Timer_elapsed']);
|
|
114
|
+
});
|
|
80
115
|
});
|
|
81
116
|
describe('let statements', () => {
|
|
82
117
|
it('inlines const values without allocating scoreboard variables', () => {
|
|
@@ -204,6 +239,23 @@ fn test() -> int {
|
|
|
204
239
|
const fn = getFunction(ir, 'foo');
|
|
205
240
|
expect(fn.blocks.length).toBeGreaterThanOrEqual(3); // entry, then, else, merge
|
|
206
241
|
});
|
|
242
|
+
it('lowers entity is-checks to execute if entity type filters', () => {
|
|
243
|
+
const ir = compile(`
|
|
244
|
+
fn scan() {
|
|
245
|
+
foreach (e in @e) {
|
|
246
|
+
if (e is Player) {
|
|
247
|
+
kill(e);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
`);
|
|
252
|
+
const foreachFn = ir.functions.find(fn => fn.name.includes('scan/foreach'));
|
|
253
|
+
const rawCmds = getRawCommands(foreachFn);
|
|
254
|
+
const isCheckCmd = rawCmds.find(cmd => cmd.startsWith('execute if entity @s[type=player] run function test:scan/then_'));
|
|
255
|
+
expect(isCheckCmd).toBeDefined();
|
|
256
|
+
const thenFn = ir.functions.find(fn => fn.name.startsWith('scan/then_'));
|
|
257
|
+
expect(getRawCommands(thenFn)).toContain('kill @s');
|
|
258
|
+
});
|
|
207
259
|
});
|
|
208
260
|
describe('while statements', () => {
|
|
209
261
|
it('creates loop structure', () => {
|
|
@@ -243,6 +295,31 @@ fn test() -> int {
|
|
|
243
295
|
const rawCmds = getRawCommands(fn);
|
|
244
296
|
expect(rawCmds.some(cmd => cmd.includes('data get storage rs:heap arr'))).toBe(true);
|
|
245
297
|
});
|
|
298
|
+
it('lowers entity is-checks inside foreach bodies', () => {
|
|
299
|
+
const ir = compile(`
|
|
300
|
+
fn test() {
|
|
301
|
+
foreach (e in @e) {
|
|
302
|
+
if (e is Player) {
|
|
303
|
+
give(@s, "diamond", 1);
|
|
304
|
+
}
|
|
305
|
+
if (e is Zombie) {
|
|
306
|
+
kill(@s);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
`);
|
|
311
|
+
const mainFn = getFunction(ir, 'test');
|
|
312
|
+
const foreachFn = ir.functions.find(f => f.name === 'test/foreach_0');
|
|
313
|
+
const thenFns = ir.functions.filter(f => /^test\/then_/.test(f.name)).sort((a, b) => a.name.localeCompare(b.name));
|
|
314
|
+
const rawCmds = getRawCommands(foreachFn);
|
|
315
|
+
const [playerThenFn, zombieThenFn] = thenFns;
|
|
316
|
+
expect(getRawCommands(mainFn)).toContain('execute as @e run function test:test/foreach_0');
|
|
317
|
+
expect(thenFns).toHaveLength(2);
|
|
318
|
+
expect(rawCmds).toContain(`execute if entity @s[type=player] run function test:${playerThenFn.name}`);
|
|
319
|
+
expect(rawCmds).toContain(`execute if entity @s[type=zombie] run function test:${zombieThenFn.name}`);
|
|
320
|
+
expect(getRawCommands(playerThenFn).some(cmd => cmd.includes('give @s diamond 1'))).toBe(true);
|
|
321
|
+
expect(getRawCommands(zombieThenFn)).toContain('kill @s');
|
|
322
|
+
});
|
|
246
323
|
});
|
|
247
324
|
describe('match statements', () => {
|
|
248
325
|
it('lowers match into guarded execute function calls', () => {
|
|
@@ -515,12 +592,12 @@ fn test() {
|
|
|
515
592
|
`);
|
|
516
593
|
const fn = getFunction(ir, 'test');
|
|
517
594
|
const rawCmds = getRawCommands(fn);
|
|
518
|
-
expect(rawCmds).toContain('scoreboard objectives setdisplay sidebar kills');
|
|
519
|
-
expect(rawCmds).toContain('scoreboard objectives setdisplay list coins');
|
|
520
|
-
expect(rawCmds).toContain('scoreboard objectives setdisplay belowName hp');
|
|
595
|
+
expect(rawCmds).toContain('scoreboard objectives setdisplay sidebar test.kills');
|
|
596
|
+
expect(rawCmds).toContain('scoreboard objectives setdisplay list test.coins');
|
|
597
|
+
expect(rawCmds).toContain('scoreboard objectives setdisplay belowName test.hp');
|
|
521
598
|
expect(rawCmds).toContain('scoreboard objectives setdisplay sidebar');
|
|
522
|
-
expect(rawCmds).toContain('scoreboard objectives add kills playerKillCount "Kill Count"');
|
|
523
|
-
expect(rawCmds).toContain('scoreboard objectives remove kills');
|
|
599
|
+
expect(rawCmds).toContain('scoreboard objectives add test.kills playerKillCount "Kill Count"');
|
|
600
|
+
expect(rawCmds).toContain('scoreboard objectives remove test.kills');
|
|
524
601
|
});
|
|
525
602
|
it('lowers bossbar management builtins', () => {
|
|
526
603
|
const ir = compile(`
|
|
@@ -546,7 +623,7 @@ fn test() {
|
|
|
546
623
|
expect(rawCmds).toContain('bossbar set ns:health visible true');
|
|
547
624
|
expect(rawCmds).toContain('bossbar set ns:health players @a');
|
|
548
625
|
expect(rawCmds).toContain('bossbar remove ns:health');
|
|
549
|
-
expect(rawCmds.some(cmd => /^execute store result score \$
|
|
626
|
+
expect(rawCmds.some(cmd => /^execute store result score \$_\d+ rs run bossbar get ns:health value$/.test(cmd))).toBe(true);
|
|
550
627
|
});
|
|
551
628
|
it('lowers team management builtins', () => {
|
|
552
629
|
const ir = compile(`
|
|
@@ -574,13 +651,13 @@ fn test() {
|
|
|
574
651
|
const ir = compile('fn test() { let x: int = random(1, 100); }');
|
|
575
652
|
const fn = getFunction(ir, 'test');
|
|
576
653
|
const rawCmds = getRawCommands(fn);
|
|
577
|
-
expect(rawCmds).toContain('scoreboard players random $
|
|
654
|
+
expect(rawCmds).toContain('scoreboard players random $_0 rs 1 100');
|
|
578
655
|
});
|
|
579
656
|
it('lowers random_native()', () => {
|
|
580
657
|
const ir = compile('fn test() { let x: int = random_native(1, 6); }');
|
|
581
658
|
const fn = getFunction(ir, 'test');
|
|
582
659
|
const rawCmds = getRawCommands(fn);
|
|
583
|
-
expect(rawCmds).toContain('execute store result score $
|
|
660
|
+
expect(rawCmds).toContain('execute store result score $_0 rs run random value 1 6');
|
|
584
661
|
});
|
|
585
662
|
it('lowers random_sequence()', () => {
|
|
586
663
|
const ir = compile('fn test() { random_sequence("loot", 42); }');
|
|
@@ -588,6 +665,40 @@ fn test() {
|
|
|
588
665
|
const rawCmds = getRawCommands(fn);
|
|
589
666
|
expect(rawCmds).toContain('random reset loot 42');
|
|
590
667
|
});
|
|
668
|
+
it('lowers setTimeout() to a scheduled helper function', () => {
|
|
669
|
+
const ir = compile('fn test() { setTimeout(100, () => { say("hi"); }); }');
|
|
670
|
+
const fn = getFunction(ir, 'test');
|
|
671
|
+
const timeoutFn = getFunction(ir, '__timeout_0');
|
|
672
|
+
const rawCmds = getRawCommands(fn);
|
|
673
|
+
const timeoutCmds = getRawCommands(timeoutFn);
|
|
674
|
+
expect(rawCmds).toContain('schedule function test:__timeout_0 100t');
|
|
675
|
+
expect(timeoutCmds).toContain('say hi');
|
|
676
|
+
});
|
|
677
|
+
it('lowers setInterval() to a self-rescheduling helper function', () => {
|
|
678
|
+
const ir = compile('fn test() { setInterval(20, () => { say("tick"); }); }');
|
|
679
|
+
const fn = getFunction(ir, 'test');
|
|
680
|
+
const intervalFn = getFunction(ir, '__interval_0');
|
|
681
|
+
const intervalBodyFn = getFunction(ir, '__interval_body_0');
|
|
682
|
+
const rawCmds = getRawCommands(fn);
|
|
683
|
+
const intervalCmds = getRawCommands(intervalFn);
|
|
684
|
+
const intervalBodyCmds = getRawCommands(intervalBodyFn);
|
|
685
|
+
expect(rawCmds).toContain('schedule function test:__interval_0 20t');
|
|
686
|
+
expect(intervalCmds).toContain('function test:__interval_body_0');
|
|
687
|
+
expect(intervalCmds).toContain('schedule function test:__interval_0 20t');
|
|
688
|
+
expect(intervalBodyCmds).toContain('say tick');
|
|
689
|
+
});
|
|
690
|
+
it('lowers clearInterval() to schedule clear for the generated interval function', () => {
|
|
691
|
+
const ir = compile(`
|
|
692
|
+
fn test() {
|
|
693
|
+
let intervalId: int = setInterval(20, () => { say("tick"); });
|
|
694
|
+
clearInterval(intervalId);
|
|
695
|
+
}
|
|
696
|
+
`);
|
|
697
|
+
const fn = getFunction(ir, 'test');
|
|
698
|
+
const rawCmds = getRawCommands(fn);
|
|
699
|
+
expect(rawCmds).toContain('schedule function test:__interval_0 20t');
|
|
700
|
+
expect(rawCmds).toContain('schedule clear test:__interval_0');
|
|
701
|
+
});
|
|
591
702
|
it('lowers data_get from entity', () => {
|
|
592
703
|
const ir = compile('fn test() { let item_count: int = data_get("entity", "@s", "SelectedItem.Count"); }');
|
|
593
704
|
const fn = getFunction(ir, 'test');
|
|
@@ -628,19 +739,25 @@ fn test() {
|
|
|
628
739
|
const ir = compile('fn test() { let score: int = scoreboard_get(@s, "score"); }');
|
|
629
740
|
const fn = getFunction(ir, 'test');
|
|
630
741
|
const rawCmds = getRawCommands(fn);
|
|
631
|
-
expect(rawCmds.some(cmd => cmd.includes('run scoreboard players get @s score'))).toBe(true);
|
|
742
|
+
expect(rawCmds.some(cmd => cmd.includes('run scoreboard players get @s test.score'))).toBe(true);
|
|
632
743
|
});
|
|
633
744
|
it('accepts bare selector targets in scoreboard_set', () => {
|
|
634
745
|
const ir = compile('fn test() { scoreboard_set(@a, "kills", 0); }');
|
|
635
746
|
const fn = getFunction(ir, 'test');
|
|
636
747
|
const rawCmds = getRawCommands(fn);
|
|
637
|
-
expect(rawCmds).toContain('scoreboard players set @a kills 0');
|
|
748
|
+
expect(rawCmds).toContain('scoreboard players set @a test.kills 0');
|
|
749
|
+
});
|
|
750
|
+
it('skips prefixing raw mc_name objectives', () => {
|
|
751
|
+
const ir = compile('fn test() { scoreboard_set(@s, #health, 100); }');
|
|
752
|
+
const fn = getFunction(ir, 'test');
|
|
753
|
+
const rawCmds = getRawCommands(fn);
|
|
754
|
+
expect(rawCmds).toContain('scoreboard players set @s health 100');
|
|
638
755
|
});
|
|
639
756
|
it('warns on quoted selectors in scoreboard_get', () => {
|
|
640
757
|
const { ir, warnings } = compileWithWarnings('fn test() { let score: int = scoreboard_get("@s", "score"); }');
|
|
641
758
|
const fn = getFunction(ir, 'test');
|
|
642
759
|
const rawCmds = getRawCommands(fn);
|
|
643
|
-
expect(rawCmds.some(cmd => cmd.includes('run scoreboard players get @s score'))).toBe(true);
|
|
760
|
+
expect(rawCmds.some(cmd => cmd.includes('run scoreboard players get @s test.score'))).toBe(true);
|
|
644
761
|
expect(warnings).toContainEqual(expect.objectContaining({
|
|
645
762
|
code: 'W_QUOTED_SELECTOR',
|
|
646
763
|
message: 'Quoted selector "@s" is deprecated; pass @s without quotes',
|
|
@@ -650,7 +767,7 @@ fn test() {
|
|
|
650
767
|
const { ir, warnings } = compileWithWarnings('fn test() { let total: int = scoreboard_get("#global", "total"); }');
|
|
651
768
|
const fn = getFunction(ir, 'test');
|
|
652
769
|
const rawCmds = getRawCommands(fn);
|
|
653
|
-
expect(rawCmds.some(cmd => cmd.includes('run scoreboard players get #global total'))).toBe(true);
|
|
770
|
+
expect(rawCmds.some(cmd => cmd.includes('run scoreboard players get #global test.total'))).toBe(true);
|
|
654
771
|
expect(warnings).toHaveLength(0);
|
|
655
772
|
});
|
|
656
773
|
it('warns on quoted selectors in data_get entity targets', () => {
|
|
@@ -663,6 +780,37 @@ fn test() {
|
|
|
663
780
|
message: 'Quoted selector "@s" is deprecated; pass @s without quotes',
|
|
664
781
|
}));
|
|
665
782
|
});
|
|
783
|
+
it('keeps already-qualified scoreboard objectives unchanged', () => {
|
|
784
|
+
const ir = compile('fn test() { scoreboard_set(@s, "custom.timer", 5); }');
|
|
785
|
+
const fn = getFunction(ir, 'test');
|
|
786
|
+
const rawCmds = getRawCommands(fn);
|
|
787
|
+
expect(rawCmds).toContain('scoreboard players set @s custom.timer 5');
|
|
788
|
+
});
|
|
789
|
+
});
|
|
790
|
+
describe('timer builtins', () => {
|
|
791
|
+
it('lowers timer builtins into schedule commands and wrapper functions', () => {
|
|
792
|
+
const ir = compile(`
|
|
793
|
+
fn test() {
|
|
794
|
+
let intervalId: int = setInterval(20, () => {
|
|
795
|
+
say("tick");
|
|
796
|
+
});
|
|
797
|
+
setTimeout(100, () => {
|
|
798
|
+
say("later");
|
|
799
|
+
});
|
|
800
|
+
clearInterval(intervalId);
|
|
801
|
+
}
|
|
802
|
+
`);
|
|
803
|
+
const fn = getFunction(ir, 'test');
|
|
804
|
+
const rawCmds = getRawCommands(fn);
|
|
805
|
+
expect(rawCmds).toContain('schedule function test:__interval_0 20t');
|
|
806
|
+
expect(rawCmds).toContain('schedule function test:__timeout_0 100t');
|
|
807
|
+
expect(rawCmds).toContain('schedule clear test:__interval_0');
|
|
808
|
+
const intervalFn = getFunction(ir, '__interval_0');
|
|
809
|
+
expect(getRawCommands(intervalFn)).toEqual([
|
|
810
|
+
'function test:__interval_body_0',
|
|
811
|
+
'schedule function test:__interval_0 20t',
|
|
812
|
+
]);
|
|
813
|
+
});
|
|
666
814
|
});
|
|
667
815
|
describe('decorators', () => {
|
|
668
816
|
it('marks @tick function', () => {
|
|
@@ -681,6 +829,13 @@ fn test() {
|
|
|
681
829
|
const fn = getFunction(ir, 'handle_advancement');
|
|
682
830
|
expect(fn.eventTrigger).toEqual({ kind: 'advancement', value: 'story/mine_diamond' });
|
|
683
831
|
});
|
|
832
|
+
it('marks @on event functions and binds player to @s', () => {
|
|
833
|
+
const ir = compile('@on(PlayerDeath) fn handle_death(player: Player) { tp(player, @p); }');
|
|
834
|
+
const fn = getFunction(ir, 'handle_death');
|
|
835
|
+
expect(fn.eventHandler).toEqual({ eventType: 'PlayerDeath', tag: 'rs.just_died' });
|
|
836
|
+
expect(fn.params).toEqual([]);
|
|
837
|
+
expect(getRawCommands(fn)).toContain('tp @s @p');
|
|
838
|
+
});
|
|
684
839
|
});
|
|
685
840
|
describe('selectors', () => {
|
|
686
841
|
it('converts selector with filters to string', () => {
|
|
@@ -815,5 +970,38 @@ fn count_down() {
|
|
|
815
970
|
expect(bodyBlock).toBeDefined();
|
|
816
971
|
});
|
|
817
972
|
});
|
|
973
|
+
describe('Global variables', () => {
|
|
974
|
+
it('registers global in IR globals with init value', () => {
|
|
975
|
+
const ir = compile('let x: int = 42;\nfn test() { say("hi"); }');
|
|
976
|
+
expect(ir.globals).toContainEqual({ name: '$x', init: 42 });
|
|
977
|
+
});
|
|
978
|
+
it('reads global variable in function body', () => {
|
|
979
|
+
const ir = compile('let count: int = 0;\nfn test() { let y: int = count; }');
|
|
980
|
+
const fn = getFunction(ir, 'test');
|
|
981
|
+
const instrs = getInstructions(fn);
|
|
982
|
+
expect(instrs.some(i => i.op === 'assign' && i.dst === '$y' && i.src.kind === 'var' && i.src.name === '$count')).toBe(true);
|
|
983
|
+
});
|
|
984
|
+
it('writes global variable in function body', () => {
|
|
985
|
+
const ir = compile('let count: int = 0;\nfn inc() { count = 5; }');
|
|
986
|
+
const fn = getFunction(ir, 'inc');
|
|
987
|
+
const instrs = getInstructions(fn);
|
|
988
|
+
expect(instrs.some(i => i.op === 'assign' && i.dst === '$count' && i.src.kind === 'const' && i.src.value === 5)).toBe(true);
|
|
989
|
+
});
|
|
990
|
+
it('compound assignment on global variable', () => {
|
|
991
|
+
const ir = compile('let count: int = 0;\nfn inc() { count += 1; }');
|
|
992
|
+
const fn = getFunction(ir, 'inc');
|
|
993
|
+
const instrs = getInstructions(fn);
|
|
994
|
+
expect(instrs.some(i => i.op === 'binop' && i.lhs.name === '$count' && i.bop === '+' && i.rhs.value === 1)).toBe(true);
|
|
995
|
+
});
|
|
996
|
+
it('const cannot be reassigned', () => {
|
|
997
|
+
const src = 'const X: int = 5;\nfn bad() { X = 10; }';
|
|
998
|
+
expect(() => compile(src)).toThrow(/Cannot assign to constant/);
|
|
999
|
+
});
|
|
1000
|
+
it('multiple globals with different init values', () => {
|
|
1001
|
+
const ir = compile('let a: int = 10;\nlet b: int = 20;\nfn test() { a = b; }');
|
|
1002
|
+
expect(ir.globals).toContainEqual({ name: '$a', init: 10 });
|
|
1003
|
+
expect(ir.globals).toContainEqual({ name: '$b', init: 20 });
|
|
1004
|
+
});
|
|
1005
|
+
});
|
|
818
1006
|
});
|
|
819
1007
|
//# sourceMappingURL=lowering.test.js.map
|