redscript-mc 1.2.30 → 2.0.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/.claude/commands/build-test.md +10 -0
- package/.claude/commands/deploy-demo.md +12 -0
- package/.claude/commands/stage-status.md +13 -0
- package/.claude/settings.json +12 -0
- package/.github/workflows/ci.yml +1 -0
- package/CLAUDE.md +231 -0
- package/demo.gif +0 -0
- package/dist/cli.js +2 -554
- package/dist/compile.js +2 -266
- package/dist/index.js +2 -159
- package/dist/lowering/index.js +5 -3
- package/dist/src/__tests__/cli.test.d.ts +1 -0
- package/dist/src/__tests__/cli.test.js +104 -0
- package/dist/src/__tests__/codegen.test.d.ts +1 -0
- package/dist/src/__tests__/codegen.test.js +152 -0
- package/dist/src/__tests__/compile-all.test.d.ts +10 -0
- package/dist/src/__tests__/compile-all.test.js +108 -0
- package/dist/src/__tests__/dce.test.d.ts +1 -0
- package/dist/src/__tests__/dce.test.js +102 -0
- package/dist/src/__tests__/diagnostics.test.d.ts +4 -0
- package/dist/src/__tests__/diagnostics.test.js +177 -0
- package/dist/src/__tests__/e2e.test.d.ts +6 -0
- package/dist/src/__tests__/e2e.test.js +1789 -0
- package/dist/src/__tests__/entity-types.test.d.ts +1 -0
- package/dist/src/__tests__/entity-types.test.js +203 -0
- package/dist/src/__tests__/formatter.test.d.ts +1 -0
- package/dist/src/__tests__/formatter.test.js +40 -0
- package/dist/src/__tests__/lexer.test.d.ts +1 -0
- package/dist/src/__tests__/lexer.test.js +343 -0
- package/dist/src/__tests__/lowering.test.d.ts +1 -0
- package/dist/src/__tests__/lowering.test.js +1015 -0
- package/dist/src/__tests__/macro.test.d.ts +8 -0
- package/dist/src/__tests__/macro.test.js +306 -0
- package/dist/src/__tests__/mc-integration.test.d.ts +12 -0
- package/dist/src/__tests__/mc-integration.test.js +817 -0
- package/dist/src/__tests__/mc-syntax.test.d.ts +1 -0
- package/dist/src/__tests__/mc-syntax.test.js +124 -0
- package/dist/src/__tests__/nbt.test.d.ts +1 -0
- package/dist/src/__tests__/nbt.test.js +82 -0
- package/dist/src/__tests__/optimizer-advanced.test.d.ts +1 -0
- package/dist/src/__tests__/optimizer-advanced.test.js +124 -0
- package/dist/src/__tests__/optimizer.test.d.ts +1 -0
- package/dist/src/__tests__/optimizer.test.js +149 -0
- package/dist/src/__tests__/parser.test.d.ts +1 -0
- package/dist/src/__tests__/parser.test.js +807 -0
- package/dist/src/__tests__/repl.test.d.ts +1 -0
- package/dist/src/__tests__/repl.test.js +27 -0
- package/dist/src/__tests__/runtime.test.d.ts +1 -0
- package/dist/src/__tests__/runtime.test.js +289 -0
- package/dist/src/__tests__/stdlib-advanced.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib-advanced.test.js +374 -0
- package/dist/src/__tests__/stdlib-bigint.test.d.ts +7 -0
- package/dist/src/__tests__/stdlib-bigint.test.js +426 -0
- package/dist/src/__tests__/stdlib-math.test.d.ts +7 -0
- package/dist/src/__tests__/stdlib-math.test.js +351 -0
- package/dist/src/__tests__/stdlib-vec.test.d.ts +4 -0
- package/dist/src/__tests__/stdlib-vec.test.js +263 -0
- package/dist/src/__tests__/structure-optimizer.test.d.ts +1 -0
- package/dist/src/__tests__/structure-optimizer.test.js +33 -0
- package/dist/src/__tests__/typechecker.test.d.ts +1 -0
- package/dist/src/__tests__/typechecker.test.js +552 -0
- package/dist/src/__tests__/var-allocator.test.d.ts +1 -0
- package/dist/src/__tests__/var-allocator.test.js +69 -0
- package/dist/src/ast/types.d.ts +515 -0
- package/dist/src/ast/types.js +9 -0
- package/dist/src/builtins/metadata.d.ts +36 -0
- package/dist/src/builtins/metadata.js +1014 -0
- package/dist/src/cli.d.ts +11 -0
- package/dist/src/cli.js +443 -0
- package/dist/src/codegen/cmdblock/index.d.ts +26 -0
- package/dist/src/codegen/cmdblock/index.js +45 -0
- package/dist/src/codegen/mcfunction/index.d.ts +40 -0
- package/dist/src/codegen/mcfunction/index.js +606 -0
- package/dist/src/codegen/structure/index.d.ts +24 -0
- package/dist/src/codegen/structure/index.js +279 -0
- package/dist/src/codegen/var-allocator.d.ts +45 -0
- package/dist/src/codegen/var-allocator.js +104 -0
- package/dist/src/compile.d.ts +37 -0
- package/dist/src/compile.js +165 -0
- package/dist/src/diagnostics/index.d.ts +44 -0
- package/dist/src/diagnostics/index.js +140 -0
- package/dist/src/events/types.d.ts +35 -0
- package/dist/src/events/types.js +59 -0
- package/dist/src/formatter/index.d.ts +1 -0
- package/dist/src/formatter/index.js +26 -0
- package/dist/src/index.d.ts +22 -0
- package/dist/src/index.js +45 -0
- package/dist/src/ir/builder.d.ts +33 -0
- package/dist/src/ir/builder.js +99 -0
- package/dist/src/ir/types.d.ts +132 -0
- package/dist/src/ir/types.js +15 -0
- package/dist/src/lexer/index.d.ts +37 -0
- package/dist/src/lexer/index.js +569 -0
- package/dist/src/lowering/index.d.ts +188 -0
- package/dist/src/lowering/index.js +3405 -0
- package/dist/src/mc-test/client.d.ts +128 -0
- package/dist/src/mc-test/client.js +174 -0
- package/dist/src/mc-test/runner.d.ts +28 -0
- package/dist/src/mc-test/runner.js +151 -0
- package/dist/src/mc-test/setup.d.ts +11 -0
- package/dist/src/mc-test/setup.js +98 -0
- package/dist/src/mc-validator/index.d.ts +17 -0
- package/dist/src/mc-validator/index.js +322 -0
- package/dist/src/nbt/index.d.ts +86 -0
- package/dist/src/nbt/index.js +250 -0
- package/dist/src/optimizer/commands.d.ts +38 -0
- package/dist/src/optimizer/commands.js +451 -0
- package/dist/src/optimizer/dce.d.ts +34 -0
- package/dist/src/optimizer/dce.js +639 -0
- package/dist/src/optimizer/passes.d.ts +34 -0
- package/dist/src/optimizer/passes.js +243 -0
- package/dist/src/optimizer/structure.d.ts +9 -0
- package/dist/src/optimizer/structure.js +356 -0
- package/dist/src/parser/index.d.ts +93 -0
- package/dist/src/parser/index.js +1687 -0
- package/dist/src/repl.d.ts +16 -0
- package/dist/src/repl.js +165 -0
- package/dist/src/runtime/index.d.ts +107 -0
- package/dist/src/runtime/index.js +1409 -0
- package/dist/src/typechecker/index.d.ts +61 -0
- package/dist/src/typechecker/index.js +1034 -0
- package/dist/src/types/entity-hierarchy.d.ts +29 -0
- package/dist/src/types/entity-hierarchy.js +107 -0
- package/dist/src2/__tests__/e2e/basic.test.d.ts +8 -0
- package/dist/src2/__tests__/e2e/basic.test.js +140 -0
- package/dist/src2/__tests__/e2e/macros.test.d.ts +9 -0
- package/dist/src2/__tests__/e2e/macros.test.js +182 -0
- package/dist/src2/__tests__/e2e/migrate.test.d.ts +13 -0
- package/dist/src2/__tests__/e2e/migrate.test.js +2739 -0
- package/dist/src2/__tests__/hir/desugar.test.d.ts +1 -0
- package/dist/src2/__tests__/hir/desugar.test.js +234 -0
- package/dist/src2/__tests__/lir/lower.test.d.ts +1 -0
- package/dist/src2/__tests__/lir/lower.test.js +559 -0
- package/dist/src2/__tests__/lir/types.test.d.ts +1 -0
- package/dist/src2/__tests__/lir/types.test.js +185 -0
- package/dist/src2/__tests__/lir/verify.test.d.ts +1 -0
- package/dist/src2/__tests__/lir/verify.test.js +221 -0
- package/dist/src2/__tests__/mir/arithmetic.test.d.ts +1 -0
- package/dist/src2/__tests__/mir/arithmetic.test.js +130 -0
- package/dist/src2/__tests__/mir/control-flow.test.d.ts +1 -0
- package/dist/src2/__tests__/mir/control-flow.test.js +205 -0
- package/dist/src2/__tests__/mir/verify.test.d.ts +1 -0
- package/dist/src2/__tests__/mir/verify.test.js +223 -0
- package/dist/src2/__tests__/optimizer/block_merge.test.d.ts +1 -0
- package/dist/src2/__tests__/optimizer/block_merge.test.js +78 -0
- package/dist/src2/__tests__/optimizer/branch_simplify.test.d.ts +1 -0
- package/dist/src2/__tests__/optimizer/branch_simplify.test.js +58 -0
- package/dist/src2/__tests__/optimizer/constant_fold.test.d.ts +1 -0
- package/dist/src2/__tests__/optimizer/constant_fold.test.js +131 -0
- package/dist/src2/__tests__/optimizer/copy_prop.test.d.ts +1 -0
- package/dist/src2/__tests__/optimizer/copy_prop.test.js +91 -0
- package/dist/src2/__tests__/optimizer/dce.test.d.ts +1 -0
- package/dist/src2/__tests__/optimizer/dce.test.js +76 -0
- package/dist/src2/__tests__/optimizer/pipeline.test.d.ts +1 -0
- package/dist/src2/__tests__/optimizer/pipeline.test.js +102 -0
- package/dist/src2/emit/compile.d.ts +19 -0
- package/dist/src2/emit/compile.js +80 -0
- package/dist/src2/emit/index.d.ts +17 -0
- package/dist/src2/emit/index.js +172 -0
- package/dist/src2/hir/lower.d.ts +15 -0
- package/dist/src2/hir/lower.js +378 -0
- package/dist/src2/hir/types.d.ts +373 -0
- package/dist/src2/hir/types.js +16 -0
- package/dist/src2/lir/lower.d.ts +15 -0
- package/dist/src2/lir/lower.js +453 -0
- package/dist/src2/lir/types.d.ts +136 -0
- package/dist/src2/lir/types.js +11 -0
- package/dist/src2/lir/verify.d.ts +14 -0
- package/dist/src2/lir/verify.js +113 -0
- package/dist/src2/mir/lower.d.ts +9 -0
- package/dist/src2/mir/lower.js +1030 -0
- package/dist/src2/mir/macro.d.ts +22 -0
- package/dist/src2/mir/macro.js +168 -0
- package/dist/src2/mir/types.d.ts +183 -0
- package/dist/src2/mir/types.js +11 -0
- package/dist/src2/mir/verify.d.ts +16 -0
- package/dist/src2/mir/verify.js +216 -0
- package/dist/src2/optimizer/block_merge.d.ts +12 -0
- package/dist/src2/optimizer/block_merge.js +84 -0
- package/dist/src2/optimizer/branch_simplify.d.ts +9 -0
- package/dist/src2/optimizer/branch_simplify.js +28 -0
- package/dist/src2/optimizer/constant_fold.d.ts +10 -0
- package/dist/src2/optimizer/constant_fold.js +85 -0
- package/dist/src2/optimizer/copy_prop.d.ts +9 -0
- package/dist/src2/optimizer/copy_prop.js +113 -0
- package/dist/src2/optimizer/dce.d.ts +8 -0
- package/dist/src2/optimizer/dce.js +155 -0
- package/dist/src2/optimizer/pipeline.d.ts +10 -0
- package/dist/src2/optimizer/pipeline.js +42 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/docs/compiler-pipeline-redesign.md +2243 -0
- package/docs/optimization-ideas.md +1076 -0
- package/editors/vscode/package-lock.json +3 -3
- package/editors/vscode/package.json +1 -1
- package/jest.config.js +1 -1
- package/package.json +6 -5
- package/scripts/postbuild.js +15 -0
- package/src/__tests__/cli.test.ts +8 -220
- package/src/__tests__/dce.test.ts +11 -56
- package/src/__tests__/diagnostics.test.ts +59 -38
- package/src/__tests__/mc-integration.test.ts +1 -2
- package/src/ast/types.ts +6 -1
- package/src/cli.ts +29 -156
- package/src/compile.ts +6 -162
- package/src/index.ts +14 -178
- package/src/mc-test/runner.ts +4 -3
- package/src/parser/index.ts +1 -1
- package/src/repl.ts +1 -1
- package/src/runtime/index.ts +1 -1
- package/src2/__tests__/e2e/basic.test.ts +154 -0
- package/src2/__tests__/e2e/macros.test.ts +199 -0
- package/src2/__tests__/e2e/migrate.test.ts +3008 -0
- package/src2/__tests__/hir/desugar.test.ts +263 -0
- package/src2/__tests__/lir/lower.test.ts +619 -0
- package/src2/__tests__/lir/types.test.ts +207 -0
- package/src2/__tests__/lir/verify.test.ts +249 -0
- package/src2/__tests__/mir/arithmetic.test.ts +156 -0
- package/src2/__tests__/mir/control-flow.test.ts +242 -0
- package/src2/__tests__/mir/verify.test.ts +254 -0
- package/src2/__tests__/optimizer/block_merge.test.ts +84 -0
- package/src2/__tests__/optimizer/branch_simplify.test.ts +64 -0
- package/src2/__tests__/optimizer/constant_fold.test.ts +145 -0
- package/src2/__tests__/optimizer/copy_prop.test.ts +99 -0
- package/src2/__tests__/optimizer/dce.test.ts +83 -0
- package/src2/__tests__/optimizer/pipeline.test.ts +116 -0
- package/src2/emit/compile.ts +99 -0
- package/src2/emit/index.ts +222 -0
- package/src2/hir/lower.ts +428 -0
- package/src2/hir/types.ts +216 -0
- package/src2/lir/lower.ts +556 -0
- package/src2/lir/types.ts +109 -0
- package/src2/lir/verify.ts +129 -0
- package/src2/mir/lower.ts +1160 -0
- package/src2/mir/macro.ts +167 -0
- package/src2/mir/types.ts +106 -0
- package/src2/mir/verify.ts +218 -0
- package/src2/optimizer/block_merge.ts +93 -0
- package/src2/optimizer/branch_simplify.ts +27 -0
- package/src2/optimizer/constant_fold.ts +88 -0
- package/src2/optimizer/copy_prop.ts +106 -0
- package/src2/optimizer/dce.ts +133 -0
- package/src2/optimizer/pipeline.ts +44 -0
- package/tsconfig.json +2 -2
- package/src/__tests__/codegen.test.ts +0 -161
- package/src/__tests__/e2e.test.ts +0 -2039
- package/src/__tests__/entity-types.test.ts +0 -236
- package/src/__tests__/lowering.test.ts +0 -1185
- package/src/__tests__/macro.test.ts +0 -343
- package/src/__tests__/nbt.test.ts +0 -58
- package/src/__tests__/optimizer-advanced.test.ts +0 -144
- package/src/__tests__/optimizer.test.ts +0 -162
- package/src/__tests__/runtime.test.ts +0 -305
- package/src/__tests__/stdlib-advanced.test.ts +0 -379
- package/src/__tests__/stdlib-bigint.test.ts +0 -427
- package/src/__tests__/stdlib-math.test.ts +0 -374
- package/src/__tests__/stdlib-vec.test.ts +0 -259
- package/src/__tests__/structure-optimizer.test.ts +0 -38
- package/src/__tests__/var-allocator.test.ts +0 -75
- package/src/codegen/cmdblock/index.ts +0 -63
- package/src/codegen/mcfunction/index.ts +0 -662
- package/src/codegen/structure/index.ts +0 -346
- package/src/codegen/var-allocator.ts +0 -104
- package/src/ir/builder.ts +0 -116
- package/src/ir/types.ts +0 -134
- package/src/lowering/index.ts +0 -3876
- package/src/optimizer/commands.ts +0 -534
- package/src/optimizer/dce.ts +0 -679
- package/src/optimizer/passes.ts +0 -250
- package/src/optimizer/structure.ts +0 -450
|
@@ -0,0 +1,807 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const lexer_1 = require("../lexer");
|
|
4
|
+
const parser_1 = require("../parser");
|
|
5
|
+
function parse(source, namespace = 'test') {
|
|
6
|
+
const tokens = new lexer_1.Lexer(source).tokenize();
|
|
7
|
+
return new parser_1.Parser(tokens).parse(namespace);
|
|
8
|
+
}
|
|
9
|
+
function parseExpr(source) {
|
|
10
|
+
const program = parse(`fn _test() { ${source}; }`);
|
|
11
|
+
const stmt = program.declarations[0].body[0];
|
|
12
|
+
if (stmt.kind !== 'expr')
|
|
13
|
+
throw new Error('Expected expr stmt');
|
|
14
|
+
return stmt.expr;
|
|
15
|
+
}
|
|
16
|
+
function parseStmt(source) {
|
|
17
|
+
const program = parse(`fn _test() { ${source} }`);
|
|
18
|
+
return program.declarations[0].body[0];
|
|
19
|
+
}
|
|
20
|
+
describe('Parser', () => {
|
|
21
|
+
describe('program structure', () => {
|
|
22
|
+
it('parses empty program', () => {
|
|
23
|
+
const program = parse('');
|
|
24
|
+
expect(program.namespace).toBe('test');
|
|
25
|
+
expect(program.declarations).toEqual([]);
|
|
26
|
+
expect(program.implBlocks).toEqual([]);
|
|
27
|
+
expect(program.enums).toEqual([]);
|
|
28
|
+
expect(program.consts).toEqual([]);
|
|
29
|
+
});
|
|
30
|
+
it('parses namespace declaration', () => {
|
|
31
|
+
const program = parse('namespace mypack;', 'default');
|
|
32
|
+
expect(program.namespace).toBe('mypack');
|
|
33
|
+
});
|
|
34
|
+
it('parses function declaration', () => {
|
|
35
|
+
const program = parse('fn foo() {}');
|
|
36
|
+
expect(program.declarations).toHaveLength(1);
|
|
37
|
+
expect(program.declarations[0].name).toBe('foo');
|
|
38
|
+
});
|
|
39
|
+
it('parses top-level const declarations', () => {
|
|
40
|
+
const program = parse('const MAX_HP: int = 100\nconst NAME: string = "Arena"');
|
|
41
|
+
expect(program.consts).toEqual([
|
|
42
|
+
{ name: 'MAX_HP', type: { kind: 'named', name: 'int' }, value: { kind: 'int_lit', value: 100 } },
|
|
43
|
+
{ name: 'NAME', type: { kind: 'named', name: 'string' }, value: { kind: 'str_lit', value: 'Arena' } },
|
|
44
|
+
]);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
describe('function declarations', () => {
|
|
48
|
+
it('parses function with no params', () => {
|
|
49
|
+
const program = parse('fn hello() {}');
|
|
50
|
+
const fn = program.declarations[0];
|
|
51
|
+
expect(fn.name).toBe('hello');
|
|
52
|
+
expect(fn.params).toEqual([]);
|
|
53
|
+
expect(fn.returnType).toEqual({ kind: 'named', name: 'void' });
|
|
54
|
+
});
|
|
55
|
+
it('parses function with params', () => {
|
|
56
|
+
const program = parse('fn add(a: int, b: int) -> int { return a + b; }');
|
|
57
|
+
const fn = program.declarations[0];
|
|
58
|
+
expect(fn.name).toBe('add');
|
|
59
|
+
expect(fn.params).toEqual([
|
|
60
|
+
{ name: 'a', type: { kind: 'named', name: 'int' }, default: undefined },
|
|
61
|
+
{ name: 'b', type: { kind: 'named', name: 'int' }, default: undefined },
|
|
62
|
+
]);
|
|
63
|
+
expect(fn.returnType).toEqual({ kind: 'named', name: 'int' });
|
|
64
|
+
});
|
|
65
|
+
it('parses function params with defaults', () => {
|
|
66
|
+
const program = parse('fn greet(name: string, formal: bool = false) {}');
|
|
67
|
+
expect(program.declarations[0].params).toEqual([
|
|
68
|
+
{ name: 'name', type: { kind: 'named', name: 'string' }, default: undefined },
|
|
69
|
+
{ name: 'formal', type: { kind: 'named', name: 'bool' }, default: { kind: 'bool_lit', value: false } },
|
|
70
|
+
]);
|
|
71
|
+
});
|
|
72
|
+
it('parses function with decorators', () => {
|
|
73
|
+
const program = parse('@tick\nfn game_loop() {}');
|
|
74
|
+
const fn = program.declarations[0];
|
|
75
|
+
expect(fn.decorators).toEqual([{ name: 'tick' }]);
|
|
76
|
+
});
|
|
77
|
+
it('parses decorator with args', () => {
|
|
78
|
+
const program = parse('@tick(rate=20)\nfn slow_loop() {}');
|
|
79
|
+
const fn = program.declarations[0];
|
|
80
|
+
expect(fn.decorators).toEqual([{ name: 'tick', args: { rate: 20 } }]);
|
|
81
|
+
});
|
|
82
|
+
it('parses multiple decorators', () => {
|
|
83
|
+
const program = parse('@tick\n@on_trigger\nfn both() {}');
|
|
84
|
+
const fn = program.declarations[0];
|
|
85
|
+
expect(fn.decorators).toHaveLength(2);
|
|
86
|
+
});
|
|
87
|
+
it('parses advancement and death decorators', () => {
|
|
88
|
+
const program = parse('@on_advancement("story/mine_diamond")\n@on_death\nfn handler() {}');
|
|
89
|
+
expect(program.declarations[0].decorators).toEqual([
|
|
90
|
+
{ name: 'on_advancement', args: { advancement: 'story/mine_diamond' } },
|
|
91
|
+
{ name: 'on_death' },
|
|
92
|
+
]);
|
|
93
|
+
});
|
|
94
|
+
it('parses @on event decorators', () => {
|
|
95
|
+
const program = parse('@on(PlayerDeath)\nfn handle_death(player: Player) {}');
|
|
96
|
+
expect(program.declarations[0].decorators).toEqual([
|
|
97
|
+
{ name: 'on', args: { eventType: 'PlayerDeath' } },
|
|
98
|
+
]);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
describe('types', () => {
|
|
102
|
+
it('parses primitive types', () => {
|
|
103
|
+
const program = parse('fn f(a: int, b: bool, c: float, d: string) {}');
|
|
104
|
+
const params = program.declarations[0].params;
|
|
105
|
+
expect(params.map(p => p.type)).toEqual([
|
|
106
|
+
{ kind: 'named', name: 'int' },
|
|
107
|
+
{ kind: 'named', name: 'bool' },
|
|
108
|
+
{ kind: 'named', name: 'float' },
|
|
109
|
+
{ kind: 'named', name: 'string' },
|
|
110
|
+
]);
|
|
111
|
+
});
|
|
112
|
+
it('parses array types', () => {
|
|
113
|
+
const program = parse('fn f(a: int[]) {}');
|
|
114
|
+
const param = program.declarations[0].params[0];
|
|
115
|
+
expect(param.type).toEqual({ kind: 'array', elem: { kind: 'named', name: 'int' } });
|
|
116
|
+
});
|
|
117
|
+
it('parses BlockPos types', () => {
|
|
118
|
+
const program = parse('fn f(pos: BlockPos) {}');
|
|
119
|
+
const param = program.declarations[0].params[0];
|
|
120
|
+
expect(param.type).toEqual({ kind: 'named', name: 'BlockPos' });
|
|
121
|
+
});
|
|
122
|
+
it('parses function types', () => {
|
|
123
|
+
const program = parse('fn apply(val: int, cb: (int) -> int) -> int { return cb(val); }');
|
|
124
|
+
expect(program.declarations[0].params[1].type).toEqual({
|
|
125
|
+
kind: 'function_type',
|
|
126
|
+
params: [{ kind: 'named', name: 'int' }],
|
|
127
|
+
return: { kind: 'named', name: 'int' },
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
it('parses enum declarations', () => {
|
|
131
|
+
const program = parse('enum Direction { North, South = 3, East, West }');
|
|
132
|
+
expect(program.enums).toEqual([
|
|
133
|
+
{
|
|
134
|
+
name: 'Direction',
|
|
135
|
+
variants: [
|
|
136
|
+
{ name: 'North', value: 0 },
|
|
137
|
+
{ name: 'South', value: 3 },
|
|
138
|
+
{ name: 'East', value: 4 },
|
|
139
|
+
{ name: 'West', value: 5 },
|
|
140
|
+
],
|
|
141
|
+
},
|
|
142
|
+
]);
|
|
143
|
+
});
|
|
144
|
+
it('parses impl blocks', () => {
|
|
145
|
+
const program = parse(`
|
|
146
|
+
struct Timer { duration: int }
|
|
147
|
+
|
|
148
|
+
impl Timer {
|
|
149
|
+
fn new(duration: int): Timer {
|
|
150
|
+
return { duration: duration };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
fn start(self) {}
|
|
154
|
+
}
|
|
155
|
+
`);
|
|
156
|
+
expect(program.implBlocks).toHaveLength(1);
|
|
157
|
+
expect(program.implBlocks[0].typeName).toBe('Timer');
|
|
158
|
+
expect(program.implBlocks[0].methods.map(method => method.name)).toEqual(['new', 'start']);
|
|
159
|
+
expect(program.implBlocks[0].methods[1].params[0]).toEqual({
|
|
160
|
+
name: 'self',
|
|
161
|
+
type: { kind: 'struct', name: 'Timer' },
|
|
162
|
+
default: undefined,
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
it('parses impl blocks with static and instance methods', () => {
|
|
166
|
+
const program = parse(`
|
|
167
|
+
struct Point { x: int, y: int }
|
|
168
|
+
|
|
169
|
+
impl Point {
|
|
170
|
+
fn new(x: int, y: int) -> Point {
|
|
171
|
+
return { x: x, y: y };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
fn distance(self) -> int {
|
|
175
|
+
return self.x + self.y;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
`);
|
|
179
|
+
expect(program.implBlocks).toHaveLength(1);
|
|
180
|
+
expect(program.implBlocks[0].typeName).toBe('Point');
|
|
181
|
+
expect(program.implBlocks[0].methods[0].params.map(param => param.name)).toEqual(['x', 'y']);
|
|
182
|
+
expect(program.implBlocks[0].methods[1].params[0]).toEqual({
|
|
183
|
+
name: 'self',
|
|
184
|
+
type: { kind: 'struct', name: 'Point' },
|
|
185
|
+
default: undefined,
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
describe('statements', () => {
|
|
190
|
+
it('parses let statement', () => {
|
|
191
|
+
const stmt = parseStmt('let x: int = 5;');
|
|
192
|
+
expect(stmt).toEqual({
|
|
193
|
+
kind: 'let',
|
|
194
|
+
name: 'x',
|
|
195
|
+
type: { kind: 'named', name: 'int' },
|
|
196
|
+
init: { kind: 'int_lit', value: 5 },
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
it('parses let without type annotation', () => {
|
|
200
|
+
const stmt = parseStmt('let x = 5;');
|
|
201
|
+
expect(stmt.kind).toBe('let');
|
|
202
|
+
expect(stmt.type).toBeUndefined();
|
|
203
|
+
});
|
|
204
|
+
it('parses return statement', () => {
|
|
205
|
+
const stmt = parseStmt('return 42;');
|
|
206
|
+
expect(stmt).toEqual({
|
|
207
|
+
kind: 'return',
|
|
208
|
+
value: { kind: 'int_lit', value: 42 },
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
it('parses empty return', () => {
|
|
212
|
+
const stmt = parseStmt('return;');
|
|
213
|
+
expect(stmt).toEqual({ kind: 'return', value: undefined });
|
|
214
|
+
});
|
|
215
|
+
it('parses if statement', () => {
|
|
216
|
+
const stmt = parseStmt('if (x > 0) { y = 1; }');
|
|
217
|
+
expect(stmt.kind).toBe('if');
|
|
218
|
+
expect(stmt.cond.kind).toBe('binary');
|
|
219
|
+
expect(stmt.then).toHaveLength(1);
|
|
220
|
+
expect(stmt.else_).toBeUndefined();
|
|
221
|
+
});
|
|
222
|
+
it('parses if-else statement', () => {
|
|
223
|
+
const stmt = parseStmt('if (x > 0) { y = 1; } else { y = 2; }');
|
|
224
|
+
expect(stmt.kind).toBe('if');
|
|
225
|
+
expect(stmt.else_).toHaveLength(1);
|
|
226
|
+
});
|
|
227
|
+
it('parses entity is-checks in if conditions', () => {
|
|
228
|
+
const stmt = parseStmt('if (e is Player) { kill(@s); }');
|
|
229
|
+
expect(stmt.kind).toBe('if');
|
|
230
|
+
expect(stmt.cond).toEqual({
|
|
231
|
+
kind: 'is_check',
|
|
232
|
+
expr: { kind: 'ident', name: 'e' },
|
|
233
|
+
entityType: 'Player',
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
it('parses entity is-checks inside foreach bodies', () => {
|
|
237
|
+
const stmt = parseStmt('foreach (e in @e) { if (e is Zombie) { kill(e); } }');
|
|
238
|
+
expect(stmt.kind).toBe('foreach');
|
|
239
|
+
const innerIf = stmt.body[0];
|
|
240
|
+
expect(innerIf.kind).toBe('if');
|
|
241
|
+
expect(innerIf.cond).toEqual({
|
|
242
|
+
kind: 'is_check',
|
|
243
|
+
expr: { kind: 'ident', name: 'e' },
|
|
244
|
+
entityType: 'Zombie',
|
|
245
|
+
});
|
|
246
|
+
});
|
|
247
|
+
it('parses while statement', () => {
|
|
248
|
+
const stmt = parseStmt('while (i > 0) { i = i - 1; }');
|
|
249
|
+
expect(stmt.kind).toBe('while');
|
|
250
|
+
expect(stmt.cond.kind).toBe('binary');
|
|
251
|
+
expect(stmt.body).toHaveLength(1);
|
|
252
|
+
});
|
|
253
|
+
it('parses for statement', () => {
|
|
254
|
+
const stmt = parseStmt('for (let i: int = 0; i < 10; i = i + 1) { say("loop"); }');
|
|
255
|
+
expect(stmt.kind).toBe('for');
|
|
256
|
+
expect(stmt.init.kind).toBe('let');
|
|
257
|
+
expect(stmt.init.name).toBe('i');
|
|
258
|
+
expect(stmt.cond.kind).toBe('binary');
|
|
259
|
+
expect(stmt.cond.op).toBe('<');
|
|
260
|
+
expect(stmt.step.kind).toBe('assign');
|
|
261
|
+
expect(stmt.body).toHaveLength(1);
|
|
262
|
+
});
|
|
263
|
+
it('parses for statement without init', () => {
|
|
264
|
+
const stmt = parseStmt('for (; i < 10; i = i + 1) { say("loop"); }');
|
|
265
|
+
expect(stmt.kind).toBe('for');
|
|
266
|
+
expect(stmt.init).toBeUndefined();
|
|
267
|
+
expect(stmt.cond.kind).toBe('binary');
|
|
268
|
+
});
|
|
269
|
+
it('parses foreach statement', () => {
|
|
270
|
+
const stmt = parseStmt('foreach (z in @e[type=zombie]) { kill(z); }');
|
|
271
|
+
expect(stmt.kind).toBe('foreach');
|
|
272
|
+
expect(stmt.binding).toBe('z');
|
|
273
|
+
expect(stmt.iterable.kind).toBe('selector');
|
|
274
|
+
expect(stmt.iterable.sel.kind).toBe('@e');
|
|
275
|
+
});
|
|
276
|
+
it('parses match statement', () => {
|
|
277
|
+
const stmt = parseStmt('match (choice) { 1 => { say("one"); } 2 => { say("two"); } _ => { say("other"); } }');
|
|
278
|
+
expect(stmt.kind).toBe('match');
|
|
279
|
+
expect(stmt.expr).toEqual({ kind: 'ident', name: 'choice' });
|
|
280
|
+
expect(stmt.arms).toEqual([
|
|
281
|
+
{ pattern: { kind: 'int_lit', value: 1 }, body: [{ kind: 'expr', expr: { kind: 'call', fn: 'say', args: [{ kind: 'str_lit', value: 'one' }] } }] },
|
|
282
|
+
{ pattern: { kind: 'int_lit', value: 2 }, body: [{ kind: 'expr', expr: { kind: 'call', fn: 'say', args: [{ kind: 'str_lit', value: 'two' }] } }] },
|
|
283
|
+
{ pattern: null, body: [{ kind: 'expr', expr: { kind: 'call', fn: 'say', args: [{ kind: 'str_lit', value: 'other' }] } }] },
|
|
284
|
+
]);
|
|
285
|
+
});
|
|
286
|
+
it('parses as block', () => {
|
|
287
|
+
const stmt = parseStmt('as @a { say("hello"); }');
|
|
288
|
+
expect(stmt.kind).toBe('as_block');
|
|
289
|
+
expect(stmt.selector.kind).toBe('@a');
|
|
290
|
+
});
|
|
291
|
+
it('parses at block', () => {
|
|
292
|
+
const stmt = parseStmt('at @s { summon("zombie"); }');
|
|
293
|
+
expect(stmt.kind).toBe('at_block');
|
|
294
|
+
expect(stmt.selector.kind).toBe('@s');
|
|
295
|
+
});
|
|
296
|
+
it('parses as at combined', () => {
|
|
297
|
+
const stmt = parseStmt('as @a at @s { particle("flame"); }');
|
|
298
|
+
expect(stmt.kind).toBe('as_at');
|
|
299
|
+
expect(stmt.as_sel.kind).toBe('@a');
|
|
300
|
+
expect(stmt.at_sel.kind).toBe('@s');
|
|
301
|
+
});
|
|
302
|
+
it('parses raw command', () => {
|
|
303
|
+
const stmt = parseStmt('raw("say hello");');
|
|
304
|
+
expect(stmt).toEqual({ kind: 'raw', cmd: 'say hello' });
|
|
305
|
+
});
|
|
306
|
+
it('parses execute as run block', () => {
|
|
307
|
+
const stmt = parseStmt('execute as @a run { say("hello"); }');
|
|
308
|
+
expect(stmt.kind).toBe('execute');
|
|
309
|
+
expect(stmt.subcommands).toHaveLength(1);
|
|
310
|
+
expect(stmt.subcommands[0]).toEqual({ kind: 'as', selector: { kind: '@a' } });
|
|
311
|
+
expect(stmt.body).toHaveLength(1);
|
|
312
|
+
});
|
|
313
|
+
it('parses execute as at run block', () => {
|
|
314
|
+
const stmt = parseStmt('execute as @a at @s run { particle("flame"); }');
|
|
315
|
+
expect(stmt.kind).toBe('execute');
|
|
316
|
+
expect(stmt.subcommands).toHaveLength(2);
|
|
317
|
+
expect(stmt.subcommands[0]).toEqual({ kind: 'as', selector: { kind: '@a' } });
|
|
318
|
+
expect(stmt.subcommands[1]).toEqual({ kind: 'at', selector: { kind: '@s' } });
|
|
319
|
+
});
|
|
320
|
+
it('parses execute with if entity condition', () => {
|
|
321
|
+
const stmt = parseStmt('execute as @a if entity @s[tag=admin] run { give(@s, "diamond", 1); }');
|
|
322
|
+
expect(stmt.kind).toBe('execute');
|
|
323
|
+
expect(stmt.subcommands).toHaveLength(2);
|
|
324
|
+
expect(stmt.subcommands[1].kind).toBe('if_entity');
|
|
325
|
+
expect(stmt.subcommands[1].selector.filters.tag).toEqual(['admin']);
|
|
326
|
+
});
|
|
327
|
+
it('parses execute with unless entity condition', () => {
|
|
328
|
+
const stmt = parseStmt('execute as @a unless entity @s[tag=dead] run { effect(@s, "regeneration", 5); }');
|
|
329
|
+
expect(stmt.kind).toBe('execute');
|
|
330
|
+
expect(stmt.subcommands).toHaveLength(2);
|
|
331
|
+
expect(stmt.subcommands[1].kind).toBe('unless_entity');
|
|
332
|
+
});
|
|
333
|
+
it('parses execute with in dimension', () => {
|
|
334
|
+
const stmt = parseStmt('execute in the_nether run { say("in nether"); }');
|
|
335
|
+
expect(stmt.kind).toBe('execute');
|
|
336
|
+
expect(stmt.subcommands).toHaveLength(1);
|
|
337
|
+
expect(stmt.subcommands[0]).toEqual({ kind: 'in', dimension: 'the_nether' });
|
|
338
|
+
});
|
|
339
|
+
it('parses complex execute chain', () => {
|
|
340
|
+
const stmt = parseStmt('execute as @a at @s if entity @s[tag=vip] in overworld run { particle("heart"); }');
|
|
341
|
+
expect(stmt.kind).toBe('execute');
|
|
342
|
+
expect(stmt.subcommands).toHaveLength(4);
|
|
343
|
+
});
|
|
344
|
+
});
|
|
345
|
+
describe('lambda expressions', () => {
|
|
346
|
+
it('parses expression-body lambdas', () => {
|
|
347
|
+
const stmt = parseStmt('let double = (x: int) => x * 2;');
|
|
348
|
+
expect(stmt.kind).toBe('let');
|
|
349
|
+
expect(stmt.init).toEqual({
|
|
350
|
+
kind: 'lambda',
|
|
351
|
+
params: [{ name: 'x', type: { kind: 'named', name: 'int' } }],
|
|
352
|
+
returnType: undefined,
|
|
353
|
+
body: {
|
|
354
|
+
kind: 'binary',
|
|
355
|
+
op: '*',
|
|
356
|
+
left: { kind: 'ident', name: 'x' },
|
|
357
|
+
right: { kind: 'int_lit', value: 2 },
|
|
358
|
+
},
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
it('parses block-body lambdas', () => {
|
|
362
|
+
const stmt = parseStmt('let process: (int) -> int = (x: int) => { let doubled: int = x * 2; return doubled + 1; };');
|
|
363
|
+
expect(stmt.kind).toBe('let');
|
|
364
|
+
expect(stmt.init.kind).toBe('lambda');
|
|
365
|
+
expect(Array.isArray(stmt.init.body)).toBe(true);
|
|
366
|
+
});
|
|
367
|
+
it('parses single-parameter lambdas without parens', () => {
|
|
368
|
+
const stmt = parseStmt('let double: (int) -> int = x => x * 2;');
|
|
369
|
+
expect(stmt.kind).toBe('let');
|
|
370
|
+
expect(stmt.init).toEqual({
|
|
371
|
+
kind: 'lambda',
|
|
372
|
+
params: [{ name: 'x' }],
|
|
373
|
+
returnType: undefined,
|
|
374
|
+
body: {
|
|
375
|
+
kind: 'binary',
|
|
376
|
+
op: '*',
|
|
377
|
+
left: { kind: 'ident', name: 'x' },
|
|
378
|
+
right: { kind: 'int_lit', value: 2 },
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
it('parses immediately-invoked lambdas', () => {
|
|
383
|
+
const expr = parseExpr('((x: int) => x * 2)(5)');
|
|
384
|
+
expect(expr).toEqual({
|
|
385
|
+
kind: 'invoke',
|
|
386
|
+
callee: {
|
|
387
|
+
kind: 'lambda',
|
|
388
|
+
params: [{ name: 'x', type: { kind: 'named', name: 'int' } }],
|
|
389
|
+
returnType: undefined,
|
|
390
|
+
body: {
|
|
391
|
+
kind: 'binary',
|
|
392
|
+
op: '*',
|
|
393
|
+
left: { kind: 'ident', name: 'x' },
|
|
394
|
+
right: { kind: 'int_lit', value: 2 },
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
args: [{ kind: 'int_lit', value: 5 }],
|
|
398
|
+
});
|
|
399
|
+
});
|
|
400
|
+
});
|
|
401
|
+
describe('expressions', () => {
|
|
402
|
+
describe('literals', () => {
|
|
403
|
+
it('parses integer literal', () => {
|
|
404
|
+
const expr = parseExpr('42');
|
|
405
|
+
expect(expr).toEqual({ kind: 'int_lit', value: 42 });
|
|
406
|
+
});
|
|
407
|
+
it('parses float literal', () => {
|
|
408
|
+
const expr = parseExpr('3.14');
|
|
409
|
+
expect(expr).toEqual({ kind: 'float_lit', value: 3.14 });
|
|
410
|
+
});
|
|
411
|
+
it('parses string literal', () => {
|
|
412
|
+
const expr = parseExpr('"hello"');
|
|
413
|
+
expect(expr).toEqual({ kind: 'str_lit', value: 'hello' });
|
|
414
|
+
});
|
|
415
|
+
it('parses interpolated string literal', () => {
|
|
416
|
+
const expr = parseExpr('"Hello ${name}, score is ${score + 1}"');
|
|
417
|
+
expect(expr).toEqual({
|
|
418
|
+
kind: 'str_interp',
|
|
419
|
+
parts: [
|
|
420
|
+
'Hello ',
|
|
421
|
+
{ kind: 'ident', name: 'name' },
|
|
422
|
+
', score is ',
|
|
423
|
+
{
|
|
424
|
+
kind: 'binary',
|
|
425
|
+
op: '+',
|
|
426
|
+
left: { kind: 'ident', name: 'score' },
|
|
427
|
+
right: { kind: 'int_lit', value: 1 },
|
|
428
|
+
},
|
|
429
|
+
],
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
it('parses f-string literal', () => {
|
|
433
|
+
const expr = parseExpr('f"Score: {x}"');
|
|
434
|
+
expect(expr).toEqual({
|
|
435
|
+
kind: 'f_string',
|
|
436
|
+
parts: [
|
|
437
|
+
{ kind: 'text', value: 'Score: ' },
|
|
438
|
+
{ kind: 'expr', expr: { kind: 'ident', name: 'x' } },
|
|
439
|
+
],
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
it('parses boolean literals', () => {
|
|
443
|
+
expect(parseExpr('true')).toEqual({ kind: 'bool_lit', value: true });
|
|
444
|
+
expect(parseExpr('false')).toEqual({ kind: 'bool_lit', value: false });
|
|
445
|
+
});
|
|
446
|
+
it('parses range literals', () => {
|
|
447
|
+
expect(parseExpr('..5')).toEqual({ kind: 'range_lit', range: { max: 5 } });
|
|
448
|
+
expect(parseExpr('1..')).toEqual({ kind: 'range_lit', range: { min: 1 } });
|
|
449
|
+
expect(parseExpr('1..10')).toEqual({ kind: 'range_lit', range: { min: 1, max: 10 } });
|
|
450
|
+
});
|
|
451
|
+
it('parses absolute block positions', () => {
|
|
452
|
+
expect(parseExpr('(0, 64, 0)')).toEqual({
|
|
453
|
+
kind: 'blockpos',
|
|
454
|
+
x: { kind: 'absolute', value: 0 },
|
|
455
|
+
y: { kind: 'absolute', value: 64 },
|
|
456
|
+
z: { kind: 'absolute', value: 0 },
|
|
457
|
+
});
|
|
458
|
+
});
|
|
459
|
+
it('parses relative block positions', () => {
|
|
460
|
+
expect(parseExpr('(~1, ~0, ~-1)')).toEqual({
|
|
461
|
+
kind: 'blockpos',
|
|
462
|
+
x: { kind: 'relative', offset: 1 },
|
|
463
|
+
y: { kind: 'relative', offset: 0 },
|
|
464
|
+
z: { kind: 'relative', offset: -1 },
|
|
465
|
+
});
|
|
466
|
+
});
|
|
467
|
+
it('parses local block positions', () => {
|
|
468
|
+
expect(parseExpr('(^0, ^1, ^0)')).toEqual({
|
|
469
|
+
kind: 'blockpos',
|
|
470
|
+
x: { kind: 'local', offset: 0 },
|
|
471
|
+
y: { kind: 'local', offset: 1 },
|
|
472
|
+
z: { kind: 'local', offset: 0 },
|
|
473
|
+
});
|
|
474
|
+
});
|
|
475
|
+
it('parses mixed block positions', () => {
|
|
476
|
+
expect(parseExpr('(~0, 64, ~0)')).toEqual({
|
|
477
|
+
kind: 'blockpos',
|
|
478
|
+
x: { kind: 'relative', offset: 0 },
|
|
479
|
+
y: { kind: 'absolute', value: 64 },
|
|
480
|
+
z: { kind: 'relative', offset: 0 },
|
|
481
|
+
});
|
|
482
|
+
});
|
|
483
|
+
});
|
|
484
|
+
describe('identifiers and calls', () => {
|
|
485
|
+
it('parses identifier', () => {
|
|
486
|
+
const expr = parseExpr('foo');
|
|
487
|
+
expect(expr).toEqual({ kind: 'ident', name: 'foo' });
|
|
488
|
+
});
|
|
489
|
+
it('parses function call', () => {
|
|
490
|
+
const expr = parseExpr('foo(1, 2)');
|
|
491
|
+
expect(expr).toEqual({
|
|
492
|
+
kind: 'call',
|
|
493
|
+
fn: 'foo',
|
|
494
|
+
args: [
|
|
495
|
+
{ kind: 'int_lit', value: 1 },
|
|
496
|
+
{ kind: 'int_lit', value: 2 },
|
|
497
|
+
],
|
|
498
|
+
});
|
|
499
|
+
});
|
|
500
|
+
it('parses no-arg call', () => {
|
|
501
|
+
const expr = parseExpr('foo()');
|
|
502
|
+
expect(expr).toEqual({ kind: 'call', fn: 'foo', args: [] });
|
|
503
|
+
});
|
|
504
|
+
it('parses enum variant member access', () => {
|
|
505
|
+
const expr = parseExpr('Direction.North');
|
|
506
|
+
expect(expr).toEqual({
|
|
507
|
+
kind: 'member',
|
|
508
|
+
obj: { kind: 'ident', name: 'Direction' },
|
|
509
|
+
field: 'North',
|
|
510
|
+
});
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
it('parses static method calls', () => {
|
|
514
|
+
const expr = parseExpr('Timer::new(100)');
|
|
515
|
+
expect(expr).toEqual({
|
|
516
|
+
kind: 'static_call',
|
|
517
|
+
type: 'Timer',
|
|
518
|
+
method: 'new',
|
|
519
|
+
args: [{ kind: 'int_lit', value: 100 }],
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
describe('binary operators', () => {
|
|
523
|
+
it('parses arithmetic', () => {
|
|
524
|
+
const expr = parseExpr('1 + 2');
|
|
525
|
+
expect(expr).toEqual({
|
|
526
|
+
kind: 'binary',
|
|
527
|
+
op: '+',
|
|
528
|
+
left: { kind: 'int_lit', value: 1 },
|
|
529
|
+
right: { kind: 'int_lit', value: 2 },
|
|
530
|
+
});
|
|
531
|
+
});
|
|
532
|
+
it('respects precedence (mul before add)', () => {
|
|
533
|
+
const expr = parseExpr('1 + 2 * 3');
|
|
534
|
+
expect(expr.kind).toBe('binary');
|
|
535
|
+
expect(expr.op).toBe('+');
|
|
536
|
+
expect(expr.right.op).toBe('*');
|
|
537
|
+
});
|
|
538
|
+
it('respects precedence (compare before logical)', () => {
|
|
539
|
+
const expr = parseExpr('a < b && c > d');
|
|
540
|
+
expect(expr.kind).toBe('binary');
|
|
541
|
+
expect(expr.op).toBe('&&');
|
|
542
|
+
expect(expr.left.op).toBe('<');
|
|
543
|
+
expect(expr.right.op).toBe('>');
|
|
544
|
+
});
|
|
545
|
+
it('is left associative', () => {
|
|
546
|
+
const expr = parseExpr('1 - 2 - 3');
|
|
547
|
+
// Should be (1 - 2) - 3
|
|
548
|
+
expect(expr.kind).toBe('binary');
|
|
549
|
+
expect(expr.op).toBe('-');
|
|
550
|
+
expect(expr.left.kind).toBe('binary');
|
|
551
|
+
expect(expr.right.kind).toBe('int_lit');
|
|
552
|
+
});
|
|
553
|
+
});
|
|
554
|
+
describe('unary operators', () => {
|
|
555
|
+
it('parses negation', () => {
|
|
556
|
+
const expr = parseExpr('-5');
|
|
557
|
+
expect(expr).toEqual({
|
|
558
|
+
kind: 'unary',
|
|
559
|
+
op: '-',
|
|
560
|
+
operand: { kind: 'int_lit', value: 5 },
|
|
561
|
+
});
|
|
562
|
+
});
|
|
563
|
+
it('parses logical not', () => {
|
|
564
|
+
const expr = parseExpr('!flag');
|
|
565
|
+
expect(expr).toEqual({
|
|
566
|
+
kind: 'unary',
|
|
567
|
+
op: '!',
|
|
568
|
+
operand: { kind: 'ident', name: 'flag' },
|
|
569
|
+
});
|
|
570
|
+
});
|
|
571
|
+
});
|
|
572
|
+
describe('assignment', () => {
|
|
573
|
+
it('parses simple assignment', () => {
|
|
574
|
+
const expr = parseExpr('x = 5');
|
|
575
|
+
expect(expr).toEqual({
|
|
576
|
+
kind: 'assign',
|
|
577
|
+
target: 'x',
|
|
578
|
+
op: '=',
|
|
579
|
+
value: { kind: 'int_lit', value: 5 },
|
|
580
|
+
});
|
|
581
|
+
});
|
|
582
|
+
it('parses compound assignment', () => {
|
|
583
|
+
const expr = parseExpr('x += 1');
|
|
584
|
+
expect(expr).toEqual({
|
|
585
|
+
kind: 'assign',
|
|
586
|
+
target: 'x',
|
|
587
|
+
op: '+=',
|
|
588
|
+
value: { kind: 'int_lit', value: 1 },
|
|
589
|
+
});
|
|
590
|
+
});
|
|
591
|
+
});
|
|
592
|
+
describe('selectors', () => {
|
|
593
|
+
it('parses simple selector', () => {
|
|
594
|
+
const expr = parseExpr('@a');
|
|
595
|
+
expect(expr).toEqual({
|
|
596
|
+
kind: 'selector',
|
|
597
|
+
raw: '@a',
|
|
598
|
+
isSingle: false,
|
|
599
|
+
sel: { kind: '@a' },
|
|
600
|
+
});
|
|
601
|
+
});
|
|
602
|
+
it('marks single-entity selectors', () => {
|
|
603
|
+
expect(parseExpr('@p')).toEqual({
|
|
604
|
+
kind: 'selector',
|
|
605
|
+
raw: '@p',
|
|
606
|
+
isSingle: true,
|
|
607
|
+
sel: { kind: '@p' },
|
|
608
|
+
});
|
|
609
|
+
expect(parseExpr('@e[limit=1, tag=target]')).toEqual({
|
|
610
|
+
kind: 'selector',
|
|
611
|
+
raw: '@e[limit=1, tag=target]',
|
|
612
|
+
isSingle: true,
|
|
613
|
+
sel: {
|
|
614
|
+
kind: '@e',
|
|
615
|
+
filters: { limit: 1, tag: ['target'] },
|
|
616
|
+
},
|
|
617
|
+
});
|
|
618
|
+
});
|
|
619
|
+
it('parses selector with type filter', () => {
|
|
620
|
+
const expr = parseExpr('@e[type=zombie]');
|
|
621
|
+
expect(expr).toEqual({
|
|
622
|
+
kind: 'selector',
|
|
623
|
+
raw: '@e[type=zombie]',
|
|
624
|
+
isSingle: false,
|
|
625
|
+
sel: {
|
|
626
|
+
kind: '@e',
|
|
627
|
+
filters: { type: 'zombie' },
|
|
628
|
+
},
|
|
629
|
+
});
|
|
630
|
+
});
|
|
631
|
+
it('parses selector with distance filter', () => {
|
|
632
|
+
const expr = parseExpr('@e[distance=..5]');
|
|
633
|
+
expect(expr.sel.filters.distance).toEqual({ max: 5 });
|
|
634
|
+
});
|
|
635
|
+
it('parses selector with tag filter', () => {
|
|
636
|
+
const expr = parseExpr('@e[tag=boss, tag=!excluded]');
|
|
637
|
+
expect(expr.sel.filters.tag).toEqual(['boss']);
|
|
638
|
+
expect(expr.sel.filters.notTag).toEqual(['excluded']);
|
|
639
|
+
});
|
|
640
|
+
it('parses selector with limit and sort', () => {
|
|
641
|
+
const expr = parseExpr('@e[limit=1, sort=nearest]');
|
|
642
|
+
expect(expr.sel.filters.limit).toBe(1);
|
|
643
|
+
expect(expr.sel.filters.sort).toBe('nearest');
|
|
644
|
+
});
|
|
645
|
+
it('parses selector with scores', () => {
|
|
646
|
+
const expr = parseExpr('@a[scores={kills=1..}]');
|
|
647
|
+
expect(expr.sel.filters.scores).toEqual({
|
|
648
|
+
kills: { min: 1 },
|
|
649
|
+
});
|
|
650
|
+
});
|
|
651
|
+
});
|
|
652
|
+
describe('member access', () => {
|
|
653
|
+
it('parses member access', () => {
|
|
654
|
+
const expr = parseExpr('entity.health');
|
|
655
|
+
expect(expr).toEqual({
|
|
656
|
+
kind: 'member',
|
|
657
|
+
obj: { kind: 'ident', name: 'entity' },
|
|
658
|
+
field: 'health',
|
|
659
|
+
});
|
|
660
|
+
});
|
|
661
|
+
it('parses array len property', () => {
|
|
662
|
+
const expr = parseExpr('arr.len');
|
|
663
|
+
expect(expr).toEqual({
|
|
664
|
+
kind: 'member',
|
|
665
|
+
obj: { kind: 'ident', name: 'arr' },
|
|
666
|
+
field: 'len',
|
|
667
|
+
});
|
|
668
|
+
});
|
|
669
|
+
});
|
|
670
|
+
describe('arrays', () => {
|
|
671
|
+
it('parses array literal', () => {
|
|
672
|
+
expect(parseExpr('[1, 2, 3]')).toEqual({
|
|
673
|
+
kind: 'array_lit',
|
|
674
|
+
elements: [
|
|
675
|
+
{ kind: 'int_lit', value: 1 },
|
|
676
|
+
{ kind: 'int_lit', value: 2 },
|
|
677
|
+
{ kind: 'int_lit', value: 3 },
|
|
678
|
+
],
|
|
679
|
+
});
|
|
680
|
+
});
|
|
681
|
+
it('parses array index access', () => {
|
|
682
|
+
expect(parseExpr('arr[i]')).toEqual({
|
|
683
|
+
kind: 'index',
|
|
684
|
+
obj: { kind: 'ident', name: 'arr' },
|
|
685
|
+
index: { kind: 'ident', name: 'i' },
|
|
686
|
+
});
|
|
687
|
+
});
|
|
688
|
+
it('parses array push call', () => {
|
|
689
|
+
expect(parseExpr('arr.push(4)')).toEqual({
|
|
690
|
+
kind: 'call',
|
|
691
|
+
fn: '__array_push',
|
|
692
|
+
args: [
|
|
693
|
+
{ kind: 'ident', name: 'arr' },
|
|
694
|
+
{ kind: 'int_lit', value: 4 },
|
|
695
|
+
],
|
|
696
|
+
});
|
|
697
|
+
});
|
|
698
|
+
it('parses array pop call', () => {
|
|
699
|
+
expect(parseExpr('arr.pop()')).toEqual({
|
|
700
|
+
kind: 'call',
|
|
701
|
+
fn: '__array_pop',
|
|
702
|
+
args: [
|
|
703
|
+
{ kind: 'ident', name: 'arr' },
|
|
704
|
+
],
|
|
705
|
+
});
|
|
706
|
+
});
|
|
707
|
+
});
|
|
708
|
+
describe('grouping', () => {
|
|
709
|
+
it('parses parenthesized expression', () => {
|
|
710
|
+
const expr = parseExpr('(1 + 2) * 3');
|
|
711
|
+
expect(expr.kind).toBe('binary');
|
|
712
|
+
expect(expr.op).toBe('*');
|
|
713
|
+
expect(expr.left.kind).toBe('binary');
|
|
714
|
+
});
|
|
715
|
+
});
|
|
716
|
+
});
|
|
717
|
+
describe('complex programs', () => {
|
|
718
|
+
it('parses add function', () => {
|
|
719
|
+
const source = `
|
|
720
|
+
fn add(a: int, b: int) -> int {
|
|
721
|
+
return a + b;
|
|
722
|
+
}
|
|
723
|
+
`;
|
|
724
|
+
const program = parse(source);
|
|
725
|
+
expect(program.declarations).toHaveLength(1);
|
|
726
|
+
const fn = program.declarations[0];
|
|
727
|
+
expect(fn.name).toBe('add');
|
|
728
|
+
expect(fn.body).toHaveLength(1);
|
|
729
|
+
expect(fn.body[0].kind).toBe('return');
|
|
730
|
+
});
|
|
731
|
+
it('parses abs function with if/else', () => {
|
|
732
|
+
const source = `
|
|
733
|
+
fn abs(x: int) -> int {
|
|
734
|
+
if (x < 0) {
|
|
735
|
+
return -x;
|
|
736
|
+
} else {
|
|
737
|
+
return x;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
`;
|
|
741
|
+
const program = parse(source);
|
|
742
|
+
const fn = program.declarations[0];
|
|
743
|
+
expect(fn.body).toHaveLength(1);
|
|
744
|
+
const ifStmt = fn.body[0];
|
|
745
|
+
expect(ifStmt.kind).toBe('if');
|
|
746
|
+
});
|
|
747
|
+
it('parses tick function', () => {
|
|
748
|
+
const source = `
|
|
749
|
+
@tick(rate=20)
|
|
750
|
+
fn heartbeat() {
|
|
751
|
+
say("still alive");
|
|
752
|
+
}
|
|
753
|
+
`;
|
|
754
|
+
const program = parse(source);
|
|
755
|
+
const fn = program.declarations[0];
|
|
756
|
+
expect(fn.decorators).toEqual([{ name: 'tick', args: { rate: 20 } }]);
|
|
757
|
+
expect(fn.body).toHaveLength(1);
|
|
758
|
+
});
|
|
759
|
+
it('parses foreach with kill', () => {
|
|
760
|
+
const source = `
|
|
761
|
+
fn kill_zombies() {
|
|
762
|
+
foreach (z in @e[type=zombie, distance=..10]) {
|
|
763
|
+
kill(z);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
`;
|
|
767
|
+
const program = parse(source);
|
|
768
|
+
const fn = program.declarations[0];
|
|
769
|
+
const stmt = fn.body[0];
|
|
770
|
+
expect(stmt.kind).toBe('foreach');
|
|
771
|
+
expect(stmt.binding).toBe('z');
|
|
772
|
+
expect(stmt.iterable.sel.filters.type).toBe('zombie');
|
|
773
|
+
expect(stmt.iterable.sel.filters.distance).toEqual({ max: 10 });
|
|
774
|
+
});
|
|
775
|
+
it('parses foreach over array', () => {
|
|
776
|
+
const source = `
|
|
777
|
+
fn walk() {
|
|
778
|
+
let arr: int[] = [1, 2, 3];
|
|
779
|
+
foreach (x in arr) {
|
|
780
|
+
say("tick");
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
`;
|
|
784
|
+
const program = parse(source);
|
|
785
|
+
const stmt = program.declarations[0].body[1];
|
|
786
|
+
expect(stmt.kind).toBe('foreach');
|
|
787
|
+
expect(stmt.binding).toBe('x');
|
|
788
|
+
expect(stmt.iterable).toEqual({ kind: 'ident', name: 'arr' });
|
|
789
|
+
});
|
|
790
|
+
it('parses while loop', () => {
|
|
791
|
+
const source = `
|
|
792
|
+
fn count_down() {
|
|
793
|
+
let i: int = 10;
|
|
794
|
+
while (i > 0) {
|
|
795
|
+
i = i - 1;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
`;
|
|
799
|
+
const program = parse(source);
|
|
800
|
+
const fn = program.declarations[0];
|
|
801
|
+
expect(fn.body).toHaveLength(2);
|
|
802
|
+
expect(fn.body[0].kind).toBe('let');
|
|
803
|
+
expect(fn.body[1].kind).toBe('while');
|
|
804
|
+
});
|
|
805
|
+
});
|
|
806
|
+
});
|
|
807
|
+
//# sourceMappingURL=parser.test.js.map
|