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 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const entity_hierarchy_1 = require("../types/entity-hierarchy");
|
|
4
|
+
const lexer_1 = require("../lexer");
|
|
5
|
+
const parser_1 = require("../parser");
|
|
6
|
+
const lowering_1 = require("../lowering");
|
|
7
|
+
function compileWithWarnings(source, namespace = 'test') {
|
|
8
|
+
const tokens = new lexer_1.Lexer(source).tokenize();
|
|
9
|
+
const ast = new parser_1.Parser(tokens).parse(namespace);
|
|
10
|
+
const lowering = new lowering_1.Lowering(namespace);
|
|
11
|
+
return { ir: lowering.lower(ast), warnings: lowering.warnings };
|
|
12
|
+
}
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Entity hierarchy utilities
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
describe('Entity type hierarchy', () => {
|
|
17
|
+
test('isSubtype: Zombie is a subtype of Mob', () => {
|
|
18
|
+
expect((0, entity_hierarchy_1.isSubtype)('Zombie', 'Mob')).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
test('isSubtype: Player is NOT a subtype of Mob', () => {
|
|
21
|
+
expect((0, entity_hierarchy_1.isSubtype)('Player', 'Mob')).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
test('isSubtype: Zombie is a subtype of Entity (transitive)', () => {
|
|
24
|
+
expect((0, entity_hierarchy_1.isSubtype)('Zombie', 'Entity')).toBe(true);
|
|
25
|
+
});
|
|
26
|
+
test('isSubtype: identity — Zombie is subtype of Zombie', () => {
|
|
27
|
+
expect((0, entity_hierarchy_1.isSubtype)('Zombie', 'Zombie')).toBe(true);
|
|
28
|
+
});
|
|
29
|
+
test('areCompatibleTypes: Player and Zombie are NOT compatible', () => {
|
|
30
|
+
expect((0, entity_hierarchy_1.areCompatibleTypes)('Player', 'Zombie')).toBe(false);
|
|
31
|
+
});
|
|
32
|
+
test('areCompatibleTypes: Zombie and Mob are compatible', () => {
|
|
33
|
+
expect((0, entity_hierarchy_1.areCompatibleTypes)('Zombie', 'Mob')).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
test('areCompatibleTypes: Mob and Zombie are compatible (reverse)', () => {
|
|
36
|
+
expect((0, entity_hierarchy_1.areCompatibleTypes)('Mob', 'Zombie')).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
test('getConcreteSubtypes: HostileMob includes Zombie, Skeleton, Creeper', () => {
|
|
39
|
+
const subtypes = (0, entity_hierarchy_1.getConcreteSubtypes)('HostileMob');
|
|
40
|
+
const names = subtypes.map(n => n.name);
|
|
41
|
+
expect(names).toContain('Zombie');
|
|
42
|
+
expect(names).toContain('Skeleton');
|
|
43
|
+
expect(names).toContain('Creeper');
|
|
44
|
+
expect(names).toContain('Blaze');
|
|
45
|
+
expect(names).toContain('CaveSpider');
|
|
46
|
+
// Should NOT include passive mobs
|
|
47
|
+
expect(names).not.toContain('Pig');
|
|
48
|
+
expect(names).not.toContain('Player');
|
|
49
|
+
});
|
|
50
|
+
test('getConcreteSubtypes: PassiveMob includes Pig, Cow, Villager', () => {
|
|
51
|
+
const subtypes = (0, entity_hierarchy_1.getConcreteSubtypes)('PassiveMob');
|
|
52
|
+
const names = subtypes.map(n => n.name);
|
|
53
|
+
expect(names).toContain('Pig');
|
|
54
|
+
expect(names).toContain('Cow');
|
|
55
|
+
expect(names).toContain('Villager');
|
|
56
|
+
expect(names).toContain('WanderingTrader');
|
|
57
|
+
expect(names).not.toContain('Zombie');
|
|
58
|
+
});
|
|
59
|
+
test('getSelectorEntityType: parses type=zombie', () => {
|
|
60
|
+
expect((0, entity_hierarchy_1.getSelectorEntityType)('@e[type=zombie]')).toBe('Zombie');
|
|
61
|
+
});
|
|
62
|
+
test('getSelectorEntityType: parses type=minecraft:skeleton', () => {
|
|
63
|
+
expect((0, entity_hierarchy_1.getSelectorEntityType)('@e[type=minecraft:skeleton]')).toBe('Skeleton');
|
|
64
|
+
});
|
|
65
|
+
test('getBaseSelectorType: @a → Player', () => {
|
|
66
|
+
expect((0, entity_hierarchy_1.getBaseSelectorType)('@a')).toBe('Player');
|
|
67
|
+
});
|
|
68
|
+
test('getBaseSelectorType: @e → Entity', () => {
|
|
69
|
+
expect((0, entity_hierarchy_1.getBaseSelectorType)('@e')).toBe('Entity');
|
|
70
|
+
});
|
|
71
|
+
test('getBaseSelectorType: @e[type=zombie] → Zombie', () => {
|
|
72
|
+
expect((0, entity_hierarchy_1.getBaseSelectorType)('@e[type=zombie]')).toBe('Zombie');
|
|
73
|
+
});
|
|
74
|
+
test('getBaseSelectorType: @s → null', () => {
|
|
75
|
+
expect((0, entity_hierarchy_1.getBaseSelectorType)('@s')).toBeNull();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
// ---------------------------------------------------------------------------
|
|
79
|
+
// W_IMPOSSIBLE_AS warning
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
describe('W_IMPOSSIBLE_AS warning', () => {
|
|
82
|
+
test('foreach @a with as @e[type=zombie] produces warning', () => {
|
|
83
|
+
const source = `
|
|
84
|
+
fn main() {
|
|
85
|
+
foreach (p in @a) {
|
|
86
|
+
as @e[type=minecraft:zombie] {
|
|
87
|
+
kill(@s);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
`;
|
|
92
|
+
const { warnings } = compileWithWarnings(source);
|
|
93
|
+
const impossible = warnings.filter(w => w.code === 'W_IMPOSSIBLE_AS');
|
|
94
|
+
expect(impossible.length).toBe(1);
|
|
95
|
+
expect(impossible[0].message).toContain('Player');
|
|
96
|
+
expect(impossible[0].message).toContain('Zombie');
|
|
97
|
+
});
|
|
98
|
+
test('foreach @e[type=zombie] with as @e[type=zombie] produces NO warning', () => {
|
|
99
|
+
const source = `
|
|
100
|
+
fn main() {
|
|
101
|
+
foreach (z in @e[type=minecraft:zombie]) {
|
|
102
|
+
as @e[type=minecraft:zombie] {
|
|
103
|
+
kill(@s);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
`;
|
|
108
|
+
const { warnings } = compileWithWarnings(source);
|
|
109
|
+
const impossible = warnings.filter(w => w.code === 'W_IMPOSSIBLE_AS');
|
|
110
|
+
expect(impossible.length).toBe(0);
|
|
111
|
+
});
|
|
112
|
+
test('foreach @e (generic) with as @e[type=zombie] produces NO warning', () => {
|
|
113
|
+
const source = `
|
|
114
|
+
fn main() {
|
|
115
|
+
foreach (e in @e) {
|
|
116
|
+
as @e[type=minecraft:zombie] {
|
|
117
|
+
kill(@s);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
`;
|
|
122
|
+
const { warnings } = compileWithWarnings(source);
|
|
123
|
+
const impossible = warnings.filter(w => w.code === 'W_IMPOSSIBLE_AS');
|
|
124
|
+
expect(impossible.length).toBe(0);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
// is_check with abstract types
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
describe('is_check compilation', () => {
|
|
131
|
+
test('concrete is_check emits single execute if entity', () => {
|
|
132
|
+
const source = `
|
|
133
|
+
fn main() {
|
|
134
|
+
foreach (e in @e) {
|
|
135
|
+
if (e is Zombie) {
|
|
136
|
+
kill(@s);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
`;
|
|
141
|
+
const { ir } = compileWithWarnings(source);
|
|
142
|
+
const thenFn = ir.functions.find(f => f.name.includes('then_'));
|
|
143
|
+
expect(thenFn).toBeDefined();
|
|
144
|
+
// The parent foreach function should contain the execute if entity command
|
|
145
|
+
const foreachFn = ir.functions.find(f => f.name.includes('foreach_'));
|
|
146
|
+
expect(foreachFn).toBeDefined();
|
|
147
|
+
const rawCmds = foreachFn.blocks.flatMap(b => b.instrs)
|
|
148
|
+
.filter((i) => i.op === 'raw')
|
|
149
|
+
.map(i => i.cmd);
|
|
150
|
+
expect(rawCmds.some(c => c.includes('type=minecraft:zombie'))).toBe(true);
|
|
151
|
+
});
|
|
152
|
+
test('abstract is_check (HostileMob) emits multiple type checks', () => {
|
|
153
|
+
const source = `
|
|
154
|
+
fn main() {
|
|
155
|
+
foreach (e in @e) {
|
|
156
|
+
if (e is HostileMob) {
|
|
157
|
+
kill(@s);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
`;
|
|
162
|
+
const { ir } = compileWithWarnings(source);
|
|
163
|
+
const foreachFn = ir.functions.find(f => f.name.includes('foreach_'));
|
|
164
|
+
expect(foreachFn).toBeDefined();
|
|
165
|
+
const rawCmds = foreachFn.blocks.flatMap(b => b.instrs)
|
|
166
|
+
.filter((i) => i.op === 'raw')
|
|
167
|
+
.map(i => i.cmd);
|
|
168
|
+
// Should have scoreboard setup and multiple type checks
|
|
169
|
+
expect(rawCmds.some(c => c.includes('scoreboard players set __is_result rs:temp 0'))).toBe(true);
|
|
170
|
+
expect(rawCmds.some(c => c.includes('type=minecraft:zombie'))).toBe(true);
|
|
171
|
+
expect(rawCmds.some(c => c.includes('type=minecraft:skeleton'))).toBe(true);
|
|
172
|
+
expect(rawCmds.some(c => c.includes('type=minecraft:creeper'))).toBe(true);
|
|
173
|
+
expect(rawCmds.some(c => c.includes('if score __is_result rs:temp matches 1'))).toBe(true);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
// selector<T> type annotation
|
|
178
|
+
// ---------------------------------------------------------------------------
|
|
179
|
+
describe('selector<T> type annotation', () => {
|
|
180
|
+
test('parses selector<Player> type', () => {
|
|
181
|
+
const source = `
|
|
182
|
+
fn greet(target: selector<Player>) {
|
|
183
|
+
say("hello");
|
|
184
|
+
}
|
|
185
|
+
`;
|
|
186
|
+
const tokens = new lexer_1.Lexer(source).tokenize();
|
|
187
|
+
const ast = new parser_1.Parser(tokens).parse('test');
|
|
188
|
+
const fn = ast.declarations[0];
|
|
189
|
+
expect(fn.params[0].type).toEqual({ kind: 'selector', entityType: 'Player' });
|
|
190
|
+
});
|
|
191
|
+
test('parses plain selector type', () => {
|
|
192
|
+
const source = `
|
|
193
|
+
fn greet(target: selector) {
|
|
194
|
+
say("hello");
|
|
195
|
+
}
|
|
196
|
+
`;
|
|
197
|
+
const tokens = new lexer_1.Lexer(source).tokenize();
|
|
198
|
+
const ast = new parser_1.Parser(tokens).parse('test');
|
|
199
|
+
const fn = ast.declarations[0];
|
|
200
|
+
expect(fn.params[0].type).toEqual({ kind: 'selector' });
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
//# sourceMappingURL=entity-types.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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const lexer_1 = require("../lexer");
|
|
4
|
+
function tokenize(source) {
|
|
5
|
+
return new lexer_1.Lexer(source).tokenize();
|
|
6
|
+
}
|
|
7
|
+
function kinds(tokens) {
|
|
8
|
+
return tokens.map(t => t.kind);
|
|
9
|
+
}
|
|
10
|
+
describe('Lexer', () => {
|
|
11
|
+
describe('keywords', () => {
|
|
12
|
+
it('recognizes all keywords', () => {
|
|
13
|
+
const tokens = tokenize('fn let const if else while for foreach match return as at in is struct impl enum trigger namespace');
|
|
14
|
+
expect(kinds(tokens)).toEqual([
|
|
15
|
+
'fn', 'let', 'const', 'if', 'else', 'while', 'for', 'foreach', 'match',
|
|
16
|
+
'return', 'as', 'at', 'in', 'is', 'struct', 'impl', 'enum', 'trigger', 'namespace', 'eof'
|
|
17
|
+
]);
|
|
18
|
+
});
|
|
19
|
+
it('tokenizes is-check and impl syntax with their dedicated keywords', () => {
|
|
20
|
+
const tokens = tokenize('if (e is Player) { } impl Point { }');
|
|
21
|
+
expect(kinds(tokens)).toEqual([
|
|
22
|
+
'if', '(', 'ident', 'is', 'ident', ')', '{', '}', 'impl', 'ident', '{', '}', 'eof',
|
|
23
|
+
]);
|
|
24
|
+
});
|
|
25
|
+
it('recognizes type keywords', () => {
|
|
26
|
+
const tokens = tokenize('int bool float string void BlockPos');
|
|
27
|
+
expect(kinds(tokens)).toEqual(['int', 'bool', 'float', 'string', 'void', 'BlockPos', 'eof']);
|
|
28
|
+
});
|
|
29
|
+
it('recognizes boolean literals', () => {
|
|
30
|
+
const tokens = tokenize('true false');
|
|
31
|
+
expect(kinds(tokens)).toEqual(['true', 'false', 'eof']);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
describe('identifiers', () => {
|
|
35
|
+
it('tokenizes simple identifiers', () => {
|
|
36
|
+
const tokens = tokenize('foo bar_baz _private x1');
|
|
37
|
+
expect(tokens.map(t => [t.kind, t.value])).toEqual([
|
|
38
|
+
['ident', 'foo'],
|
|
39
|
+
['ident', 'bar_baz'],
|
|
40
|
+
['ident', '_private'],
|
|
41
|
+
['ident', 'x1'],
|
|
42
|
+
['eof', ''],
|
|
43
|
+
]);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
describe('literals', () => {
|
|
47
|
+
it('tokenizes integer literals', () => {
|
|
48
|
+
const tokens = tokenize('42 0 123');
|
|
49
|
+
expect(tokens.map(t => [t.kind, t.value])).toEqual([
|
|
50
|
+
['int_lit', '42'],
|
|
51
|
+
['int_lit', '0'],
|
|
52
|
+
['int_lit', '123'],
|
|
53
|
+
['eof', ''],
|
|
54
|
+
]);
|
|
55
|
+
});
|
|
56
|
+
it('tokenizes float literals', () => {
|
|
57
|
+
const tokens = tokenize('3.14 0.5 10.0');
|
|
58
|
+
expect(tokens.map(t => [t.kind, t.value])).toEqual([
|
|
59
|
+
['float_lit', '3.14'],
|
|
60
|
+
['float_lit', '0.5'],
|
|
61
|
+
['float_lit', '10.0'],
|
|
62
|
+
['eof', ''],
|
|
63
|
+
]);
|
|
64
|
+
});
|
|
65
|
+
it('tokenizes string literals', () => {
|
|
66
|
+
const tokens = tokenize('"hello" "world" "with \\"quotes\\""');
|
|
67
|
+
expect(tokens.map(t => [t.kind, t.value])).toEqual([
|
|
68
|
+
['string_lit', 'hello'],
|
|
69
|
+
['string_lit', 'world'],
|
|
70
|
+
['string_lit', 'with "quotes"'],
|
|
71
|
+
['eof', ''],
|
|
72
|
+
]);
|
|
73
|
+
});
|
|
74
|
+
it('tokenizes interpolated strings as a single string token', () => {
|
|
75
|
+
const tokens = tokenize('"hello ${name + 1}"');
|
|
76
|
+
expect(tokens.map(t => [t.kind, t.value])).toEqual([
|
|
77
|
+
['string_lit', 'hello ${name + 1}'],
|
|
78
|
+
['eof', ''],
|
|
79
|
+
]);
|
|
80
|
+
});
|
|
81
|
+
it('tokenizes f-strings as a dedicated token', () => {
|
|
82
|
+
const tokens = tokenize('f"Hello {name}!"');
|
|
83
|
+
expect(tokens.map(t => [t.kind, t.value])).toEqual([
|
|
84
|
+
['f_string', 'Hello {name}!'],
|
|
85
|
+
['eof', ''],
|
|
86
|
+
]);
|
|
87
|
+
});
|
|
88
|
+
it('tokenizes byte literals (b suffix)', () => {
|
|
89
|
+
const tokens = tokenize('20b 0B 127b');
|
|
90
|
+
expect(tokens.map(t => [t.kind, t.value])).toEqual([
|
|
91
|
+
['byte_lit', '20b'],
|
|
92
|
+
['byte_lit', '0B'],
|
|
93
|
+
['byte_lit', '127b'],
|
|
94
|
+
['eof', ''],
|
|
95
|
+
]);
|
|
96
|
+
});
|
|
97
|
+
it('tokenizes short literals (s suffix)', () => {
|
|
98
|
+
const tokens = tokenize('100s 0S 32767s');
|
|
99
|
+
expect(tokens.map(t => [t.kind, t.value])).toEqual([
|
|
100
|
+
['short_lit', '100s'],
|
|
101
|
+
['short_lit', '0S'],
|
|
102
|
+
['short_lit', '32767s'],
|
|
103
|
+
['eof', ''],
|
|
104
|
+
]);
|
|
105
|
+
});
|
|
106
|
+
it('tokenizes long literals (L suffix)', () => {
|
|
107
|
+
const tokens = tokenize('1000L 0l 999999L');
|
|
108
|
+
expect(tokens.map(t => [t.kind, t.value])).toEqual([
|
|
109
|
+
['long_lit', '1000L'],
|
|
110
|
+
['long_lit', '0l'],
|
|
111
|
+
['long_lit', '999999L'],
|
|
112
|
+
['eof', ''],
|
|
113
|
+
]);
|
|
114
|
+
});
|
|
115
|
+
it('tokenizes float literals with f suffix', () => {
|
|
116
|
+
const tokens = tokenize('3.14f 0.5F 10.0f');
|
|
117
|
+
expect(tokens.map(t => [t.kind, t.value])).toEqual([
|
|
118
|
+
['float_lit', '3.14f'],
|
|
119
|
+
['float_lit', '0.5F'],
|
|
120
|
+
['float_lit', '10.0f'],
|
|
121
|
+
['eof', ''],
|
|
122
|
+
]);
|
|
123
|
+
});
|
|
124
|
+
it('tokenizes double literals (d suffix)', () => {
|
|
125
|
+
const tokens = tokenize('3.14d 0.5D 10.0d');
|
|
126
|
+
expect(tokens.map(t => [t.kind, t.value])).toEqual([
|
|
127
|
+
['double_lit', '3.14d'],
|
|
128
|
+
['double_lit', '0.5D'],
|
|
129
|
+
['double_lit', '10.0d'],
|
|
130
|
+
['eof', ''],
|
|
131
|
+
]);
|
|
132
|
+
});
|
|
133
|
+
it('tokenizes integer with f/d suffix as float/double', () => {
|
|
134
|
+
const tokens = tokenize('5f 10d');
|
|
135
|
+
expect(tokens.map(t => [t.kind, t.value])).toEqual([
|
|
136
|
+
['float_lit', '5f'],
|
|
137
|
+
['double_lit', '10d'],
|
|
138
|
+
['eof', ''],
|
|
139
|
+
]);
|
|
140
|
+
});
|
|
141
|
+
it('does not treat suffix-like letters in identifiers as NBT suffixes', () => {
|
|
142
|
+
// 1b2 should not be byte_lit — the 'b' is followed by a digit
|
|
143
|
+
const tokens = tokenize('1b2');
|
|
144
|
+
expect(tokens.map(t => [t.kind, t.value])).toEqual([
|
|
145
|
+
['int_lit', '1'],
|
|
146
|
+
['ident', 'b2'],
|
|
147
|
+
['eof', ''],
|
|
148
|
+
]);
|
|
149
|
+
});
|
|
150
|
+
it('tokenizes range literals', () => {
|
|
151
|
+
const tokens = tokenize('..5 1.. 1..10');
|
|
152
|
+
expect(tokens.map(t => [t.kind, t.value])).toEqual([
|
|
153
|
+
['range_lit', '..5'],
|
|
154
|
+
['range_lit', '1..'],
|
|
155
|
+
['range_lit', '1..10'],
|
|
156
|
+
['eof', ''],
|
|
157
|
+
]);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
describe('operators', () => {
|
|
161
|
+
it('tokenizes arithmetic operators', () => {
|
|
162
|
+
const tokens = tokenize('+ - * / %');
|
|
163
|
+
expect(kinds(tokens)).toEqual(['+', '-', '*', '/', '%', 'eof']);
|
|
164
|
+
});
|
|
165
|
+
it('tokenizes relative and local coordinates', () => {
|
|
166
|
+
const tokens = tokenize('~ ~5 ~-3 ^ ^10 ^-2');
|
|
167
|
+
expect(kinds(tokens)).toEqual(['rel_coord', 'rel_coord', 'rel_coord', 'local_coord', 'local_coord', 'local_coord', 'eof']);
|
|
168
|
+
expect(tokens[0].value).toBe('~');
|
|
169
|
+
expect(tokens[1].value).toBe('~5');
|
|
170
|
+
expect(tokens[2].value).toBe('~-3');
|
|
171
|
+
expect(tokens[3].value).toBe('^');
|
|
172
|
+
expect(tokens[4].value).toBe('^10');
|
|
173
|
+
expect(tokens[5].value).toBe('^-2');
|
|
174
|
+
});
|
|
175
|
+
it('tokenizes comparison operators', () => {
|
|
176
|
+
const tokens = tokenize('== != < <= > >=');
|
|
177
|
+
expect(kinds(tokens)).toEqual(['==', '!=', '<', '<=', '>', '>=', 'eof']);
|
|
178
|
+
});
|
|
179
|
+
it('tokenizes logical operators', () => {
|
|
180
|
+
const tokens = tokenize('&& || !');
|
|
181
|
+
expect(kinds(tokens)).toEqual(['&&', '||', '!', 'eof']);
|
|
182
|
+
});
|
|
183
|
+
it('tokenizes assignment operators', () => {
|
|
184
|
+
const tokens = tokenize('= += -= *= /= %=');
|
|
185
|
+
expect(kinds(tokens)).toEqual(['=', '+=', '-=', '*=', '/=', '%=', 'eof']);
|
|
186
|
+
});
|
|
187
|
+
it('tokenizes arrow operator', () => {
|
|
188
|
+
const tokens = tokenize('->');
|
|
189
|
+
expect(kinds(tokens)).toEqual(['->', 'eof']);
|
|
190
|
+
});
|
|
191
|
+
it('tokenizes fat arrow operator', () => {
|
|
192
|
+
const tokens = tokenize('=>');
|
|
193
|
+
expect(kinds(tokens)).toEqual(['=>', 'eof']);
|
|
194
|
+
});
|
|
195
|
+
it('tokenizes static method separators for impl methods', () => {
|
|
196
|
+
const tokens = tokenize('Point::new()');
|
|
197
|
+
expect(kinds(tokens)).toEqual(['ident', '::', 'ident', '(', ')', 'eof']);
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
describe('delimiters', () => {
|
|
201
|
+
it('tokenizes all delimiters', () => {
|
|
202
|
+
const tokens = tokenize('{ } ( ) [ ] , ; : .');
|
|
203
|
+
expect(kinds(tokens)).toEqual(['{', '}', '(', ')', '[', ']', ',', ';', ':', '.', 'eof']);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
describe('selectors', () => {
|
|
207
|
+
it('tokenizes simple selectors', () => {
|
|
208
|
+
const tokens = tokenize('@a @e @s @p @r @n');
|
|
209
|
+
expect(tokens.filter(t => t.kind !== 'eof').map(t => [t.kind, t.value])).toEqual([
|
|
210
|
+
['selector', '@a'],
|
|
211
|
+
['selector', '@e'],
|
|
212
|
+
['selector', '@s'],
|
|
213
|
+
['selector', '@p'],
|
|
214
|
+
['selector', '@r'],
|
|
215
|
+
['selector', '@n'],
|
|
216
|
+
]);
|
|
217
|
+
});
|
|
218
|
+
it('tokenizes selectors with parameters', () => {
|
|
219
|
+
const tokens = tokenize('@e[type=zombie] @a[distance=..5]');
|
|
220
|
+
expect(tokens.filter(t => t.kind !== 'eof').map(t => [t.kind, t.value])).toEqual([
|
|
221
|
+
['selector', '@e[type=zombie]'],
|
|
222
|
+
['selector', '@a[distance=..5]'],
|
|
223
|
+
]);
|
|
224
|
+
});
|
|
225
|
+
it('tokenizes selectors with complex NBT', () => {
|
|
226
|
+
const tokens = tokenize('@e[type=zombie, nbt={NoAI:1b}]');
|
|
227
|
+
expect(tokens.filter(t => t.kind !== 'eof').map(t => [t.kind, t.value])).toEqual([
|
|
228
|
+
['selector', '@e[type=zombie, nbt={NoAI:1b}]'],
|
|
229
|
+
]);
|
|
230
|
+
});
|
|
231
|
+
it('handles nested braces in selector NBT', () => {
|
|
232
|
+
const tokens = tokenize('@e[nbt={Items:[{id:"stone"}]}]');
|
|
233
|
+
expect(tokens.filter(t => t.kind !== 'eof').map(t => [t.kind, t.value])).toEqual([
|
|
234
|
+
['selector', '@e[nbt={Items:[{id:"stone"}]}]'],
|
|
235
|
+
]);
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
describe('decorators', () => {
|
|
239
|
+
it('tokenizes simple decorators', () => {
|
|
240
|
+
const tokens = tokenize('@tick @on_trigger');
|
|
241
|
+
expect(tokens.filter(t => t.kind !== 'eof').map(t => [t.kind, t.value])).toEqual([
|
|
242
|
+
['decorator', '@tick'],
|
|
243
|
+
['decorator', '@on_trigger'],
|
|
244
|
+
]);
|
|
245
|
+
});
|
|
246
|
+
it('tokenizes decorators with arguments', () => {
|
|
247
|
+
const tokens = tokenize('@tick(rate=20)');
|
|
248
|
+
expect(tokens.filter(t => t.kind !== 'eof').map(t => [t.kind, t.value])).toEqual([
|
|
249
|
+
['decorator', '@tick(rate=20)'],
|
|
250
|
+
]);
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
describe('raw commands', () => {
|
|
254
|
+
it('tokenizes raw command', () => {
|
|
255
|
+
const tokens = tokenize('raw("say hello")');
|
|
256
|
+
expect(tokens.filter(t => t.kind !== 'eof').map(t => [t.kind, t.value])).toEqual([
|
|
257
|
+
['raw_cmd', 'say hello'],
|
|
258
|
+
]);
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
describe('comments', () => {
|
|
262
|
+
it('skips line comments', () => {
|
|
263
|
+
const tokens = tokenize('let x = 5 // this is a comment\nlet y = 10');
|
|
264
|
+
expect(kinds(tokens)).toEqual(['let', 'ident', '=', 'int_lit', 'let', 'ident', '=', 'int_lit', 'eof']);
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
describe('complex expressions', () => {
|
|
268
|
+
it('tokenizes function declaration', () => {
|
|
269
|
+
const source = 'fn add(a: int, b: int) -> int { return a + b; }';
|
|
270
|
+
const tokens = tokenize(source);
|
|
271
|
+
expect(kinds(tokens)).toEqual([
|
|
272
|
+
'fn', 'ident', '(', 'ident', ':', 'int', ',', 'ident', ':', 'int', ')',
|
|
273
|
+
'->', 'int', '{', 'return', 'ident', '+', 'ident', ';', '}', 'eof'
|
|
274
|
+
]);
|
|
275
|
+
});
|
|
276
|
+
it('tokenizes foreach statement', () => {
|
|
277
|
+
const source = 'foreach (z in @e[type=zombie]) { kill(z); }';
|
|
278
|
+
const tokens = tokenize(source);
|
|
279
|
+
expect(kinds(tokens)).toEqual([
|
|
280
|
+
'foreach', '(', 'ident', 'in', 'selector', ')', '{', 'ident', '(', 'ident', ')', ';', '}', 'eof'
|
|
281
|
+
]);
|
|
282
|
+
});
|
|
283
|
+
it('tokenizes decorated function', () => {
|
|
284
|
+
const source = '@tick(rate=20)\nfn heartbeat() { say("alive"); }';
|
|
285
|
+
const tokens = tokenize(source);
|
|
286
|
+
expect(kinds(tokens)).toEqual([
|
|
287
|
+
'decorator', 'fn', 'ident', '(', ')', '{', 'ident', '(', 'string_lit', ')', ';', '}', 'eof'
|
|
288
|
+
]);
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
describe('line/column tracking', () => {
|
|
292
|
+
it('tracks line and column correctly', () => {
|
|
293
|
+
const source = 'let x\nlet y';
|
|
294
|
+
const tokens = tokenize(source);
|
|
295
|
+
expect(tokens[0]).toMatchObject({ kind: 'let', line: 1, col: 1 });
|
|
296
|
+
expect(tokens[1]).toMatchObject({ kind: 'ident', value: 'x', line: 1, col: 5 });
|
|
297
|
+
expect(tokens[2]).toMatchObject({ kind: 'let', line: 2, col: 1 });
|
|
298
|
+
expect(tokens[3]).toMatchObject({ kind: 'ident', value: 'y', line: 2, col: 5 });
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
describe('edge cases', () => {
|
|
302
|
+
it('handles empty input', () => {
|
|
303
|
+
const tokens = tokenize('');
|
|
304
|
+
expect(kinds(tokens)).toEqual(['eof']);
|
|
305
|
+
});
|
|
306
|
+
it('handles whitespace only', () => {
|
|
307
|
+
const tokens = tokenize(' \n\t ');
|
|
308
|
+
expect(kinds(tokens)).toEqual(['eof']);
|
|
309
|
+
});
|
|
310
|
+
it('distinguishes selector from decorator', () => {
|
|
311
|
+
// @a is selector (single char followed by non-letter)
|
|
312
|
+
// @aa would be decorator
|
|
313
|
+
const tokens = tokenize('@a @aa');
|
|
314
|
+
expect(tokens.filter(t => t.kind !== 'eof').map(t => [t.kind, t.value])).toEqual([
|
|
315
|
+
['selector', '@a'],
|
|
316
|
+
['decorator', '@aa'],
|
|
317
|
+
]);
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
describe('Block comments', () => {
|
|
322
|
+
it('skips single-line block comment', () => {
|
|
323
|
+
const src = `/* comment */ fn test() {}`;
|
|
324
|
+
const tokens = tokenize(src);
|
|
325
|
+
expect(tokens.map(t => t.kind)).not.toContain('/');
|
|
326
|
+
expect(tokens.find(t => t.kind === 'fn')).toBeDefined();
|
|
327
|
+
});
|
|
328
|
+
it('skips multi-line block comment', () => {
|
|
329
|
+
const src = `/**
|
|
330
|
+
* JSDoc comment
|
|
331
|
+
*/
|
|
332
|
+
fn test() {}`;
|
|
333
|
+
const tokens = tokenize(src);
|
|
334
|
+
expect(tokens.map(t => t.kind)).not.toContain('/');
|
|
335
|
+
expect(tokens.find(t => t.kind === 'fn')).toBeDefined();
|
|
336
|
+
});
|
|
337
|
+
it('handles block comment with asterisks', () => {
|
|
338
|
+
const src = `/*** stars ***/fn x(){}`;
|
|
339
|
+
const tokens = tokenize(src);
|
|
340
|
+
expect(tokens.find(t => t.kind === 'fn')).toBeDefined();
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
//# sourceMappingURL=lexer.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|