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
|
@@ -88,6 +88,43 @@ fn test() -> int {
|
|
|
88
88
|
const call = getInstructions(specialized!).find(i => i.op === 'call') as any
|
|
89
89
|
expect(call.fn).toBe('__lambda_0')
|
|
90
90
|
})
|
|
91
|
+
|
|
92
|
+
it('lowers impl methods to prefixed function names', () => {
|
|
93
|
+
const ir = compile(`
|
|
94
|
+
struct Timer { duration: int }
|
|
95
|
+
|
|
96
|
+
impl Timer {
|
|
97
|
+
fn elapsed(self) -> int {
|
|
98
|
+
return self.duration;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
`)
|
|
102
|
+
expect(getFunction(ir, 'Timer_elapsed')).toBeDefined()
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
it('lowers impl instance and static method calls', () => {
|
|
106
|
+
const ir = compile(`
|
|
107
|
+
struct Timer { duration: int }
|
|
108
|
+
|
|
109
|
+
impl Timer {
|
|
110
|
+
fn new(duration: int) -> Timer {
|
|
111
|
+
return { duration: duration };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
fn elapsed(self) -> int {
|
|
115
|
+
return self.duration;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
fn test() -> int {
|
|
120
|
+
let timer: Timer = Timer::new(10);
|
|
121
|
+
return timer.elapsed();
|
|
122
|
+
}
|
|
123
|
+
`)
|
|
124
|
+
const fn = getFunction(ir, 'test')!
|
|
125
|
+
const calls = getInstructions(fn).filter((instr): instr is IRInstr & { op: 'call' } => instr.op === 'call')
|
|
126
|
+
expect(calls.map(call => call.fn)).toEqual(['Timer_new', 'Timer_elapsed'])
|
|
127
|
+
})
|
|
91
128
|
})
|
|
92
129
|
|
|
93
130
|
describe('let statements', () => {
|
|
@@ -235,6 +272,25 @@ fn test() -> int {
|
|
|
235
272
|
const fn = getFunction(ir, 'foo')!
|
|
236
273
|
expect(fn.blocks.length).toBeGreaterThanOrEqual(3) // entry, then, else, merge
|
|
237
274
|
})
|
|
275
|
+
|
|
276
|
+
it('lowers entity is-checks to execute if entity type filters', () => {
|
|
277
|
+
const ir = compile(`
|
|
278
|
+
fn scan() {
|
|
279
|
+
foreach (e in @e) {
|
|
280
|
+
if (e is Player) {
|
|
281
|
+
kill(e);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
`)
|
|
286
|
+
const foreachFn = ir.functions.find(fn => fn.name.includes('scan/foreach'))!
|
|
287
|
+
const rawCmds = getRawCommands(foreachFn)
|
|
288
|
+
const isCheckCmd = rawCmds.find(cmd => cmd.startsWith('execute if entity @s[type=player] run function test:scan/then_'))
|
|
289
|
+
expect(isCheckCmd).toBeDefined()
|
|
290
|
+
|
|
291
|
+
const thenFn = ir.functions.find(fn => fn.name.startsWith('scan/then_'))!
|
|
292
|
+
expect(getRawCommands(thenFn)).toContain('kill @s')
|
|
293
|
+
})
|
|
238
294
|
})
|
|
239
295
|
|
|
240
296
|
describe('while statements', () => {
|
|
@@ -282,6 +338,33 @@ fn test() -> int {
|
|
|
282
338
|
const rawCmds = getRawCommands(fn)
|
|
283
339
|
expect(rawCmds.some(cmd => cmd.includes('data get storage rs:heap arr'))).toBe(true)
|
|
284
340
|
})
|
|
341
|
+
|
|
342
|
+
it('lowers entity is-checks inside foreach bodies', () => {
|
|
343
|
+
const ir = compile(`
|
|
344
|
+
fn test() {
|
|
345
|
+
foreach (e in @e) {
|
|
346
|
+
if (e is Player) {
|
|
347
|
+
give(@s, "diamond", 1);
|
|
348
|
+
}
|
|
349
|
+
if (e is Zombie) {
|
|
350
|
+
kill(@s);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
`)
|
|
355
|
+
const mainFn = getFunction(ir, 'test')!
|
|
356
|
+
const foreachFn = ir.functions.find(f => f.name === 'test/foreach_0')!
|
|
357
|
+
const thenFns = ir.functions.filter(f => /^test\/then_/.test(f.name)).sort((a, b) => a.name.localeCompare(b.name))
|
|
358
|
+
const rawCmds = getRawCommands(foreachFn)
|
|
359
|
+
const [playerThenFn, zombieThenFn] = thenFns
|
|
360
|
+
|
|
361
|
+
expect(getRawCommands(mainFn)).toContain('execute as @e run function test:test/foreach_0')
|
|
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}`)
|
|
365
|
+
expect(getRawCommands(playerThenFn).some(cmd => cmd.includes('give @s diamond 1'))).toBe(true)
|
|
366
|
+
expect(getRawCommands(zombieThenFn)).toContain('kill @s')
|
|
367
|
+
})
|
|
285
368
|
})
|
|
286
369
|
|
|
287
370
|
describe('match statements', () => {
|
|
@@ -603,12 +686,12 @@ fn test() {
|
|
|
603
686
|
`)
|
|
604
687
|
const fn = getFunction(ir, 'test')!
|
|
605
688
|
const rawCmds = getRawCommands(fn)
|
|
606
|
-
expect(rawCmds).toContain('scoreboard objectives setdisplay sidebar kills')
|
|
607
|
-
expect(rawCmds).toContain('scoreboard objectives setdisplay list coins')
|
|
608
|
-
expect(rawCmds).toContain('scoreboard objectives setdisplay belowName hp')
|
|
689
|
+
expect(rawCmds).toContain('scoreboard objectives setdisplay sidebar test.kills')
|
|
690
|
+
expect(rawCmds).toContain('scoreboard objectives setdisplay list test.coins')
|
|
691
|
+
expect(rawCmds).toContain('scoreboard objectives setdisplay belowName test.hp')
|
|
609
692
|
expect(rawCmds).toContain('scoreboard objectives setdisplay sidebar')
|
|
610
|
-
expect(rawCmds).toContain('scoreboard objectives add kills playerKillCount "Kill Count"')
|
|
611
|
-
expect(rawCmds).toContain('scoreboard objectives remove kills')
|
|
693
|
+
expect(rawCmds).toContain('scoreboard objectives add test.kills playerKillCount "Kill Count"')
|
|
694
|
+
expect(rawCmds).toContain('scoreboard objectives remove test.kills')
|
|
612
695
|
})
|
|
613
696
|
|
|
614
697
|
it('lowers bossbar management builtins', () => {
|
|
@@ -635,7 +718,7 @@ fn test() {
|
|
|
635
718
|
expect(rawCmds).toContain('bossbar set ns:health visible true')
|
|
636
719
|
expect(rawCmds).toContain('bossbar set ns:health players @a')
|
|
637
720
|
expect(rawCmds).toContain('bossbar remove ns:health')
|
|
638
|
-
expect(rawCmds.some(cmd => /^execute store result score \$
|
|
721
|
+
expect(rawCmds.some(cmd => /^execute store result score \$_\d+ rs run bossbar get ns:health value$/.test(cmd))).toBe(true)
|
|
639
722
|
})
|
|
640
723
|
|
|
641
724
|
it('lowers team management builtins', () => {
|
|
@@ -665,14 +748,14 @@ fn test() {
|
|
|
665
748
|
const ir = compile('fn test() { let x: int = random(1, 100); }')
|
|
666
749
|
const fn = getFunction(ir, 'test')!
|
|
667
750
|
const rawCmds = getRawCommands(fn)
|
|
668
|
-
expect(rawCmds).toContain('scoreboard players random $
|
|
751
|
+
expect(rawCmds).toContain('scoreboard players random $_0 rs 1 100')
|
|
669
752
|
})
|
|
670
753
|
|
|
671
754
|
it('lowers random_native()', () => {
|
|
672
755
|
const ir = compile('fn test() { let x: int = random_native(1, 6); }')
|
|
673
756
|
const fn = getFunction(ir, 'test')!
|
|
674
757
|
const rawCmds = getRawCommands(fn)
|
|
675
|
-
expect(rawCmds).toContain('execute store result score $
|
|
758
|
+
expect(rawCmds).toContain('execute store result score $_0 rs run random value 1 6')
|
|
676
759
|
})
|
|
677
760
|
|
|
678
761
|
it('lowers random_sequence()', () => {
|
|
@@ -682,6 +765,43 @@ fn test() {
|
|
|
682
765
|
expect(rawCmds).toContain('random reset loot 42')
|
|
683
766
|
})
|
|
684
767
|
|
|
768
|
+
it('lowers setTimeout() to a scheduled helper function', () => {
|
|
769
|
+
const ir = compile('fn test() { setTimeout(100, () => { say("hi"); }); }')
|
|
770
|
+
const fn = getFunction(ir, 'test')!
|
|
771
|
+
const timeoutFn = getFunction(ir, '__timeout_0')!
|
|
772
|
+
const rawCmds = getRawCommands(fn)
|
|
773
|
+
const timeoutCmds = getRawCommands(timeoutFn)
|
|
774
|
+
expect(rawCmds).toContain('schedule function test:__timeout_0 100t')
|
|
775
|
+
expect(timeoutCmds).toContain('say hi')
|
|
776
|
+
})
|
|
777
|
+
|
|
778
|
+
it('lowers setInterval() to a self-rescheduling helper function', () => {
|
|
779
|
+
const ir = compile('fn test() { setInterval(20, () => { say("tick"); }); }')
|
|
780
|
+
const fn = getFunction(ir, 'test')!
|
|
781
|
+
const intervalFn = getFunction(ir, '__interval_0')!
|
|
782
|
+
const intervalBodyFn = getFunction(ir, '__interval_body_0')!
|
|
783
|
+
const rawCmds = getRawCommands(fn)
|
|
784
|
+
const intervalCmds = getRawCommands(intervalFn)
|
|
785
|
+
const intervalBodyCmds = getRawCommands(intervalBodyFn)
|
|
786
|
+
expect(rawCmds).toContain('schedule function test:__interval_0 20t')
|
|
787
|
+
expect(intervalCmds).toContain('function test:__interval_body_0')
|
|
788
|
+
expect(intervalCmds).toContain('schedule function test:__interval_0 20t')
|
|
789
|
+
expect(intervalBodyCmds).toContain('say tick')
|
|
790
|
+
})
|
|
791
|
+
|
|
792
|
+
it('lowers clearInterval() to schedule clear for the generated interval function', () => {
|
|
793
|
+
const ir = compile(`
|
|
794
|
+
fn test() {
|
|
795
|
+
let intervalId: int = setInterval(20, () => { say("tick"); });
|
|
796
|
+
clearInterval(intervalId);
|
|
797
|
+
}
|
|
798
|
+
`)
|
|
799
|
+
const fn = getFunction(ir, 'test')!
|
|
800
|
+
const rawCmds = getRawCommands(fn)
|
|
801
|
+
expect(rawCmds).toContain('schedule function test:__interval_0 20t')
|
|
802
|
+
expect(rawCmds).toContain('schedule clear test:__interval_0')
|
|
803
|
+
})
|
|
804
|
+
|
|
685
805
|
it('lowers data_get from entity', () => {
|
|
686
806
|
const ir = compile('fn test() { let item_count: int = data_get("entity", "@s", "SelectedItem.Count"); }')
|
|
687
807
|
const fn = getFunction(ir, 'test')!
|
|
@@ -736,7 +856,7 @@ fn test() {
|
|
|
736
856
|
const fn = getFunction(ir, 'test')!
|
|
737
857
|
const rawCmds = getRawCommands(fn)
|
|
738
858
|
expect(rawCmds.some(cmd =>
|
|
739
|
-
cmd.includes('run scoreboard players get @s score')
|
|
859
|
+
cmd.includes('run scoreboard players get @s test.score')
|
|
740
860
|
)).toBe(true)
|
|
741
861
|
})
|
|
742
862
|
|
|
@@ -744,7 +864,14 @@ fn test() {
|
|
|
744
864
|
const ir = compile('fn test() { scoreboard_set(@a, "kills", 0); }')
|
|
745
865
|
const fn = getFunction(ir, 'test')!
|
|
746
866
|
const rawCmds = getRawCommands(fn)
|
|
747
|
-
expect(rawCmds).toContain('scoreboard players set @a kills 0')
|
|
867
|
+
expect(rawCmds).toContain('scoreboard players set @a test.kills 0')
|
|
868
|
+
})
|
|
869
|
+
|
|
870
|
+
it('skips prefixing raw mc_name objectives', () => {
|
|
871
|
+
const ir = compile('fn test() { scoreboard_set(@s, #health, 100); }')
|
|
872
|
+
const fn = getFunction(ir, 'test')!
|
|
873
|
+
const rawCmds = getRawCommands(fn)
|
|
874
|
+
expect(rawCmds).toContain('scoreboard players set @s health 100')
|
|
748
875
|
})
|
|
749
876
|
|
|
750
877
|
it('warns on quoted selectors in scoreboard_get', () => {
|
|
@@ -752,7 +879,7 @@ fn test() {
|
|
|
752
879
|
const fn = getFunction(ir, 'test')!
|
|
753
880
|
const rawCmds = getRawCommands(fn)
|
|
754
881
|
expect(rawCmds.some(cmd =>
|
|
755
|
-
cmd.includes('run scoreboard players get @s score')
|
|
882
|
+
cmd.includes('run scoreboard players get @s test.score')
|
|
756
883
|
)).toBe(true)
|
|
757
884
|
expect(warnings).toContainEqual(expect.objectContaining({
|
|
758
885
|
code: 'W_QUOTED_SELECTOR',
|
|
@@ -765,7 +892,7 @@ fn test() {
|
|
|
765
892
|
const fn = getFunction(ir, 'test')!
|
|
766
893
|
const rawCmds = getRawCommands(fn)
|
|
767
894
|
expect(rawCmds.some(cmd =>
|
|
768
|
-
cmd.includes('run scoreboard players get #global total')
|
|
895
|
+
cmd.includes('run scoreboard players get #global test.total')
|
|
769
896
|
)).toBe(true)
|
|
770
897
|
expect(warnings).toHaveLength(0)
|
|
771
898
|
})
|
|
@@ -782,6 +909,40 @@ fn test() {
|
|
|
782
909
|
message: 'Quoted selector "@s" is deprecated; pass @s without quotes',
|
|
783
910
|
}))
|
|
784
911
|
})
|
|
912
|
+
|
|
913
|
+
it('keeps already-qualified scoreboard objectives unchanged', () => {
|
|
914
|
+
const ir = compile('fn test() { scoreboard_set(@s, "custom.timer", 5); }')
|
|
915
|
+
const fn = getFunction(ir, 'test')!
|
|
916
|
+
const rawCmds = getRawCommands(fn)
|
|
917
|
+
expect(rawCmds).toContain('scoreboard players set @s custom.timer 5')
|
|
918
|
+
})
|
|
919
|
+
})
|
|
920
|
+
|
|
921
|
+
describe('timer builtins', () => {
|
|
922
|
+
it('lowers timer builtins into schedule commands and wrapper functions', () => {
|
|
923
|
+
const ir = compile(`
|
|
924
|
+
fn test() {
|
|
925
|
+
let intervalId: int = setInterval(20, () => {
|
|
926
|
+
say("tick");
|
|
927
|
+
});
|
|
928
|
+
setTimeout(100, () => {
|
|
929
|
+
say("later");
|
|
930
|
+
});
|
|
931
|
+
clearInterval(intervalId);
|
|
932
|
+
}
|
|
933
|
+
`)
|
|
934
|
+
const fn = getFunction(ir, 'test')!
|
|
935
|
+
const rawCmds = getRawCommands(fn)
|
|
936
|
+
expect(rawCmds).toContain('schedule function test:__interval_0 20t')
|
|
937
|
+
expect(rawCmds).toContain('schedule function test:__timeout_0 100t')
|
|
938
|
+
expect(rawCmds).toContain('schedule clear test:__interval_0')
|
|
939
|
+
|
|
940
|
+
const intervalFn = getFunction(ir, '__interval_0')!
|
|
941
|
+
expect(getRawCommands(intervalFn)).toEqual([
|
|
942
|
+
'function test:__interval_body_0',
|
|
943
|
+
'schedule function test:__interval_0 20t',
|
|
944
|
+
])
|
|
945
|
+
})
|
|
785
946
|
})
|
|
786
947
|
|
|
787
948
|
describe('decorators', () => {
|
|
@@ -803,6 +964,14 @@ fn test() {
|
|
|
803
964
|
const fn = getFunction(ir, 'handle_advancement')!
|
|
804
965
|
expect(fn.eventTrigger).toEqual({ kind: 'advancement', value: 'story/mine_diamond' })
|
|
805
966
|
})
|
|
967
|
+
|
|
968
|
+
it('marks @on event functions and binds player to @s', () => {
|
|
969
|
+
const ir = compile('@on(PlayerDeath) fn handle_death(player: Player) { tp(player, @p); }')
|
|
970
|
+
const fn = getFunction(ir, 'handle_death')!
|
|
971
|
+
expect(fn.eventHandler).toEqual({ eventType: 'PlayerDeath', tag: 'rs.just_died' })
|
|
972
|
+
expect(fn.params).toEqual([])
|
|
973
|
+
expect(getRawCommands(fn)).toContain('tp @s @p')
|
|
974
|
+
})
|
|
806
975
|
})
|
|
807
976
|
|
|
808
977
|
describe('selectors', () => {
|
|
@@ -959,4 +1128,49 @@ fn count_down() {
|
|
|
959
1128
|
expect(bodyBlock).toBeDefined()
|
|
960
1129
|
})
|
|
961
1130
|
})
|
|
1131
|
+
|
|
1132
|
+
describe('Global variables', () => {
|
|
1133
|
+
it('registers global in IR globals with init value', () => {
|
|
1134
|
+
const ir = compile('let x: int = 42;\nfn test() { say("hi"); }')
|
|
1135
|
+
expect(ir.globals).toContainEqual({ name: '$x', init: 42 })
|
|
1136
|
+
})
|
|
1137
|
+
|
|
1138
|
+
it('reads global variable in function body', () => {
|
|
1139
|
+
const ir = compile('let count: int = 0;\nfn test() { let y: int = count; }')
|
|
1140
|
+
const fn = getFunction(ir, 'test')!
|
|
1141
|
+
const instrs = getInstructions(fn)
|
|
1142
|
+
expect(instrs.some(i =>
|
|
1143
|
+
i.op === 'assign' && i.dst === '$y' && (i.src as any).kind === 'var' && (i.src as any).name === '$count'
|
|
1144
|
+
)).toBe(true)
|
|
1145
|
+
})
|
|
1146
|
+
|
|
1147
|
+
it('writes global variable in function body', () => {
|
|
1148
|
+
const ir = compile('let count: int = 0;\nfn inc() { count = 5; }')
|
|
1149
|
+
const fn = getFunction(ir, 'inc')!
|
|
1150
|
+
const instrs = getInstructions(fn)
|
|
1151
|
+
expect(instrs.some(i =>
|
|
1152
|
+
i.op === 'assign' && i.dst === '$count' && (i.src as any).kind === 'const' && (i.src as any).value === 5
|
|
1153
|
+
)).toBe(true)
|
|
1154
|
+
})
|
|
1155
|
+
|
|
1156
|
+
it('compound assignment on global variable', () => {
|
|
1157
|
+
const ir = compile('let count: int = 0;\nfn inc() { count += 1; }')
|
|
1158
|
+
const fn = getFunction(ir, 'inc')!
|
|
1159
|
+
const instrs = getInstructions(fn)
|
|
1160
|
+
expect(instrs.some(i =>
|
|
1161
|
+
i.op === 'binop' && (i.lhs as any).name === '$count' && i.bop === '+' && (i.rhs as any).value === 1
|
|
1162
|
+
)).toBe(true)
|
|
1163
|
+
})
|
|
1164
|
+
|
|
1165
|
+
it('const cannot be reassigned', () => {
|
|
1166
|
+
const src = 'const X: int = 5;\nfn bad() { X = 10; }'
|
|
1167
|
+
expect(() => compile(src)).toThrow(/Cannot assign to constant/)
|
|
1168
|
+
})
|
|
1169
|
+
|
|
1170
|
+
it('multiple globals with different init values', () => {
|
|
1171
|
+
const ir = compile('let a: int = 10;\nlet b: int = 20;\nfn test() { a = b; }')
|
|
1172
|
+
expect(ir.globals).toContainEqual({ name: '$a', init: 10 })
|
|
1173
|
+
expect(ir.globals).toContainEqual({ name: '$b', init: 20 })
|
|
1174
|
+
})
|
|
1175
|
+
})
|
|
962
1176
|
})
|