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,2739 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Migration tests — representative cases from the existing 920-test suite
|
|
4
|
+
* run through the NEW v2 compiler pipeline.
|
|
5
|
+
*
|
|
6
|
+
* Pattern:
|
|
7
|
+
* 1. Compile source with src2/emit/compile.ts
|
|
8
|
+
* 2. Load .mcfunction files into MCRuntime
|
|
9
|
+
* 3. Execute functions and assert scoreboard state
|
|
10
|
+
*
|
|
11
|
+
* NOTE: v2 uses objective `__<ns>` (not `rs`), load function is `ns:load`
|
|
12
|
+
* (not `ns:__load`), and return values go to `$ret` on the objective.
|
|
13
|
+
*/
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
const compile_1 = require("../../emit/compile");
|
|
16
|
+
const runtime_1 = require("../../../src/runtime");
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Helpers
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
const NS = 'test';
|
|
21
|
+
const OBJ = `__${NS}`;
|
|
22
|
+
function getFile(files, pathSubstr) {
|
|
23
|
+
return files.find(f => f.path.includes(pathSubstr))?.content;
|
|
24
|
+
}
|
|
25
|
+
/** Compile source, load all .mcfunction files into MCRuntime, init objective */
|
|
26
|
+
function makeRuntime(source, namespace = NS) {
|
|
27
|
+
const result = (0, compile_1.compile)(source, { namespace });
|
|
28
|
+
const rt = new runtime_1.MCRuntime(namespace);
|
|
29
|
+
for (const file of result.files) {
|
|
30
|
+
if (!file.path.endsWith('.mcfunction'))
|
|
31
|
+
continue;
|
|
32
|
+
const m = file.path.match(/data\/([^/]+)\/function\/(.+)\.mcfunction$/);
|
|
33
|
+
if (!m)
|
|
34
|
+
continue;
|
|
35
|
+
rt.loadFunction(`${m[1]}:${m[2]}`, file.content.split('\n'));
|
|
36
|
+
}
|
|
37
|
+
// Init the objective (v2 load function creates it)
|
|
38
|
+
rt.execFunction(`${namespace}:load`);
|
|
39
|
+
return rt;
|
|
40
|
+
}
|
|
41
|
+
/** Execute a function and return the $ret value on the v2 objective */
|
|
42
|
+
function callAndGetRet(rt, fnName, namespace = NS) {
|
|
43
|
+
rt.execFunction(`${namespace}:${fnName}`);
|
|
44
|
+
return rt.getScore('$ret', `__${namespace}`);
|
|
45
|
+
}
|
|
46
|
+
// ===========================================================================
|
|
47
|
+
// 1. Compilation smoke tests — does the compiler not crash?
|
|
48
|
+
// ===========================================================================
|
|
49
|
+
describe('v2 migration: compilation smoke', () => {
|
|
50
|
+
const cases = [
|
|
51
|
+
['empty function', 'fn noop(): void {}'],
|
|
52
|
+
['return constant', 'fn f(): int { return 42; }'],
|
|
53
|
+
['arithmetic', 'fn f(): int { return 1 + 2; }'],
|
|
54
|
+
['variable', 'fn f(): int { let x: int = 10; return x; }'],
|
|
55
|
+
['negation', 'fn f(): int { return -5; }'],
|
|
56
|
+
['comparison', 'fn f(): bool { return 3 > 2; }'],
|
|
57
|
+
['if/else', 'fn f(x: int): int { if (x > 0) { return 1; } else { return 0; } }'],
|
|
58
|
+
['while loop', 'fn f(): void { let i: int = 0; while (i < 10) { i = i + 1; } }'],
|
|
59
|
+
['multiple functions', 'fn a(): int { return 1; }\nfn b(): int { return 2; }'],
|
|
60
|
+
['function call', 'fn add(a: int, b: int): int { return a + b; }\nfn main(): int { return add(3, 4); }'],
|
|
61
|
+
['boolean AND', 'fn f(): bool { return true && false; }'],
|
|
62
|
+
['boolean OR', 'fn f(): bool { return true || false; }'],
|
|
63
|
+
['boolean NOT', 'fn f(): bool { return !true; }'],
|
|
64
|
+
['nested arithmetic', 'fn f(): int { return (1 + 2) * (3 - 4); }'],
|
|
65
|
+
['modulo', 'fn f(): int { return 10 % 3; }'],
|
|
66
|
+
['@tick decorator', '@tick fn game_tick(): void { let x: int = 1; }'],
|
|
67
|
+
['@load decorator', '@load fn setup(): void { let x: int = 0; }'],
|
|
68
|
+
['chained comparison', 'fn f(x: int): bool { return x >= 0 && x <= 100; }'],
|
|
69
|
+
];
|
|
70
|
+
test.each(cases)('%s compiles without error', (_name, source) => {
|
|
71
|
+
expect(() => (0, compile_1.compile)(source, { namespace: NS })).not.toThrow();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
// ===========================================================================
|
|
75
|
+
// 2. Structural tests — output shape
|
|
76
|
+
// ===========================================================================
|
|
77
|
+
describe('v2 migration: output structure', () => {
|
|
78
|
+
test('pack.mcmeta present with pack_format 26', () => {
|
|
79
|
+
const result = (0, compile_1.compile)('fn noop(): void {}', { namespace: NS });
|
|
80
|
+
const meta = getFile(result.files, 'pack.mcmeta');
|
|
81
|
+
expect(meta).toBeDefined();
|
|
82
|
+
expect(JSON.parse(meta).pack.pack_format).toBe(26);
|
|
83
|
+
});
|
|
84
|
+
test('load.mcfunction creates scoreboard objective', () => {
|
|
85
|
+
const result = (0, compile_1.compile)('fn noop(): void {}', { namespace: NS });
|
|
86
|
+
const load = getFile(result.files, 'load.mcfunction');
|
|
87
|
+
expect(load).toBeDefined();
|
|
88
|
+
expect(load).toContain(`scoreboard objectives add ${OBJ} dummy`);
|
|
89
|
+
});
|
|
90
|
+
test('load.json always includes namespace:load', () => {
|
|
91
|
+
const result = (0, compile_1.compile)('fn noop(): void {}', { namespace: NS });
|
|
92
|
+
const loadJson = getFile(result.files, 'load.json');
|
|
93
|
+
expect(loadJson).toBeDefined();
|
|
94
|
+
expect(JSON.parse(loadJson).values).toContain(`${NS}:load`);
|
|
95
|
+
});
|
|
96
|
+
test('@tick function appears in tick.json', () => {
|
|
97
|
+
const result = (0, compile_1.compile)('@tick fn game_tick(): void { let x: int = 1; }', { namespace: NS });
|
|
98
|
+
const tickJson = getFile(result.files, 'tick.json');
|
|
99
|
+
expect(tickJson).toBeDefined();
|
|
100
|
+
expect(JSON.parse(tickJson).values).toContain(`${NS}:game_tick`);
|
|
101
|
+
});
|
|
102
|
+
test('@load function appears in load.json', () => {
|
|
103
|
+
const result = (0, compile_1.compile)('@load fn setup(): void { let x: int = 0; }', { namespace: NS });
|
|
104
|
+
const loadJson = getFile(result.files, 'load.json');
|
|
105
|
+
expect(loadJson).toBeDefined();
|
|
106
|
+
expect(JSON.parse(loadJson).values).toContain(`${NS}:setup`);
|
|
107
|
+
});
|
|
108
|
+
test('no tick.json when no @tick functions', () => {
|
|
109
|
+
const result = (0, compile_1.compile)('fn noop(): void {}', { namespace: NS });
|
|
110
|
+
const tickJson = getFile(result.files, 'tick.json');
|
|
111
|
+
expect(tickJson).toBeUndefined();
|
|
112
|
+
});
|
|
113
|
+
test('function names are lowercased in output paths', () => {
|
|
114
|
+
const result = (0, compile_1.compile)('fn MyFunc(): void {}', { namespace: NS });
|
|
115
|
+
const fn = result.files.find(f => f.path.includes('myfunc.mcfunction'));
|
|
116
|
+
expect(fn).toBeDefined();
|
|
117
|
+
});
|
|
118
|
+
test('simple function produces scoreboard commands', () => {
|
|
119
|
+
const result = (0, compile_1.compile)('fn add(a: int, b: int): int { return a + b; }', { namespace: NS });
|
|
120
|
+
const fn = getFile(result.files, 'add.mcfunction');
|
|
121
|
+
expect(fn).toBeDefined();
|
|
122
|
+
expect(fn).toContain('scoreboard players operation');
|
|
123
|
+
expect(fn).toContain(OBJ);
|
|
124
|
+
});
|
|
125
|
+
test('constant assignment produces score_set', () => {
|
|
126
|
+
const result = (0, compile_1.compile)('fn init(): int { let x: int = 42; return x; }', { namespace: NS });
|
|
127
|
+
const fn = getFile(result.files, 'init.mcfunction');
|
|
128
|
+
expect(fn).toBeDefined();
|
|
129
|
+
expect(fn).toContain('scoreboard players set');
|
|
130
|
+
expect(fn).toContain('42');
|
|
131
|
+
});
|
|
132
|
+
test('if/else produces conditional call pattern', () => {
|
|
133
|
+
const source = `
|
|
134
|
+
fn check(x: int): int {
|
|
135
|
+
if (x > 0) { return 1; } else { return 0; }
|
|
136
|
+
}
|
|
137
|
+
`;
|
|
138
|
+
const result = (0, compile_1.compile)(source, { namespace: NS });
|
|
139
|
+
const fn = getFile(result.files, 'check.mcfunction');
|
|
140
|
+
expect(fn).toBeDefined();
|
|
141
|
+
expect(fn).toContain('execute if score');
|
|
142
|
+
expect(fn).toContain('matches');
|
|
143
|
+
expect(fn).toContain('run function');
|
|
144
|
+
});
|
|
145
|
+
test('while loop produces loop structure with function calls', () => {
|
|
146
|
+
const source = `
|
|
147
|
+
fn count(): void {
|
|
148
|
+
let i: int = 0;
|
|
149
|
+
while (i < 10) { i = i + 1; }
|
|
150
|
+
}
|
|
151
|
+
`;
|
|
152
|
+
const result = (0, compile_1.compile)(source, { namespace: NS });
|
|
153
|
+
const fnFiles = result.files.filter(f => f.path.endsWith('.mcfunction'));
|
|
154
|
+
expect(fnFiles.length).toBeGreaterThan(1); // main + loop blocks
|
|
155
|
+
const allContent = fnFiles.map(f => f.content).join('\n');
|
|
156
|
+
expect(allContent).toContain('execute if score');
|
|
157
|
+
expect(allContent).toContain('run function');
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
// ===========================================================================
|
|
161
|
+
// 3. Runtime behavioural tests — execute and check scoreboard values
|
|
162
|
+
// ===========================================================================
|
|
163
|
+
describe('v2 migration: return values', () => {
|
|
164
|
+
test('return constant 42', () => {
|
|
165
|
+
const rt = makeRuntime('fn f(): int { return 42; }');
|
|
166
|
+
expect(callAndGetRet(rt, 'f')).toBe(42);
|
|
167
|
+
});
|
|
168
|
+
test('return zero', () => {
|
|
169
|
+
const rt = makeRuntime('fn f(): int { return 0; }');
|
|
170
|
+
expect(callAndGetRet(rt, 'f')).toBe(0);
|
|
171
|
+
});
|
|
172
|
+
test('return negative', () => {
|
|
173
|
+
const rt = makeRuntime('fn f(): int { return -10; }');
|
|
174
|
+
expect(callAndGetRet(rt, 'f')).toBe(-10);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
describe('v2 migration: arithmetic', () => {
|
|
178
|
+
test('1 + 2 = 3', () => {
|
|
179
|
+
const rt = makeRuntime('fn f(): int { return 1 + 2; }');
|
|
180
|
+
expect(callAndGetRet(rt, 'f')).toBe(3);
|
|
181
|
+
});
|
|
182
|
+
test('10 - 3 = 7', () => {
|
|
183
|
+
const rt = makeRuntime('fn f(): int { return 10 - 3; }');
|
|
184
|
+
expect(callAndGetRet(rt, 'f')).toBe(7);
|
|
185
|
+
});
|
|
186
|
+
test('4 * 5 = 20', () => {
|
|
187
|
+
const rt = makeRuntime('fn f(): int { return 4 * 5; }');
|
|
188
|
+
expect(callAndGetRet(rt, 'f')).toBe(20);
|
|
189
|
+
});
|
|
190
|
+
test('20 / 4 = 5', () => {
|
|
191
|
+
const rt = makeRuntime('fn f(): int { return 20 / 4; }');
|
|
192
|
+
expect(callAndGetRet(rt, 'f')).toBe(5);
|
|
193
|
+
});
|
|
194
|
+
test('10 % 3 = 1', () => {
|
|
195
|
+
const rt = makeRuntime('fn f(): int { return 10 % 3; }');
|
|
196
|
+
expect(callAndGetRet(rt, 'f')).toBe(1);
|
|
197
|
+
});
|
|
198
|
+
test('chained: (2 + 3) * 4 = 20', () => {
|
|
199
|
+
const rt = makeRuntime('fn f(): int { return (2 + 3) * 4; }');
|
|
200
|
+
expect(callAndGetRet(rt, 'f')).toBe(20);
|
|
201
|
+
});
|
|
202
|
+
test('negation: -(5) = -5', () => {
|
|
203
|
+
const rt = makeRuntime('fn f(): int { return -(5); }');
|
|
204
|
+
expect(callAndGetRet(rt, 'f')).toBe(-5);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
describe('v2 migration: variables', () => {
|
|
208
|
+
test('let and return', () => {
|
|
209
|
+
const rt = makeRuntime('fn f(): int { let x: int = 42; return x; }');
|
|
210
|
+
expect(callAndGetRet(rt, 'f')).toBe(42);
|
|
211
|
+
});
|
|
212
|
+
test('variable reassignment', () => {
|
|
213
|
+
const rt = makeRuntime(`
|
|
214
|
+
fn f(): int {
|
|
215
|
+
let x: int = 1;
|
|
216
|
+
x = 10;
|
|
217
|
+
return x;
|
|
218
|
+
}
|
|
219
|
+
`);
|
|
220
|
+
expect(callAndGetRet(rt, 'f')).toBe(10);
|
|
221
|
+
});
|
|
222
|
+
test('multiple variables', () => {
|
|
223
|
+
const rt = makeRuntime(`
|
|
224
|
+
fn f(): int {
|
|
225
|
+
let a: int = 3;
|
|
226
|
+
let b: int = 7;
|
|
227
|
+
return a + b;
|
|
228
|
+
}
|
|
229
|
+
`);
|
|
230
|
+
expect(callAndGetRet(rt, 'f')).toBe(10);
|
|
231
|
+
});
|
|
232
|
+
test('variable used in expression', () => {
|
|
233
|
+
const rt = makeRuntime(`
|
|
234
|
+
fn f(): int {
|
|
235
|
+
let x: int = 5;
|
|
236
|
+
let y: int = x * 2 + 1;
|
|
237
|
+
return y;
|
|
238
|
+
}
|
|
239
|
+
`);
|
|
240
|
+
expect(callAndGetRet(rt, 'f')).toBe(11);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
describe('v2 migration: comparisons', () => {
|
|
244
|
+
test('3 > 2 is true (1)', () => {
|
|
245
|
+
const rt = makeRuntime('fn f(): int { if (3 > 2) { return 1; } else { return 0; } }');
|
|
246
|
+
expect(callAndGetRet(rt, 'f')).toBe(1);
|
|
247
|
+
});
|
|
248
|
+
test('2 > 3 is false (0)', () => {
|
|
249
|
+
const rt = makeRuntime('fn f(): int { if (2 > 3) { return 1; } else { return 0; } }');
|
|
250
|
+
expect(callAndGetRet(rt, 'f')).toBe(0);
|
|
251
|
+
});
|
|
252
|
+
test('5 == 5 is true', () => {
|
|
253
|
+
const rt = makeRuntime('fn f(): int { if (5 == 5) { return 1; } else { return 0; } }');
|
|
254
|
+
expect(callAndGetRet(rt, 'f')).toBe(1);
|
|
255
|
+
});
|
|
256
|
+
test('5 != 3 is true', () => {
|
|
257
|
+
const rt = makeRuntime('fn f(): int { if (5 != 3) { return 1; } else { return 0; } }');
|
|
258
|
+
expect(callAndGetRet(rt, 'f')).toBe(1);
|
|
259
|
+
});
|
|
260
|
+
test('3 <= 3 is true', () => {
|
|
261
|
+
const rt = makeRuntime('fn f(): int { if (3 <= 3) { return 1; } else { return 0; } }');
|
|
262
|
+
expect(callAndGetRet(rt, 'f')).toBe(1);
|
|
263
|
+
});
|
|
264
|
+
test('4 >= 5 is false', () => {
|
|
265
|
+
const rt = makeRuntime('fn f(): int { if (4 >= 5) { return 1; } else { return 0; } }');
|
|
266
|
+
expect(callAndGetRet(rt, 'f')).toBe(0);
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
describe('v2 migration: if/else control flow', () => {
|
|
270
|
+
test('if-true branch taken', () => {
|
|
271
|
+
const rt = makeRuntime(`
|
|
272
|
+
fn f(): int {
|
|
273
|
+
let x: int = 10;
|
|
274
|
+
if (x > 5) { return 1; } else { return 0; }
|
|
275
|
+
}
|
|
276
|
+
`);
|
|
277
|
+
expect(callAndGetRet(rt, 'f')).toBe(1);
|
|
278
|
+
});
|
|
279
|
+
test('if-false branch taken', () => {
|
|
280
|
+
const rt = makeRuntime(`
|
|
281
|
+
fn f(): int {
|
|
282
|
+
let x: int = 2;
|
|
283
|
+
if (x > 5) { return 1; } else { return 0; }
|
|
284
|
+
}
|
|
285
|
+
`);
|
|
286
|
+
expect(callAndGetRet(rt, 'f')).toBe(0);
|
|
287
|
+
});
|
|
288
|
+
test('if without else — falls through', () => {
|
|
289
|
+
const rt = makeRuntime(`
|
|
290
|
+
fn f(): int {
|
|
291
|
+
let x: int = 0;
|
|
292
|
+
if (true) { x = 42; }
|
|
293
|
+
return x;
|
|
294
|
+
}
|
|
295
|
+
`);
|
|
296
|
+
expect(callAndGetRet(rt, 'f')).toBe(42);
|
|
297
|
+
});
|
|
298
|
+
test('nested if/else', () => {
|
|
299
|
+
const rt = makeRuntime(`
|
|
300
|
+
fn f(): int {
|
|
301
|
+
let x: int = 15;
|
|
302
|
+
if (x > 20) {
|
|
303
|
+
return 3;
|
|
304
|
+
} else {
|
|
305
|
+
if (x > 10) {
|
|
306
|
+
return 2;
|
|
307
|
+
} else {
|
|
308
|
+
return 1;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
`);
|
|
313
|
+
expect(callAndGetRet(rt, 'f')).toBe(2);
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
describe('v2 migration: while loops', () => {
|
|
317
|
+
test('simple countdown', () => {
|
|
318
|
+
const rt = makeRuntime(`
|
|
319
|
+
fn f(): int {
|
|
320
|
+
let i: int = 10;
|
|
321
|
+
while (i > 0) { i = i - 1; }
|
|
322
|
+
return i;
|
|
323
|
+
}
|
|
324
|
+
`);
|
|
325
|
+
expect(callAndGetRet(rt, 'f')).toBe(0);
|
|
326
|
+
});
|
|
327
|
+
test('sum 1 to 5', () => {
|
|
328
|
+
const rt = makeRuntime(`
|
|
329
|
+
fn f(): int {
|
|
330
|
+
let sum: int = 0;
|
|
331
|
+
let i: int = 1;
|
|
332
|
+
while (i <= 5) {
|
|
333
|
+
sum = sum + i;
|
|
334
|
+
i = i + 1;
|
|
335
|
+
}
|
|
336
|
+
return sum;
|
|
337
|
+
}
|
|
338
|
+
`);
|
|
339
|
+
expect(callAndGetRet(rt, 'f')).toBe(15);
|
|
340
|
+
});
|
|
341
|
+
test('while false — never executes', () => {
|
|
342
|
+
const rt = makeRuntime(`
|
|
343
|
+
fn f(): int {
|
|
344
|
+
let x: int = 99;
|
|
345
|
+
while (false) { x = 0; }
|
|
346
|
+
return x;
|
|
347
|
+
}
|
|
348
|
+
`);
|
|
349
|
+
expect(callAndGetRet(rt, 'f')).toBe(99);
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
describe('v2 migration: function calls', () => {
|
|
353
|
+
test('call simple function', () => {
|
|
354
|
+
const rt = makeRuntime(`
|
|
355
|
+
fn double(x: int): int { return x * 2; }
|
|
356
|
+
fn f(): int { return double(21); }
|
|
357
|
+
`);
|
|
358
|
+
expect(callAndGetRet(rt, 'f')).toBe(42);
|
|
359
|
+
});
|
|
360
|
+
test('call with two params', () => {
|
|
361
|
+
const rt = makeRuntime(`
|
|
362
|
+
fn add(a: int, b: int): int { return a + b; }
|
|
363
|
+
fn f(): int { return add(17, 25); }
|
|
364
|
+
`);
|
|
365
|
+
expect(callAndGetRet(rt, 'f')).toBe(42);
|
|
366
|
+
});
|
|
367
|
+
test('chain of calls', () => {
|
|
368
|
+
const rt = makeRuntime(`
|
|
369
|
+
fn inc(x: int): int { return x + 1; }
|
|
370
|
+
fn f(): int { return inc(inc(inc(0))); }
|
|
371
|
+
`);
|
|
372
|
+
expect(callAndGetRet(rt, 'f')).toBe(3);
|
|
373
|
+
});
|
|
374
|
+
test('call function with no return used in expression', () => {
|
|
375
|
+
const rt = makeRuntime(`
|
|
376
|
+
fn five(): int { return 5; }
|
|
377
|
+
fn f(): int {
|
|
378
|
+
let x: int = five() + five();
|
|
379
|
+
return x;
|
|
380
|
+
}
|
|
381
|
+
`);
|
|
382
|
+
expect(callAndGetRet(rt, 'f')).toBe(10);
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
describe('v2 migration: boolean logic', () => {
|
|
386
|
+
test('true AND true = 1', () => {
|
|
387
|
+
const rt = makeRuntime(`
|
|
388
|
+
fn f(): int {
|
|
389
|
+
if (true && true) { return 1; } else { return 0; }
|
|
390
|
+
}
|
|
391
|
+
`);
|
|
392
|
+
expect(callAndGetRet(rt, 'f')).toBe(1);
|
|
393
|
+
});
|
|
394
|
+
test('true AND false = 0', () => {
|
|
395
|
+
const rt = makeRuntime(`
|
|
396
|
+
fn f(): int {
|
|
397
|
+
if (true && false) { return 1; } else { return 0; }
|
|
398
|
+
}
|
|
399
|
+
`);
|
|
400
|
+
expect(callAndGetRet(rt, 'f')).toBe(0);
|
|
401
|
+
});
|
|
402
|
+
test('false OR true = 1', () => {
|
|
403
|
+
const rt = makeRuntime(`
|
|
404
|
+
fn f(): int {
|
|
405
|
+
if (false || true) { return 1; } else { return 0; }
|
|
406
|
+
}
|
|
407
|
+
`);
|
|
408
|
+
expect(callAndGetRet(rt, 'f')).toBe(1);
|
|
409
|
+
});
|
|
410
|
+
test('NOT true = 0', () => {
|
|
411
|
+
const rt = makeRuntime(`
|
|
412
|
+
fn f(): int {
|
|
413
|
+
if (!true) { return 1; } else { return 0; }
|
|
414
|
+
}
|
|
415
|
+
`);
|
|
416
|
+
expect(callAndGetRet(rt, 'f')).toBe(0);
|
|
417
|
+
});
|
|
418
|
+
});
|
|
419
|
+
// ===========================================================================
|
|
420
|
+
// 4. More complex patterns from the original test suite
|
|
421
|
+
// ===========================================================================
|
|
422
|
+
describe('v2 migration: compound expressions', () => {
|
|
423
|
+
test('abs via if/else', () => {
|
|
424
|
+
const rt = makeRuntime(`
|
|
425
|
+
fn abs(x: int): int {
|
|
426
|
+
if (x < 0) { return -x; } else { return x; }
|
|
427
|
+
}
|
|
428
|
+
fn f(): int { return abs(-7); }
|
|
429
|
+
`);
|
|
430
|
+
expect(callAndGetRet(rt, 'f')).toBe(7);
|
|
431
|
+
});
|
|
432
|
+
test('max of two', () => {
|
|
433
|
+
const rt = makeRuntime(`
|
|
434
|
+
fn max(a: int, b: int): int {
|
|
435
|
+
if (a > b) { return a; } else { return b; }
|
|
436
|
+
}
|
|
437
|
+
fn f(): int { return max(3, 7); }
|
|
438
|
+
`);
|
|
439
|
+
expect(callAndGetRet(rt, 'f')).toBe(7);
|
|
440
|
+
});
|
|
441
|
+
test('factorial iterative', () => {
|
|
442
|
+
const rt = makeRuntime(`
|
|
443
|
+
fn fact(n: int): int {
|
|
444
|
+
let result: int = 1;
|
|
445
|
+
let i: int = 1;
|
|
446
|
+
while (i <= n) {
|
|
447
|
+
result = result * i;
|
|
448
|
+
i = i + 1;
|
|
449
|
+
}
|
|
450
|
+
return result;
|
|
451
|
+
}
|
|
452
|
+
fn f(): int { return fact(5); }
|
|
453
|
+
`);
|
|
454
|
+
expect(callAndGetRet(rt, 'f')).toBe(120);
|
|
455
|
+
});
|
|
456
|
+
test('fibonacci iterative', () => {
|
|
457
|
+
const rt = makeRuntime(`
|
|
458
|
+
fn fib(n: int): int {
|
|
459
|
+
let a: int = 0;
|
|
460
|
+
let b: int = 1;
|
|
461
|
+
let i: int = 0;
|
|
462
|
+
while (i < n) {
|
|
463
|
+
let tmp: int = b;
|
|
464
|
+
b = a + b;
|
|
465
|
+
a = tmp;
|
|
466
|
+
i = i + 1;
|
|
467
|
+
}
|
|
468
|
+
return a;
|
|
469
|
+
}
|
|
470
|
+
fn f(): int { return fib(10); }
|
|
471
|
+
`);
|
|
472
|
+
expect(callAndGetRet(rt, 'f')).toBe(55);
|
|
473
|
+
});
|
|
474
|
+
});
|
|
475
|
+
// ===========================================================================
|
|
476
|
+
// 5. Break / continue
|
|
477
|
+
// ===========================================================================
|
|
478
|
+
describe('v2 migration: break and continue', () => {
|
|
479
|
+
test('break exits loop early', () => {
|
|
480
|
+
const rt = makeRuntime(`
|
|
481
|
+
fn f(): int {
|
|
482
|
+
let i: int = 0;
|
|
483
|
+
while (true) {
|
|
484
|
+
if (i == 5) { break; }
|
|
485
|
+
i = i + 1;
|
|
486
|
+
}
|
|
487
|
+
return i;
|
|
488
|
+
}
|
|
489
|
+
`);
|
|
490
|
+
expect(callAndGetRet(rt, 'f')).toBe(5);
|
|
491
|
+
});
|
|
492
|
+
test('continue skips iteration', () => {
|
|
493
|
+
const rt = makeRuntime(`
|
|
494
|
+
fn f(): int {
|
|
495
|
+
let sum: int = 0;
|
|
496
|
+
let i: int = 0;
|
|
497
|
+
while (i < 10) {
|
|
498
|
+
i = i + 1;
|
|
499
|
+
if (i % 2 == 0) { continue; }
|
|
500
|
+
sum = sum + i;
|
|
501
|
+
}
|
|
502
|
+
return sum;
|
|
503
|
+
}
|
|
504
|
+
`);
|
|
505
|
+
// sum of odd numbers 1..9 = 1+3+5+7+9 = 25
|
|
506
|
+
expect(callAndGetRet(rt, 'f')).toBe(25);
|
|
507
|
+
});
|
|
508
|
+
});
|
|
509
|
+
// ===========================================================================
|
|
510
|
+
// 6. Compound assignment operators (desugared in HIR)
|
|
511
|
+
// ===========================================================================
|
|
512
|
+
describe('v2 migration: compound assignment', () => {
|
|
513
|
+
test('+= operator', () => {
|
|
514
|
+
const rt = makeRuntime(`
|
|
515
|
+
fn f(): int { let x: int = 10; x += 5; return x; }
|
|
516
|
+
`);
|
|
517
|
+
expect(callAndGetRet(rt, 'f')).toBe(15);
|
|
518
|
+
});
|
|
519
|
+
test('-= operator', () => {
|
|
520
|
+
const rt = makeRuntime(`
|
|
521
|
+
fn f(): int { let x: int = 10; x -= 3; return x; }
|
|
522
|
+
`);
|
|
523
|
+
expect(callAndGetRet(rt, 'f')).toBe(7);
|
|
524
|
+
});
|
|
525
|
+
test('*= operator', () => {
|
|
526
|
+
const rt = makeRuntime(`
|
|
527
|
+
fn f(): int { let x: int = 4; x *= 5; return x; }
|
|
528
|
+
`);
|
|
529
|
+
expect(callAndGetRet(rt, 'f')).toBe(20);
|
|
530
|
+
});
|
|
531
|
+
});
|
|
532
|
+
// ===========================================================================
|
|
533
|
+
// 7. Multiple return paths
|
|
534
|
+
// ===========================================================================
|
|
535
|
+
describe('v2 migration: multiple return paths', () => {
|
|
536
|
+
test('early return from if', () => {
|
|
537
|
+
const rt = makeRuntime(`
|
|
538
|
+
fn f(): int {
|
|
539
|
+
let x: int = 42;
|
|
540
|
+
if (x > 10) { return x; }
|
|
541
|
+
return 0;
|
|
542
|
+
}
|
|
543
|
+
`);
|
|
544
|
+
expect(callAndGetRet(rt, 'f')).toBe(42);
|
|
545
|
+
});
|
|
546
|
+
test('return from else path', () => {
|
|
547
|
+
const rt = makeRuntime(`
|
|
548
|
+
fn f(): int {
|
|
549
|
+
let x: int = 5;
|
|
550
|
+
if (x > 10) { return 1; }
|
|
551
|
+
return 2;
|
|
552
|
+
}
|
|
553
|
+
`);
|
|
554
|
+
expect(callAndGetRet(rt, 'f')).toBe(2);
|
|
555
|
+
});
|
|
556
|
+
test('return from nested if chains', () => {
|
|
557
|
+
const rt = makeRuntime(`
|
|
558
|
+
fn classify(x: int): int {
|
|
559
|
+
if (x < 0) { return -1; }
|
|
560
|
+
if (x == 0) { return 0; }
|
|
561
|
+
return 1;
|
|
562
|
+
}
|
|
563
|
+
fn f(): int {
|
|
564
|
+
return classify(-5) + classify(0) + classify(7);
|
|
565
|
+
}
|
|
566
|
+
`);
|
|
567
|
+
// -1 + 0 + 1 = 0
|
|
568
|
+
expect(callAndGetRet(rt, 'f')).toBe(0);
|
|
569
|
+
});
|
|
570
|
+
});
|
|
571
|
+
// ===========================================================================
|
|
572
|
+
// 8. Mutual function calls
|
|
573
|
+
// ===========================================================================
|
|
574
|
+
describe('v2 migration: mutual calls and recursion-like patterns', () => {
|
|
575
|
+
test('function calling function calling function', () => {
|
|
576
|
+
const rt = makeRuntime(`
|
|
577
|
+
fn a(): int { return 1; }
|
|
578
|
+
fn b(): int { return a() + 2; }
|
|
579
|
+
fn c(): int { return b() + 3; }
|
|
580
|
+
fn f(): int { return c(); }
|
|
581
|
+
`);
|
|
582
|
+
expect(callAndGetRet(rt, 'f')).toBe(6);
|
|
583
|
+
});
|
|
584
|
+
test('iterative power function', () => {
|
|
585
|
+
const rt = makeRuntime(`
|
|
586
|
+
fn pow(base: int, exp: int): int {
|
|
587
|
+
let result: int = 1;
|
|
588
|
+
let i: int = 0;
|
|
589
|
+
while (i < exp) {
|
|
590
|
+
result = result * base;
|
|
591
|
+
i = i + 1;
|
|
592
|
+
}
|
|
593
|
+
return result;
|
|
594
|
+
}
|
|
595
|
+
fn f(): int { return pow(2, 10); }
|
|
596
|
+
`);
|
|
597
|
+
expect(callAndGetRet(rt, 'f')).toBe(1024);
|
|
598
|
+
});
|
|
599
|
+
});
|
|
600
|
+
// ===========================================================================
|
|
601
|
+
// 9. Edge cases
|
|
602
|
+
// ===========================================================================
|
|
603
|
+
describe('v2 migration: edge cases', () => {
|
|
604
|
+
test('zero division (MC truncates to 0)', () => {
|
|
605
|
+
// MC scoreboard division by zero returns 0
|
|
606
|
+
const rt = makeRuntime('fn f(): int { return 10 / 0; }');
|
|
607
|
+
// This may throw or return 0 depending on MCRuntime behavior
|
|
608
|
+
try {
|
|
609
|
+
const val = callAndGetRet(rt, 'f');
|
|
610
|
+
expect(val).toBe(0);
|
|
611
|
+
}
|
|
612
|
+
catch {
|
|
613
|
+
// Division by zero is undefined in MC — just ensure no crash
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
test('deeply nested arithmetic', () => {
|
|
617
|
+
const rt = makeRuntime(`
|
|
618
|
+
fn f(): int {
|
|
619
|
+
return ((1 + 2) * (3 + 4)) - ((5 - 6) * (7 + 8));
|
|
620
|
+
}
|
|
621
|
+
`);
|
|
622
|
+
// (3 * 7) - ((-1) * 15) = 21 - (-15) = 36
|
|
623
|
+
expect(callAndGetRet(rt, 'f')).toBe(36);
|
|
624
|
+
});
|
|
625
|
+
test('many variables', () => {
|
|
626
|
+
const rt = makeRuntime(`
|
|
627
|
+
fn f(): int {
|
|
628
|
+
let a: int = 1;
|
|
629
|
+
let b: int = 2;
|
|
630
|
+
let c: int = 3;
|
|
631
|
+
let d: int = 4;
|
|
632
|
+
let e: int = 5;
|
|
633
|
+
return a + b + c + d + e;
|
|
634
|
+
}
|
|
635
|
+
`);
|
|
636
|
+
expect(callAndGetRet(rt, 'f')).toBe(15);
|
|
637
|
+
});
|
|
638
|
+
test('nested while loops', () => {
|
|
639
|
+
const rt = makeRuntime(`
|
|
640
|
+
fn f(): int {
|
|
641
|
+
let sum: int = 0;
|
|
642
|
+
let i: int = 0;
|
|
643
|
+
while (i < 3) {
|
|
644
|
+
let j: int = 0;
|
|
645
|
+
while (j < 3) {
|
|
646
|
+
sum = sum + 1;
|
|
647
|
+
j = j + 1;
|
|
648
|
+
}
|
|
649
|
+
i = i + 1;
|
|
650
|
+
}
|
|
651
|
+
return sum;
|
|
652
|
+
}
|
|
653
|
+
`);
|
|
654
|
+
expect(callAndGetRet(rt, 'f')).toBe(9);
|
|
655
|
+
});
|
|
656
|
+
test('if inside while', () => {
|
|
657
|
+
const rt = makeRuntime(`
|
|
658
|
+
fn f(): int {
|
|
659
|
+
let count: int = 0;
|
|
660
|
+
let i: int = 0;
|
|
661
|
+
while (i < 10) {
|
|
662
|
+
if (i % 3 == 0) {
|
|
663
|
+
count = count + 1;
|
|
664
|
+
}
|
|
665
|
+
i = i + 1;
|
|
666
|
+
}
|
|
667
|
+
return count;
|
|
668
|
+
}
|
|
669
|
+
`);
|
|
670
|
+
// 0, 3, 6, 9 are divisible by 3 → count = 4
|
|
671
|
+
expect(callAndGetRet(rt, 'f')).toBe(4);
|
|
672
|
+
});
|
|
673
|
+
test('while inside if', () => {
|
|
674
|
+
const rt = makeRuntime(`
|
|
675
|
+
fn f(): int {
|
|
676
|
+
let x: int = 1;
|
|
677
|
+
if (x > 0) {
|
|
678
|
+
let sum: int = 0;
|
|
679
|
+
let i: int = 0;
|
|
680
|
+
while (i < 5) {
|
|
681
|
+
sum = sum + i;
|
|
682
|
+
i = i + 1;
|
|
683
|
+
}
|
|
684
|
+
return sum;
|
|
685
|
+
}
|
|
686
|
+
return -1;
|
|
687
|
+
}
|
|
688
|
+
`);
|
|
689
|
+
expect(callAndGetRet(rt, 'f')).toBe(10);
|
|
690
|
+
});
|
|
691
|
+
});
|
|
692
|
+
// ===========================================================================
|
|
693
|
+
// 10. Function calls with 3+ parameters
|
|
694
|
+
// ===========================================================================
|
|
695
|
+
describe('v2 migration: multi-param functions', () => {
|
|
696
|
+
test('function with 3 parameters', () => {
|
|
697
|
+
const rt = makeRuntime(`
|
|
698
|
+
fn add3(a: int, b: int, c: int): int { return a + b + c; }
|
|
699
|
+
fn f(): int { return add3(10, 20, 30); }
|
|
700
|
+
`);
|
|
701
|
+
expect(callAndGetRet(rt, 'f')).toBe(60);
|
|
702
|
+
});
|
|
703
|
+
test('function with 4 parameters', () => {
|
|
704
|
+
const rt = makeRuntime(`
|
|
705
|
+
fn sum4(a: int, b: int, c: int, d: int): int { return a + b + c + d; }
|
|
706
|
+
fn f(): int { return sum4(1, 2, 3, 4); }
|
|
707
|
+
`);
|
|
708
|
+
expect(callAndGetRet(rt, 'f')).toBe(10);
|
|
709
|
+
});
|
|
710
|
+
test('function with 5 parameters', () => {
|
|
711
|
+
const rt = makeRuntime(`
|
|
712
|
+
fn sum5(a: int, b: int, c: int, d: int, e: int): int {
|
|
713
|
+
return a + b + c + d + e;
|
|
714
|
+
}
|
|
715
|
+
fn f(): int { return sum5(2, 4, 6, 8, 10); }
|
|
716
|
+
`);
|
|
717
|
+
expect(callAndGetRet(rt, 'f')).toBe(30);
|
|
718
|
+
});
|
|
719
|
+
test('3-param function with mixed operations', () => {
|
|
720
|
+
const rt = makeRuntime(`
|
|
721
|
+
fn weighted(a: int, b: int, w: int): int {
|
|
722
|
+
return a * w + b * (100 - w);
|
|
723
|
+
}
|
|
724
|
+
fn f(): int { return weighted(10, 5, 60); }
|
|
725
|
+
`);
|
|
726
|
+
// 10 * 60 + 5 * 40 = 600 + 200 = 800
|
|
727
|
+
expect(callAndGetRet(rt, 'f')).toBe(800);
|
|
728
|
+
});
|
|
729
|
+
});
|
|
730
|
+
// ===========================================================================
|
|
731
|
+
// 11. Call chains and nested calls
|
|
732
|
+
// ===========================================================================
|
|
733
|
+
describe('v2 migration: call chains', () => {
|
|
734
|
+
test('4-deep call chain', () => {
|
|
735
|
+
const rt = makeRuntime(`
|
|
736
|
+
fn a(): int { return 1; }
|
|
737
|
+
fn b(): int { return a() + 10; }
|
|
738
|
+
fn c(): int { return b() + 100; }
|
|
739
|
+
fn d(): int { return c() + 1000; }
|
|
740
|
+
fn f(): int { return d(); }
|
|
741
|
+
`);
|
|
742
|
+
expect(callAndGetRet(rt, 'f')).toBe(1111);
|
|
743
|
+
});
|
|
744
|
+
test('function calling same function multiple times', () => {
|
|
745
|
+
const rt = makeRuntime(`
|
|
746
|
+
fn square(x: int): int { return x * x; }
|
|
747
|
+
fn f(): int { return square(3) + square(4); }
|
|
748
|
+
`);
|
|
749
|
+
// 9 + 16 = 25
|
|
750
|
+
expect(callAndGetRet(rt, 'f')).toBe(25);
|
|
751
|
+
});
|
|
752
|
+
test('function result used as argument', () => {
|
|
753
|
+
const rt = makeRuntime(`
|
|
754
|
+
fn add(a: int, b: int): int { return a + b; }
|
|
755
|
+
fn f(): int { return add(add(1, 2), add(3, 4)); }
|
|
756
|
+
`);
|
|
757
|
+
// add(3, 7) = 10
|
|
758
|
+
expect(callAndGetRet(rt, 'f')).toBe(10);
|
|
759
|
+
});
|
|
760
|
+
test('mutual helper functions', () => {
|
|
761
|
+
const rt = makeRuntime(`
|
|
762
|
+
fn double(x: int): int { return x * 2; }
|
|
763
|
+
fn triple(x: int): int { return x * 3; }
|
|
764
|
+
fn f(): int { return double(5) + triple(5); }
|
|
765
|
+
`);
|
|
766
|
+
expect(callAndGetRet(rt, 'f')).toBe(25);
|
|
767
|
+
});
|
|
768
|
+
});
|
|
769
|
+
// ===========================================================================
|
|
770
|
+
// 12. Math-style patterns (inline, no stdlib import)
|
|
771
|
+
// ===========================================================================
|
|
772
|
+
describe('v2 migration: math patterns', () => {
|
|
773
|
+
test('min of two numbers', () => {
|
|
774
|
+
const rt = makeRuntime(`
|
|
775
|
+
fn min(a: int, b: int): int {
|
|
776
|
+
if (a < b) { return a; } else { return b; }
|
|
777
|
+
}
|
|
778
|
+
fn f(): int { return min(7, 3); }
|
|
779
|
+
`);
|
|
780
|
+
expect(callAndGetRet(rt, 'f')).toBe(3);
|
|
781
|
+
});
|
|
782
|
+
test('min returns first when equal', () => {
|
|
783
|
+
const rt = makeRuntime(`
|
|
784
|
+
fn min(a: int, b: int): int {
|
|
785
|
+
if (a < b) { return a; } else { return b; }
|
|
786
|
+
}
|
|
787
|
+
fn f(): int { return min(5, 5); }
|
|
788
|
+
`);
|
|
789
|
+
expect(callAndGetRet(rt, 'f')).toBe(5);
|
|
790
|
+
});
|
|
791
|
+
test('abs of positive stays positive', () => {
|
|
792
|
+
const rt = makeRuntime(`
|
|
793
|
+
fn abs(x: int): int {
|
|
794
|
+
if (x < 0) { return -x; } else { return x; }
|
|
795
|
+
}
|
|
796
|
+
fn f(): int { return abs(42); }
|
|
797
|
+
`);
|
|
798
|
+
expect(callAndGetRet(rt, 'f')).toBe(42);
|
|
799
|
+
});
|
|
800
|
+
test('abs of zero is zero', () => {
|
|
801
|
+
const rt = makeRuntime(`
|
|
802
|
+
fn abs(x: int): int {
|
|
803
|
+
if (x < 0) { return -x; } else { return x; }
|
|
804
|
+
}
|
|
805
|
+
fn f(): int { return abs(0); }
|
|
806
|
+
`);
|
|
807
|
+
expect(callAndGetRet(rt, 'f')).toBe(0);
|
|
808
|
+
});
|
|
809
|
+
test('clamp value in range', () => {
|
|
810
|
+
const rt = makeRuntime(`
|
|
811
|
+
fn clamp(val: int, lo: int, hi: int): int {
|
|
812
|
+
if (val < lo) { return lo; }
|
|
813
|
+
if (val > hi) { return hi; }
|
|
814
|
+
return val;
|
|
815
|
+
}
|
|
816
|
+
fn f(): int { return clamp(50, 0, 100); }
|
|
817
|
+
`);
|
|
818
|
+
expect(callAndGetRet(rt, 'f')).toBe(50);
|
|
819
|
+
});
|
|
820
|
+
test('clamp below minimum', () => {
|
|
821
|
+
const rt = makeRuntime(`
|
|
822
|
+
fn clamp(val: int, lo: int, hi: int): int {
|
|
823
|
+
if (val < lo) { return lo; }
|
|
824
|
+
if (val > hi) { return hi; }
|
|
825
|
+
return val;
|
|
826
|
+
}
|
|
827
|
+
fn f(): int { return clamp(-5, 0, 100); }
|
|
828
|
+
`);
|
|
829
|
+
expect(callAndGetRet(rt, 'f')).toBe(0);
|
|
830
|
+
});
|
|
831
|
+
test('clamp above maximum', () => {
|
|
832
|
+
const rt = makeRuntime(`
|
|
833
|
+
fn clamp(val: int, lo: int, hi: int): int {
|
|
834
|
+
if (val < lo) { return lo; }
|
|
835
|
+
if (val > hi) { return hi; }
|
|
836
|
+
return val;
|
|
837
|
+
}
|
|
838
|
+
fn f(): int { return clamp(200, 0, 100); }
|
|
839
|
+
`);
|
|
840
|
+
expect(callAndGetRet(rt, 'f')).toBe(100);
|
|
841
|
+
});
|
|
842
|
+
test('sign function', () => {
|
|
843
|
+
const rt = makeRuntime(`
|
|
844
|
+
fn sign(x: int): int {
|
|
845
|
+
if (x > 0) { return 1; }
|
|
846
|
+
if (x < 0) { return -1; }
|
|
847
|
+
return 0;
|
|
848
|
+
}
|
|
849
|
+
fn f(): int { return sign(-42) + sign(0) + sign(99); }
|
|
850
|
+
`);
|
|
851
|
+
// -1 + 0 + 1 = 0
|
|
852
|
+
expect(callAndGetRet(rt, 'f')).toBe(0);
|
|
853
|
+
});
|
|
854
|
+
});
|
|
855
|
+
// ===========================================================================
|
|
856
|
+
// 13. More break/continue patterns
|
|
857
|
+
// ===========================================================================
|
|
858
|
+
describe('v2 migration: advanced break/continue', () => {
|
|
859
|
+
test('break in nested if inside loop', () => {
|
|
860
|
+
const rt = makeRuntime(`
|
|
861
|
+
fn f(): int {
|
|
862
|
+
let sum: int = 0;
|
|
863
|
+
let i: int = 0;
|
|
864
|
+
while (i < 100) {
|
|
865
|
+
sum = sum + i;
|
|
866
|
+
i = i + 1;
|
|
867
|
+
if (sum > 10) { break; }
|
|
868
|
+
}
|
|
869
|
+
return sum;
|
|
870
|
+
}
|
|
871
|
+
`);
|
|
872
|
+
// 0+1+2+3+4+5 = 15 > 10, breaks at i=6
|
|
873
|
+
expect(callAndGetRet(rt, 'f')).toBe(15);
|
|
874
|
+
});
|
|
875
|
+
test('continue with counter (using == pattern)', () => {
|
|
876
|
+
const rt = makeRuntime(`
|
|
877
|
+
fn f(): int {
|
|
878
|
+
let count: int = 0;
|
|
879
|
+
let i: int = 0;
|
|
880
|
+
while (i < 10) {
|
|
881
|
+
i = i + 1;
|
|
882
|
+
if (i % 3 == 0) { continue; }
|
|
883
|
+
count = count + 1;
|
|
884
|
+
}
|
|
885
|
+
return count;
|
|
886
|
+
}
|
|
887
|
+
`);
|
|
888
|
+
// i=1..10, skip 3,6,9 → count = 7
|
|
889
|
+
expect(callAndGetRet(rt, 'f')).toBe(7);
|
|
890
|
+
});
|
|
891
|
+
test('break from while(true) with accumulator', () => {
|
|
892
|
+
const rt = makeRuntime(`
|
|
893
|
+
fn f(): int {
|
|
894
|
+
let n: int = 1;
|
|
895
|
+
while (true) {
|
|
896
|
+
n = n * 2;
|
|
897
|
+
if (n >= 64) { break; }
|
|
898
|
+
}
|
|
899
|
+
return n;
|
|
900
|
+
}
|
|
901
|
+
`);
|
|
902
|
+
expect(callAndGetRet(rt, 'f')).toBe(64);
|
|
903
|
+
});
|
|
904
|
+
test('continue and break in same loop', () => {
|
|
905
|
+
const rt = makeRuntime(`
|
|
906
|
+
fn f(): int {
|
|
907
|
+
let sum: int = 0;
|
|
908
|
+
let i: int = 0;
|
|
909
|
+
while (true) {
|
|
910
|
+
i = i + 1;
|
|
911
|
+
if (i > 10) { break; }
|
|
912
|
+
if (i % 2 == 0) { continue; }
|
|
913
|
+
sum = sum + i;
|
|
914
|
+
}
|
|
915
|
+
return sum;
|
|
916
|
+
}
|
|
917
|
+
`);
|
|
918
|
+
// odd numbers 1..10: 1+3+5+7+9 = 25
|
|
919
|
+
expect(callAndGetRet(rt, 'f')).toBe(25);
|
|
920
|
+
});
|
|
921
|
+
});
|
|
922
|
+
// ===========================================================================
|
|
923
|
+
// 14. for loops (desugared to while in HIR)
|
|
924
|
+
// ===========================================================================
|
|
925
|
+
describe('v2 migration: for loops', () => {
|
|
926
|
+
test('simple for loop sum', () => {
|
|
927
|
+
const rt = makeRuntime(`
|
|
928
|
+
fn f(): int {
|
|
929
|
+
let sum: int = 0;
|
|
930
|
+
for (let i: int = 1; i <= 10; i = i + 1) {
|
|
931
|
+
sum = sum + i;
|
|
932
|
+
}
|
|
933
|
+
return sum;
|
|
934
|
+
}
|
|
935
|
+
`);
|
|
936
|
+
expect(callAndGetRet(rt, 'f')).toBe(55);
|
|
937
|
+
});
|
|
938
|
+
test('for loop with multiplication', () => {
|
|
939
|
+
const rt = makeRuntime(`
|
|
940
|
+
fn f(): int {
|
|
941
|
+
let product: int = 1;
|
|
942
|
+
for (let i: int = 1; i <= 6; i = i + 1) {
|
|
943
|
+
product = product * i;
|
|
944
|
+
}
|
|
945
|
+
return product;
|
|
946
|
+
}
|
|
947
|
+
`);
|
|
948
|
+
// 6! = 720
|
|
949
|
+
expect(callAndGetRet(rt, 'f')).toBe(720);
|
|
950
|
+
});
|
|
951
|
+
test('for loop counting down', () => {
|
|
952
|
+
const rt = makeRuntime(`
|
|
953
|
+
fn f(): int {
|
|
954
|
+
let last: int = 0;
|
|
955
|
+
for (let i: int = 10; i > 0; i = i - 1) {
|
|
956
|
+
last = i;
|
|
957
|
+
}
|
|
958
|
+
return last;
|
|
959
|
+
}
|
|
960
|
+
`);
|
|
961
|
+
expect(callAndGetRet(rt, 'f')).toBe(1);
|
|
962
|
+
});
|
|
963
|
+
test('nested for loops', () => {
|
|
964
|
+
const rt = makeRuntime(`
|
|
965
|
+
fn f(): int {
|
|
966
|
+
let count: int = 0;
|
|
967
|
+
for (let i: int = 0; i < 4; i = i + 1) {
|
|
968
|
+
for (let j: int = 0; j < 3; j = j + 1) {
|
|
969
|
+
count = count + 1;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
return count;
|
|
973
|
+
}
|
|
974
|
+
`);
|
|
975
|
+
expect(callAndGetRet(rt, 'f')).toBe(12);
|
|
976
|
+
});
|
|
977
|
+
test('for loop with break', () => {
|
|
978
|
+
const rt = makeRuntime(`
|
|
979
|
+
fn f(): int {
|
|
980
|
+
let result: int = 0;
|
|
981
|
+
for (let i: int = 0; i < 100; i = i + 1) {
|
|
982
|
+
if (i == 7) {
|
|
983
|
+
result = i;
|
|
984
|
+
break;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
return result;
|
|
988
|
+
}
|
|
989
|
+
`);
|
|
990
|
+
expect(callAndGetRet(rt, 'f')).toBe(7);
|
|
991
|
+
});
|
|
992
|
+
});
|
|
993
|
+
// ===========================================================================
|
|
994
|
+
// 15. @tick and @load runtime behavior
|
|
995
|
+
// ===========================================================================
|
|
996
|
+
describe('v2 migration: @tick/@load runtime', () => {
|
|
997
|
+
test('@tick function executes and modifies state', () => {
|
|
998
|
+
const source = `
|
|
999
|
+
let counter: int = 0;
|
|
1000
|
+
@tick fn game_tick(): void {
|
|
1001
|
+
counter = counter + 1;
|
|
1002
|
+
}
|
|
1003
|
+
fn get_counter(): int { return counter; }
|
|
1004
|
+
`;
|
|
1005
|
+
// @tick functions should compile and be callable
|
|
1006
|
+
expect(() => (0, compile_1.compile)(source, { namespace: NS })).not.toThrow();
|
|
1007
|
+
});
|
|
1008
|
+
test('@load function runs on load', () => {
|
|
1009
|
+
const source = `
|
|
1010
|
+
@load fn setup(): void {
|
|
1011
|
+
let x: int = 42;
|
|
1012
|
+
}
|
|
1013
|
+
`;
|
|
1014
|
+
const result = (0, compile_1.compile)(source, { namespace: NS });
|
|
1015
|
+
const loadJson = getFile(result.files, 'load.json');
|
|
1016
|
+
expect(loadJson).toBeDefined();
|
|
1017
|
+
const values = JSON.parse(loadJson).values;
|
|
1018
|
+
expect(values).toContain(`${NS}:setup`);
|
|
1019
|
+
expect(values).toContain(`${NS}:load`);
|
|
1020
|
+
});
|
|
1021
|
+
test('@tick and @load on different functions', () => {
|
|
1022
|
+
const source = `
|
|
1023
|
+
@tick fn on_tick(): void { let x: int = 1; }
|
|
1024
|
+
@load fn on_load(): void { let y: int = 2; }
|
|
1025
|
+
`;
|
|
1026
|
+
const result = (0, compile_1.compile)(source, { namespace: NS });
|
|
1027
|
+
const tickJson = getFile(result.files, 'tick.json');
|
|
1028
|
+
const loadJson = getFile(result.files, 'load.json');
|
|
1029
|
+
expect(tickJson).toBeDefined();
|
|
1030
|
+
expect(JSON.parse(tickJson).values).toContain(`${NS}:on_tick`);
|
|
1031
|
+
expect(loadJson).toBeDefined();
|
|
1032
|
+
expect(JSON.parse(loadJson).values).toContain(`${NS}:on_load`);
|
|
1033
|
+
});
|
|
1034
|
+
test('multiple @tick functions all appear in tick.json', () => {
|
|
1035
|
+
const source = `
|
|
1036
|
+
@tick fn tick1(): void { let x: int = 1; }
|
|
1037
|
+
@tick fn tick2(): void { let y: int = 2; }
|
|
1038
|
+
`;
|
|
1039
|
+
const result = (0, compile_1.compile)(source, { namespace: NS });
|
|
1040
|
+
const tickJson = getFile(result.files, 'tick.json');
|
|
1041
|
+
expect(tickJson).toBeDefined();
|
|
1042
|
+
const values = JSON.parse(tickJson).values;
|
|
1043
|
+
expect(values).toContain(`${NS}:tick1`);
|
|
1044
|
+
expect(values).toContain(`${NS}:tick2`);
|
|
1045
|
+
});
|
|
1046
|
+
test('@tick function with logic compiles and runs', () => {
|
|
1047
|
+
const rt = makeRuntime(`
|
|
1048
|
+
@tick fn game_tick(): void {
|
|
1049
|
+
let x: int = 5;
|
|
1050
|
+
let y: int = x + 10;
|
|
1051
|
+
}
|
|
1052
|
+
fn f(): int { return 1; }
|
|
1053
|
+
`);
|
|
1054
|
+
// Tick function should be callable without crashing
|
|
1055
|
+
rt.execFunction(`${NS}:game_tick`);
|
|
1056
|
+
expect(callAndGetRet(rt, 'f')).toBe(1);
|
|
1057
|
+
});
|
|
1058
|
+
test('@load function body executes correctly in runtime', () => {
|
|
1059
|
+
const rt = makeRuntime(`
|
|
1060
|
+
@load fn init(): void {
|
|
1061
|
+
let x: int = 100;
|
|
1062
|
+
}
|
|
1063
|
+
fn f(): int { return 42; }
|
|
1064
|
+
`);
|
|
1065
|
+
expect(callAndGetRet(rt, 'f')).toBe(42);
|
|
1066
|
+
});
|
|
1067
|
+
});
|
|
1068
|
+
// ===========================================================================
|
|
1069
|
+
// 16. Struct declaration and field access (smoke + structural)
|
|
1070
|
+
// ===========================================================================
|
|
1071
|
+
describe('v2 migration: struct smoke tests', () => {
|
|
1072
|
+
test('struct declaration compiles', () => {
|
|
1073
|
+
expect(() => (0, compile_1.compile)(`
|
|
1074
|
+
struct Point { x: int, y: int }
|
|
1075
|
+
fn f(): int { return 1; }
|
|
1076
|
+
`, { namespace: NS })).not.toThrow();
|
|
1077
|
+
});
|
|
1078
|
+
test('struct literal compiles', () => {
|
|
1079
|
+
expect(() => (0, compile_1.compile)(`
|
|
1080
|
+
struct Vec2 { x: int, y: int }
|
|
1081
|
+
fn f(): int {
|
|
1082
|
+
let p: Vec2 = { x: 10, y: 20 };
|
|
1083
|
+
return 1;
|
|
1084
|
+
}
|
|
1085
|
+
`, { namespace: NS })).not.toThrow();
|
|
1086
|
+
});
|
|
1087
|
+
test('struct with impl compiles', () => {
|
|
1088
|
+
expect(() => (0, compile_1.compile)(`
|
|
1089
|
+
struct Counter { value: int }
|
|
1090
|
+
impl Counter {
|
|
1091
|
+
fn new(): Counter {
|
|
1092
|
+
return { value: 0 };
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
fn f(): int { return 1; }
|
|
1096
|
+
`, { namespace: NS })).not.toThrow();
|
|
1097
|
+
});
|
|
1098
|
+
test('struct field access compiles', () => {
|
|
1099
|
+
expect(() => (0, compile_1.compile)(`
|
|
1100
|
+
struct Point { x: int, y: int }
|
|
1101
|
+
fn f(): int {
|
|
1102
|
+
let p: Point = { x: 5, y: 10 };
|
|
1103
|
+
let val: int = p.x;
|
|
1104
|
+
return val;
|
|
1105
|
+
}
|
|
1106
|
+
`, { namespace: NS })).not.toThrow();
|
|
1107
|
+
});
|
|
1108
|
+
test('struct field assignment compiles', () => {
|
|
1109
|
+
expect(() => (0, compile_1.compile)(`
|
|
1110
|
+
struct Point { x: int, y: int }
|
|
1111
|
+
fn f(): void {
|
|
1112
|
+
let p: Point = { x: 5, y: 10 };
|
|
1113
|
+
p.x = 20;
|
|
1114
|
+
}
|
|
1115
|
+
`, { namespace: NS })).not.toThrow();
|
|
1116
|
+
});
|
|
1117
|
+
test('struct method call via static_call compiles', () => {
|
|
1118
|
+
expect(() => (0, compile_1.compile)(`
|
|
1119
|
+
struct Vec2 { x: int, y: int }
|
|
1120
|
+
impl Vec2 {
|
|
1121
|
+
fn new(x: int, y: int): Vec2 {
|
|
1122
|
+
return { x: x, y: y };
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
fn f(): void {
|
|
1126
|
+
let v: Vec2 = Vec2::new(1, 2);
|
|
1127
|
+
}
|
|
1128
|
+
`, { namespace: NS })).not.toThrow();
|
|
1129
|
+
});
|
|
1130
|
+
test('struct static method generates function file', () => {
|
|
1131
|
+
const result = (0, compile_1.compile)(`
|
|
1132
|
+
struct Vec2 { x: int, y: int }
|
|
1133
|
+
impl Vec2 {
|
|
1134
|
+
fn new(x: int, y: int): Vec2 {
|
|
1135
|
+
return { x: x, y: y };
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
fn f(): void {
|
|
1139
|
+
let v: Vec2 = Vec2::new(1, 2);
|
|
1140
|
+
}
|
|
1141
|
+
`, { namespace: NS });
|
|
1142
|
+
// impl methods are named Type::method in LIR → type/method in path
|
|
1143
|
+
const fnFiles = result.files.filter(f => f.path.endsWith('.mcfunction'));
|
|
1144
|
+
expect(fnFiles.length).toBeGreaterThanOrEqual(2); // at least f + Vec2::new
|
|
1145
|
+
});
|
|
1146
|
+
test('multiple struct declarations compile', () => {
|
|
1147
|
+
expect(() => (0, compile_1.compile)(`
|
|
1148
|
+
struct Point { x: int, y: int }
|
|
1149
|
+
struct Color { r: int, g: int, b: int }
|
|
1150
|
+
fn f(): int { return 1; }
|
|
1151
|
+
`, { namespace: NS })).not.toThrow();
|
|
1152
|
+
});
|
|
1153
|
+
});
|
|
1154
|
+
// ===========================================================================
|
|
1155
|
+
// 17. Execute context blocks (structural)
|
|
1156
|
+
// ===========================================================================
|
|
1157
|
+
describe('v2 migration: execute blocks', () => {
|
|
1158
|
+
test('as @a block compiles', () => {
|
|
1159
|
+
expect(() => (0, compile_1.compile)(`
|
|
1160
|
+
fn f(): void {
|
|
1161
|
+
as @a {
|
|
1162
|
+
let x: int = 1;
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
`, { namespace: NS })).not.toThrow();
|
|
1166
|
+
});
|
|
1167
|
+
test('as @a generates execute as command', () => {
|
|
1168
|
+
const result = (0, compile_1.compile)(`
|
|
1169
|
+
fn f(): void {
|
|
1170
|
+
as @a {
|
|
1171
|
+
let x: int = 1;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
`, { namespace: NS });
|
|
1175
|
+
const allContent = result.files
|
|
1176
|
+
.filter(f => f.path.endsWith('.mcfunction'))
|
|
1177
|
+
.map(f => f.content).join('\n');
|
|
1178
|
+
expect(allContent).toContain('execute as @a run function');
|
|
1179
|
+
});
|
|
1180
|
+
test('at @s block compiles', () => {
|
|
1181
|
+
expect(() => (0, compile_1.compile)(`
|
|
1182
|
+
fn f(): void {
|
|
1183
|
+
at @s {
|
|
1184
|
+
let x: int = 1;
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
`, { namespace: NS })).not.toThrow();
|
|
1188
|
+
});
|
|
1189
|
+
test('as @e at @s compiles', () => {
|
|
1190
|
+
expect(() => (0, compile_1.compile)(`
|
|
1191
|
+
fn f(): void {
|
|
1192
|
+
as @e at @s {
|
|
1193
|
+
let x: int = 1;
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
`, { namespace: NS })).not.toThrow();
|
|
1197
|
+
});
|
|
1198
|
+
test('as @e at @s generates execute command', () => {
|
|
1199
|
+
const result = (0, compile_1.compile)(`
|
|
1200
|
+
fn f(): void {
|
|
1201
|
+
as @e at @s {
|
|
1202
|
+
let x: int = 1;
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
`, { namespace: NS });
|
|
1206
|
+
const allContent = result.files
|
|
1207
|
+
.filter(f => f.path.endsWith('.mcfunction'))
|
|
1208
|
+
.map(f => f.content).join('\n');
|
|
1209
|
+
expect(allContent).toContain('execute as @e at @s run function');
|
|
1210
|
+
});
|
|
1211
|
+
test('nested execute blocks compile', () => {
|
|
1212
|
+
expect(() => (0, compile_1.compile)(`
|
|
1213
|
+
fn f(): void {
|
|
1214
|
+
as @a {
|
|
1215
|
+
as @e {
|
|
1216
|
+
let x: int = 1;
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
`, { namespace: NS })).not.toThrow();
|
|
1221
|
+
});
|
|
1222
|
+
test('execute body creates helper function', () => {
|
|
1223
|
+
const result = (0, compile_1.compile)(`
|
|
1224
|
+
fn f(): void {
|
|
1225
|
+
as @a {
|
|
1226
|
+
let x: int = 42;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
`, { namespace: NS });
|
|
1230
|
+
const fnFiles = result.files.filter(f => f.path.endsWith('.mcfunction'));
|
|
1231
|
+
// Should have at least: load, f, f__exec helper
|
|
1232
|
+
expect(fnFiles.length).toBeGreaterThanOrEqual(3);
|
|
1233
|
+
});
|
|
1234
|
+
});
|
|
1235
|
+
// ===========================================================================
|
|
1236
|
+
// 18. foreach (structural)
|
|
1237
|
+
// ===========================================================================
|
|
1238
|
+
describe('v2 migration: foreach', () => {
|
|
1239
|
+
test('foreach with selector compiles', () => {
|
|
1240
|
+
expect(() => (0, compile_1.compile)(`
|
|
1241
|
+
fn f(): void {
|
|
1242
|
+
foreach (e in @e) {
|
|
1243
|
+
let x: int = 1;
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
`, { namespace: NS })).not.toThrow();
|
|
1247
|
+
});
|
|
1248
|
+
test('foreach generates execute as ... run function', () => {
|
|
1249
|
+
const result = (0, compile_1.compile)(`
|
|
1250
|
+
fn f(): void {
|
|
1251
|
+
foreach (e in @e) {
|
|
1252
|
+
let x: int = 1;
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
`, { namespace: NS });
|
|
1256
|
+
const allContent = result.files
|
|
1257
|
+
.filter(f => f.path.endsWith('.mcfunction'))
|
|
1258
|
+
.map(f => f.content).join('\n');
|
|
1259
|
+
expect(allContent).toContain('execute as @e run function');
|
|
1260
|
+
});
|
|
1261
|
+
test('foreach with @a selector', () => {
|
|
1262
|
+
const result = (0, compile_1.compile)(`
|
|
1263
|
+
fn f(): void {
|
|
1264
|
+
foreach (p in @a) {
|
|
1265
|
+
let x: int = 1;
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
`, { namespace: NS });
|
|
1269
|
+
const allContent = result.files
|
|
1270
|
+
.filter(f => f.path.endsWith('.mcfunction'))
|
|
1271
|
+
.map(f => f.content).join('\n');
|
|
1272
|
+
expect(allContent).toContain('execute as @a run function');
|
|
1273
|
+
});
|
|
1274
|
+
test('foreach creates helper function', () => {
|
|
1275
|
+
const result = (0, compile_1.compile)(`
|
|
1276
|
+
fn f(): void {
|
|
1277
|
+
foreach (e in @e) {
|
|
1278
|
+
let x: int = 1;
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
`, { namespace: NS });
|
|
1282
|
+
const fnFiles = result.files.filter(f => f.path.endsWith('.mcfunction'));
|
|
1283
|
+
// Should have at least: load, f, foreach helper
|
|
1284
|
+
expect(fnFiles.length).toBeGreaterThanOrEqual(3);
|
|
1285
|
+
});
|
|
1286
|
+
});
|
|
1287
|
+
// ===========================================================================
|
|
1288
|
+
// 19. Selectors (smoke)
|
|
1289
|
+
// ===========================================================================
|
|
1290
|
+
describe('v2 migration: selectors', () => {
|
|
1291
|
+
test('@a selector in foreach compiles', () => {
|
|
1292
|
+
expect(() => (0, compile_1.compile)(`
|
|
1293
|
+
fn f(): void {
|
|
1294
|
+
foreach (p in @a) { let x: int = 1; }
|
|
1295
|
+
}
|
|
1296
|
+
`, { namespace: NS })).not.toThrow();
|
|
1297
|
+
});
|
|
1298
|
+
test('@e selector in foreach compiles', () => {
|
|
1299
|
+
expect(() => (0, compile_1.compile)(`
|
|
1300
|
+
fn f(): void {
|
|
1301
|
+
foreach (e in @e) { let x: int = 1; }
|
|
1302
|
+
}
|
|
1303
|
+
`, { namespace: NS })).not.toThrow();
|
|
1304
|
+
});
|
|
1305
|
+
test('@s selector in as block compiles', () => {
|
|
1306
|
+
expect(() => (0, compile_1.compile)(`
|
|
1307
|
+
fn f(): void {
|
|
1308
|
+
at @s { let x: int = 1; }
|
|
1309
|
+
}
|
|
1310
|
+
`, { namespace: NS })).not.toThrow();
|
|
1311
|
+
});
|
|
1312
|
+
test('@p selector compiles', () => {
|
|
1313
|
+
expect(() => (0, compile_1.compile)(`
|
|
1314
|
+
fn f(): void {
|
|
1315
|
+
foreach (p in @p) { let x: int = 1; }
|
|
1316
|
+
}
|
|
1317
|
+
`, { namespace: NS })).not.toThrow();
|
|
1318
|
+
});
|
|
1319
|
+
});
|
|
1320
|
+
// ===========================================================================
|
|
1321
|
+
// 20. Raw commands
|
|
1322
|
+
// ===========================================================================
|
|
1323
|
+
describe('v2 migration: raw commands', () => {
|
|
1324
|
+
test('raw command compiles', () => {
|
|
1325
|
+
expect(() => (0, compile_1.compile)(`
|
|
1326
|
+
fn f(): void {
|
|
1327
|
+
raw("say hello world");
|
|
1328
|
+
}
|
|
1329
|
+
`, { namespace: NS })).not.toThrow();
|
|
1330
|
+
});
|
|
1331
|
+
test('raw command appears in output', () => {
|
|
1332
|
+
const result = (0, compile_1.compile)(`
|
|
1333
|
+
fn f(): void {
|
|
1334
|
+
raw("say hello world");
|
|
1335
|
+
}
|
|
1336
|
+
`, { namespace: NS });
|
|
1337
|
+
const fn = getFile(result.files, '/f.mcfunction');
|
|
1338
|
+
expect(fn).toBeDefined();
|
|
1339
|
+
expect(fn).toContain('say hello world');
|
|
1340
|
+
});
|
|
1341
|
+
test('multiple raw commands', () => {
|
|
1342
|
+
const result = (0, compile_1.compile)(`
|
|
1343
|
+
fn f(): void {
|
|
1344
|
+
raw("say line one");
|
|
1345
|
+
raw("say line two");
|
|
1346
|
+
}
|
|
1347
|
+
`, { namespace: NS });
|
|
1348
|
+
const fn = getFile(result.files, '/f.mcfunction');
|
|
1349
|
+
expect(fn).toContain('say line one');
|
|
1350
|
+
expect(fn).toContain('say line two');
|
|
1351
|
+
});
|
|
1352
|
+
});
|
|
1353
|
+
// ===========================================================================
|
|
1354
|
+
// 21. Match statement
|
|
1355
|
+
// ===========================================================================
|
|
1356
|
+
describe('v2 migration: match statement', () => {
|
|
1357
|
+
test('match compiles without error', () => {
|
|
1358
|
+
expect(() => (0, compile_1.compile)(`
|
|
1359
|
+
fn f(x: int): int {
|
|
1360
|
+
match (x) {
|
|
1361
|
+
1 => { return 10; }
|
|
1362
|
+
2 => { return 20; }
|
|
1363
|
+
_ => { return 0; }
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
`, { namespace: NS })).not.toThrow();
|
|
1367
|
+
});
|
|
1368
|
+
test('match selects correct arm', () => {
|
|
1369
|
+
const rt = makeRuntime(`
|
|
1370
|
+
fn classify(x: int): int {
|
|
1371
|
+
match (x) {
|
|
1372
|
+
1 => { return 10; }
|
|
1373
|
+
2 => { return 20; }
|
|
1374
|
+
3 => { return 30; }
|
|
1375
|
+
_ => { return 0; }
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
1378
|
+
fn f(): int { return classify(2); }
|
|
1379
|
+
`);
|
|
1380
|
+
expect(callAndGetRet(rt, 'f')).toBe(20);
|
|
1381
|
+
});
|
|
1382
|
+
test('match default arm', () => {
|
|
1383
|
+
const rt = makeRuntime(`
|
|
1384
|
+
fn classify(x: int): int {
|
|
1385
|
+
match (x) {
|
|
1386
|
+
1 => { return 10; }
|
|
1387
|
+
_ => { return 99; }
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
fn f(): int { return classify(42); }
|
|
1391
|
+
`);
|
|
1392
|
+
expect(callAndGetRet(rt, 'f')).toBe(99);
|
|
1393
|
+
});
|
|
1394
|
+
test('match first arm', () => {
|
|
1395
|
+
const rt = makeRuntime(`
|
|
1396
|
+
fn classify(x: int): int {
|
|
1397
|
+
match (x) {
|
|
1398
|
+
1 => { return 10; }
|
|
1399
|
+
2 => { return 20; }
|
|
1400
|
+
_ => { return 0; }
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
fn f(): int { return classify(1); }
|
|
1404
|
+
`);
|
|
1405
|
+
expect(callAndGetRet(rt, 'f')).toBe(10);
|
|
1406
|
+
});
|
|
1407
|
+
});
|
|
1408
|
+
// ===========================================================================
|
|
1409
|
+
// 22. Complex integration patterns
|
|
1410
|
+
// ===========================================================================
|
|
1411
|
+
describe('v2 migration: complex patterns', () => {
|
|
1412
|
+
test('GCD iterative (Euclid)', () => {
|
|
1413
|
+
const rt = makeRuntime(`
|
|
1414
|
+
fn gcd(a: int, b: int): int {
|
|
1415
|
+
while (b != 0) {
|
|
1416
|
+
let temp: int = b;
|
|
1417
|
+
b = a % b;
|
|
1418
|
+
a = temp;
|
|
1419
|
+
}
|
|
1420
|
+
return a;
|
|
1421
|
+
}
|
|
1422
|
+
fn f(): int { return gcd(48, 18); }
|
|
1423
|
+
`);
|
|
1424
|
+
expect(callAndGetRet(rt, 'f')).toBe(6);
|
|
1425
|
+
});
|
|
1426
|
+
test('sum of squares', () => {
|
|
1427
|
+
const rt = makeRuntime(`
|
|
1428
|
+
fn sum_sq(n: int): int {
|
|
1429
|
+
let sum: int = 0;
|
|
1430
|
+
let i: int = 1;
|
|
1431
|
+
while (i <= n) {
|
|
1432
|
+
sum = sum + i * i;
|
|
1433
|
+
i = i + 1;
|
|
1434
|
+
}
|
|
1435
|
+
return sum;
|
|
1436
|
+
}
|
|
1437
|
+
fn f(): int { return sum_sq(5); }
|
|
1438
|
+
`);
|
|
1439
|
+
// 1 + 4 + 9 + 16 + 25 = 55
|
|
1440
|
+
expect(callAndGetRet(rt, 'f')).toBe(55);
|
|
1441
|
+
});
|
|
1442
|
+
test('collatz steps', () => {
|
|
1443
|
+
const rt = makeRuntime(`
|
|
1444
|
+
fn collatz(n: int): int {
|
|
1445
|
+
let steps: int = 0;
|
|
1446
|
+
while (n != 1) {
|
|
1447
|
+
if (n % 2 == 0) {
|
|
1448
|
+
n = n / 2;
|
|
1449
|
+
} else {
|
|
1450
|
+
n = n * 3 + 1;
|
|
1451
|
+
}
|
|
1452
|
+
steps = steps + 1;
|
|
1453
|
+
}
|
|
1454
|
+
return steps;
|
|
1455
|
+
}
|
|
1456
|
+
fn f(): int { return collatz(6); }
|
|
1457
|
+
`);
|
|
1458
|
+
// 6 → 3 → 10 → 5 → 16 → 8 → 4 → 2 → 1 = 8 steps
|
|
1459
|
+
expect(callAndGetRet(rt, 'f')).toBe(8);
|
|
1460
|
+
});
|
|
1461
|
+
test('is_prime check', () => {
|
|
1462
|
+
const rt = makeRuntime(`
|
|
1463
|
+
fn is_prime(n: int): int {
|
|
1464
|
+
if (n <= 1) { return 0; }
|
|
1465
|
+
let i: int = 2;
|
|
1466
|
+
while (i * i <= n) {
|
|
1467
|
+
if (n % i == 0) { return 0; }
|
|
1468
|
+
i = i + 1;
|
|
1469
|
+
}
|
|
1470
|
+
return 1;
|
|
1471
|
+
}
|
|
1472
|
+
fn f(): int {
|
|
1473
|
+
return is_prime(2) + is_prime(7) + is_prime(11) + is_prime(4) + is_prime(9);
|
|
1474
|
+
}
|
|
1475
|
+
`);
|
|
1476
|
+
// 2=prime(1), 7=prime(1), 11=prime(1), 4=not(0), 9=not(0) → 3
|
|
1477
|
+
expect(callAndGetRet(rt, 'f')).toBe(3);
|
|
1478
|
+
});
|
|
1479
|
+
test('bubble sort pass count', () => {
|
|
1480
|
+
const rt = makeRuntime(`
|
|
1481
|
+
fn f(): int {
|
|
1482
|
+
let a: int = 5;
|
|
1483
|
+
let b: int = 3;
|
|
1484
|
+
let c: int = 8;
|
|
1485
|
+
let d: int = 1;
|
|
1486
|
+
let swaps: int = 0;
|
|
1487
|
+
|
|
1488
|
+
// Pass 1
|
|
1489
|
+
if (a > b) { let t: int = a; a = b; b = t; swaps = swaps + 1; }
|
|
1490
|
+
if (b > c) { let t: int = b; b = c; c = t; swaps = swaps + 1; }
|
|
1491
|
+
if (c > d) { let t: int = c; c = d; d = t; swaps = swaps + 1; }
|
|
1492
|
+
|
|
1493
|
+
// Pass 2
|
|
1494
|
+
if (a > b) { let t: int = a; a = b; b = t; swaps = swaps + 1; }
|
|
1495
|
+
if (b > c) { let t: int = b; b = c; c = t; swaps = swaps + 1; }
|
|
1496
|
+
|
|
1497
|
+
// Pass 3
|
|
1498
|
+
if (a > b) { let t: int = a; a = b; b = t; swaps = swaps + 1; }
|
|
1499
|
+
|
|
1500
|
+
return swaps;
|
|
1501
|
+
}
|
|
1502
|
+
`);
|
|
1503
|
+
// 5,3,8,1 → pass1: swap(5,3)→3,5,8,1 swap(8,1)→3,5,1,8 → pass2: swap(5,1)→3,1,5,8 → pass3: swap(3,1)→1,3,5,8
|
|
1504
|
+
// swaps: 1+0+1 + 0+1 + 1 = 4
|
|
1505
|
+
expect(callAndGetRet(rt, 'f')).toBe(4);
|
|
1506
|
+
});
|
|
1507
|
+
test('linear search', () => {
|
|
1508
|
+
const rt = makeRuntime(`
|
|
1509
|
+
fn f(): int {
|
|
1510
|
+
let a: int = 10;
|
|
1511
|
+
let b: int = 20;
|
|
1512
|
+
let c: int = 30;
|
|
1513
|
+
let d: int = 40;
|
|
1514
|
+
let e: int = 50;
|
|
1515
|
+
let target: int = 30;
|
|
1516
|
+
|
|
1517
|
+
if (a == target) { return 0; }
|
|
1518
|
+
if (b == target) { return 1; }
|
|
1519
|
+
if (c == target) { return 2; }
|
|
1520
|
+
if (d == target) { return 3; }
|
|
1521
|
+
if (e == target) { return 4; }
|
|
1522
|
+
return -1;
|
|
1523
|
+
}
|
|
1524
|
+
`);
|
|
1525
|
+
expect(callAndGetRet(rt, 'f')).toBe(2);
|
|
1526
|
+
});
|
|
1527
|
+
});
|
|
1528
|
+
// ===========================================================================
|
|
1529
|
+
// 23. Const declarations
|
|
1530
|
+
// ===========================================================================
|
|
1531
|
+
describe('v2 migration: const declarations', () => {
|
|
1532
|
+
test('const inlined in expression', () => {
|
|
1533
|
+
expect(() => (0, compile_1.compile)(`
|
|
1534
|
+
const MAX: int = 100;
|
|
1535
|
+
fn f(): int { return MAX; }
|
|
1536
|
+
`, { namespace: NS })).not.toThrow();
|
|
1537
|
+
});
|
|
1538
|
+
});
|
|
1539
|
+
// ===========================================================================
|
|
1540
|
+
// 24. Boolean logic edge cases
|
|
1541
|
+
// ===========================================================================
|
|
1542
|
+
describe('v2 migration: boolean edge cases', () => {
|
|
1543
|
+
test('double negation', () => {
|
|
1544
|
+
const rt = makeRuntime(`
|
|
1545
|
+
fn f(): int {
|
|
1546
|
+
if (!!true) { return 1; } else { return 0; }
|
|
1547
|
+
}
|
|
1548
|
+
`);
|
|
1549
|
+
expect(callAndGetRet(rt, 'f')).toBe(1);
|
|
1550
|
+
});
|
|
1551
|
+
test('complex boolean expression', () => {
|
|
1552
|
+
const rt = makeRuntime(`
|
|
1553
|
+
fn f(): int {
|
|
1554
|
+
let a: int = 5;
|
|
1555
|
+
let b: int = 10;
|
|
1556
|
+
if (a > 0 && b > 0 && a < b) { return 1; } else { return 0; }
|
|
1557
|
+
}
|
|
1558
|
+
`);
|
|
1559
|
+
expect(callAndGetRet(rt, 'f')).toBe(1);
|
|
1560
|
+
});
|
|
1561
|
+
test('OR short-circuit: first true', () => {
|
|
1562
|
+
const rt = makeRuntime(`
|
|
1563
|
+
fn f(): int {
|
|
1564
|
+
if (true || false) { return 1; } else { return 0; }
|
|
1565
|
+
}
|
|
1566
|
+
`);
|
|
1567
|
+
expect(callAndGetRet(rt, 'f')).toBe(1);
|
|
1568
|
+
});
|
|
1569
|
+
test('AND with comparison', () => {
|
|
1570
|
+
const rt = makeRuntime(`
|
|
1571
|
+
fn f(): int {
|
|
1572
|
+
let x: int = 5;
|
|
1573
|
+
if (x >= 1 && x <= 10) { return 1; } else { return 0; }
|
|
1574
|
+
}
|
|
1575
|
+
`);
|
|
1576
|
+
expect(callAndGetRet(rt, 'f')).toBe(1);
|
|
1577
|
+
});
|
|
1578
|
+
test('AND false short-circuit', () => {
|
|
1579
|
+
const rt = makeRuntime(`
|
|
1580
|
+
fn f(): int {
|
|
1581
|
+
if (false && true) { return 1; } else { return 0; }
|
|
1582
|
+
}
|
|
1583
|
+
`);
|
|
1584
|
+
expect(callAndGetRet(rt, 'f')).toBe(0);
|
|
1585
|
+
});
|
|
1586
|
+
});
|
|
1587
|
+
// ===========================================================================
|
|
1588
|
+
// 25. Struct declaration and field access (behavioral)
|
|
1589
|
+
// ===========================================================================
|
|
1590
|
+
// TODO: Struct field access/assignment is stubbed at MIR level (returns const 0).
|
|
1591
|
+
// These behavioral tests will pass once MIR struct lowering is implemented.
|
|
1592
|
+
// See src2/mir/lower.ts — struct_lit, member, member_assign are opaque at MIR.
|
|
1593
|
+
describe('v2 migration: struct behavioral', () => {
|
|
1594
|
+
test('struct literal creates instance (compiles)', () => {
|
|
1595
|
+
expect(() => (0, compile_1.compile)(`
|
|
1596
|
+
struct Point { x: int, y: int }
|
|
1597
|
+
fn f(): int {
|
|
1598
|
+
let p: Point = { x: 10, y: 20 };
|
|
1599
|
+
return 1;
|
|
1600
|
+
}
|
|
1601
|
+
`, { namespace: NS })).not.toThrow();
|
|
1602
|
+
});
|
|
1603
|
+
test('struct field read returns correct value', () => {
|
|
1604
|
+
const rt = makeRuntime(`
|
|
1605
|
+
struct Point { x: int, y: int }
|
|
1606
|
+
fn f(): int {
|
|
1607
|
+
let p: Point = { x: 42, y: 10 };
|
|
1608
|
+
return p.x;
|
|
1609
|
+
}
|
|
1610
|
+
`);
|
|
1611
|
+
expect(callAndGetRet(rt, 'f')).toBe(42);
|
|
1612
|
+
});
|
|
1613
|
+
test('struct field read second field', () => {
|
|
1614
|
+
const rt = makeRuntime(`
|
|
1615
|
+
struct Point { x: int, y: int }
|
|
1616
|
+
fn f(): int {
|
|
1617
|
+
let p: Point = { x: 5, y: 99 };
|
|
1618
|
+
return p.y;
|
|
1619
|
+
}
|
|
1620
|
+
`);
|
|
1621
|
+
expect(callAndGetRet(rt, 'f')).toBe(99);
|
|
1622
|
+
});
|
|
1623
|
+
test('struct field write and read back', () => {
|
|
1624
|
+
const rt = makeRuntime(`
|
|
1625
|
+
struct Point { x: int, y: int }
|
|
1626
|
+
fn f(): int {
|
|
1627
|
+
let p: Point = { x: 1, y: 2 };
|
|
1628
|
+
p.x = 50;
|
|
1629
|
+
return p.x;
|
|
1630
|
+
}
|
|
1631
|
+
`);
|
|
1632
|
+
expect(callAndGetRet(rt, 'f')).toBe(50);
|
|
1633
|
+
});
|
|
1634
|
+
test('struct field arithmetic', () => {
|
|
1635
|
+
const rt = makeRuntime(`
|
|
1636
|
+
struct Vec2 { x: int, y: int }
|
|
1637
|
+
fn f(): int {
|
|
1638
|
+
let v: Vec2 = { x: 3, y: 4 };
|
|
1639
|
+
return v.x + v.y;
|
|
1640
|
+
}
|
|
1641
|
+
`);
|
|
1642
|
+
expect(callAndGetRet(rt, 'f')).toBe(7);
|
|
1643
|
+
});
|
|
1644
|
+
test('struct field assignment from expression', () => {
|
|
1645
|
+
const rt = makeRuntime(`
|
|
1646
|
+
struct Counter { value: int }
|
|
1647
|
+
fn f(): int {
|
|
1648
|
+
let c: Counter = { value: 10 };
|
|
1649
|
+
c.value = c.value + 5;
|
|
1650
|
+
return c.value;
|
|
1651
|
+
}
|
|
1652
|
+
`);
|
|
1653
|
+
expect(callAndGetRet(rt, 'f')).toBe(15);
|
|
1654
|
+
});
|
|
1655
|
+
test('two struct instances', () => {
|
|
1656
|
+
const rt = makeRuntime(`
|
|
1657
|
+
struct Point { x: int, y: int }
|
|
1658
|
+
fn f(): int {
|
|
1659
|
+
let a: Point = { x: 1, y: 2 };
|
|
1660
|
+
let b: Point = { x: 10, y: 20 };
|
|
1661
|
+
return a.x + b.x;
|
|
1662
|
+
}
|
|
1663
|
+
`);
|
|
1664
|
+
expect(callAndGetRet(rt, 'f')).toBe(11);
|
|
1665
|
+
});
|
|
1666
|
+
test('struct with three fields', () => {
|
|
1667
|
+
const rt = makeRuntime(`
|
|
1668
|
+
struct Vec3 { x: int, y: int, z: int }
|
|
1669
|
+
fn f(): int {
|
|
1670
|
+
let v: Vec3 = { x: 1, y: 2, z: 3 };
|
|
1671
|
+
return v.x + v.y + v.z;
|
|
1672
|
+
}
|
|
1673
|
+
`);
|
|
1674
|
+
expect(callAndGetRet(rt, 'f')).toBe(6);
|
|
1675
|
+
});
|
|
1676
|
+
});
|
|
1677
|
+
// ===========================================================================
|
|
1678
|
+
// 26. Struct impl methods (behavioral)
|
|
1679
|
+
// ===========================================================================
|
|
1680
|
+
// TODO: Struct impl methods depend on struct field access/assignment (stubbed).
|
|
1681
|
+
// These tests will pass once MIR struct lowering is implemented.
|
|
1682
|
+
describe('v2 migration: struct impl methods', () => {
|
|
1683
|
+
test('static constructor method', () => {
|
|
1684
|
+
const rt = makeRuntime(`
|
|
1685
|
+
struct Point { x: int, y: int }
|
|
1686
|
+
impl Point {
|
|
1687
|
+
fn new(x: int, y: int): Point {
|
|
1688
|
+
return { x: x, y: y };
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
fn f(): int {
|
|
1692
|
+
let p: Point = Point::new(10, 20);
|
|
1693
|
+
return p.x;
|
|
1694
|
+
}
|
|
1695
|
+
`);
|
|
1696
|
+
expect(callAndGetRet(rt, 'f')).toBe(10);
|
|
1697
|
+
});
|
|
1698
|
+
test('instance method on self', () => {
|
|
1699
|
+
const rt = makeRuntime(`
|
|
1700
|
+
struct Vec2 { x: int, y: int }
|
|
1701
|
+
impl Vec2 {
|
|
1702
|
+
fn sum(self): int {
|
|
1703
|
+
return self.x + self.y;
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
fn f(): int {
|
|
1707
|
+
let v: Vec2 = { x: 3, y: 7 };
|
|
1708
|
+
return v.sum();
|
|
1709
|
+
}
|
|
1710
|
+
`);
|
|
1711
|
+
expect(callAndGetRet(rt, 'f')).toBe(10);
|
|
1712
|
+
});
|
|
1713
|
+
test('static method and instance method together', () => {
|
|
1714
|
+
const rt = makeRuntime(`
|
|
1715
|
+
struct Point { x: int, y: int }
|
|
1716
|
+
impl Point {
|
|
1717
|
+
fn new(x: int, y: int): Point {
|
|
1718
|
+
return { x: x, y: y };
|
|
1719
|
+
}
|
|
1720
|
+
fn distance(self): int {
|
|
1721
|
+
return self.x + self.y;
|
|
1722
|
+
}
|
|
1723
|
+
}
|
|
1724
|
+
fn f(): int {
|
|
1725
|
+
let p: Point = Point::new(5, 15);
|
|
1726
|
+
return p.distance();
|
|
1727
|
+
}
|
|
1728
|
+
`);
|
|
1729
|
+
expect(callAndGetRet(rt, 'f')).toBe(20);
|
|
1730
|
+
});
|
|
1731
|
+
test('impl method generates separate function file', () => {
|
|
1732
|
+
const result = (0, compile_1.compile)(`
|
|
1733
|
+
struct Vec2 { x: int, y: int }
|
|
1734
|
+
impl Vec2 {
|
|
1735
|
+
fn new(x: int, y: int): Vec2 {
|
|
1736
|
+
return { x: x, y: y };
|
|
1737
|
+
}
|
|
1738
|
+
fn sum(self): int {
|
|
1739
|
+
return self.x + self.y;
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
fn f(): int { return 1; }
|
|
1743
|
+
`, { namespace: NS });
|
|
1744
|
+
const fnNames = result.files
|
|
1745
|
+
.filter(f => f.path.endsWith('.mcfunction'))
|
|
1746
|
+
.map(f => f.path);
|
|
1747
|
+
// Should have Vec2_new or vec2/new or similar
|
|
1748
|
+
expect(fnNames.length).toBeGreaterThanOrEqual(3);
|
|
1749
|
+
});
|
|
1750
|
+
test('multiple impl methods compile', () => {
|
|
1751
|
+
expect(() => (0, compile_1.compile)(`
|
|
1752
|
+
struct Counter { value: int }
|
|
1753
|
+
impl Counter {
|
|
1754
|
+
fn new(): Counter { return { value: 0 }; }
|
|
1755
|
+
fn increment(self): void { self.value = self.value + 1; }
|
|
1756
|
+
fn get(self): int { return self.value; }
|
|
1757
|
+
fn reset(self): void { self.value = 0; }
|
|
1758
|
+
}
|
|
1759
|
+
fn f(): int { return 1; }
|
|
1760
|
+
`, { namespace: NS })).not.toThrow();
|
|
1761
|
+
});
|
|
1762
|
+
});
|
|
1763
|
+
// ===========================================================================
|
|
1764
|
+
// 27. @tick and @load runtime behavior
|
|
1765
|
+
// ===========================================================================
|
|
1766
|
+
describe('v2 migration: @tick/@load runtime', () => {
|
|
1767
|
+
test('@tick function is registered in tick.json', () => {
|
|
1768
|
+
const result = (0, compile_1.compile)(`
|
|
1769
|
+
@tick fn game_loop(): void { let x: int = 1; }
|
|
1770
|
+
fn f(): int { return 1; }
|
|
1771
|
+
`, { namespace: NS });
|
|
1772
|
+
const tickJson = getFile(result.files, 'tick.json');
|
|
1773
|
+
expect(tickJson).toBeDefined();
|
|
1774
|
+
expect(tickJson).toContain(`${NS}:game_loop`);
|
|
1775
|
+
});
|
|
1776
|
+
test('@load function is registered in load.json', () => {
|
|
1777
|
+
const result = (0, compile_1.compile)(`
|
|
1778
|
+
@load fn setup(): void { let x: int = 1; }
|
|
1779
|
+
fn f(): int { return 1; }
|
|
1780
|
+
`, { namespace: NS });
|
|
1781
|
+
const loadJson = getFile(result.files, 'load.json');
|
|
1782
|
+
expect(loadJson).toBeDefined();
|
|
1783
|
+
expect(loadJson).toContain(`${NS}:setup`);
|
|
1784
|
+
});
|
|
1785
|
+
test('multiple @tick functions all registered', () => {
|
|
1786
|
+
const result = (0, compile_1.compile)(`
|
|
1787
|
+
@tick fn tick_a(): void { let x: int = 1; }
|
|
1788
|
+
@tick fn tick_b(): void { let y: int = 2; }
|
|
1789
|
+
fn f(): int { return 1; }
|
|
1790
|
+
`, { namespace: NS });
|
|
1791
|
+
const tickJson = getFile(result.files, 'tick.json');
|
|
1792
|
+
expect(tickJson).toContain(`${NS}:tick_a`);
|
|
1793
|
+
expect(tickJson).toContain(`${NS}:tick_b`);
|
|
1794
|
+
});
|
|
1795
|
+
test('multiple @load functions all registered', () => {
|
|
1796
|
+
const result = (0, compile_1.compile)(`
|
|
1797
|
+
@load fn init_a(): void { let x: int = 1; }
|
|
1798
|
+
@load fn init_b(): void { let y: int = 2; }
|
|
1799
|
+
fn f(): int { return 1; }
|
|
1800
|
+
`, { namespace: NS });
|
|
1801
|
+
const loadJson = getFile(result.files, 'load.json');
|
|
1802
|
+
expect(loadJson).toContain(`${NS}:init_a`);
|
|
1803
|
+
expect(loadJson).toContain(`${NS}:init_b`);
|
|
1804
|
+
});
|
|
1805
|
+
test('@tick function executes without crashing', () => {
|
|
1806
|
+
const rt = makeRuntime(`
|
|
1807
|
+
@tick fn heartbeat(): void {
|
|
1808
|
+
let counter: int = 0;
|
|
1809
|
+
counter = counter + 1;
|
|
1810
|
+
}
|
|
1811
|
+
fn f(): int { return 99; }
|
|
1812
|
+
`);
|
|
1813
|
+
rt.execFunction(`${NS}:heartbeat`);
|
|
1814
|
+
rt.execFunction(`${NS}:heartbeat`);
|
|
1815
|
+
expect(callAndGetRet(rt, 'f')).toBe(99);
|
|
1816
|
+
});
|
|
1817
|
+
test('@load function runs during makeRuntime init', () => {
|
|
1818
|
+
const rt = makeRuntime(`
|
|
1819
|
+
@load fn init(): void {
|
|
1820
|
+
let x: int = 42;
|
|
1821
|
+
}
|
|
1822
|
+
fn f(): int { return 1; }
|
|
1823
|
+
`);
|
|
1824
|
+
// If load function crashes, makeRuntime would throw
|
|
1825
|
+
expect(callAndGetRet(rt, 'f')).toBe(1);
|
|
1826
|
+
});
|
|
1827
|
+
test('@tick with logic body', () => {
|
|
1828
|
+
const rt = makeRuntime(`
|
|
1829
|
+
@tick fn tick(): void {
|
|
1830
|
+
let x: int = 5;
|
|
1831
|
+
let y: int = 10;
|
|
1832
|
+
let sum: int = x + y;
|
|
1833
|
+
}
|
|
1834
|
+
fn f(): int { return 1; }
|
|
1835
|
+
`);
|
|
1836
|
+
rt.execFunction(`${NS}:tick`);
|
|
1837
|
+
expect(callAndGetRet(rt, 'f')).toBe(1);
|
|
1838
|
+
});
|
|
1839
|
+
test('@tick and @load coexist', () => {
|
|
1840
|
+
const result = (0, compile_1.compile)(`
|
|
1841
|
+
@tick fn game_tick(): void { let x: int = 1; }
|
|
1842
|
+
@load fn game_init(): void { let y: int = 2; }
|
|
1843
|
+
fn f(): int { return 1; }
|
|
1844
|
+
`, { namespace: NS });
|
|
1845
|
+
const tickJson = getFile(result.files, 'tick.json');
|
|
1846
|
+
const loadJson = getFile(result.files, 'load.json');
|
|
1847
|
+
expect(tickJson).toContain(`${NS}:game_tick`);
|
|
1848
|
+
expect(loadJson).toContain(`${NS}:game_init`);
|
|
1849
|
+
});
|
|
1850
|
+
});
|
|
1851
|
+
// ===========================================================================
|
|
1852
|
+
// 28. Execute context blocks (behavioral)
|
|
1853
|
+
// ===========================================================================
|
|
1854
|
+
describe('v2 migration: execute blocks behavioral', () => {
|
|
1855
|
+
test('as @a generates correct execute as command', () => {
|
|
1856
|
+
const result = (0, compile_1.compile)(`
|
|
1857
|
+
fn f(): void {
|
|
1858
|
+
as @a {
|
|
1859
|
+
raw("say hello");
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
`, { namespace: NS });
|
|
1863
|
+
const allContent = result.files
|
|
1864
|
+
.filter(f => f.path.endsWith('.mcfunction'))
|
|
1865
|
+
.map(f => f.content).join('\n');
|
|
1866
|
+
expect(allContent).toContain('execute as @a run function');
|
|
1867
|
+
});
|
|
1868
|
+
test('at @s generates correct execute at command', () => {
|
|
1869
|
+
const result = (0, compile_1.compile)(`
|
|
1870
|
+
fn f(): void {
|
|
1871
|
+
at @s {
|
|
1872
|
+
raw("particle flame ~ ~ ~ 0 0 0 0 1");
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
`, { namespace: NS });
|
|
1876
|
+
const allContent = result.files
|
|
1877
|
+
.filter(f => f.path.endsWith('.mcfunction'))
|
|
1878
|
+
.map(f => f.content).join('\n');
|
|
1879
|
+
expect(allContent).toContain('execute at @s run function');
|
|
1880
|
+
});
|
|
1881
|
+
test('as @e at @s generates combined execute', () => {
|
|
1882
|
+
const result = (0, compile_1.compile)(`
|
|
1883
|
+
fn f(): void {
|
|
1884
|
+
as @e at @s {
|
|
1885
|
+
raw("particle flame ~ ~ ~ 0 0 0 0 1");
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
`, { namespace: NS });
|
|
1889
|
+
const allContent = result.files
|
|
1890
|
+
.filter(f => f.path.endsWith('.mcfunction'))
|
|
1891
|
+
.map(f => f.content).join('\n');
|
|
1892
|
+
expect(allContent).toContain('execute as @e at @s run function');
|
|
1893
|
+
});
|
|
1894
|
+
test('execute body raw command appears in helper', () => {
|
|
1895
|
+
const result = (0, compile_1.compile)(`
|
|
1896
|
+
fn f(): void {
|
|
1897
|
+
as @a {
|
|
1898
|
+
raw("say inside execute");
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
`, { namespace: NS });
|
|
1902
|
+
const allContent = result.files
|
|
1903
|
+
.filter(f => f.path.endsWith('.mcfunction'))
|
|
1904
|
+
.map(f => f.content).join('\n');
|
|
1905
|
+
expect(allContent).toContain('say inside execute');
|
|
1906
|
+
});
|
|
1907
|
+
test('as @e[type=zombie] with selector args', () => {
|
|
1908
|
+
const result = (0, compile_1.compile)(`
|
|
1909
|
+
fn f(): void {
|
|
1910
|
+
as @e[type=zombie] {
|
|
1911
|
+
raw("say I am zombie");
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
`, { namespace: NS });
|
|
1915
|
+
const allContent = result.files
|
|
1916
|
+
.filter(f => f.path.endsWith('.mcfunction'))
|
|
1917
|
+
.map(f => f.content).join('\n');
|
|
1918
|
+
expect(allContent).toContain('execute as @e[type=zombie] run function');
|
|
1919
|
+
});
|
|
1920
|
+
test('nested execute blocks both generate functions', () => {
|
|
1921
|
+
const result = (0, compile_1.compile)(`
|
|
1922
|
+
fn f(): void {
|
|
1923
|
+
as @a {
|
|
1924
|
+
at @s {
|
|
1925
|
+
raw("particle flame ~ ~ ~ 0 0 0 0 1");
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
`, { namespace: NS });
|
|
1930
|
+
const fnFiles = result.files.filter(f => f.path.endsWith('.mcfunction'));
|
|
1931
|
+
// At least: load, f, as-helper, at-helper
|
|
1932
|
+
expect(fnFiles.length).toBeGreaterThanOrEqual(3);
|
|
1933
|
+
});
|
|
1934
|
+
test('execute with variable assignment in body', () => {
|
|
1935
|
+
expect(() => (0, compile_1.compile)(`
|
|
1936
|
+
fn f(): void {
|
|
1937
|
+
as @a {
|
|
1938
|
+
let x: int = 42;
|
|
1939
|
+
let y: int = x + 1;
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
`, { namespace: NS })).not.toThrow();
|
|
1943
|
+
});
|
|
1944
|
+
});
|
|
1945
|
+
// ===========================================================================
|
|
1946
|
+
// 29. Function calls with multiple args and return values
|
|
1947
|
+
// ===========================================================================
|
|
1948
|
+
describe('v2 migration: multi-arg functions', () => {
|
|
1949
|
+
test('function with 3 parameters', () => {
|
|
1950
|
+
const rt = makeRuntime(`
|
|
1951
|
+
fn add3(a: int, b: int, c: int): int {
|
|
1952
|
+
return a + b + c;
|
|
1953
|
+
}
|
|
1954
|
+
fn f(): int { return add3(10, 20, 30); }
|
|
1955
|
+
`);
|
|
1956
|
+
expect(callAndGetRet(rt, 'f')).toBe(60);
|
|
1957
|
+
});
|
|
1958
|
+
test('function with 4 parameters', () => {
|
|
1959
|
+
const rt = makeRuntime(`
|
|
1960
|
+
fn sum4(a: int, b: int, c: int, d: int): int {
|
|
1961
|
+
return a + b + c + d;
|
|
1962
|
+
}
|
|
1963
|
+
fn f(): int { return sum4(1, 2, 3, 4); }
|
|
1964
|
+
`);
|
|
1965
|
+
expect(callAndGetRet(rt, 'f')).toBe(10);
|
|
1966
|
+
});
|
|
1967
|
+
test('function with 5 parameters', () => {
|
|
1968
|
+
const rt = makeRuntime(`
|
|
1969
|
+
fn sum5(a: int, b: int, c: int, d: int, e: int): int {
|
|
1970
|
+
return a + b + c + d + e;
|
|
1971
|
+
}
|
|
1972
|
+
fn f(): int { return sum5(2, 4, 6, 8, 10); }
|
|
1973
|
+
`);
|
|
1974
|
+
expect(callAndGetRet(rt, 'f')).toBe(30);
|
|
1975
|
+
});
|
|
1976
|
+
test('function calling function (2-deep chain)', () => {
|
|
1977
|
+
const rt = makeRuntime(`
|
|
1978
|
+
fn double(x: int): int { return x * 2; }
|
|
1979
|
+
fn quadruple(x: int): int { return double(double(x)); }
|
|
1980
|
+
fn f(): int { return quadruple(3); }
|
|
1981
|
+
`);
|
|
1982
|
+
expect(callAndGetRet(rt, 'f')).toBe(12);
|
|
1983
|
+
});
|
|
1984
|
+
test('function calling function (3-deep chain)', () => {
|
|
1985
|
+
const rt = makeRuntime(`
|
|
1986
|
+
fn inc(x: int): int { return x + 1; }
|
|
1987
|
+
fn add2(x: int): int { return inc(inc(x)); }
|
|
1988
|
+
fn add4(x: int): int { return add2(add2(x)); }
|
|
1989
|
+
fn f(): int { return add4(10); }
|
|
1990
|
+
`);
|
|
1991
|
+
expect(callAndGetRet(rt, 'f')).toBe(14);
|
|
1992
|
+
});
|
|
1993
|
+
test('function result used in arithmetic', () => {
|
|
1994
|
+
const rt = makeRuntime(`
|
|
1995
|
+
fn square(x: int): int { return x * x; }
|
|
1996
|
+
fn f(): int { return square(3) + square(4); }
|
|
1997
|
+
`);
|
|
1998
|
+
// 9 + 16 = 25
|
|
1999
|
+
expect(callAndGetRet(rt, 'f')).toBe(25);
|
|
2000
|
+
});
|
|
2001
|
+
test('function with param used in condition', () => {
|
|
2002
|
+
const rt = makeRuntime(`
|
|
2003
|
+
fn max_val(a: int, b: int, c: int): int {
|
|
2004
|
+
let m: int = a;
|
|
2005
|
+
if (b > m) { m = b; }
|
|
2006
|
+
if (c > m) { m = c; }
|
|
2007
|
+
return m;
|
|
2008
|
+
}
|
|
2009
|
+
fn f(): int { return max_val(5, 12, 8); }
|
|
2010
|
+
`);
|
|
2011
|
+
expect(callAndGetRet(rt, 'f')).toBe(12);
|
|
2012
|
+
});
|
|
2013
|
+
test('recursive-like pattern via loop', () => {
|
|
2014
|
+
const rt = makeRuntime(`
|
|
2015
|
+
fn power(base: int, exp: int): int {
|
|
2016
|
+
let result: int = 1;
|
|
2017
|
+
let i: int = 0;
|
|
2018
|
+
while (i < exp) {
|
|
2019
|
+
result = result * base;
|
|
2020
|
+
i = i + 1;
|
|
2021
|
+
}
|
|
2022
|
+
return result;
|
|
2023
|
+
}
|
|
2024
|
+
fn f(): int { return power(2, 8); }
|
|
2025
|
+
`);
|
|
2026
|
+
expect(callAndGetRet(rt, 'f')).toBe(256);
|
|
2027
|
+
});
|
|
2028
|
+
});
|
|
2029
|
+
// ===========================================================================
|
|
2030
|
+
// 30. Math-style stdlib patterns (inline, not via import)
|
|
2031
|
+
// ===========================================================================
|
|
2032
|
+
describe('v2 migration: math patterns', () => {
|
|
2033
|
+
test('min of two values', () => {
|
|
2034
|
+
const rt = makeRuntime(`
|
|
2035
|
+
fn min(a: int, b: int): int {
|
|
2036
|
+
if (a < b) { return a; } else { return b; }
|
|
2037
|
+
}
|
|
2038
|
+
fn f(): int { return min(7, 3); }
|
|
2039
|
+
`);
|
|
2040
|
+
expect(callAndGetRet(rt, 'f')).toBe(3);
|
|
2041
|
+
});
|
|
2042
|
+
test('max of two values', () => {
|
|
2043
|
+
const rt = makeRuntime(`
|
|
2044
|
+
fn max(a: int, b: int): int {
|
|
2045
|
+
if (a > b) { return a; } else { return b; }
|
|
2046
|
+
}
|
|
2047
|
+
fn f(): int { return max(7, 3); }
|
|
2048
|
+
`);
|
|
2049
|
+
expect(callAndGetRet(rt, 'f')).toBe(7);
|
|
2050
|
+
});
|
|
2051
|
+
test('abs of positive', () => {
|
|
2052
|
+
const rt = makeRuntime(`
|
|
2053
|
+
fn abs(x: int): int {
|
|
2054
|
+
if (x < 0) { return 0 - x; } else { return x; }
|
|
2055
|
+
}
|
|
2056
|
+
fn f(): int { return abs(42); }
|
|
2057
|
+
`);
|
|
2058
|
+
expect(callAndGetRet(rt, 'f')).toBe(42);
|
|
2059
|
+
});
|
|
2060
|
+
test('abs of negative', () => {
|
|
2061
|
+
const rt = makeRuntime(`
|
|
2062
|
+
fn abs(x: int): int {
|
|
2063
|
+
if (x < 0) { return 0 - x; } else { return x; }
|
|
2064
|
+
}
|
|
2065
|
+
fn f(): int { return abs(-15); }
|
|
2066
|
+
`);
|
|
2067
|
+
expect(callAndGetRet(rt, 'f')).toBe(15);
|
|
2068
|
+
});
|
|
2069
|
+
test('clamp value in range (below min)', () => {
|
|
2070
|
+
const rt = makeRuntime(`
|
|
2071
|
+
fn clamp(x: int, lo: int, hi: int): int {
|
|
2072
|
+
if (x < lo) { return lo; }
|
|
2073
|
+
if (x > hi) { return hi; }
|
|
2074
|
+
return x;
|
|
2075
|
+
}
|
|
2076
|
+
fn f(): int { return clamp(-5, 0, 100); }
|
|
2077
|
+
`);
|
|
2078
|
+
expect(callAndGetRet(rt, 'f')).toBe(0);
|
|
2079
|
+
});
|
|
2080
|
+
test('clamp value in range (above max)', () => {
|
|
2081
|
+
const rt = makeRuntime(`
|
|
2082
|
+
fn clamp(x: int, lo: int, hi: int): int {
|
|
2083
|
+
if (x < lo) { return lo; }
|
|
2084
|
+
if (x > hi) { return hi; }
|
|
2085
|
+
return x;
|
|
2086
|
+
}
|
|
2087
|
+
fn f(): int { return clamp(200, 0, 100); }
|
|
2088
|
+
`);
|
|
2089
|
+
expect(callAndGetRet(rt, 'f')).toBe(100);
|
|
2090
|
+
});
|
|
2091
|
+
test('clamp value in range (within)', () => {
|
|
2092
|
+
const rt = makeRuntime(`
|
|
2093
|
+
fn clamp(x: int, lo: int, hi: int): int {
|
|
2094
|
+
if (x < lo) { return lo; }
|
|
2095
|
+
if (x > hi) { return hi; }
|
|
2096
|
+
return x;
|
|
2097
|
+
}
|
|
2098
|
+
fn f(): int { return clamp(50, 0, 100); }
|
|
2099
|
+
`);
|
|
2100
|
+
expect(callAndGetRet(rt, 'f')).toBe(50);
|
|
2101
|
+
});
|
|
2102
|
+
test('sign function', () => {
|
|
2103
|
+
const rt = makeRuntime(`
|
|
2104
|
+
fn sign(x: int): int {
|
|
2105
|
+
if (x > 0) { return 1; }
|
|
2106
|
+
if (x < 0) { return -1; }
|
|
2107
|
+
return 0;
|
|
2108
|
+
}
|
|
2109
|
+
fn f(): int { return sign(-42) + sign(0) + sign(100); }
|
|
2110
|
+
`);
|
|
2111
|
+
// -1 + 0 + 1 = 0
|
|
2112
|
+
expect(callAndGetRet(rt, 'f')).toBe(0);
|
|
2113
|
+
});
|
|
2114
|
+
test('integer division rounding', () => {
|
|
2115
|
+
const rt = makeRuntime(`
|
|
2116
|
+
fn div_round(a: int, b: int): int {
|
|
2117
|
+
return (a + b / 2) / b;
|
|
2118
|
+
}
|
|
2119
|
+
fn f(): int { return div_round(7, 3); }
|
|
2120
|
+
`);
|
|
2121
|
+
// (7 + 1) / 3 = 2 (integer)
|
|
2122
|
+
expect(callAndGetRet(rt, 'f')).toBe(2);
|
|
2123
|
+
});
|
|
2124
|
+
test('is_even / is_odd', () => {
|
|
2125
|
+
const rt = makeRuntime(`
|
|
2126
|
+
fn is_even(x: int): int {
|
|
2127
|
+
if (x % 2 == 0) { return 1; } else { return 0; }
|
|
2128
|
+
}
|
|
2129
|
+
fn f(): int {
|
|
2130
|
+
return is_even(4) + is_even(7) + is_even(0) + is_even(13);
|
|
2131
|
+
}
|
|
2132
|
+
`);
|
|
2133
|
+
// 1 + 0 + 1 + 0 = 2
|
|
2134
|
+
expect(callAndGetRet(rt, 'f')).toBe(2);
|
|
2135
|
+
});
|
|
2136
|
+
});
|
|
2137
|
+
// ===========================================================================
|
|
2138
|
+
// 31. Break/continue in loops (behavioral)
|
|
2139
|
+
// ===========================================================================
|
|
2140
|
+
describe('v2 migration: break/continue behavioral', () => {
|
|
2141
|
+
test('break exits while loop early', () => {
|
|
2142
|
+
const rt = makeRuntime(`
|
|
2143
|
+
fn f(): int {
|
|
2144
|
+
let i: int = 0;
|
|
2145
|
+
let sum: int = 0;
|
|
2146
|
+
while (i < 100) {
|
|
2147
|
+
if (i == 5) { break; }
|
|
2148
|
+
sum = sum + i;
|
|
2149
|
+
i = i + 1;
|
|
2150
|
+
}
|
|
2151
|
+
return sum;
|
|
2152
|
+
}
|
|
2153
|
+
`);
|
|
2154
|
+
// 0+1+2+3+4 = 10
|
|
2155
|
+
expect(callAndGetRet(rt, 'f')).toBe(10);
|
|
2156
|
+
});
|
|
2157
|
+
test('continue skips to next iteration', () => {
|
|
2158
|
+
const rt = makeRuntime(`
|
|
2159
|
+
fn f(): int {
|
|
2160
|
+
let i: int = 0;
|
|
2161
|
+
let sum: int = 0;
|
|
2162
|
+
while (i < 10) {
|
|
2163
|
+
i = i + 1;
|
|
2164
|
+
if (i % 2 == 1) { continue; }
|
|
2165
|
+
sum = sum + i;
|
|
2166
|
+
}
|
|
2167
|
+
return sum;
|
|
2168
|
+
}
|
|
2169
|
+
`);
|
|
2170
|
+
// 2+4+6+8+10 = 30
|
|
2171
|
+
expect(callAndGetRet(rt, 'f')).toBe(30);
|
|
2172
|
+
});
|
|
2173
|
+
test('break in for loop', () => {
|
|
2174
|
+
const rt = makeRuntime(`
|
|
2175
|
+
fn f(): int {
|
|
2176
|
+
let result: int = 0;
|
|
2177
|
+
for i in 0..100 {
|
|
2178
|
+
if (i == 7) { break; }
|
|
2179
|
+
result = result + 1;
|
|
2180
|
+
}
|
|
2181
|
+
return result;
|
|
2182
|
+
}
|
|
2183
|
+
`);
|
|
2184
|
+
expect(callAndGetRet(rt, 'f')).toBe(7);
|
|
2185
|
+
});
|
|
2186
|
+
test('continue in for loop', () => {
|
|
2187
|
+
const rt = makeRuntime(`
|
|
2188
|
+
fn f(): int {
|
|
2189
|
+
let sum: int = 0;
|
|
2190
|
+
for i in 0..10 {
|
|
2191
|
+
if (i % 3 == 0) { continue; }
|
|
2192
|
+
sum = sum + 1;
|
|
2193
|
+
}
|
|
2194
|
+
return sum;
|
|
2195
|
+
}
|
|
2196
|
+
`);
|
|
2197
|
+
// 10 iterations, skip i=0,3,6,9 → 6 counted
|
|
2198
|
+
expect(callAndGetRet(rt, 'f')).toBe(6);
|
|
2199
|
+
});
|
|
2200
|
+
test('break with accumulator', () => {
|
|
2201
|
+
const rt = makeRuntime(`
|
|
2202
|
+
fn f(): int {
|
|
2203
|
+
let product: int = 1;
|
|
2204
|
+
let i: int = 1;
|
|
2205
|
+
while (i <= 10) {
|
|
2206
|
+
product = product * i;
|
|
2207
|
+
if (product > 100) { break; }
|
|
2208
|
+
i = i + 1;
|
|
2209
|
+
}
|
|
2210
|
+
return product;
|
|
2211
|
+
}
|
|
2212
|
+
`);
|
|
2213
|
+
// 1*1=1, 1*2=2, 2*3=6, 6*4=24, 24*5=120 > 100 → break → 120
|
|
2214
|
+
expect(callAndGetRet(rt, 'f')).toBe(120);
|
|
2215
|
+
});
|
|
2216
|
+
test('multiple breaks in loop (first wins)', () => {
|
|
2217
|
+
const rt = makeRuntime(`
|
|
2218
|
+
fn f(): int {
|
|
2219
|
+
let i: int = 0;
|
|
2220
|
+
while (i < 20) {
|
|
2221
|
+
if (i == 3) { break; }
|
|
2222
|
+
if (i == 7) { break; }
|
|
2223
|
+
i = i + 1;
|
|
2224
|
+
}
|
|
2225
|
+
return i;
|
|
2226
|
+
}
|
|
2227
|
+
`);
|
|
2228
|
+
expect(callAndGetRet(rt, 'f')).toBe(3);
|
|
2229
|
+
});
|
|
2230
|
+
test('break and continue in same loop', () => {
|
|
2231
|
+
const rt = makeRuntime(`
|
|
2232
|
+
fn f(): int {
|
|
2233
|
+
let i: int = 0;
|
|
2234
|
+
let sum: int = 0;
|
|
2235
|
+
while (i < 20) {
|
|
2236
|
+
i = i + 1;
|
|
2237
|
+
if (i % 2 == 0) { continue; }
|
|
2238
|
+
if (i > 10) { break; }
|
|
2239
|
+
sum = sum + i;
|
|
2240
|
+
}
|
|
2241
|
+
return sum;
|
|
2242
|
+
}
|
|
2243
|
+
`);
|
|
2244
|
+
// odd numbers 1,3,5,7,9 → sum = 25; i=11 is odd > 10 → break
|
|
2245
|
+
expect(callAndGetRet(rt, 'f')).toBe(25);
|
|
2246
|
+
});
|
|
2247
|
+
});
|
|
2248
|
+
// ===========================================================================
|
|
2249
|
+
// 32. Foreach (behavioral)
|
|
2250
|
+
// ===========================================================================
|
|
2251
|
+
describe('v2 migration: foreach behavioral', () => {
|
|
2252
|
+
test('foreach with @e generates execute as run function', () => {
|
|
2253
|
+
const result = (0, compile_1.compile)(`
|
|
2254
|
+
fn f(): void {
|
|
2255
|
+
foreach (e in @e) {
|
|
2256
|
+
raw("say hi");
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
`, { namespace: NS });
|
|
2260
|
+
const allContent = result.files
|
|
2261
|
+
.filter(f => f.path.endsWith('.mcfunction'))
|
|
2262
|
+
.map(f => f.content).join('\n');
|
|
2263
|
+
expect(allContent).toContain('execute as @e run function');
|
|
2264
|
+
expect(allContent).toContain('say hi');
|
|
2265
|
+
});
|
|
2266
|
+
test('foreach with @a selector', () => {
|
|
2267
|
+
const result = (0, compile_1.compile)(`
|
|
2268
|
+
fn f(): void {
|
|
2269
|
+
foreach (p in @a) {
|
|
2270
|
+
raw("effect give @s speed 1 1");
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
`, { namespace: NS });
|
|
2274
|
+
const allContent = result.files
|
|
2275
|
+
.filter(f => f.path.endsWith('.mcfunction'))
|
|
2276
|
+
.map(f => f.content).join('\n');
|
|
2277
|
+
expect(allContent).toContain('execute as @a run function');
|
|
2278
|
+
expect(allContent).toContain('effect give @s speed 1 1');
|
|
2279
|
+
});
|
|
2280
|
+
test('foreach with filtered selector', () => {
|
|
2281
|
+
const result = (0, compile_1.compile)(`
|
|
2282
|
+
fn f(): void {
|
|
2283
|
+
foreach (z in @e[type=zombie]) {
|
|
2284
|
+
raw("kill @s");
|
|
2285
|
+
}
|
|
2286
|
+
}
|
|
2287
|
+
`, { namespace: NS });
|
|
2288
|
+
const allContent = result.files
|
|
2289
|
+
.filter(f => f.path.endsWith('.mcfunction'))
|
|
2290
|
+
.map(f => f.content).join('\n');
|
|
2291
|
+
expect(allContent).toContain('execute as @e[type=zombie] run function');
|
|
2292
|
+
});
|
|
2293
|
+
test('foreach with complex body compiles', () => {
|
|
2294
|
+
expect(() => (0, compile_1.compile)(`
|
|
2295
|
+
fn f(): void {
|
|
2296
|
+
foreach (e in @e[type=zombie,distance=..10]) {
|
|
2297
|
+
raw("effect give @s slowness 1 1");
|
|
2298
|
+
raw("damage @s 2");
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
`, { namespace: NS })).not.toThrow();
|
|
2302
|
+
});
|
|
2303
|
+
});
|
|
2304
|
+
// ===========================================================================
|
|
2305
|
+
// 33. For loop advanced patterns
|
|
2306
|
+
// ===========================================================================
|
|
2307
|
+
describe('v2 migration: for loop advanced', () => {
|
|
2308
|
+
test('for loop with range 0..0 produces 0 iterations', () => {
|
|
2309
|
+
const rt = makeRuntime(`
|
|
2310
|
+
fn f(): int {
|
|
2311
|
+
let count: int = 0;
|
|
2312
|
+
for i in 0..0 {
|
|
2313
|
+
count = count + 1;
|
|
2314
|
+
}
|
|
2315
|
+
return count;
|
|
2316
|
+
}
|
|
2317
|
+
`);
|
|
2318
|
+
expect(callAndGetRet(rt, 'f')).toBe(0);
|
|
2319
|
+
});
|
|
2320
|
+
test('for loop with range 0..1 produces 1 iteration', () => {
|
|
2321
|
+
const rt = makeRuntime(`
|
|
2322
|
+
fn f(): int {
|
|
2323
|
+
let count: int = 0;
|
|
2324
|
+
for i in 0..1 {
|
|
2325
|
+
count = count + 1;
|
|
2326
|
+
}
|
|
2327
|
+
return count;
|
|
2328
|
+
}
|
|
2329
|
+
`);
|
|
2330
|
+
expect(callAndGetRet(rt, 'f')).toBe(1);
|
|
2331
|
+
});
|
|
2332
|
+
test('for loop accumulates sum', () => {
|
|
2333
|
+
const rt = makeRuntime(`
|
|
2334
|
+
fn f(): int {
|
|
2335
|
+
let sum: int = 0;
|
|
2336
|
+
for i in 0..5 {
|
|
2337
|
+
sum = sum + i;
|
|
2338
|
+
}
|
|
2339
|
+
return sum;
|
|
2340
|
+
}
|
|
2341
|
+
`);
|
|
2342
|
+
// 0+1+2+3+4 = 10
|
|
2343
|
+
expect(callAndGetRet(rt, 'f')).toBe(10);
|
|
2344
|
+
});
|
|
2345
|
+
test('for loop with large range', () => {
|
|
2346
|
+
const rt = makeRuntime(`
|
|
2347
|
+
fn f(): int {
|
|
2348
|
+
let count: int = 0;
|
|
2349
|
+
for i in 0..20 {
|
|
2350
|
+
count = count + 1;
|
|
2351
|
+
}
|
|
2352
|
+
return count;
|
|
2353
|
+
}
|
|
2354
|
+
`);
|
|
2355
|
+
expect(callAndGetRet(rt, 'f')).toBe(20);
|
|
2356
|
+
});
|
|
2357
|
+
test('nested for loops (product)', () => {
|
|
2358
|
+
const rt = makeRuntime(`
|
|
2359
|
+
fn f(): int {
|
|
2360
|
+
let count: int = 0;
|
|
2361
|
+
for i in 0..3 {
|
|
2362
|
+
for j in 0..4 {
|
|
2363
|
+
count = count + 1;
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
return count;
|
|
2367
|
+
}
|
|
2368
|
+
`);
|
|
2369
|
+
expect(callAndGetRet(rt, 'f')).toBe(12);
|
|
2370
|
+
});
|
|
2371
|
+
});
|
|
2372
|
+
// ===========================================================================
|
|
2373
|
+
// 34. Complex control flow patterns
|
|
2374
|
+
// ===========================================================================
|
|
2375
|
+
describe('v2 migration: complex control flow', () => {
|
|
2376
|
+
test('nested loops with outer counter', () => {
|
|
2377
|
+
const rt = makeRuntime(`
|
|
2378
|
+
fn f(): int {
|
|
2379
|
+
let total: int = 0;
|
|
2380
|
+
let i: int = 0;
|
|
2381
|
+
while (i < 3) {
|
|
2382
|
+
let j: int = 0;
|
|
2383
|
+
while (j < 3) {
|
|
2384
|
+
total = total + 1;
|
|
2385
|
+
j = j + 1;
|
|
2386
|
+
}
|
|
2387
|
+
i = i + 1;
|
|
2388
|
+
}
|
|
2389
|
+
return total;
|
|
2390
|
+
}
|
|
2391
|
+
`);
|
|
2392
|
+
expect(callAndGetRet(rt, 'f')).toBe(9);
|
|
2393
|
+
});
|
|
2394
|
+
test('loop with early return', () => {
|
|
2395
|
+
const rt = makeRuntime(`
|
|
2396
|
+
fn find_first_gt(threshold: int): int {
|
|
2397
|
+
let i: int = 0;
|
|
2398
|
+
while (i < 100) {
|
|
2399
|
+
if (i * i > threshold) { return i; }
|
|
2400
|
+
i = i + 1;
|
|
2401
|
+
}
|
|
2402
|
+
return -1;
|
|
2403
|
+
}
|
|
2404
|
+
fn f(): int { return find_first_gt(50); }
|
|
2405
|
+
`);
|
|
2406
|
+
// 8*8=64 > 50 → return 8
|
|
2407
|
+
expect(callAndGetRet(rt, 'f')).toBe(8);
|
|
2408
|
+
});
|
|
2409
|
+
test('multiple early returns in if/else chain', () => {
|
|
2410
|
+
const rt = makeRuntime(`
|
|
2411
|
+
fn classify(x: int): int {
|
|
2412
|
+
if (x < 0) { return -1; }
|
|
2413
|
+
if (x == 0) { return 0; }
|
|
2414
|
+
if (x < 10) { return 1; }
|
|
2415
|
+
if (x < 100) { return 2; }
|
|
2416
|
+
return 3;
|
|
2417
|
+
}
|
|
2418
|
+
fn f(): int {
|
|
2419
|
+
return classify(-5) + classify(0) + classify(7) + classify(50) + classify(200);
|
|
2420
|
+
}
|
|
2421
|
+
`);
|
|
2422
|
+
// -1 + 0 + 1 + 2 + 3 = 5
|
|
2423
|
+
expect(callAndGetRet(rt, 'f')).toBe(5);
|
|
2424
|
+
});
|
|
2425
|
+
test('while with complex condition', () => {
|
|
2426
|
+
const rt = makeRuntime(`
|
|
2427
|
+
fn f(): int {
|
|
2428
|
+
let a: int = 10;
|
|
2429
|
+
let b: int = 20;
|
|
2430
|
+
while (a < b) {
|
|
2431
|
+
a = a + 3;
|
|
2432
|
+
b = b - 1;
|
|
2433
|
+
}
|
|
2434
|
+
return a;
|
|
2435
|
+
}
|
|
2436
|
+
`);
|
|
2437
|
+
// a=10,b=20 → a=13,b=19 → a=16,b=18 → a=19,b=17 → exit (19 >= 17)
|
|
2438
|
+
expect(callAndGetRet(rt, 'f')).toBe(19);
|
|
2439
|
+
});
|
|
2440
|
+
test('deeply nested if/else', () => {
|
|
2441
|
+
const rt = makeRuntime(`
|
|
2442
|
+
fn f(): int {
|
|
2443
|
+
let x: int = 7;
|
|
2444
|
+
if (x > 0) {
|
|
2445
|
+
if (x > 5) {
|
|
2446
|
+
if (x > 10) {
|
|
2447
|
+
return 3;
|
|
2448
|
+
} else {
|
|
2449
|
+
return 2;
|
|
2450
|
+
}
|
|
2451
|
+
} else {
|
|
2452
|
+
return 1;
|
|
2453
|
+
}
|
|
2454
|
+
} else {
|
|
2455
|
+
return 0;
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
`);
|
|
2459
|
+
expect(callAndGetRet(rt, 'f')).toBe(2);
|
|
2460
|
+
});
|
|
2461
|
+
});
|
|
2462
|
+
// ===========================================================================
|
|
2463
|
+
// 35. Raw commands (extended)
|
|
2464
|
+
// ===========================================================================
|
|
2465
|
+
describe('v2 migration: raw commands extended', () => {
|
|
2466
|
+
test('raw command with execute', () => {
|
|
2467
|
+
const result = (0, compile_1.compile)(`
|
|
2468
|
+
fn f(): void {
|
|
2469
|
+
raw("execute as @a at @s run particle flame ~ ~ ~ 0.5 0.5 0.5 0 10");
|
|
2470
|
+
}
|
|
2471
|
+
`, { namespace: NS });
|
|
2472
|
+
const fn = getFile(result.files, '/f.mcfunction');
|
|
2473
|
+
expect(fn).toContain('execute as @a at @s run particle flame');
|
|
2474
|
+
});
|
|
2475
|
+
test('raw command with scoreboard', () => {
|
|
2476
|
+
const result = (0, compile_1.compile)(`
|
|
2477
|
+
fn f(): void {
|
|
2478
|
+
raw("scoreboard players set @s health 20");
|
|
2479
|
+
}
|
|
2480
|
+
`, { namespace: NS });
|
|
2481
|
+
const fn = getFile(result.files, '/f.mcfunction');
|
|
2482
|
+
expect(fn).toContain('scoreboard players set @s health 20');
|
|
2483
|
+
});
|
|
2484
|
+
test('raw command with tellraw', () => {
|
|
2485
|
+
const result = (0, compile_1.compile)(`
|
|
2486
|
+
fn f(): void {
|
|
2487
|
+
raw("tellraw @a {\\"text\\":\\"Hello World\\"}");
|
|
2488
|
+
}
|
|
2489
|
+
`, { namespace: NS });
|
|
2490
|
+
const fn = getFile(result.files, '/f.mcfunction');
|
|
2491
|
+
expect(fn).toContain('tellraw @a');
|
|
2492
|
+
});
|
|
2493
|
+
test('raw command mixed with regular code', () => {
|
|
2494
|
+
const rt = makeRuntime(`
|
|
2495
|
+
fn f(): int {
|
|
2496
|
+
let x: int = 10;
|
|
2497
|
+
raw("say test");
|
|
2498
|
+
let y: int = x + 5;
|
|
2499
|
+
return y;
|
|
2500
|
+
}
|
|
2501
|
+
`);
|
|
2502
|
+
expect(callAndGetRet(rt, 'f')).toBe(15);
|
|
2503
|
+
});
|
|
2504
|
+
test('multiple raw commands interleaved', () => {
|
|
2505
|
+
const rt = makeRuntime(`
|
|
2506
|
+
fn f(): int {
|
|
2507
|
+
raw("say start");
|
|
2508
|
+
let x: int = 1;
|
|
2509
|
+
raw("say middle");
|
|
2510
|
+
x = x + 9;
|
|
2511
|
+
raw("say end");
|
|
2512
|
+
return x;
|
|
2513
|
+
}
|
|
2514
|
+
`);
|
|
2515
|
+
expect(callAndGetRet(rt, 'f')).toBe(10);
|
|
2516
|
+
});
|
|
2517
|
+
});
|
|
2518
|
+
// ===========================================================================
|
|
2519
|
+
// 36. Variable scoping and edge cases
|
|
2520
|
+
// ===========================================================================
|
|
2521
|
+
describe('v2 migration: variable scoping', () => {
|
|
2522
|
+
test('same variable name in different functions', () => {
|
|
2523
|
+
const rt = makeRuntime(`
|
|
2524
|
+
fn foo(): int {
|
|
2525
|
+
let x: int = 10;
|
|
2526
|
+
return x;
|
|
2527
|
+
}
|
|
2528
|
+
fn bar(): int {
|
|
2529
|
+
let x: int = 20;
|
|
2530
|
+
return x;
|
|
2531
|
+
}
|
|
2532
|
+
fn f(): int { return foo() + bar(); }
|
|
2533
|
+
`);
|
|
2534
|
+
expect(callAndGetRet(rt, 'f')).toBe(30);
|
|
2535
|
+
});
|
|
2536
|
+
test('variable reassignment chain', () => {
|
|
2537
|
+
const rt = makeRuntime(`
|
|
2538
|
+
fn f(): int {
|
|
2539
|
+
let x: int = 1;
|
|
2540
|
+
x = x + 1;
|
|
2541
|
+
x = x * 3;
|
|
2542
|
+
x = x - 2;
|
|
2543
|
+
x = x + 10;
|
|
2544
|
+
return x;
|
|
2545
|
+
}
|
|
2546
|
+
`);
|
|
2547
|
+
// 1 → 2 → 6 → 4 → 14
|
|
2548
|
+
expect(callAndGetRet(rt, 'f')).toBe(14);
|
|
2549
|
+
});
|
|
2550
|
+
test('many local variables', () => {
|
|
2551
|
+
const rt = makeRuntime(`
|
|
2552
|
+
fn f(): int {
|
|
2553
|
+
let a: int = 1;
|
|
2554
|
+
let b: int = 2;
|
|
2555
|
+
let c: int = 3;
|
|
2556
|
+
let d: int = 4;
|
|
2557
|
+
let e: int = 5;
|
|
2558
|
+
let g: int = 6;
|
|
2559
|
+
let h: int = 7;
|
|
2560
|
+
return a + b + c + d + e + g + h;
|
|
2561
|
+
}
|
|
2562
|
+
`);
|
|
2563
|
+
expect(callAndGetRet(rt, 'f')).toBe(28);
|
|
2564
|
+
});
|
|
2565
|
+
test('variable used across if/else branches', () => {
|
|
2566
|
+
const rt = makeRuntime(`
|
|
2567
|
+
fn f(): int {
|
|
2568
|
+
let result: int = 0;
|
|
2569
|
+
let x: int = 5;
|
|
2570
|
+
if (x > 3) {
|
|
2571
|
+
result = 100;
|
|
2572
|
+
} else {
|
|
2573
|
+
result = 200;
|
|
2574
|
+
}
|
|
2575
|
+
return result;
|
|
2576
|
+
}
|
|
2577
|
+
`);
|
|
2578
|
+
expect(callAndGetRet(rt, 'f')).toBe(100);
|
|
2579
|
+
});
|
|
2580
|
+
test('variable modified in loop', () => {
|
|
2581
|
+
const rt = makeRuntime(`
|
|
2582
|
+
fn f(): int {
|
|
2583
|
+
let x: int = 1;
|
|
2584
|
+
let i: int = 0;
|
|
2585
|
+
while (i < 5) {
|
|
2586
|
+
x = x * 2;
|
|
2587
|
+
i = i + 1;
|
|
2588
|
+
}
|
|
2589
|
+
return x;
|
|
2590
|
+
}
|
|
2591
|
+
`);
|
|
2592
|
+
// 1 → 2 → 4 → 8 → 16 → 32
|
|
2593
|
+
expect(callAndGetRet(rt, 'f')).toBe(32);
|
|
2594
|
+
});
|
|
2595
|
+
});
|
|
2596
|
+
// ===========================================================================
|
|
2597
|
+
// 37. Compound assignment operators
|
|
2598
|
+
// ===========================================================================
|
|
2599
|
+
describe('v2 migration: compound assignment extended', () => {
|
|
2600
|
+
test('+= in loop', () => {
|
|
2601
|
+
const rt = makeRuntime(`
|
|
2602
|
+
fn f(): int {
|
|
2603
|
+
let sum: int = 0;
|
|
2604
|
+
let i: int = 1;
|
|
2605
|
+
while (i <= 5) {
|
|
2606
|
+
sum += i;
|
|
2607
|
+
i += 1;
|
|
2608
|
+
}
|
|
2609
|
+
return sum;
|
|
2610
|
+
}
|
|
2611
|
+
`);
|
|
2612
|
+
expect(callAndGetRet(rt, 'f')).toBe(15);
|
|
2613
|
+
});
|
|
2614
|
+
test('-= countdown', () => {
|
|
2615
|
+
const rt = makeRuntime(`
|
|
2616
|
+
fn f(): int {
|
|
2617
|
+
let x: int = 100;
|
|
2618
|
+
x -= 30;
|
|
2619
|
+
x -= 25;
|
|
2620
|
+
x -= 10;
|
|
2621
|
+
return x;
|
|
2622
|
+
}
|
|
2623
|
+
`);
|
|
2624
|
+
expect(callAndGetRet(rt, 'f')).toBe(35);
|
|
2625
|
+
});
|
|
2626
|
+
test('*= doubling', () => {
|
|
2627
|
+
const rt = makeRuntime(`
|
|
2628
|
+
fn f(): int {
|
|
2629
|
+
let x: int = 1;
|
|
2630
|
+
x *= 2;
|
|
2631
|
+
x *= 3;
|
|
2632
|
+
x *= 4;
|
|
2633
|
+
return x;
|
|
2634
|
+
}
|
|
2635
|
+
`);
|
|
2636
|
+
expect(callAndGetRet(rt, 'f')).toBe(24);
|
|
2637
|
+
});
|
|
2638
|
+
test('/= division', () => {
|
|
2639
|
+
const rt = makeRuntime(`
|
|
2640
|
+
fn f(): int {
|
|
2641
|
+
let x: int = 1000;
|
|
2642
|
+
x /= 2;
|
|
2643
|
+
x /= 5;
|
|
2644
|
+
return x;
|
|
2645
|
+
}
|
|
2646
|
+
`);
|
|
2647
|
+
expect(callAndGetRet(rt, 'f')).toBe(100);
|
|
2648
|
+
});
|
|
2649
|
+
test('%= modulo', () => {
|
|
2650
|
+
const rt = makeRuntime(`
|
|
2651
|
+
fn f(): int {
|
|
2652
|
+
let x: int = 17;
|
|
2653
|
+
x %= 5;
|
|
2654
|
+
return x;
|
|
2655
|
+
}
|
|
2656
|
+
`);
|
|
2657
|
+
expect(callAndGetRet(rt, 'f')).toBe(2);
|
|
2658
|
+
});
|
|
2659
|
+
});
|
|
2660
|
+
// ===========================================================================
|
|
2661
|
+
// 38. Complex algorithmic patterns
|
|
2662
|
+
// ===========================================================================
|
|
2663
|
+
describe('v2 migration: algorithmic patterns', () => {
|
|
2664
|
+
test('digit sum', () => {
|
|
2665
|
+
const rt = makeRuntime(`
|
|
2666
|
+
fn digit_sum(n: int): int {
|
|
2667
|
+
let sum: int = 0;
|
|
2668
|
+
while (n > 0) {
|
|
2669
|
+
sum = sum + n % 10;
|
|
2670
|
+
n = n / 10;
|
|
2671
|
+
}
|
|
2672
|
+
return sum;
|
|
2673
|
+
}
|
|
2674
|
+
fn f(): int { return digit_sum(12345); }
|
|
2675
|
+
`);
|
|
2676
|
+
// 1+2+3+4+5 = 15
|
|
2677
|
+
expect(callAndGetRet(rt, 'f')).toBe(15);
|
|
2678
|
+
});
|
|
2679
|
+
test('count digits', () => {
|
|
2680
|
+
const rt = makeRuntime(`
|
|
2681
|
+
fn count_digits(n: int): int {
|
|
2682
|
+
if (n == 0) { return 1; }
|
|
2683
|
+
let count: int = 0;
|
|
2684
|
+
while (n > 0) {
|
|
2685
|
+
n = n / 10;
|
|
2686
|
+
count = count + 1;
|
|
2687
|
+
}
|
|
2688
|
+
return count;
|
|
2689
|
+
}
|
|
2690
|
+
fn f(): int { return count_digits(9876); }
|
|
2691
|
+
`);
|
|
2692
|
+
expect(callAndGetRet(rt, 'f')).toBe(4);
|
|
2693
|
+
});
|
|
2694
|
+
test('sum of divisors', () => {
|
|
2695
|
+
const rt = makeRuntime(`
|
|
2696
|
+
fn sum_divisors(n: int): int {
|
|
2697
|
+
let sum: int = 0;
|
|
2698
|
+
let i: int = 1;
|
|
2699
|
+
while (i <= n) {
|
|
2700
|
+
if (n % i == 0) {
|
|
2701
|
+
sum = sum + i;
|
|
2702
|
+
}
|
|
2703
|
+
i = i + 1;
|
|
2704
|
+
}
|
|
2705
|
+
return sum;
|
|
2706
|
+
}
|
|
2707
|
+
fn f(): int { return sum_divisors(12); }
|
|
2708
|
+
`);
|
|
2709
|
+
// 1+2+3+4+6+12 = 28
|
|
2710
|
+
expect(callAndGetRet(rt, 'f')).toBe(28);
|
|
2711
|
+
});
|
|
2712
|
+
test('triangle number', () => {
|
|
2713
|
+
const rt = makeRuntime(`
|
|
2714
|
+
fn triangle(n: int): int {
|
|
2715
|
+
return n * (n + 1) / 2;
|
|
2716
|
+
}
|
|
2717
|
+
fn f(): int { return triangle(10); }
|
|
2718
|
+
`);
|
|
2719
|
+
expect(callAndGetRet(rt, 'f')).toBe(55);
|
|
2720
|
+
});
|
|
2721
|
+
test('is_perfect_square', () => {
|
|
2722
|
+
const rt = makeRuntime(`
|
|
2723
|
+
fn is_perfect_sq(n: int): int {
|
|
2724
|
+
let i: int = 0;
|
|
2725
|
+
while (i * i <= n) {
|
|
2726
|
+
if (i * i == n) { return 1; }
|
|
2727
|
+
i = i + 1;
|
|
2728
|
+
}
|
|
2729
|
+
return 0;
|
|
2730
|
+
}
|
|
2731
|
+
fn f(): int {
|
|
2732
|
+
return is_perfect_sq(16) + is_perfect_sq(25) + is_perfect_sq(10);
|
|
2733
|
+
}
|
|
2734
|
+
`);
|
|
2735
|
+
// 1 + 1 + 0 = 2
|
|
2736
|
+
expect(callAndGetRet(rt, 'f')).toBe(2);
|
|
2737
|
+
});
|
|
2738
|
+
});
|
|
2739
|
+
//# sourceMappingURL=migrate.test.js.map
|