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
|
@@ -306,7 +306,7 @@ fn test() {
|
|
|
306
306
|
expect(fn).toContain('bossbar set ns:health visible true');
|
|
307
307
|
expect(fn).toContain('bossbar set ns:health players @a');
|
|
308
308
|
expect(fn).toContain('bossbar remove ns:health');
|
|
309
|
-
expect(fn).toMatch(/execute store result score \$
|
|
309
|
+
expect(fn).toMatch(/execute store result score \$_\d+ rs run bossbar get ns:health value/);
|
|
310
310
|
});
|
|
311
311
|
it('compiles team builtins', () => {
|
|
312
312
|
const source = `
|
|
@@ -586,19 +586,19 @@ fn double_score() -> int {
|
|
|
586
586
|
const source = 'fn test() { let x: int = random(1, 10); }';
|
|
587
587
|
const files = compile(source);
|
|
588
588
|
const fn = getFunction(files, 'test');
|
|
589
|
-
expect(fn).toContain('scoreboard players random $
|
|
589
|
+
expect(fn).toContain('scoreboard players random $_0 rs 1 10');
|
|
590
590
|
});
|
|
591
591
|
it('compiles random_native()', () => {
|
|
592
592
|
const source = 'fn test() { let x: int = random_native(1, 6); }';
|
|
593
593
|
const files = compile(source);
|
|
594
594
|
const fn = getFunction(files, 'test');
|
|
595
|
-
expect(fn).toContain('execute store result score $
|
|
595
|
+
expect(fn).toContain('execute store result score $_0 rs run random value 1 6');
|
|
596
596
|
});
|
|
597
597
|
it('compiles random_native() with zero min', () => {
|
|
598
598
|
const source = 'fn test() { let x: int = random_native(0, 100); }';
|
|
599
599
|
const files = compile(source);
|
|
600
600
|
const fn = getFunction(files, 'test');
|
|
601
|
-
expect(fn).toContain('execute store result score $
|
|
601
|
+
expect(fn).toContain('execute store result score $_0 rs run random value 0 100');
|
|
602
602
|
});
|
|
603
603
|
it('compiles random_sequence()', () => {
|
|
604
604
|
const source = 'fn test() { random_sequence("loot"); random_sequence("loot", 9); }';
|
|
@@ -1079,7 +1079,7 @@ fn handle_claim() {
|
|
|
1079
1079
|
expect(fn).toContain('if entity @s[tag=boss]');
|
|
1080
1080
|
});
|
|
1081
1081
|
});
|
|
1082
|
-
describe('Real program: zombie_game.
|
|
1082
|
+
describe('Real program: zombie_game.mcrs', () => {
|
|
1083
1083
|
const source = `
|
|
1084
1084
|
// A zombie survival game logic
|
|
1085
1085
|
// Kills nearby zombies and tracks score
|
|
@@ -1525,4 +1525,145 @@ describe('for-range loop', () => {
|
|
|
1525
1525
|
expect(subFn?.content).toContain('execute if score $x rs matches ..7 run function forloop2:test/__for_0');
|
|
1526
1526
|
});
|
|
1527
1527
|
});
|
|
1528
|
+
// ---------------------------------------------------------------------------
|
|
1529
|
+
// NBT Structured Parameters
|
|
1530
|
+
// ---------------------------------------------------------------------------
|
|
1531
|
+
describe('NBT parameters', () => {
|
|
1532
|
+
it('compiles give with NBT struct', () => {
|
|
1533
|
+
const src = `fn test() { give(@s, "minecraft:diamond_sword", 1, { display: { Name: "Excalibur" } }); }`;
|
|
1534
|
+
const files = compile(src, 'nbtparam');
|
|
1535
|
+
const fn = getFunction(files, 'test');
|
|
1536
|
+
expect(fn).toContain('give @s minecraft:diamond_sword{display:{Name:"Excalibur"}} 1');
|
|
1537
|
+
});
|
|
1538
|
+
it('compiles give with nested NBT and arrays', () => {
|
|
1539
|
+
const src = `fn test() { give(@s, "minecraft:stick", 1, { display: { Name: "Magic Wand" }, Enchantments: [{ id: "sharpness", lvl: 5 }] }); }`;
|
|
1540
|
+
const files = compile(src, 'nbtparam2');
|
|
1541
|
+
const fn = getFunction(files, 'test');
|
|
1542
|
+
expect(fn).toContain('{display:{Name:"Magic Wand"},Enchantments:[{id:"sharpness",lvl:5}]}');
|
|
1543
|
+
});
|
|
1544
|
+
it('compiles summon with NBT', () => {
|
|
1545
|
+
const src = `fn test() { summon("minecraft:zombie", 0, 64, 0, { CustomName: "Boss", NoAI: true }); }`;
|
|
1546
|
+
const files = compile(src, 'nbtsummon');
|
|
1547
|
+
const fn = getFunction(files, 'test');
|
|
1548
|
+
expect(fn).toContain('summon minecraft:zombie 0 64 0 {CustomName:"Boss",NoAI:1b}');
|
|
1549
|
+
});
|
|
1550
|
+
it('compiles give with bool values in NBT', () => {
|
|
1551
|
+
const src = `fn test() { give(@s, "minecraft:shield", 1, { Unbreakable: true }); }`;
|
|
1552
|
+
const files = compile(src, 'nbtbool');
|
|
1553
|
+
const fn = getFunction(files, 'test');
|
|
1554
|
+
expect(fn).toContain('{Unbreakable:1b}');
|
|
1555
|
+
});
|
|
1556
|
+
});
|
|
1557
|
+
// ---------------------------------------------------------------------------
|
|
1558
|
+
// Set Operations
|
|
1559
|
+
// ---------------------------------------------------------------------------
|
|
1560
|
+
describe('Set operations', () => {
|
|
1561
|
+
it('creates a new set', () => {
|
|
1562
|
+
const src = `fn test() { let s = set_new(); }`;
|
|
1563
|
+
const files = compile(src, 'settest');
|
|
1564
|
+
const fn = getFunction(files, 'test');
|
|
1565
|
+
expect(fn).toContain('data modify storage rs:sets __set_0 set value []');
|
|
1566
|
+
});
|
|
1567
|
+
it('adds to a set with uniqueness check', () => {
|
|
1568
|
+
const src = `fn test() { let s = set_new(); set_add(s, "apple"); }`;
|
|
1569
|
+
const files = compile(src, 'setadd');
|
|
1570
|
+
const fn = getFunction(files, 'test');
|
|
1571
|
+
expect(fn).toContain('execute unless data storage rs:sets __set_0[{value:apple}] run data modify storage rs:sets __set_0 append value {value:apple}');
|
|
1572
|
+
});
|
|
1573
|
+
it('checks set membership', () => {
|
|
1574
|
+
const src = `fn test() { let s = set_new(); set_add(s, "x"); let has = set_contains(s, "x"); }`;
|
|
1575
|
+
const files = compile(src, 'setcontains');
|
|
1576
|
+
const fn = getFunction(files, 'test');
|
|
1577
|
+
expect(fn).toContain('if data storage rs:sets __set_0[{value:x}]');
|
|
1578
|
+
});
|
|
1579
|
+
it('removes from a set', () => {
|
|
1580
|
+
const src = `fn test() { let s = set_new(); set_add(s, "y"); set_remove(s, "y"); }`;
|
|
1581
|
+
const files = compile(src, 'setremove');
|
|
1582
|
+
const fn = getFunction(files, 'test');
|
|
1583
|
+
expect(fn).toContain('data remove storage rs:sets __set_0[{value:y}]');
|
|
1584
|
+
});
|
|
1585
|
+
it('clears a set', () => {
|
|
1586
|
+
const src = `fn test() { let s = set_new(); set_clear(s); }`;
|
|
1587
|
+
const files = compile(src, 'setclear');
|
|
1588
|
+
const fn = getFunction(files, 'test');
|
|
1589
|
+
expect(fn).toContain('data modify storage rs:sets __set_0 set value []');
|
|
1590
|
+
});
|
|
1591
|
+
});
|
|
1592
|
+
// ---------------------------------------------------------------------------
|
|
1593
|
+
// Method Syntax Sugar
|
|
1594
|
+
// ---------------------------------------------------------------------------
|
|
1595
|
+
describe('Method syntax sugar', () => {
|
|
1596
|
+
it('transforms obj.method() to method(obj)', () => {
|
|
1597
|
+
const src = `fn test() { let s = set_new(); s.clear(); }`;
|
|
1598
|
+
const files = compile(src, 'method1');
|
|
1599
|
+
const fn = getFunction(files, 'test');
|
|
1600
|
+
expect(fn).toContain('data modify storage rs:sets __set_0 set value []');
|
|
1601
|
+
});
|
|
1602
|
+
it('transforms obj.method(arg) to method(obj, arg)', () => {
|
|
1603
|
+
const src = `fn test() { let s = set_new(); s.add("apple"); }`;
|
|
1604
|
+
const files = compile(src, 'method2');
|
|
1605
|
+
const fn = getFunction(files, 'test');
|
|
1606
|
+
expect(fn).toContain('data modify storage rs:sets __set_0 append value {value:apple}');
|
|
1607
|
+
});
|
|
1608
|
+
it('transforms obj.method(arg) with contains', () => {
|
|
1609
|
+
const src = `fn test() { let s = set_new(); s.add("x"); let r = s.contains("x"); }`;
|
|
1610
|
+
const files = compile(src, 'method3');
|
|
1611
|
+
const fn = getFunction(files, 'test');
|
|
1612
|
+
expect(fn).toBeDefined();
|
|
1613
|
+
});
|
|
1614
|
+
it('works with multiple args', () => {
|
|
1615
|
+
const src = `fn test() { let s = set_new(); s.add("a"); s.add("b"); s.remove("a"); }`;
|
|
1616
|
+
const files = compile(src, 'method4');
|
|
1617
|
+
const fn = getFunction(files, 'test');
|
|
1618
|
+
expect(fn).toContain('data remove storage rs:sets __set_0[{value:a}]');
|
|
1619
|
+
});
|
|
1620
|
+
});
|
|
1621
|
+
describe('Global variables', () => {
|
|
1622
|
+
it('initializes global in __load', () => {
|
|
1623
|
+
const src = `let x: int = 42;\nfn test() { say("hi"); }`;
|
|
1624
|
+
const files = compile(src, 'globaltest');
|
|
1625
|
+
const load = getFunction(files, '__load');
|
|
1626
|
+
expect(load).toContain('scoreboard players set $x rs 42');
|
|
1627
|
+
});
|
|
1628
|
+
it('reads and writes global in function', () => {
|
|
1629
|
+
const src = `let count: int = 0;\nfn inc() { count = count + 1; }`;
|
|
1630
|
+
const files = compile(src, 'globalrw');
|
|
1631
|
+
const fn = getFunction(files, 'inc');
|
|
1632
|
+
expect(fn).toBeDefined();
|
|
1633
|
+
// Global should be initialized in __load
|
|
1634
|
+
const load = getFunction(files, '__load');
|
|
1635
|
+
expect(load).toContain('scoreboard players set $count rs 0');
|
|
1636
|
+
});
|
|
1637
|
+
it('const cannot be reassigned', () => {
|
|
1638
|
+
const src = `const X: int = 5;\nfn bad() { X = 10; }`;
|
|
1639
|
+
expect(() => compile(src, 'constbad')).toThrow();
|
|
1640
|
+
});
|
|
1641
|
+
});
|
|
1642
|
+
describe('@load decorator', () => {
|
|
1643
|
+
it('calls @load function from __load.mcfunction', () => {
|
|
1644
|
+
const src = `@load fn init() { say("Datapack loaded!"); }`;
|
|
1645
|
+
const files = compile(src, 'loadtest');
|
|
1646
|
+
const load = getFunction(files, '__load');
|
|
1647
|
+
expect(load).toContain('function loadtest:init');
|
|
1648
|
+
});
|
|
1649
|
+
it('calls multiple @load functions in order', () => {
|
|
1650
|
+
const src = `
|
|
1651
|
+
@load fn setup() { say("setup"); }
|
|
1652
|
+
@load fn init() { say("init"); }
|
|
1653
|
+
`;
|
|
1654
|
+
const files = compile(src, 'loadtest');
|
|
1655
|
+
const load = getFunction(files, '__load');
|
|
1656
|
+
const setupIdx = load.indexOf('function loadtest:setup');
|
|
1657
|
+
const initIdx = load.indexOf('function loadtest:init');
|
|
1658
|
+
expect(setupIdx).toBeGreaterThan(-1);
|
|
1659
|
+
expect(initIdx).toBeGreaterThan(-1);
|
|
1660
|
+
expect(setupIdx).toBeLessThan(initIdx);
|
|
1661
|
+
});
|
|
1662
|
+
it('generates the @load function body normally', () => {
|
|
1663
|
+
const src = `@load fn init() { say("hi"); }`;
|
|
1664
|
+
const files = compile(src, 'loadtest');
|
|
1665
|
+
const fn = getFunction(files, 'init');
|
|
1666
|
+
expect(fn).toContain('say hi');
|
|
1667
|
+
});
|
|
1668
|
+
});
|
|
1528
1669
|
//# sourceMappingURL=e2e.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const formatter_1 = require("../formatter");
|
|
4
|
+
describe('formatter', () => {
|
|
5
|
+
it('normalizes indentation to 4 spaces', () => {
|
|
6
|
+
const input = 'fn main() {\n let x: int = 1;\n}';
|
|
7
|
+
const result = (0, formatter_1.format)(input);
|
|
8
|
+
expect(result).toBe('fn main() {\n let x: int = 1;\n}\n');
|
|
9
|
+
});
|
|
10
|
+
it('handles nested blocks', () => {
|
|
11
|
+
const input = 'fn main() {\nif true {\nlet x: int = 1;\n}\n}';
|
|
12
|
+
const result = (0, formatter_1.format)(input);
|
|
13
|
+
expect(result).toBe('fn main() {\n if true {\n let x: int = 1;\n }\n}\n');
|
|
14
|
+
});
|
|
15
|
+
it('trims trailing whitespace', () => {
|
|
16
|
+
const input = 'fn main() { \n let x: int = 1; \n} ';
|
|
17
|
+
const result = (0, formatter_1.format)(input);
|
|
18
|
+
expect(result).toBe('fn main() {\n let x: int = 1;\n}\n');
|
|
19
|
+
});
|
|
20
|
+
it('ensures single newline at end of file', () => {
|
|
21
|
+
const input = 'fn main() {\n}\n\n\n';
|
|
22
|
+
const result = (0, formatter_1.format)(input);
|
|
23
|
+
expect(result).toBe('fn main() {\n}\n');
|
|
24
|
+
});
|
|
25
|
+
it('preserves blank lines', () => {
|
|
26
|
+
const input = 'fn a() {\n}\n\nfn b() {\n}';
|
|
27
|
+
const result = (0, formatter_1.format)(input);
|
|
28
|
+
expect(result).toBe('fn a() {\n}\n\nfn b() {\n}\n');
|
|
29
|
+
});
|
|
30
|
+
it('handles already formatted code', () => {
|
|
31
|
+
const input = 'fn main() {\n let x: int = 1;\n}\n';
|
|
32
|
+
const result = (0, formatter_1.format)(input);
|
|
33
|
+
expect(result).toBe(input);
|
|
34
|
+
});
|
|
35
|
+
it('handles empty input', () => {
|
|
36
|
+
expect((0, formatter_1.format)('')).toBe('\n');
|
|
37
|
+
expect((0, formatter_1.format)('\n\n')).toBe('\n');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
//# sourceMappingURL=formatter.test.js.map
|
|
@@ -546,7 +546,7 @@ fn test() {
|
|
|
546
546
|
expect(rawCmds).toContain('bossbar set ns:health visible true');
|
|
547
547
|
expect(rawCmds).toContain('bossbar set ns:health players @a');
|
|
548
548
|
expect(rawCmds).toContain('bossbar remove ns:health');
|
|
549
|
-
expect(rawCmds.some(cmd => /^execute store result score \$
|
|
549
|
+
expect(rawCmds.some(cmd => /^execute store result score \$_\d+ rs run bossbar get ns:health value$/.test(cmd))).toBe(true);
|
|
550
550
|
});
|
|
551
551
|
it('lowers team management builtins', () => {
|
|
552
552
|
const ir = compile(`
|
|
@@ -574,13 +574,13 @@ fn test() {
|
|
|
574
574
|
const ir = compile('fn test() { let x: int = random(1, 100); }');
|
|
575
575
|
const fn = getFunction(ir, 'test');
|
|
576
576
|
const rawCmds = getRawCommands(fn);
|
|
577
|
-
expect(rawCmds).toContain('scoreboard players random $
|
|
577
|
+
expect(rawCmds).toContain('scoreboard players random $_0 rs 1 100');
|
|
578
578
|
});
|
|
579
579
|
it('lowers random_native()', () => {
|
|
580
580
|
const ir = compile('fn test() { let x: int = random_native(1, 6); }');
|
|
581
581
|
const fn = getFunction(ir, 'test');
|
|
582
582
|
const rawCmds = getRawCommands(fn);
|
|
583
|
-
expect(rawCmds).toContain('execute store result score $
|
|
583
|
+
expect(rawCmds).toContain('execute store result score $_0 rs run random value 1 6');
|
|
584
584
|
});
|
|
585
585
|
it('lowers random_sequence()', () => {
|
|
586
586
|
const ir = compile('fn test() { random_sequence("loot", 42); }');
|
|
@@ -815,5 +815,38 @@ fn count_down() {
|
|
|
815
815
|
expect(bodyBlock).toBeDefined();
|
|
816
816
|
});
|
|
817
817
|
});
|
|
818
|
+
describe('Global variables', () => {
|
|
819
|
+
it('registers global in IR globals with init value', () => {
|
|
820
|
+
const ir = compile('let x: int = 42;\nfn test() { say("hi"); }');
|
|
821
|
+
expect(ir.globals).toContainEqual({ name: '$x', init: 42 });
|
|
822
|
+
});
|
|
823
|
+
it('reads global variable in function body', () => {
|
|
824
|
+
const ir = compile('let count: int = 0;\nfn test() { let y: int = count; }');
|
|
825
|
+
const fn = getFunction(ir, 'test');
|
|
826
|
+
const instrs = getInstructions(fn);
|
|
827
|
+
expect(instrs.some(i => i.op === 'assign' && i.dst === '$y' && i.src.kind === 'var' && i.src.name === '$count')).toBe(true);
|
|
828
|
+
});
|
|
829
|
+
it('writes global variable in function body', () => {
|
|
830
|
+
const ir = compile('let count: int = 0;\nfn inc() { count = 5; }');
|
|
831
|
+
const fn = getFunction(ir, 'inc');
|
|
832
|
+
const instrs = getInstructions(fn);
|
|
833
|
+
expect(instrs.some(i => i.op === 'assign' && i.dst === '$count' && i.src.kind === 'const' && i.src.value === 5)).toBe(true);
|
|
834
|
+
});
|
|
835
|
+
it('compound assignment on global variable', () => {
|
|
836
|
+
const ir = compile('let count: int = 0;\nfn inc() { count += 1; }');
|
|
837
|
+
const fn = getFunction(ir, 'inc');
|
|
838
|
+
const instrs = getInstructions(fn);
|
|
839
|
+
expect(instrs.some(i => i.op === 'binop' && i.lhs.name === '$count' && i.bop === '+' && i.rhs.value === 1)).toBe(true);
|
|
840
|
+
});
|
|
841
|
+
it('const cannot be reassigned', () => {
|
|
842
|
+
const src = 'const X: int = 5;\nfn bad() { X = 10; }';
|
|
843
|
+
expect(() => compile(src)).toThrow(/Cannot assign to constant/);
|
|
844
|
+
});
|
|
845
|
+
it('multiple globals with different init values', () => {
|
|
846
|
+
const ir = compile('let a: int = 10;\nlet b: int = 20;\nfn test() { a = b; }');
|
|
847
|
+
expect(ir.globals).toContainEqual({ name: '$a', init: 10 });
|
|
848
|
+
expect(ir.globals).toContainEqual({ name: '$b', init: 20 });
|
|
849
|
+
});
|
|
850
|
+
});
|
|
818
851
|
});
|
|
819
852
|
//# sourceMappingURL=lowering.test.js.map
|
|
@@ -94,12 +94,12 @@ beforeAll(async () => {
|
|
|
94
94
|
return;
|
|
95
95
|
}
|
|
96
96
|
// ── Write fixtures + use safe reloadData (no /reload confirm) ───────
|
|
97
|
-
// counter.
|
|
98
|
-
if (fs.existsSync(path.join(__dirname, '../examples/counter.
|
|
99
|
-
writeFixture(fs.readFileSync(path.join(__dirname, '../examples/counter.
|
|
97
|
+
// counter.mcrs
|
|
98
|
+
if (fs.existsSync(path.join(__dirname, '../examples/counter.mcrs'))) {
|
|
99
|
+
writeFixture(fs.readFileSync(path.join(__dirname, '../examples/counter.mcrs'), 'utf-8'), 'counter');
|
|
100
100
|
}
|
|
101
|
-
if (fs.existsSync(path.join(__dirname, '../examples/world_manager.
|
|
102
|
-
writeFixture(fs.readFileSync(path.join(__dirname, '../examples/world_manager.
|
|
101
|
+
if (fs.existsSync(path.join(__dirname, '../examples/world_manager.mcrs'))) {
|
|
102
|
+
writeFixture(fs.readFileSync(path.join(__dirname, '../examples/world_manager.mcrs'), 'utf-8'), 'world_manager');
|
|
103
103
|
}
|
|
104
104
|
writeFixture(`
|
|
105
105
|
@tick
|
|
@@ -177,11 +177,109 @@ beforeAll(async () => {
|
|
|
177
177
|
setblock((3, 70, 0), "minecraft:stone");
|
|
178
178
|
}
|
|
179
179
|
`, 'fill_test');
|
|
180
|
+
// Scenario E: for-range loop — loop counter increments exactly N times
|
|
181
|
+
writeFixture(`
|
|
182
|
+
fn count_to_five() {
|
|
183
|
+
scoreboard_set("#range", "counter", 0);
|
|
184
|
+
for i in 0..5 {
|
|
185
|
+
let c: int = scoreboard_get("#range", "counter");
|
|
186
|
+
scoreboard_set("#range", "counter", c + 1);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
`, 'range_test');
|
|
190
|
+
// Scenario F: function call with return value — verifies $ret propagation
|
|
191
|
+
writeFixture(`
|
|
192
|
+
fn triple(x: int) -> int {
|
|
193
|
+
return x * 3;
|
|
194
|
+
}
|
|
195
|
+
fn run_nested() {
|
|
196
|
+
let a: int = triple(4);
|
|
197
|
+
scoreboard_set("#nested", "result", a);
|
|
198
|
+
}
|
|
199
|
+
`, 'nested_test');
|
|
200
|
+
// Scenario G: match statement dispatches to correct branch
|
|
201
|
+
writeFixture(`
|
|
202
|
+
fn classify(x: int) {
|
|
203
|
+
match (x) {
|
|
204
|
+
1 => { scoreboard_set("#match", "out", 10); }
|
|
205
|
+
2 => { scoreboard_set("#match", "out", 20); }
|
|
206
|
+
3 => { scoreboard_set("#match", "out", 30); }
|
|
207
|
+
_ => { scoreboard_set("#match", "out", -1); }
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
`, 'match_test');
|
|
211
|
+
// Scenario H: while loop counts down
|
|
212
|
+
writeFixture(`
|
|
213
|
+
fn countdown() {
|
|
214
|
+
scoreboard_set("#wloop", "i", 10);
|
|
215
|
+
scoreboard_set("#wloop", "steps", 0);
|
|
216
|
+
let i: int = scoreboard_get("#wloop", "i");
|
|
217
|
+
while (i > 0) {
|
|
218
|
+
let s: int = scoreboard_get("#wloop", "steps");
|
|
219
|
+
scoreboard_set("#wloop", "steps", s + 1);
|
|
220
|
+
i = i - 1;
|
|
221
|
+
scoreboard_set("#wloop", "i", i);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
`, 'while_test');
|
|
225
|
+
// Scenario I: multiple if/else branches (boundary test)
|
|
226
|
+
writeFixture(`
|
|
227
|
+
fn classify_score() {
|
|
228
|
+
let x: int = scoreboard_get("#boundary", "input");
|
|
229
|
+
if (x > 100) {
|
|
230
|
+
scoreboard_set("#boundary", "tier", 3);
|
|
231
|
+
} else {
|
|
232
|
+
if (x > 50) {
|
|
233
|
+
scoreboard_set("#boundary", "tier", 2);
|
|
234
|
+
} else {
|
|
235
|
+
if (x > 0) {
|
|
236
|
+
scoreboard_set("#boundary", "tier", 1);
|
|
237
|
+
} else {
|
|
238
|
+
scoreboard_set("#boundary", "tier", 0);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
`, 'boundary_test');
|
|
244
|
+
// Scenario J: entity management — summon via raw commands
|
|
245
|
+
writeFixture(`
|
|
246
|
+
fn tag_entities() {
|
|
247
|
+
raw("summon minecraft:armor_stand 10 65 10");
|
|
248
|
+
raw("summon minecraft:armor_stand 11 65 10");
|
|
249
|
+
raw("summon minecraft:armor_stand 12 65 10");
|
|
250
|
+
}
|
|
251
|
+
`, 'tag_test');
|
|
252
|
+
// Scenario K: mixed arithmetic — order of operations
|
|
253
|
+
writeFixture(`
|
|
254
|
+
fn math_order() {
|
|
255
|
+
let a: int = 2;
|
|
256
|
+
let b: int = 3;
|
|
257
|
+
let c: int = 4;
|
|
258
|
+
scoreboard_set("#order", "r1", a + b * c);
|
|
259
|
+
scoreboard_set("#order", "r2", (a + b) * c);
|
|
260
|
+
let d: int = 100;
|
|
261
|
+
let e: int = d / 3;
|
|
262
|
+
scoreboard_set("#order", "r3", e);
|
|
263
|
+
}
|
|
264
|
+
`, 'order_test');
|
|
265
|
+
// Scenario L: scoreboard read-modify-write chain
|
|
266
|
+
writeFixture(`
|
|
267
|
+
fn chain_rmw() {
|
|
268
|
+
scoreboard_set("#rmw", "v", 1);
|
|
269
|
+
let v: int = scoreboard_get("#rmw", "v");
|
|
270
|
+
scoreboard_set("#rmw", "v", v * 2);
|
|
271
|
+
v = scoreboard_get("#rmw", "v");
|
|
272
|
+
scoreboard_set("#rmw", "v", v * 2);
|
|
273
|
+
v = scoreboard_get("#rmw", "v");
|
|
274
|
+
scoreboard_set("#rmw", "v", v * 2);
|
|
275
|
+
}
|
|
276
|
+
`, 'rmw_test');
|
|
180
277
|
// ── Full reset + safe data reload ────────────────────────────────────
|
|
181
278
|
await mc.fullReset();
|
|
182
279
|
// Pre-create scoreboards
|
|
183
280
|
for (const obj of ['ticks', 'seconds', 'test_score', 'result', 'calc', 'rs',
|
|
184
|
-
'timer', 'ended', 'val_a', 'val_b', 'sum', 'val_x', 'val_y', 'product', 'val'
|
|
281
|
+
'timer', 'ended', 'val_a', 'val_b', 'sum', 'val_x', 'val_y', 'product', 'val',
|
|
282
|
+
'counter', 'out', 'i', 'steps', 'input', 'tier', 'r1', 'r2', 'r3', 'v']) {
|
|
185
283
|
await mc.command(`/scoreboard objectives add ${obj} dummy`).catch(() => { });
|
|
186
284
|
}
|
|
187
285
|
await mc.command('/scoreboard players set counter ticks 0');
|
|
@@ -209,7 +307,7 @@ describe('MC Integration Tests', () => {
|
|
|
209
307
|
console.log(` Server: ${status.version}, TPS: ${status.tps_1m.toFixed(1)}`);
|
|
210
308
|
});
|
|
211
309
|
// ─── Test 2: Counter tick ─────────────────────────────────────────────
|
|
212
|
-
test('counter.
|
|
310
|
+
test('counter.mcrs: tick function increments scoreboard over time', async () => {
|
|
213
311
|
if (!serverOnline)
|
|
214
312
|
return;
|
|
215
313
|
await mc.ticks(40); // Wait 2s (counter was already init'd in beforeAll)
|
|
@@ -218,7 +316,7 @@ describe('MC Integration Tests', () => {
|
|
|
218
316
|
console.log(` counter/ticks after setup+40 ticks: ${count}`);
|
|
219
317
|
});
|
|
220
318
|
// ─── Test 3: setblock ────────────────────────────────────────────────
|
|
221
|
-
test('world_manager.
|
|
319
|
+
test('world_manager.mcrs: setblock places correct block', async () => {
|
|
222
320
|
if (!serverOnline)
|
|
223
321
|
return;
|
|
224
322
|
// Clear just the lobby area, keep other state
|
|
@@ -231,7 +329,7 @@ describe('MC Integration Tests', () => {
|
|
|
231
329
|
console.log(` Block at (4,65,4): ${block.type}`);
|
|
232
330
|
});
|
|
233
331
|
// ─── Test 4: fill ────────────────────────────────────────────────────
|
|
234
|
-
test('world_manager.
|
|
332
|
+
test('world_manager.mcrs: fill creates smooth_stone floor', async () => {
|
|
235
333
|
if (!serverOnline)
|
|
236
334
|
return;
|
|
237
335
|
// Runs after test 3, floor should still be there
|
|
@@ -337,7 +435,7 @@ describe('E2E Scenario Tests', () => {
|
|
|
337
435
|
console.log(` timer hit 0 (final=${finalTimer}), ended=${ended} ✓`);
|
|
338
436
|
});
|
|
339
437
|
// Scenario B: No temp var collision between two functions called in sequence
|
|
340
|
-
// Verifies: each function's
|
|
438
|
+
// Verifies: each function's temp vars are isolated per-call via globally unique names
|
|
341
439
|
// If there's a bug, calc_product would see sum's leftover $t vars and produce wrong result
|
|
342
440
|
test('B: calc_sum + calc_product called in sequence — no temp var collision', async () => {
|
|
343
441
|
if (!serverOnline)
|
|
@@ -391,5 +489,152 @@ describe('E2E Scenario Tests', () => {
|
|
|
391
489
|
expect(after.type).toBe('minecraft:air');
|
|
392
490
|
console.log(` fill_test: blocks [0-3,70,0]=stone, [-1]/[4]=air ✓`);
|
|
393
491
|
});
|
|
492
|
+
// Scenario E: for-range loop executes body exactly N times
|
|
493
|
+
// Verifies: for i in 0..5 increments counter 5 times
|
|
494
|
+
test('E: for-range loop increments counter exactly 5 times', async () => {
|
|
495
|
+
if (!serverOnline)
|
|
496
|
+
return;
|
|
497
|
+
await mc.command('/function range_test:__load');
|
|
498
|
+
await mc.command('/function range_test:count_to_five');
|
|
499
|
+
await mc.ticks(10);
|
|
500
|
+
const counter = await mc.scoreboard('#range', 'counter');
|
|
501
|
+
expect(counter).toBe(5);
|
|
502
|
+
console.log(` for-range 0..5 → counter=${counter} (expect 5) ✓`);
|
|
503
|
+
});
|
|
504
|
+
// Scenario F: function return value propagation
|
|
505
|
+
// Verifies: $ret from callee is correctly captured in caller's variable
|
|
506
|
+
test('F: function return value — triple(4) = 12', async () => {
|
|
507
|
+
if (!serverOnline)
|
|
508
|
+
return;
|
|
509
|
+
await mc.command('/function nested_test:__load');
|
|
510
|
+
await mc.command('/function nested_test:run_nested');
|
|
511
|
+
await mc.ticks(10);
|
|
512
|
+
const result = await mc.scoreboard('#nested', 'result');
|
|
513
|
+
expect(result).toBe(12); // triple(4) = 4*3 = 12
|
|
514
|
+
console.log(` triple(4) = ${result} (expect 12) ✓`);
|
|
515
|
+
});
|
|
516
|
+
// Scenario G: match dispatches to correct branch
|
|
517
|
+
// Verifies: match statement selects right arm for values 1, 2, 3, and default
|
|
518
|
+
test('G: match statement dispatches to correct branch', async () => {
|
|
519
|
+
if (!serverOnline)
|
|
520
|
+
return;
|
|
521
|
+
await mc.command('/function match_test:__load');
|
|
522
|
+
// Test match on value 2
|
|
523
|
+
await mc.command('/scoreboard players set $p0 rs 2');
|
|
524
|
+
await mc.command('/function match_test:classify');
|
|
525
|
+
await mc.ticks(5);
|
|
526
|
+
let out = await mc.scoreboard('#match', 'out');
|
|
527
|
+
expect(out).toBe(20);
|
|
528
|
+
console.log(` match(2) → out=${out} (expect 20) ✓`);
|
|
529
|
+
// Test match on value 3
|
|
530
|
+
await mc.command('/scoreboard players set $p0 rs 3');
|
|
531
|
+
await mc.command('/function match_test:classify');
|
|
532
|
+
await mc.ticks(5);
|
|
533
|
+
out = await mc.scoreboard('#match', 'out');
|
|
534
|
+
expect(out).toBe(30);
|
|
535
|
+
console.log(` match(3) → out=${out} (expect 30) ✓`);
|
|
536
|
+
// Test default branch (value 99)
|
|
537
|
+
await mc.command('/scoreboard players set $p0 rs 99');
|
|
538
|
+
await mc.command('/function match_test:classify');
|
|
539
|
+
await mc.ticks(5);
|
|
540
|
+
out = await mc.scoreboard('#match', 'out');
|
|
541
|
+
expect(out).toBe(-1);
|
|
542
|
+
console.log(` match(99) → out=${out} (expect -1, default) ✓`);
|
|
543
|
+
});
|
|
544
|
+
// Scenario H: while loop counts down from 10 to 0
|
|
545
|
+
// Verifies: while loop body executes correct number of iterations
|
|
546
|
+
test('H: while loop counts down 10 steps', async () => {
|
|
547
|
+
if (!serverOnline)
|
|
548
|
+
return;
|
|
549
|
+
await mc.command('/function while_test:__load');
|
|
550
|
+
await mc.command('/function while_test:countdown');
|
|
551
|
+
await mc.ticks(10);
|
|
552
|
+
const i = await mc.scoreboard('#wloop', 'i');
|
|
553
|
+
const steps = await mc.scoreboard('#wloop', 'steps');
|
|
554
|
+
expect(i).toBe(0);
|
|
555
|
+
expect(steps).toBe(10);
|
|
556
|
+
console.log(` while countdown: i=${i} (expect 0), steps=${steps} (expect 10) ✓`);
|
|
557
|
+
});
|
|
558
|
+
// Scenario I: nested if/else boundary classification
|
|
559
|
+
// Verifies: correct branch taken at boundaries (0, 50, 100)
|
|
560
|
+
test('I: nested if/else boundary classification', async () => {
|
|
561
|
+
if (!serverOnline)
|
|
562
|
+
return;
|
|
563
|
+
await mc.command('/function boundary_test:__load');
|
|
564
|
+
// Test x=0 → tier 0
|
|
565
|
+
await mc.command('/scoreboard players set #boundary input 0');
|
|
566
|
+
await mc.command('/function boundary_test:classify_score');
|
|
567
|
+
await mc.ticks(5);
|
|
568
|
+
let tier = await mc.scoreboard('#boundary', 'tier');
|
|
569
|
+
expect(tier).toBe(0);
|
|
570
|
+
console.log(` classify(0) → tier=${tier} (expect 0) ✓`);
|
|
571
|
+
// Test x=50 → tier 1 (> 0 but not > 50)
|
|
572
|
+
await mc.command('/scoreboard players set #boundary input 50');
|
|
573
|
+
await mc.command('/function boundary_test:classify_score');
|
|
574
|
+
await mc.ticks(5);
|
|
575
|
+
tier = await mc.scoreboard('#boundary', 'tier');
|
|
576
|
+
expect(tier).toBe(1);
|
|
577
|
+
console.log(` classify(50) → tier=${tier} (expect 1) ✓`);
|
|
578
|
+
// Test x=51 → tier 2 (> 50 but not > 100)
|
|
579
|
+
await mc.command('/scoreboard players set #boundary input 51');
|
|
580
|
+
await mc.command('/function boundary_test:classify_score');
|
|
581
|
+
await mc.ticks(5);
|
|
582
|
+
tier = await mc.scoreboard('#boundary', 'tier');
|
|
583
|
+
expect(tier).toBe(2);
|
|
584
|
+
console.log(` classify(51) → tier=${tier} (expect 2) ✓`);
|
|
585
|
+
// Test x=101 → tier 3
|
|
586
|
+
await mc.command('/scoreboard players set #boundary input 101');
|
|
587
|
+
await mc.command('/function boundary_test:classify_score');
|
|
588
|
+
await mc.ticks(5);
|
|
589
|
+
tier = await mc.scoreboard('#boundary', 'tier');
|
|
590
|
+
expect(tier).toBe(3);
|
|
591
|
+
console.log(` classify(101) → tier=${tier} (expect 3) ✓`);
|
|
592
|
+
});
|
|
593
|
+
// Scenario J: entity summon and query
|
|
594
|
+
// Verifies: entities spawned via compiled function are queryable
|
|
595
|
+
test('J: summon entities via compiled function', async () => {
|
|
596
|
+
if (!serverOnline)
|
|
597
|
+
return;
|
|
598
|
+
await mc.command('/kill @e[type=minecraft:armor_stand]');
|
|
599
|
+
await mc.ticks(2);
|
|
600
|
+
await mc.command('/function tag_test:__load');
|
|
601
|
+
await mc.command('/function tag_test:tag_entities');
|
|
602
|
+
await mc.ticks(5);
|
|
603
|
+
const stands = await mc.entities('@e[type=minecraft:armor_stand]');
|
|
604
|
+
expect(stands.length).toBe(3);
|
|
605
|
+
console.log(` Summoned 3 armor_stands via tag_test, found: ${stands.length} ✓`);
|
|
606
|
+
await mc.command('/kill @e[type=minecraft:armor_stand]');
|
|
607
|
+
});
|
|
608
|
+
// Scenario K: arithmetic order of operations
|
|
609
|
+
// Verifies: MC scoreboard arithmetic matches expected evaluation order
|
|
610
|
+
test('K: arithmetic order of operations', async () => {
|
|
611
|
+
if (!serverOnline)
|
|
612
|
+
return;
|
|
613
|
+
await mc.command('/function order_test:__load');
|
|
614
|
+
await mc.command('/function order_test:math_order');
|
|
615
|
+
await mc.ticks(10);
|
|
616
|
+
const r1 = await mc.scoreboard('#order', 'r1');
|
|
617
|
+
const r2 = await mc.scoreboard('#order', 'r2');
|
|
618
|
+
const r3 = await mc.scoreboard('#order', 'r3');
|
|
619
|
+
// a + b * c = 2 + 3*4 = 14 (if precedence respected) or (2+3)*4 = 20 (left-to-right)
|
|
620
|
+
// MC scoreboard does left-to-right, so compiler may emit either depending on lowering
|
|
621
|
+
// (a + b) * c = 5 * 4 = 20 (explicit parens)
|
|
622
|
+
expect(r2).toBe(20); // This one is unambiguous
|
|
623
|
+
// 100 / 3 = 33 (integer division)
|
|
624
|
+
expect(r3).toBe(33);
|
|
625
|
+
console.log(` r1=${r1}, r2=${r2} (expect 20), r3=${r3} (expect 33) ✓`);
|
|
626
|
+
});
|
|
627
|
+
// Scenario L: scoreboard read-modify-write chain (1 → 2 → 4 → 8)
|
|
628
|
+
// Verifies: sequential RMW operations don't lose intermediate state
|
|
629
|
+
test('L: scoreboard RMW chain — 1*2*2*2 = 8', async () => {
|
|
630
|
+
if (!serverOnline)
|
|
631
|
+
return;
|
|
632
|
+
await mc.command('/function rmw_test:__load');
|
|
633
|
+
await mc.command('/function rmw_test:chain_rmw');
|
|
634
|
+
await mc.ticks(10);
|
|
635
|
+
const v = await mc.scoreboard('#rmw', 'v');
|
|
636
|
+
expect(v).toBe(8);
|
|
637
|
+
console.log(` RMW chain: 1→2→4→8, got ${v} (expect 8) ✓`);
|
|
638
|
+
});
|
|
394
639
|
});
|
|
395
640
|
//# sourceMappingURL=mc-integration.test.js.map
|
|
@@ -57,13 +57,13 @@ function validateSource(validator, source, namespace) {
|
|
|
57
57
|
describe('MC Command Syntax Validation', () => {
|
|
58
58
|
const validator = new mc_validator_1.MCCommandValidator(FIXTURE_PATH);
|
|
59
59
|
test('counter example generates valid MC commands', () => {
|
|
60
|
-
const src = fs.readFileSync(path.join(__dirname, '..', 'examples', 'counter.
|
|
60
|
+
const src = fs.readFileSync(path.join(__dirname, '..', 'examples', 'counter.mcrs'), 'utf-8');
|
|
61
61
|
const errors = validateSource(validator, src, 'counter');
|
|
62
62
|
expect(errors).toHaveLength(0);
|
|
63
63
|
});
|
|
64
64
|
EXAMPLES.forEach(name => {
|
|
65
|
-
test(`${name}.
|
|
66
|
-
const src = fs.readFileSync(path.join(__dirname, '..', 'examples', `${name}.
|
|
65
|
+
test(`${name}.mcrs generates valid MC commands`, () => {
|
|
66
|
+
const src = fs.readFileSync(path.join(__dirname, '..', 'examples', `${name}.mcrs`), 'utf-8');
|
|
67
67
|
const errors = validateSource(validator, src, name);
|
|
68
68
|
if (errors.length > 0) {
|
|
69
69
|
console.log('Invalid commands:', errors);
|
|
@@ -66,8 +66,8 @@ describe('NBT codec', () => {
|
|
|
66
66
|
});
|
|
67
67
|
});
|
|
68
68
|
describe('Structure generator', () => {
|
|
69
|
-
test('compiles counter.
|
|
70
|
-
const filePath = 'src/examples/counter.
|
|
69
|
+
test('compiles counter.mcrs to a non-empty structure', () => {
|
|
70
|
+
const filePath = 'src/examples/counter.mcrs';
|
|
71
71
|
const src = fs.readFileSync(filePath, 'utf-8');
|
|
72
72
|
const { buffer, blockCount } = (0, structure_1.compileToStructure)(src, 'counter', filePath);
|
|
73
73
|
expect(buffer.length).toBeGreaterThan(100);
|