redscript-mc 1.2.24 → 1.2.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish-extension-on-ci.yml +1 -0
- package/dist/__tests__/cli.test.js +1 -1
- package/dist/__tests__/codegen.test.js +12 -6
- package/dist/__tests__/e2e.test.js +6 -6
- package/dist/__tests__/lowering.test.js +8 -8
- package/dist/__tests__/optimizer.test.js +31 -0
- package/dist/__tests__/stdlib-advanced.test.d.ts +4 -0
- package/dist/__tests__/stdlib-advanced.test.js +264 -0
- package/dist/__tests__/stdlib-math.test.d.ts +7 -0
- package/dist/__tests__/stdlib-math.test.js +352 -0
- package/dist/__tests__/stdlib-vec.test.d.ts +4 -0
- package/dist/__tests__/stdlib-vec.test.js +264 -0
- package/dist/ast/types.d.ts +17 -1
- package/dist/codegen/mcfunction/index.js +159 -18
- package/dist/codegen/var-allocator.d.ts +17 -0
- package/dist/codegen/var-allocator.js +33 -3
- package/dist/compile.d.ts +14 -0
- package/dist/compile.js +62 -5
- package/dist/index.js +20 -1
- package/dist/ir/types.d.ts +4 -0
- package/dist/lexer/index.d.ts +1 -1
- package/dist/lexer/index.js +1 -0
- package/dist/lowering/index.d.ts +5 -0
- package/dist/lowering/index.js +83 -10
- package/dist/optimizer/dce.js +21 -5
- package/dist/optimizer/passes.js +18 -6
- package/dist/optimizer/structure.js +7 -0
- package/dist/parser/index.d.ts +5 -0
- package/dist/parser/index.js +43 -2
- package/dist/runtime/index.d.ts +6 -0
- package/dist/runtime/index.js +109 -9
- package/editors/vscode/package-lock.json +3 -3
- package/editors/vscode/package.json +1 -1
- package/package.json +1 -1
- package/src/__tests__/cli.test.ts +1 -1
- package/src/__tests__/codegen.test.ts +12 -6
- package/src/__tests__/e2e.test.ts +6 -6
- package/src/__tests__/lowering.test.ts +8 -8
- package/src/__tests__/optimizer.test.ts +33 -0
- package/src/__tests__/stdlib-advanced.test.ts +259 -0
- package/src/__tests__/stdlib-math.test.ts +374 -0
- package/src/__tests__/stdlib-vec.test.ts +259 -0
- package/src/ast/types.ts +11 -1
- package/src/codegen/mcfunction/index.ts +148 -19
- package/src/codegen/var-allocator.ts +36 -3
- package/src/compile.ts +72 -5
- package/src/index.ts +21 -1
- package/src/ir/types.ts +2 -0
- package/src/lexer/index.ts +2 -1
- package/src/lowering/index.ts +96 -10
- package/src/optimizer/dce.ts +22 -5
- package/src/optimizer/passes.ts +18 -5
- package/src/optimizer/structure.ts +6 -1
- package/src/parser/index.ts +47 -2
- package/src/runtime/index.ts +108 -10
- package/src/stdlib/advanced.mcrs +249 -0
- package/src/stdlib/math.mcrs +259 -19
- package/src/stdlib/vec.mcrs +246 -0
|
@@ -64,6 +64,7 @@ jobs:
|
|
|
64
64
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
65
65
|
git add editors/vscode/package.json editors/vscode/package-lock.json
|
|
66
66
|
git commit -m "chore: auto-bump vscode extension to ${NEW_VER} [skip ci]" || echo "Nothing to commit"
|
|
67
|
+
git pull --rebase origin main || true
|
|
67
68
|
git push
|
|
68
69
|
id: bump
|
|
69
70
|
|
|
@@ -176,7 +176,7 @@ describe('CLI API', () => {
|
|
|
176
176
|
const mainFn = result.files.find(file => file.path.endsWith('/main.mcfunction'));
|
|
177
177
|
expect(doneFn?.content).toContain('scoreboard players get timer_ticks rs');
|
|
178
178
|
expect(doneFn?.content).toContain('return run scoreboard players get');
|
|
179
|
-
expect(mainFn?.content).toContain('execute if score $
|
|
179
|
+
expect(mainFn?.content).toContain('execute if score $main_finished rs matches 1..');
|
|
180
180
|
});
|
|
181
181
|
it('Timer.tick increments', () => {
|
|
182
182
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'redscript-timer-tick-'));
|
|
@@ -17,26 +17,32 @@ describe('generateDatapack', () => {
|
|
|
17
17
|
expect(load?.content).toContain('scoreboard players set $counter rs 0');
|
|
18
18
|
});
|
|
19
19
|
it('generates function file for simple add(a, b)', () => {
|
|
20
|
+
// IR now uses { kind: 'param', index: i } for param-copy instructions,
|
|
21
|
+
// matching what the lowering emits. Pass mangle:false so we can check
|
|
22
|
+
// readable names without worrying about sequential mangled names.
|
|
20
23
|
const mod = {
|
|
21
24
|
namespace: 'mypack',
|
|
22
25
|
globals: [],
|
|
23
26
|
functions: [{
|
|
24
27
|
name: 'add',
|
|
25
|
-
params: ['a', 'b'],
|
|
26
|
-
locals: ['a', 'b', 'result'],
|
|
28
|
+
params: ['$a', '$b'],
|
|
29
|
+
locals: ['$a', '$b', '$result'],
|
|
27
30
|
blocks: [{
|
|
28
31
|
label: 'entry',
|
|
29
32
|
instrs: [
|
|
30
|
-
|
|
33
|
+
// param-copy instructions (what the lowering now emits)
|
|
34
|
+
{ op: 'assign', dst: '$a', src: { kind: 'param', index: 0 } },
|
|
35
|
+
{ op: 'assign', dst: '$b', src: { kind: 'param', index: 1 } },
|
|
36
|
+
{ op: 'binop', dst: '$result', lhs: { kind: 'var', name: '$a' }, bop: '+', rhs: { kind: 'var', name: '$b' } },
|
|
31
37
|
],
|
|
32
|
-
term: { op: 'return', value: { kind: 'var', name: 'result' } },
|
|
38
|
+
term: { op: 'return', value: { kind: 'var', name: '$result' } },
|
|
33
39
|
}],
|
|
34
40
|
}],
|
|
35
41
|
};
|
|
36
|
-
const files = (0, mcfunction_1.
|
|
42
|
+
const files = (0, mcfunction_1.generateDatapackWithStats)(mod, { mangle: false }).files;
|
|
37
43
|
const fn = files.find(f => f.path.includes('add.mcfunction'));
|
|
38
44
|
expect(fn).toBeDefined();
|
|
39
|
-
//
|
|
45
|
+
// param setup emitted from the IR
|
|
40
46
|
expect(fn.content).toContain('scoreboard players operation $a rs = $p0 rs');
|
|
41
47
|
expect(fn.content).toContain('scoreboard players operation $b rs = $p1 rs');
|
|
42
48
|
// Should have add operation
|
|
@@ -104,7 +104,7 @@ fn main() {
|
|
|
104
104
|
`);
|
|
105
105
|
const mainFn = getFunction(files, 'main');
|
|
106
106
|
expect(mainFn).toBeDefined();
|
|
107
|
-
expect(mainFn).toContain('scoreboard players set $
|
|
107
|
+
expect(mainFn).toContain('scoreboard players set $main_hp rs 105');
|
|
108
108
|
expect(mainFn).toContain('Arena Battle');
|
|
109
109
|
});
|
|
110
110
|
});
|
|
@@ -1683,7 +1683,7 @@ describe('for-range loop', () => {
|
|
|
1683
1683
|
const src = `fn test() { for i in 0..5 { say("hi"); } }`;
|
|
1684
1684
|
const files = compile(src, 'forloop');
|
|
1685
1685
|
const fn = getFunction(files, 'test');
|
|
1686
|
-
expect(fn).toContain('scoreboard players set $
|
|
1686
|
+
expect(fn).toContain('scoreboard players set $test_i rs 0');
|
|
1687
1687
|
});
|
|
1688
1688
|
it('generates loop sub-function with increment and condition', () => {
|
|
1689
1689
|
const src = `fn test() { for i in 0..5 { say("hi"); } }`;
|
|
@@ -1691,16 +1691,16 @@ describe('for-range loop', () => {
|
|
|
1691
1691
|
const subFn = files.find(f => f.path.includes('__for_0'));
|
|
1692
1692
|
expect(subFn).toBeDefined();
|
|
1693
1693
|
expect(subFn?.content).toContain('say hi');
|
|
1694
|
-
expect(subFn?.content).toContain('scoreboard players add $
|
|
1695
|
-
expect(subFn?.content).toContain('execute if score $
|
|
1694
|
+
expect(subFn?.content).toContain('scoreboard players add $test_i rs 1');
|
|
1695
|
+
expect(subFn?.content).toContain('execute if score $test_i rs matches ..4 run function forloop:test/__for_0');
|
|
1696
1696
|
});
|
|
1697
1697
|
it('supports non-zero start', () => {
|
|
1698
1698
|
const src = `fn test() { for x in 3..8 { say("loop"); } }`;
|
|
1699
1699
|
const files = compile(src, 'forloop2');
|
|
1700
1700
|
const fn = getFunction(files, 'test');
|
|
1701
|
-
expect(fn).toContain('scoreboard players set $
|
|
1701
|
+
expect(fn).toContain('scoreboard players set $test_x rs 3');
|
|
1702
1702
|
const subFn = files.find(f => f.path.includes('__for_0'));
|
|
1703
|
-
expect(subFn?.content).toContain('execute if score $
|
|
1703
|
+
expect(subFn?.content).toContain('execute if score $test_x rs matches ..7 run function forloop2:test/__for_0');
|
|
1704
1704
|
});
|
|
1705
1705
|
});
|
|
1706
1706
|
// ---------------------------------------------------------------------------
|
|
@@ -42,7 +42,7 @@ describe('Lowering', () => {
|
|
|
42
42
|
const ir = compile('fn foo(x: int) {}');
|
|
43
43
|
const fn = getFunction(ir, 'foo');
|
|
44
44
|
const instrs = getInstructions(fn);
|
|
45
|
-
expect(instrs.some(i => i.op === 'assign' && i.dst === '$
|
|
45
|
+
expect(instrs.some(i => i.op === 'assign' && i.dst === '$foo_x' && i.src.kind === 'param' && i.src.index === 0)).toBe(true);
|
|
46
46
|
});
|
|
47
47
|
it('fills in missing default arguments at call sites', () => {
|
|
48
48
|
const ir = compile(`
|
|
@@ -124,14 +124,14 @@ fn foo() {
|
|
|
124
124
|
`);
|
|
125
125
|
const fn = getFunction(ir, 'foo');
|
|
126
126
|
const instrs = getInstructions(fn);
|
|
127
|
-
expect(instrs.some(i => i.op === 'assign' && i.dst === '$
|
|
127
|
+
expect(instrs.some(i => i.op === 'assign' && i.dst === '$foo_x' && i.src.kind === 'const' && i.src.value === 100)).toBe(true);
|
|
128
128
|
expect(ir.globals).not.toContain('$MAX_HP');
|
|
129
129
|
});
|
|
130
130
|
it('lowers let with literal', () => {
|
|
131
131
|
const ir = compile('fn foo() { let x: int = 42; }');
|
|
132
132
|
const fn = getFunction(ir, 'foo');
|
|
133
133
|
const instrs = getInstructions(fn);
|
|
134
|
-
expect(instrs.some(i => i.op === 'assign' && i.dst === '$
|
|
134
|
+
expect(instrs.some(i => i.op === 'assign' && i.dst === '$foo_x' && i.src.value === 42)).toBe(true);
|
|
135
135
|
});
|
|
136
136
|
it('lowers let with expression', () => {
|
|
137
137
|
const ir = compile('fn foo(a: int) { let x: int = a + 1; }');
|
|
@@ -496,15 +496,15 @@ fn choose(dir: Direction) {
|
|
|
496
496
|
const ir = compile('fn test() { let score: int = 7; say("You have ${score} points"); }');
|
|
497
497
|
const fn = getFunction(ir, 'test');
|
|
498
498
|
const rawCmds = getRawCommands(fn);
|
|
499
|
-
expect(rawCmds).toContain('tellraw @a ["",{"text":"You have "},{"score":{"name":"$
|
|
499
|
+
expect(rawCmds).toContain('tellraw @a ["",{"text":"You have "},{"score":{"name":"$test_score","objective":"rs"}},{"text":" points"}]');
|
|
500
500
|
});
|
|
501
501
|
it('lowers f-string output builtins to tellraw/title JSON components', () => {
|
|
502
502
|
const ir = compile('fn test() { let score: int = 7; say(f"Score: {score}"); tellraw(@a, f"Score: {score}"); actionbar(@s, f"Score: {score}"); title(@s, f"Score: {score}"); }');
|
|
503
503
|
const fn = getFunction(ir, 'test');
|
|
504
504
|
const rawCmds = getRawCommands(fn);
|
|
505
|
-
expect(rawCmds).toContain('tellraw @a ["",{"text":"Score: "},{"score":{"name":"$
|
|
506
|
-
expect(rawCmds).toContain('title @s actionbar ["",{"text":"Score: "},{"score":{"name":"$
|
|
507
|
-
expect(rawCmds).toContain('title @s title ["",{"text":"Score: "},{"score":{"name":"$
|
|
505
|
+
expect(rawCmds).toContain('tellraw @a ["",{"text":"Score: "},{"score":{"name":"$test_score","objective":"rs"}}]');
|
|
506
|
+
expect(rawCmds).toContain('title @s actionbar ["",{"text":"Score: "},{"score":{"name":"$test_score","objective":"rs"}}]');
|
|
507
|
+
expect(rawCmds).toContain('title @s title ["",{"text":"Score: "},{"score":{"name":"$test_score","objective":"rs"}}]');
|
|
508
508
|
});
|
|
509
509
|
it('lowers summon()', () => {
|
|
510
510
|
const ir = compile('fn test() { summon("zombie"); }');
|
|
@@ -987,7 +987,7 @@ fn count_down() {
|
|
|
987
987
|
const ir = compile('let count: int = 0;\nfn test() { let y: int = count; }');
|
|
988
988
|
const fn = getFunction(ir, 'test');
|
|
989
989
|
const instrs = getInstructions(fn);
|
|
990
|
-
expect(instrs.some(i => i.op === 'assign' && i.dst === '$
|
|
990
|
+
expect(instrs.some(i => i.op === 'assign' && i.dst === '$test_y' && i.src.kind === 'var' && i.src.name === '$count')).toBe(true);
|
|
991
991
|
});
|
|
992
992
|
it('writes global variable in function body', () => {
|
|
993
993
|
const ir = compile('let count: int = 0;\nfn inc() { count = 5; }');
|
|
@@ -95,6 +95,37 @@ describe('deadCodeElimination', () => {
|
|
|
95
95
|
expect(opt.blocks[0].instrs[0].dst).toBe('$used_by_raw');
|
|
96
96
|
});
|
|
97
97
|
});
|
|
98
|
+
describe('copyPropagation – stale alias invalidation', () => {
|
|
99
|
+
it('does not propagate $tmp = $y after $y is overwritten (swap pattern)', () => {
|
|
100
|
+
// Simulates: let tmp = y; y = x % y; x = tmp
|
|
101
|
+
// The copy $tmp = $y must be invalidated when $y is reassigned.
|
|
102
|
+
// Before fix: x = tmp was propagated to x = y (new y, wrong value).
|
|
103
|
+
const fn = makeFn([
|
|
104
|
+
{ op: 'assign', dst: '$tmp', src: { kind: 'var', name: '$y' } }, // tmp = y
|
|
105
|
+
{ op: 'binop', dst: '$r', lhs: { kind: 'var', name: '$x' }, bop: '%', rhs: { kind: 'var', name: '$y' } }, // r = x%y
|
|
106
|
+
{ op: 'assign', dst: '$y', src: { kind: 'var', name: '$r' } }, // y = r ← stale: tmp still points to OLD y
|
|
107
|
+
{ op: 'assign', dst: '$x', src: { kind: 'var', name: '$tmp' } }, // x = tmp (should NOT be x = y)
|
|
108
|
+
]);
|
|
109
|
+
const opt = (0, passes_1.copyPropagation)(fn);
|
|
110
|
+
const instrs = opt.blocks[0].instrs;
|
|
111
|
+
const xAssign = instrs.find((i) => i.dst === '$x');
|
|
112
|
+
// x = tmp must NOT be optimised to x = $y (stale) or x = $r (new y).
|
|
113
|
+
// It should stay as x = $tmp (the original copy).
|
|
114
|
+
expect(xAssign.src).toEqual({ kind: 'var', name: '$tmp' });
|
|
115
|
+
});
|
|
116
|
+
it('still propagates simple non-conflicting copies', () => {
|
|
117
|
+
// a = 5; b = a; c = b → after propagation b and c should both be const 5
|
|
118
|
+
const fn = makeFn([
|
|
119
|
+
{ op: 'assign', dst: '$a', src: { kind: 'const', value: 5 } },
|
|
120
|
+
{ op: 'assign', dst: '$b', src: { kind: 'var', name: '$a' } },
|
|
121
|
+
{ op: 'assign', dst: '$c', src: { kind: 'var', name: '$b' } },
|
|
122
|
+
]);
|
|
123
|
+
const opt = (0, passes_1.copyPropagation)(fn);
|
|
124
|
+
const instrs = opt.blocks[0].instrs;
|
|
125
|
+
const cAssign = instrs.find((i) => i.dst === '$c');
|
|
126
|
+
expect(cAssign.src).toEqual({ kind: 'const', value: 5 });
|
|
127
|
+
});
|
|
128
|
+
});
|
|
98
129
|
describe('optimize pipeline', () => {
|
|
99
130
|
it('combines all passes', () => {
|
|
100
131
|
// t0 = 2 + 3 (→ constant fold → t0 = 5)
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* stdlib/advanced.mcrs — runtime behavioural tests
|
|
4
|
+
*/
|
|
5
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
+
if (k2 === undefined) k2 = k;
|
|
7
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
+
}
|
|
11
|
+
Object.defineProperty(o, k2, desc);
|
|
12
|
+
}) : (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
o[k2] = m[k];
|
|
15
|
+
}));
|
|
16
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
+
}) : function(o, v) {
|
|
19
|
+
o["default"] = v;
|
|
20
|
+
});
|
|
21
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
+
var ownKeys = function(o) {
|
|
23
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
+
var ar = [];
|
|
25
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
+
return ar;
|
|
27
|
+
};
|
|
28
|
+
return ownKeys(o);
|
|
29
|
+
};
|
|
30
|
+
return function (mod) {
|
|
31
|
+
if (mod && mod.__esModule) return mod;
|
|
32
|
+
var result = {};
|
|
33
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
+
__setModuleDefault(result, mod);
|
|
35
|
+
return result;
|
|
36
|
+
};
|
|
37
|
+
})();
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const compile_1 = require("../compile");
|
|
42
|
+
const runtime_1 = require("../runtime");
|
|
43
|
+
const MATH_SRC = fs.readFileSync(path.join(__dirname, '../../src/stdlib/math.mcrs'), 'utf-8');
|
|
44
|
+
const ADV_SRC = fs.readFileSync(path.join(__dirname, '../../src/stdlib/advanced.mcrs'), 'utf-8');
|
|
45
|
+
function run(driver) {
|
|
46
|
+
const result = (0, compile_1.compile)(driver, {
|
|
47
|
+
namespace: 'advtest',
|
|
48
|
+
librarySources: [MATH_SRC, ADV_SRC],
|
|
49
|
+
});
|
|
50
|
+
if (!result.success)
|
|
51
|
+
throw new Error(result.error?.message ?? 'compile failed');
|
|
52
|
+
const rt = new runtime_1.MCRuntime('advtest');
|
|
53
|
+
for (const file of result.files ?? []) {
|
|
54
|
+
if (!file.path.endsWith('.mcfunction'))
|
|
55
|
+
continue;
|
|
56
|
+
const match = file.path.match(/data\/([^/]+)\/function\/(.+)\.mcfunction$/);
|
|
57
|
+
if (!match)
|
|
58
|
+
continue;
|
|
59
|
+
rt.loadFunction(`${match[1]}:${match[2]}`, file.content.split('\n'));
|
|
60
|
+
}
|
|
61
|
+
rt.load();
|
|
62
|
+
return rt;
|
|
63
|
+
}
|
|
64
|
+
function scoreOf(rt, key) {
|
|
65
|
+
return rt.getScore('out', `advtest.${key}`);
|
|
66
|
+
}
|
|
67
|
+
// ─── fib ─────────────────────────────────────────────────────────────────────
|
|
68
|
+
describe('fib', () => {
|
|
69
|
+
it.each([
|
|
70
|
+
[0, 0],
|
|
71
|
+
[1, 1],
|
|
72
|
+
[2, 1],
|
|
73
|
+
[5, 5],
|
|
74
|
+
[10, 55],
|
|
75
|
+
[20, 6765],
|
|
76
|
+
])('fib(%d) == %d', (n, expected) => {
|
|
77
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", fib(${n})); }`);
|
|
78
|
+
rt.execFunction('test');
|
|
79
|
+
expect(scoreOf(rt, 'r')).toBe(expected);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
// ─── is_prime ─────────────────────────────────────────────────────────────────
|
|
83
|
+
describe('is_prime', () => {
|
|
84
|
+
it.each([
|
|
85
|
+
[0, 0], [1, 0], [2, 1], [3, 1], [4, 0],
|
|
86
|
+
[7, 1], [9, 0], [11, 1], [97, 1], [100, 0],
|
|
87
|
+
])('is_prime(%d) == %d', (n, expected) => {
|
|
88
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", is_prime(${n})); }`);
|
|
89
|
+
rt.execFunction('test');
|
|
90
|
+
expect(scoreOf(rt, 'r')).toBe(expected);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
// ─── collatz_steps ───────────────────────────────────────────────────────────
|
|
94
|
+
describe('collatz_steps', () => {
|
|
95
|
+
it.each([
|
|
96
|
+
[1, 0],
|
|
97
|
+
[2, 1],
|
|
98
|
+
[4, 2],
|
|
99
|
+
[6, 8],
|
|
100
|
+
[27, 111],
|
|
101
|
+
])('collatz_steps(%d) == %d', (n, expected) => {
|
|
102
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", collatz_steps(${n})); }`);
|
|
103
|
+
rt.execFunction('test');
|
|
104
|
+
expect(scoreOf(rt, 'r')).toBe(expected);
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
// ─── digit helpers ───────────────────────────────────────────────────────────
|
|
108
|
+
describe('digit_sum', () => {
|
|
109
|
+
it.each([
|
|
110
|
+
[0, 0], [1, 1], [9, 9], [123, 6], [999, 27], [-42, 6],
|
|
111
|
+
])('digit_sum(%d) == %d', (n, expected) => {
|
|
112
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", digit_sum(${n})); }`);
|
|
113
|
+
rt.execFunction('test');
|
|
114
|
+
expect(scoreOf(rt, 'r')).toBe(expected);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
describe('count_digits', () => {
|
|
118
|
+
it.each([
|
|
119
|
+
[0, 1], [9, 1], [10, 2], [100, 3], [9999, 4],
|
|
120
|
+
])('count_digits(%d) == %d', (n, expected) => {
|
|
121
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", count_digits(${n})); }`);
|
|
122
|
+
rt.execFunction('test');
|
|
123
|
+
expect(scoreOf(rt, 'r')).toBe(expected);
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
describe('reverse_int', () => {
|
|
127
|
+
it.each([
|
|
128
|
+
[12345, 54321],
|
|
129
|
+
[100, 1],
|
|
130
|
+
[7, 7],
|
|
131
|
+
])('reverse_int(%d) == %d', (n, expected) => {
|
|
132
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", reverse_int(${n})); }`);
|
|
133
|
+
rt.execFunction('test');
|
|
134
|
+
expect(scoreOf(rt, 'r')).toBe(expected);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
// ─── mod_pow ─────────────────────────────────────────────────────────────────
|
|
138
|
+
describe('mod_pow', () => {
|
|
139
|
+
it.each([
|
|
140
|
+
[2, 10, 1000, 24], // 2^10 = 1024, 1024 mod 1000 = 24
|
|
141
|
+
[3, 4, 100, 81], // 3^4 = 81
|
|
142
|
+
[2, 0, 10, 1], // any^0 = 1
|
|
143
|
+
[5, 1, 100, 5],
|
|
144
|
+
[7, 3, 13, 343 % 13], // 343 mod 13 = 5
|
|
145
|
+
])('mod_pow(%d,%d,%d) == %d', (b, e, m, expected) => {
|
|
146
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", mod_pow(${b},${e},${m})); }`);
|
|
147
|
+
rt.execFunction('test');
|
|
148
|
+
expect(scoreOf(rt, 'r')).toBe(expected);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
// ─── hash_int ────────────────────────────────────────────────────────────────
|
|
152
|
+
describe('hash_int', () => {
|
|
153
|
+
it('is deterministic', () => {
|
|
154
|
+
const rt1 = run(`fn test() { scoreboard_set("out", "r", hash_int(42)); }`);
|
|
155
|
+
rt1.execFunction('test');
|
|
156
|
+
const rt2 = run(`fn test() { scoreboard_set("out", "r", hash_int(42)); }`);
|
|
157
|
+
rt2.execFunction('test');
|
|
158
|
+
expect(scoreOf(rt1, 'r')).toBe(scoreOf(rt2, 'r'));
|
|
159
|
+
});
|
|
160
|
+
it('different inputs → different outputs', () => {
|
|
161
|
+
const rt = run(`fn test() {
|
|
162
|
+
scoreboard_set("out", "a", hash_int(0));
|
|
163
|
+
scoreboard_set("out", "b", hash_int(1));
|
|
164
|
+
scoreboard_set("out", "c", hash_int(1000));
|
|
165
|
+
}`);
|
|
166
|
+
rt.execFunction('test');
|
|
167
|
+
const a = scoreOf(rt, 'a');
|
|
168
|
+
const b = scoreOf(rt, 'b');
|
|
169
|
+
const c = scoreOf(rt, 'c');
|
|
170
|
+
expect(a).not.toBe(b);
|
|
171
|
+
expect(b).not.toBe(c);
|
|
172
|
+
});
|
|
173
|
+
it('output is non-negative', () => {
|
|
174
|
+
for (const n of [-1000, -1, 0, 1, 999, 46340]) {
|
|
175
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", hash_int(${n})); }`);
|
|
176
|
+
rt.execFunction('test');
|
|
177
|
+
expect(scoreOf(rt, 'r')).toBeGreaterThanOrEqual(0);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
// ─── noise1d ─────────────────────────────────────────────────────────────────
|
|
182
|
+
describe('noise1d', () => {
|
|
183
|
+
it('output in [0, 999]', () => {
|
|
184
|
+
for (const x of [0, 100, 500, 999, 1000, 2000]) {
|
|
185
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", noise1d(${x})); }`);
|
|
186
|
+
rt.execFunction('test');
|
|
187
|
+
const v = scoreOf(rt, 'r');
|
|
188
|
+
expect(v).toBeGreaterThanOrEqual(0);
|
|
189
|
+
expect(v).toBeLessThan(1000);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
it('is deterministic', () => {
|
|
193
|
+
const rt1 = run(`fn test() { scoreboard_set("out", "r", noise1d(1234)); }`);
|
|
194
|
+
rt1.execFunction('test');
|
|
195
|
+
const rt2 = run(`fn test() { scoreboard_set("out", "r", noise1d(1234)); }`);
|
|
196
|
+
rt2.execFunction('test');
|
|
197
|
+
expect(scoreOf(rt1, 'r')).toBe(scoreOf(rt2, 'r'));
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
// ─── bezier ──────────────────────────────────────────────────────────────────
|
|
201
|
+
describe('bezier_quad', () => {
|
|
202
|
+
it.each([
|
|
203
|
+
[0, 500, 1000, 0, 0], // t=0: start
|
|
204
|
+
[0, 500, 1000, 1000, 1000], // t=1000: end
|
|
205
|
+
[0, 1000, 0, 500, 500], // arch midpoint
|
|
206
|
+
])('bezier_quad(%d,%d,%d,t=%d) == %d', (p0, p1, p2, t, expected) => {
|
|
207
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", bezier_quad(${p0},${p1},${p2},${t})); }`);
|
|
208
|
+
rt.execFunction('test');
|
|
209
|
+
expect(scoreOf(rt, 'r')).toBe(expected);
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
describe('bezier_cubic', () => {
|
|
213
|
+
it('t=0: start', () => {
|
|
214
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", bezier_cubic(0,333,667,1000,0)); }`);
|
|
215
|
+
rt.execFunction('test');
|
|
216
|
+
expect(scoreOf(rt, 'r')).toBe(0);
|
|
217
|
+
});
|
|
218
|
+
it('t=1000: end', () => {
|
|
219
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", bezier_cubic(0,333,667,1000,1000)); }`);
|
|
220
|
+
rt.execFunction('test');
|
|
221
|
+
expect(scoreOf(rt, 'r')).toBe(1000);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
// ─── mandelbrot_iter ─────────────────────────────────────────────────────────
|
|
225
|
+
describe('mandelbrot_iter', () => {
|
|
226
|
+
// c = 0 + 0i → always in set (z always 0)
|
|
227
|
+
it('origin (0,0) stays bounded for max_iter=20', () => {
|
|
228
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", mandelbrot_iter(0,0,20)); }`);
|
|
229
|
+
rt.execFunction('test');
|
|
230
|
+
expect(scoreOf(rt, 'r')).toBe(20);
|
|
231
|
+
});
|
|
232
|
+
// c = 2 + 0i → escapes immediately (|z1| = 2, |z2| = 6 > 2)
|
|
233
|
+
it('c=(2000,0) escapes quickly', () => {
|
|
234
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", mandelbrot_iter(2000,0,50)); }`);
|
|
235
|
+
rt.execFunction('test');
|
|
236
|
+
expect(scoreOf(rt, 'r')).toBeLessThan(5);
|
|
237
|
+
});
|
|
238
|
+
// c = -1 + 0i → stays bounded (period-2 cycle: 0 → -1 → 0 → ...)
|
|
239
|
+
it('c=(-1000,0) is in the set', () => {
|
|
240
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", mandelbrot_iter(-1000,0,50)); }`);
|
|
241
|
+
rt.execFunction('test');
|
|
242
|
+
expect(scoreOf(rt, 'r')).toBe(50);
|
|
243
|
+
});
|
|
244
|
+
// c = 0.5 + 0.5i → escapes
|
|
245
|
+
it('c=(500,500) escapes before max_iter=100', () => {
|
|
246
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", mandelbrot_iter(500,500,100)); }`);
|
|
247
|
+
rt.execFunction('test');
|
|
248
|
+
expect(scoreOf(rt, 'r')).toBeLessThan(100);
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
// ─── julia_iter ──────────────────────────────────────────────────────────────
|
|
252
|
+
describe('julia_iter', () => {
|
|
253
|
+
it('z0=(0,0) with c=(0,0) stays bounded', () => {
|
|
254
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", julia_iter(0,0,0,0,20)); }`);
|
|
255
|
+
rt.execFunction('test');
|
|
256
|
+
expect(scoreOf(rt, 'r')).toBe(20);
|
|
257
|
+
});
|
|
258
|
+
it('z0=(3000,0) escapes immediately', () => {
|
|
259
|
+
const rt = run(`fn test() { scoreboard_set("out", "r", julia_iter(3000,0,0,0,20)); }`);
|
|
260
|
+
rt.execFunction('test');
|
|
261
|
+
expect(scoreOf(rt, 'r')).toBe(0);
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
//# sourceMappingURL=stdlib-advanced.test.js.map
|