redscript-mc 2.2.1 → 2.4.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/CHANGELOG.md +31 -0
- package/README.md +18 -2
- package/dist/src/__tests__/array-dynamic.test.d.ts +12 -0
- package/dist/src/__tests__/array-dynamic.test.js +131 -0
- package/dist/src/__tests__/array-write.test.d.ts +11 -0
- package/dist/src/__tests__/array-write.test.js +149 -0
- package/dist/src/__tests__/tuner/engine.test.d.ts +4 -0
- package/dist/src/__tests__/tuner/engine.test.js +232 -0
- package/dist/src/ast/types.d.ts +7 -0
- package/dist/src/emit/modules.js +5 -0
- package/dist/src/hir/lower.js +29 -0
- package/dist/src/hir/monomorphize.js +2 -0
- package/dist/src/hir/types.d.ts +9 -2
- package/dist/src/lir/lower.js +131 -0
- package/dist/src/mir/lower.js +73 -3
- package/dist/src/mir/macro.js +5 -0
- package/dist/src/mir/types.d.ts +12 -0
- package/dist/src/mir/verify.js +7 -0
- package/dist/src/optimizer/copy_prop.js +5 -0
- package/dist/src/optimizer/coroutine.js +12 -0
- package/dist/src/optimizer/dce.js +9 -0
- package/dist/src/optimizer/unroll.js +3 -0
- package/dist/src/parser/index.js +5 -0
- package/dist/src/tuner/adapters/ln-polynomial.d.ts +23 -0
- package/dist/src/tuner/adapters/ln-polynomial.js +142 -0
- package/dist/src/tuner/adapters/sqrt-newton.d.ts +28 -0
- package/dist/src/tuner/adapters/sqrt-newton.js +125 -0
- package/dist/src/tuner/cli.d.ts +5 -0
- package/dist/src/tuner/cli.js +168 -0
- package/dist/src/tuner/engine.d.ts +17 -0
- package/dist/src/tuner/engine.js +215 -0
- package/dist/src/tuner/metrics.d.ts +15 -0
- package/dist/src/tuner/metrics.js +51 -0
- package/dist/src/tuner/simulator.d.ts +35 -0
- package/dist/src/tuner/simulator.js +78 -0
- package/dist/src/tuner/types.d.ts +32 -0
- package/dist/src/tuner/types.js +6 -0
- package/dist/src/typechecker/index.js +5 -0
- package/docs/STDLIB_ROADMAP.md +142 -0
- package/editors/vscode/package-lock.json +3 -3
- package/editors/vscode/package.json +1 -1
- package/package.json +1 -1
- package/src/__tests__/array-dynamic.test.ts +147 -0
- package/src/__tests__/array-write.test.ts +169 -0
- package/src/__tests__/tuner/engine.test.ts +260 -0
- package/src/ast/types.ts +1 -0
- package/src/emit/modules.ts +5 -0
- package/src/hir/lower.ts +30 -0
- package/src/hir/monomorphize.ts +2 -0
- package/src/hir/types.ts +3 -1
- package/src/lir/lower.ts +151 -0
- package/src/mir/lower.ts +75 -3
- package/src/mir/macro.ts +5 -0
- package/src/mir/types.ts +2 -0
- package/src/mir/verify.ts +7 -0
- package/src/optimizer/copy_prop.ts +5 -0
- package/src/optimizer/coroutine.ts +9 -0
- package/src/optimizer/dce.ts +6 -0
- package/src/optimizer/unroll.ts +3 -0
- package/src/parser/index.ts +9 -0
- package/src/stdlib/bigint.mcrs +155 -192
- package/src/stdlib/bits.mcrs +158 -0
- package/src/stdlib/color.mcrs +160 -0
- package/src/stdlib/geometry.mcrs +124 -0
- package/src/stdlib/list.mcrs +96 -0
- package/src/stdlib/math.mcrs +227 -0
- package/src/stdlib/math_hp.mcrs +65 -0
- package/src/stdlib/random.mcrs +67 -0
- package/src/stdlib/signal.mcrs +112 -0
- package/src/stdlib/timer.mcrs +32 -0
- package/src/stdlib/vec.mcrs +27 -0
- package/src/tuner/adapters/ln-polynomial.ts +147 -0
- package/src/tuner/adapters/sqrt-newton.ts +135 -0
- package/src/tuner/cli.ts +158 -0
- package/src/tuner/engine.ts +272 -0
- package/src/tuner/metrics.ts +66 -0
- package/src/tuner/simulator.ts +69 -0
- package/src/tuner/types.ts +44 -0
- package/src/typechecker/index.ts +6 -0
- package/docs/ARCHITECTURE.zh.md +0 -1088
- package/docs/COMPILATION_STATS.md +0 -142
- package/docs/IMPLEMENTATION_GUIDE.md +0 -512
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to RedScript will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [2.4.0] - 2026-03-17
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Dynamic array index read: `arr[i]` where `i` is a variable (MC Function Macro, MC 1.20.2+)
|
|
9
|
+
- Dynamic array index write: `arr[i] = val`, `arr[i] += val` compound assignment
|
|
10
|
+
- `list_push(arr, val)` / `list_pop(arr)` / `list_len(arr)` builtins for NBT array manipulation
|
|
11
|
+
|
|
12
|
+
### Known Limitations
|
|
13
|
+
- Array parameters in function calls do not pass the array by reference yet; use `while` loops with dynamic index directly in the calling scope
|
|
14
|
+
- `for` loops with dynamic array access may incorrectly inline when loop bounds are constants; use `while` loops instead
|
|
15
|
+
|
|
16
|
+
## [2.3.0] - 2026-03-17
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- `stdlib/math.mcrs`: `ln` (SA-tuned atanh series, max_error < 0.0006), `sqrt_fx` (×10000 scale), `exp_fx` (Horner Taylor + 2^k scaling)
|
|
20
|
+
- `stdlib/math_hp.mcrs`: `sin_hp`/`cos_hp` using MC entity rotation trick (double precision), `init_trig()` bootstrap
|
|
21
|
+
- `stdlib/random.mcrs`: LCG (`next_lcg`, `random_range`, `random_bool`) + PCG (`pcg_next_lo/hi`, `pcg_output`)
|
|
22
|
+
- `stdlib/color.mcrs`: RGB packing/unpacking, `rgb_lerp`, HSL↔RGB conversion (`hsl_to_r/g/b`, `rgb_to_h/s/l`)
|
|
23
|
+
- `stdlib/bits.mcrs`: bitwise AND/OR/XOR/NOT, left/right shift, bit get/set/clear/toggle, popcount (all integer-simulated)
|
|
24
|
+
- `stdlib/list.mcrs`: `sort3`, min/max/avg for 3 and 5 values, weighted choice utilities
|
|
25
|
+
- `stdlib/geometry.mcrs`: AABB/sphere/cylinder contains checks, parabola physics, grid/tile helpers, angle normalization, MC sun angle
|
|
26
|
+
- `stdlib/signal.mcrs`: uniform, normal (12-sample approximation), exponential distribution, bernoulli trial, weighted2/3 choice
|
|
27
|
+
- `stdlib/bigint.mcrs`: 96-bit base-10000 arithmetic (add, sub, mul, div, cmp, int32↔bigint3 conversion)
|
|
28
|
+
- `src/tuner/`: hyperparameter search framework (Nelder-Mead + Simulated Annealing) for stdlib coefficient optimization
|
|
29
|
+
- `adapters/ln-polynomial.ts`: atanh series adapter
|
|
30
|
+
- `adapters/sqrt-newton.ts`: Newton iteration adapter
|
|
31
|
+
- CLI: `redscript tune --adapter <name> [--strategy nm|sa] [--budget N] [--out path]`
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
- `stdlib/vec.mcrs`: added 2D/3D add/sub/scale/neg component helpers
|
|
35
|
+
|
|
5
36
|
## [2.1.1] - 2026-03-16
|
|
6
37
|
|
|
7
38
|
### Added
|
package/README.md
CHANGED
|
@@ -306,8 +306,16 @@ redscript validate <file> Validate MC commands
|
|
|
306
306
|
RedScript ships a built-in standard library. Use the short form — no path needed:
|
|
307
307
|
|
|
308
308
|
```rs
|
|
309
|
-
import "stdlib/math" // fixed-point math
|
|
310
|
-
import "stdlib/
|
|
309
|
+
import "stdlib/math" // fixed-point math: ln, sqrt_fx, exp_fx, sin_fixed, cos_fixed...
|
|
310
|
+
import "stdlib/math_hp" // high-precision trig via entity rotation (init_trig required)
|
|
311
|
+
import "stdlib/vec" // 2D/3D vector: dot, cross, length, distance, atan2, rotate...
|
|
312
|
+
import "stdlib/random" // LCG & PCG random number generators
|
|
313
|
+
import "stdlib/color" // RGB/HSL color packing, blending, conversion
|
|
314
|
+
import "stdlib/bits" // bitwise AND/OR/XOR/NOT/shift/popcount (integer-simulated)
|
|
315
|
+
import "stdlib/list" // sort3, min/max/avg, weighted utilities
|
|
316
|
+
import "stdlib/geometry" // AABB/sphere contains, parabola physics, angle helpers
|
|
317
|
+
import "stdlib/signal" // normal/exponential distributions, bernoulli, weighted choice
|
|
318
|
+
import "stdlib/bigint" // 96-bit base-10000 arithmetic (add/sub/mul/div/cmp)
|
|
311
319
|
import "stdlib/combat" // damage, kill-check helpers
|
|
312
320
|
import "stdlib/player" // health, alive check, range
|
|
313
321
|
import "stdlib/cooldown" // per-player cooldown tracking
|
|
@@ -319,6 +327,8 @@ Custom library paths can be added with `--include <dir>` so your own modules wor
|
|
|
319
327
|
|
|
320
328
|
All stdlib files use `module library;` — only the functions you actually call are compiled in.
|
|
321
329
|
|
|
330
|
+
> Parts of the standard library are inspired by [kaer-3058/large_number](https://github.com/kaer-3058/large_number), a comprehensive math library for Minecraft datapacks.
|
|
331
|
+
|
|
322
332
|
```rs
|
|
323
333
|
import "stdlib/math" // abs, sign, min, max, clamp, lerp, isqrt, sqrt_fixed,
|
|
324
334
|
// pow_int, gcd, lcm, sin_fixed, cos_fixed, map, ceil_div,
|
|
@@ -444,6 +454,12 @@ See [CHANGELOG.md](./CHANGELOG.md) for the full release notes.
|
|
|
444
454
|
|
|
445
455
|
---
|
|
446
456
|
|
|
457
|
+
## Acknowledgements
|
|
458
|
+
|
|
459
|
+
Parts of the standard library are inspired by [kaer-3058/large_number](https://github.com/kaer-3058/large_number), a comprehensive math library for Minecraft datapacks. RedScript provides a higher-level, type-safe API over similar algorithms.
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
447
463
|
<div align="center">
|
|
448
464
|
|
|
449
465
|
MIT License · Copyright © 2026 [bkmashiro](https://github.com/bkmashiro)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for dynamic array index read: arr[i] where i is a variable.
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - MIR: nbt_read_dynamic instruction is emitted instead of falling back to
|
|
6
|
+
* copy(obj) (which returned the array length, not the value)
|
|
7
|
+
* - LIR/Emit: generates a macro helper function and calls it with
|
|
8
|
+
* `function ns:__dyn_idx_... with storage rs:macro_args`
|
|
9
|
+
* - The generated .mcfunction contains 'with storage' (function macro call)
|
|
10
|
+
* - The helper function contains the $return macro line
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Tests for dynamic array index read: arr[i] where i is a variable.
|
|
4
|
+
*
|
|
5
|
+
* Covers:
|
|
6
|
+
* - MIR: nbt_read_dynamic instruction is emitted instead of falling back to
|
|
7
|
+
* copy(obj) (which returned the array length, not the value)
|
|
8
|
+
* - LIR/Emit: generates a macro helper function and calls it with
|
|
9
|
+
* `function ns:__dyn_idx_... with storage rs:macro_args`
|
|
10
|
+
* - The generated .mcfunction contains 'with storage' (function macro call)
|
|
11
|
+
* - The helper function contains the $return macro line
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
const compile_1 = require("../emit/compile");
|
|
15
|
+
// Helper: find file in compiled output by path substring
|
|
16
|
+
function getFile(files, pathSubstr) {
|
|
17
|
+
const f = files.find(f => f.path.includes(pathSubstr));
|
|
18
|
+
return f?.content;
|
|
19
|
+
}
|
|
20
|
+
// Helper: get the content of the function file for `fnName` in namespace
|
|
21
|
+
function getFunctionBody(files, fnName, ns = 'test') {
|
|
22
|
+
const content = getFile(files, `${fnName}.mcfunction`);
|
|
23
|
+
if (!content) {
|
|
24
|
+
// list files for debug
|
|
25
|
+
const paths = files.map(f => f.path).join('\n');
|
|
26
|
+
throw new Error(`Function '${fnName}' not found in output. Files:\n${paths}`);
|
|
27
|
+
}
|
|
28
|
+
return content;
|
|
29
|
+
}
|
|
30
|
+
describe('Dynamic array index read: arr[i]', () => {
|
|
31
|
+
const src = `
|
|
32
|
+
fn test() {
|
|
33
|
+
let nums: int[] = [10, 20, 30, 40, 50];
|
|
34
|
+
let i: int = 2;
|
|
35
|
+
i = i + 1;
|
|
36
|
+
let v: int = nums[i];
|
|
37
|
+
scoreboard_set("#out", "test", v);
|
|
38
|
+
}
|
|
39
|
+
`;
|
|
40
|
+
let files;
|
|
41
|
+
beforeAll(() => {
|
|
42
|
+
const result = (0, compile_1.compile)(src, { namespace: 'test' });
|
|
43
|
+
files = result.files;
|
|
44
|
+
});
|
|
45
|
+
test('compiles without error', () => {
|
|
46
|
+
// beforeAll would have thrown if compilation failed
|
|
47
|
+
expect(files.length).toBeGreaterThan(0);
|
|
48
|
+
});
|
|
49
|
+
test('test function contains "with storage" (macro call)', () => {
|
|
50
|
+
const body = getFunctionBody(files, 'test');
|
|
51
|
+
expect(body).toContain('with storage');
|
|
52
|
+
});
|
|
53
|
+
test('test function does NOT contain fallback "scoreboard players set #out test 5"', () => {
|
|
54
|
+
// Old fallback would copy the array length (5 elements) as the result
|
|
55
|
+
const body = getFunctionBody(files, 'test');
|
|
56
|
+
expect(body).not.toContain('scoreboard players set #out test 5');
|
|
57
|
+
});
|
|
58
|
+
test('a macro helper function is generated for the array', () => {
|
|
59
|
+
// Should have a function file matching __dyn_idx_
|
|
60
|
+
const helperFile = files.find(f => f.path.includes('__dyn_idx_'));
|
|
61
|
+
expect(helperFile).toBeDefined();
|
|
62
|
+
});
|
|
63
|
+
test('macro helper function contains $return run data get', () => {
|
|
64
|
+
const helperFile = files.find(f => f.path.includes('__dyn_idx_'));
|
|
65
|
+
expect(helperFile).toBeDefined();
|
|
66
|
+
expect(helperFile.content).toContain('$return run data get');
|
|
67
|
+
expect(helperFile.content).toContain('$(arr_idx)');
|
|
68
|
+
});
|
|
69
|
+
test('macro helper function references the correct array path (nums)', () => {
|
|
70
|
+
const helperFile = files.find(f => f.path.includes('__dyn_idx_'));
|
|
71
|
+
expect(helperFile).toBeDefined();
|
|
72
|
+
expect(helperFile.content).toContain('nums[$(arr_idx)]');
|
|
73
|
+
});
|
|
74
|
+
test('test function stores index to rs:macro_args', () => {
|
|
75
|
+
const body = getFunctionBody(files, 'test');
|
|
76
|
+
// Should store the index value into rs:macro_args arr_idx
|
|
77
|
+
expect(body).toContain('rs:macro_args');
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
describe('Dynamic array index: constant index still uses direct nbt_read', () => {
|
|
81
|
+
const src = `
|
|
82
|
+
fn test_const() {
|
|
83
|
+
let nums: int[] = [10, 20, 30];
|
|
84
|
+
let v: int = nums[1];
|
|
85
|
+
scoreboard_set("#out", "test", v);
|
|
86
|
+
}
|
|
87
|
+
`;
|
|
88
|
+
let files;
|
|
89
|
+
beforeAll(() => {
|
|
90
|
+
const result = (0, compile_1.compile)(src, { namespace: 'test' });
|
|
91
|
+
files = result.files;
|
|
92
|
+
});
|
|
93
|
+
test('constant index does NOT generate macro call (uses direct data get)', () => {
|
|
94
|
+
const body = getFunctionBody(files, 'test_const');
|
|
95
|
+
// Direct nbt_read emits store_nbt_to_score → execute store result score ... run data get ...
|
|
96
|
+
// without 'with storage'
|
|
97
|
+
expect(body).not.toContain('with storage');
|
|
98
|
+
expect(body).toContain('data get storage');
|
|
99
|
+
expect(body).toContain('nums[1]');
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
describe('Dynamic array index: multiple arrays, separate helpers', () => {
|
|
103
|
+
const src = `
|
|
104
|
+
fn test_multi() {
|
|
105
|
+
let a: int[] = [1, 2, 3];
|
|
106
|
+
let b: int[] = [10, 20, 30];
|
|
107
|
+
let i: int = 1;
|
|
108
|
+
i = i + 0;
|
|
109
|
+
let va: int = a[i];
|
|
110
|
+
let vb: int = b[i];
|
|
111
|
+
scoreboard_set("#va", "test", va);
|
|
112
|
+
scoreboard_set("#vb", "test", vb);
|
|
113
|
+
}
|
|
114
|
+
`;
|
|
115
|
+
let files;
|
|
116
|
+
beforeAll(() => {
|
|
117
|
+
const result = (0, compile_1.compile)(src, { namespace: 'test' });
|
|
118
|
+
files = result.files;
|
|
119
|
+
});
|
|
120
|
+
test('two separate macro helpers are generated for arrays a and b', () => {
|
|
121
|
+
const helperFiles = files.filter(f => f.path.includes('__dyn_idx_'));
|
|
122
|
+
expect(helperFiles.length).toBe(2);
|
|
123
|
+
});
|
|
124
|
+
test('each helper references its respective array path', () => {
|
|
125
|
+
const helperFiles = files.filter(f => f.path.includes('__dyn_idx_'));
|
|
126
|
+
const contents = helperFiles.map(f => f.content).join('\n');
|
|
127
|
+
expect(contents).toContain('a[$(arr_idx)]');
|
|
128
|
+
expect(contents).toContain('b[$(arr_idx)]');
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
//# sourceMappingURL=array-dynamic.test.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for array index write: arr[i] = val (constant and dynamic index).
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - Parser: arr[i] = val parses as index_assign (no "Expected ';'" error)
|
|
6
|
+
* - MIR: constant index → nbt_write, dynamic index → nbt_write_dynamic
|
|
7
|
+
* - LIR/Emit: constant index uses store_score_to_nbt to path[N]
|
|
8
|
+
* dynamic index generates a macro helper function for write
|
|
9
|
+
* - Compound assignments: arr[i] += 5 desugars to read + write
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Tests for array index write: arr[i] = val (constant and dynamic index).
|
|
4
|
+
*
|
|
5
|
+
* Covers:
|
|
6
|
+
* - Parser: arr[i] = val parses as index_assign (no "Expected ';'" error)
|
|
7
|
+
* - MIR: constant index → nbt_write, dynamic index → nbt_write_dynamic
|
|
8
|
+
* - LIR/Emit: constant index uses store_score_to_nbt to path[N]
|
|
9
|
+
* dynamic index generates a macro helper function for write
|
|
10
|
+
* - Compound assignments: arr[i] += 5 desugars to read + write
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
const compile_1 = require("../emit/compile");
|
|
14
|
+
// Helper: find file in compiled output by path substring
|
|
15
|
+
function getFile(files, pathSubstr) {
|
|
16
|
+
const f = files.find(f => f.path.includes(pathSubstr));
|
|
17
|
+
return f?.content;
|
|
18
|
+
}
|
|
19
|
+
// Helper: get the content of the function file for `fnName` in namespace
|
|
20
|
+
function getFunctionBody(files, fnName, ns = 'test') {
|
|
21
|
+
const content = getFile(files, `${fnName}.mcfunction`);
|
|
22
|
+
if (!content) {
|
|
23
|
+
const paths = files.map(f => f.path).join('\n');
|
|
24
|
+
throw new Error(`Function '${fnName}' not found in output. Files:\n${paths}`);
|
|
25
|
+
}
|
|
26
|
+
return content;
|
|
27
|
+
}
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// Constant index write: arr[1] = 99
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
describe('Constant index write: arr[1] = 99', () => {
|
|
32
|
+
const src = `
|
|
33
|
+
fn test() {
|
|
34
|
+
let nums: int[] = [10, 20, 30];
|
|
35
|
+
nums[1] = 99;
|
|
36
|
+
scoreboard_set("#out", "test", nums[1]);
|
|
37
|
+
}
|
|
38
|
+
`;
|
|
39
|
+
let files;
|
|
40
|
+
beforeAll(() => {
|
|
41
|
+
const result = (0, compile_1.compile)(src, { namespace: 'test' });
|
|
42
|
+
files = result.files;
|
|
43
|
+
});
|
|
44
|
+
test('compiles without error', () => {
|
|
45
|
+
expect(files.length).toBeGreaterThan(0);
|
|
46
|
+
});
|
|
47
|
+
test('test function contains nbt store to array path [1] (constant write)', () => {
|
|
48
|
+
const body = getFunctionBody(files, 'test');
|
|
49
|
+
// Should write to path like "nums[1]" via execute store result storage
|
|
50
|
+
expect(body).toMatch(/nums\[1\]/);
|
|
51
|
+
});
|
|
52
|
+
test('test function reads back from nums[1] after writing', () => {
|
|
53
|
+
const body = getFunctionBody(files, 'test');
|
|
54
|
+
// Should also read nums[1] for scoreboard_set
|
|
55
|
+
expect(body).toMatch(/nums\[1\]/);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// Dynamic index write: arr[i] = 99
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
describe('Dynamic index write: arr[i] = 99', () => {
|
|
62
|
+
const src = `
|
|
63
|
+
fn test() {
|
|
64
|
+
let nums: int[] = [10, 20, 30];
|
|
65
|
+
let i: int = 1;
|
|
66
|
+
nums[i] = 99;
|
|
67
|
+
scoreboard_set("#out", "test", nums[i]);
|
|
68
|
+
}
|
|
69
|
+
`;
|
|
70
|
+
let files;
|
|
71
|
+
beforeAll(() => {
|
|
72
|
+
const result = (0, compile_1.compile)(src, { namespace: 'test' });
|
|
73
|
+
files = result.files;
|
|
74
|
+
});
|
|
75
|
+
test('compiles without error', () => {
|
|
76
|
+
expect(files.length).toBeGreaterThan(0);
|
|
77
|
+
});
|
|
78
|
+
test('test function contains "with storage" (macro call for write)', () => {
|
|
79
|
+
const body = getFunctionBody(files, 'test');
|
|
80
|
+
expect(body).toContain('with storage');
|
|
81
|
+
});
|
|
82
|
+
test('a __dyn_wrt_ helper function is generated', () => {
|
|
83
|
+
const helperFile = files.find(f => f.path.includes('__dyn_wrt_'));
|
|
84
|
+
expect(helperFile).toBeDefined();
|
|
85
|
+
});
|
|
86
|
+
test('the write helper contains a macro line with arr_idx and arr_val', () => {
|
|
87
|
+
const helperFile = files.find(f => f.path.includes('__dyn_wrt_'));
|
|
88
|
+
expect(helperFile).toBeDefined();
|
|
89
|
+
expect(helperFile.content).toContain('$(arr_idx)');
|
|
90
|
+
expect(helperFile.content).toContain('$(arr_val)');
|
|
91
|
+
});
|
|
92
|
+
test('the write helper uses data modify set value', () => {
|
|
93
|
+
const helperFile = files.find(f => f.path.includes('__dyn_wrt_'));
|
|
94
|
+
expect(helperFile.content).toContain('data modify storage');
|
|
95
|
+
expect(helperFile.content).toContain('set value');
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// Compound assignment: arr[i] += 5
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
describe('Compound index assignment: arr[i] += 5', () => {
|
|
102
|
+
const src = `
|
|
103
|
+
fn test() {
|
|
104
|
+
let nums: int[] = [10, 20, 30];
|
|
105
|
+
let i: int = 0;
|
|
106
|
+
nums[i] += 5;
|
|
107
|
+
scoreboard_set("#out", "test", nums[i]);
|
|
108
|
+
}
|
|
109
|
+
`;
|
|
110
|
+
let files;
|
|
111
|
+
beforeAll(() => {
|
|
112
|
+
const result = (0, compile_1.compile)(src, { namespace: 'test' });
|
|
113
|
+
files = result.files;
|
|
114
|
+
});
|
|
115
|
+
test('compiles without error', () => {
|
|
116
|
+
expect(files.length).toBeGreaterThan(0);
|
|
117
|
+
});
|
|
118
|
+
test('compound assignment generates both read and write macro calls', () => {
|
|
119
|
+
const body = getFunctionBody(files, 'test');
|
|
120
|
+
// Should call with storage at least twice (read for += and write + scoreboard_set read)
|
|
121
|
+
const matches = (body.match(/with storage/g) || []).length;
|
|
122
|
+
expect(matches).toBeGreaterThanOrEqual(1);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
// Constant compound assignment: arr[0] += 5
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
describe('Constant compound index assignment: arr[0] += 5', () => {
|
|
129
|
+
const src = `
|
|
130
|
+
fn test() {
|
|
131
|
+
let nums: int[] = [10, 20, 30];
|
|
132
|
+
nums[0] += 5;
|
|
133
|
+
scoreboard_set("#out", "test", nums[0]);
|
|
134
|
+
}
|
|
135
|
+
`;
|
|
136
|
+
let files;
|
|
137
|
+
beforeAll(() => {
|
|
138
|
+
const result = (0, compile_1.compile)(src, { namespace: 'test' });
|
|
139
|
+
files = result.files;
|
|
140
|
+
});
|
|
141
|
+
test('compiles without error', () => {
|
|
142
|
+
expect(files.length).toBeGreaterThan(0);
|
|
143
|
+
});
|
|
144
|
+
test('test function contains array path [0] for read and write', () => {
|
|
145
|
+
const body = getFunctionBody(files, 'test');
|
|
146
|
+
expect(body).toMatch(/nums\[0\]/);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
//# sourceMappingURL=array-write.test.js.map
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Tests for the redscript tuner engine, simulator, and ln-polynomial adapter.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const engine_1 = require("../../tuner/engine");
|
|
7
|
+
const simulator_1 = require("../../tuner/simulator");
|
|
8
|
+
const metrics_1 = require("../../tuner/metrics");
|
|
9
|
+
const ln_polynomial_1 = require("../../tuner/adapters/ln-polynomial");
|
|
10
|
+
const sqrt_newton_1 = require("../../tuner/adapters/sqrt-newton");
|
|
11
|
+
// ─── simulator tests ──────────────────────────────────────────────────────────
|
|
12
|
+
describe('simulator', () => {
|
|
13
|
+
test('i32 truncates to int32', () => {
|
|
14
|
+
expect((0, simulator_1.i32)(3.7)).toBe(3);
|
|
15
|
+
expect((0, simulator_1.i32)(-3.7)).toBe(-3);
|
|
16
|
+
expect((0, simulator_1.i32)(2147483648)).toBe(-2147483648); // overflow wraps
|
|
17
|
+
expect((0, simulator_1.i32)(0)).toBe(0);
|
|
18
|
+
});
|
|
19
|
+
test('fixedMul basic', () => {
|
|
20
|
+
// 10000 * 10000 / 10000 = 10000
|
|
21
|
+
expect((0, simulator_1.fixedMul)(10000, 10000, 10000)).toBe(10000);
|
|
22
|
+
// 5000 * 2 / 10000 = 1
|
|
23
|
+
expect((0, simulator_1.fixedMul)(5000, 2, 10000)).toBe(1);
|
|
24
|
+
});
|
|
25
|
+
test('fixedMul returns Infinity on overflow', () => {
|
|
26
|
+
expect((0, simulator_1.fixedMul)(2147483647, 2147483647, 1)).toBe(Infinity);
|
|
27
|
+
});
|
|
28
|
+
test('isOverflow detects out-of-range', () => {
|
|
29
|
+
expect((0, simulator_1.isOverflow)(2147483648)).toBe(true);
|
|
30
|
+
expect((0, simulator_1.isOverflow)(-2147483649)).toBe(true);
|
|
31
|
+
expect((0, simulator_1.isOverflow)(Infinity)).toBe(true);
|
|
32
|
+
expect((0, simulator_1.isOverflow)(NaN)).toBe(true);
|
|
33
|
+
expect((0, simulator_1.isOverflow)(0)).toBe(false);
|
|
34
|
+
expect((0, simulator_1.isOverflow)(2147483647)).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
// ─── Nelder-Mead convergence test ────────────────────────────────────────────
|
|
38
|
+
describe('Nelder-Mead engine', () => {
|
|
39
|
+
test('converges to minimum of (x-3)^2', () => {
|
|
40
|
+
// Simple 1D minimization: minimize (x-3)^2
|
|
41
|
+
const mockAdapter = {
|
|
42
|
+
name: 'test-quadratic',
|
|
43
|
+
description: 'Minimize (x-3)^2',
|
|
44
|
+
params: [
|
|
45
|
+
{ name: 'x', range: [-10, 10], integer: false },
|
|
46
|
+
],
|
|
47
|
+
simulate(input, params) {
|
|
48
|
+
// Return the residual as a scaled integer
|
|
49
|
+
const x = params['x'];
|
|
50
|
+
return Math.round(x * 10000);
|
|
51
|
+
},
|
|
52
|
+
reference(_input) {
|
|
53
|
+
// Target: x = 3 → value 30000
|
|
54
|
+
return 30000;
|
|
55
|
+
},
|
|
56
|
+
sampleInputs() {
|
|
57
|
+
return [1]; // single input, target value is 3.0 (×10000 = 30000)
|
|
58
|
+
},
|
|
59
|
+
generateCode(params) {
|
|
60
|
+
return `// x = ${params['x']}`;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
const result = (0, engine_1.search)(mockAdapter, 5000);
|
|
64
|
+
// Should converge close to x=3
|
|
65
|
+
expect(result.params['x']).toBeCloseTo(3.0, 1);
|
|
66
|
+
expect(result.maxError).toBeLessThan(0.1);
|
|
67
|
+
});
|
|
68
|
+
test('handles integer constraints', () => {
|
|
69
|
+
const mockAdapter = {
|
|
70
|
+
name: 'test-integer',
|
|
71
|
+
description: 'Integer parameter test',
|
|
72
|
+
params: [
|
|
73
|
+
{ name: 'n', range: [0, 10], integer: true },
|
|
74
|
+
],
|
|
75
|
+
simulate(input, params) {
|
|
76
|
+
// Should snap to integer 7
|
|
77
|
+
return Math.round(params['n'] * 10000);
|
|
78
|
+
},
|
|
79
|
+
reference(_input) {
|
|
80
|
+
return 70000; // 7.0 × 10000
|
|
81
|
+
},
|
|
82
|
+
sampleInputs() {
|
|
83
|
+
return [1];
|
|
84
|
+
},
|
|
85
|
+
generateCode() {
|
|
86
|
+
return '';
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
const result = (0, engine_1.search)(mockAdapter, 2000);
|
|
90
|
+
// Should find n close to 7
|
|
91
|
+
expect(Math.round(result.params['n'])).toBe(7);
|
|
92
|
+
});
|
|
93
|
+
test('i32 overflow penalization', () => {
|
|
94
|
+
const mockAdapter = {
|
|
95
|
+
name: 'test-overflow',
|
|
96
|
+
description: 'Test overflow penalization',
|
|
97
|
+
params: [
|
|
98
|
+
{ name: 'scale', range: [1, 1000], integer: true },
|
|
99
|
+
],
|
|
100
|
+
simulate(_input, params) {
|
|
101
|
+
// Always overflow for any scale >= 500
|
|
102
|
+
if (params['scale'] >= 500)
|
|
103
|
+
return Infinity;
|
|
104
|
+
return params['scale'] * 10000;
|
|
105
|
+
},
|
|
106
|
+
reference(_input) {
|
|
107
|
+
return 2000000; // target: scale=200 → 2000000
|
|
108
|
+
},
|
|
109
|
+
sampleInputs() {
|
|
110
|
+
return [1];
|
|
111
|
+
},
|
|
112
|
+
generateCode() {
|
|
113
|
+
return '';
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
const { maxError, mae, rmse } = (0, metrics_1.evaluate)(mockAdapter, { scale: 2147483647 });
|
|
117
|
+
expect(maxError).toBe(Infinity);
|
|
118
|
+
expect(mae).toBe(Infinity);
|
|
119
|
+
expect(rmse).toBe(Infinity);
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
// ─── ln-polynomial adapter tests ─────────────────────────────────────────────
|
|
123
|
+
describe('ln-polynomial adapter', () => {
|
|
124
|
+
const defaultParams = ln_polynomial_1.defaultParams; // { A1: 20000, A3: 6667, A5: 4000 }
|
|
125
|
+
test('sample inputs cover the valid range', () => {
|
|
126
|
+
const inputs = ln_polynomial_1.lnPolynomialAdapter.sampleInputs();
|
|
127
|
+
expect(inputs.length).toBeGreaterThan(50);
|
|
128
|
+
// All inputs should be positive
|
|
129
|
+
expect(inputs.every(x => x > 0)).toBe(true);
|
|
130
|
+
});
|
|
131
|
+
test('reference matches Math.log', () => {
|
|
132
|
+
const SCALE = 10000;
|
|
133
|
+
// ln(1.0) = 0
|
|
134
|
+
expect(ln_polynomial_1.lnPolynomialAdapter.reference(SCALE)).toBeCloseTo(0, 5);
|
|
135
|
+
// ln(2.0) ≈ 0.6931 → 6931
|
|
136
|
+
expect(ln_polynomial_1.lnPolynomialAdapter.reference(2 * SCALE)).toBeCloseTo(6931.47, 0);
|
|
137
|
+
// ln(0.5) ≈ -0.6931 → -6931
|
|
138
|
+
expect(ln_polynomial_1.lnPolynomialAdapter.reference(5000)).toBeCloseTo(-6931.47, 0);
|
|
139
|
+
});
|
|
140
|
+
test('simulate produces reasonable output for x=1 (no error)', () => {
|
|
141
|
+
const result = ln_polynomial_1.lnPolynomialAdapter.simulate(10000, defaultParams);
|
|
142
|
+
// ln(1.0) = 0; allow some approximation error
|
|
143
|
+
expect(Math.abs(result)).toBeLessThan(500); // within 0.05
|
|
144
|
+
});
|
|
145
|
+
test('simulate returns Infinity for invalid input', () => {
|
|
146
|
+
const result = ln_polynomial_1.lnPolynomialAdapter.simulate(0, defaultParams);
|
|
147
|
+
expect(result).toBeLessThan(0); // negative sentinel or -MAX_INT
|
|
148
|
+
});
|
|
149
|
+
test('max_error < 0.001 with default atanh coefficients', () => {
|
|
150
|
+
const metrics = (0, metrics_1.evaluate)(ln_polynomial_1.lnPolynomialAdapter, defaultParams);
|
|
151
|
+
expect(metrics.maxError).toBeLessThan(0.001);
|
|
152
|
+
}, 10000);
|
|
153
|
+
test('search improves over default params', () => {
|
|
154
|
+
// Run a short search and confirm it doesn't get worse
|
|
155
|
+
const baseMetrics = (0, metrics_1.evaluate)(ln_polynomial_1.lnPolynomialAdapter, defaultParams);
|
|
156
|
+
const result = (0, engine_1.search)(ln_polynomial_1.lnPolynomialAdapter, 500); // short budget for test speed
|
|
157
|
+
// Either same or better
|
|
158
|
+
expect(result.maxError).toBeLessThanOrEqual(baseMetrics.maxError * 2);
|
|
159
|
+
expect(result.maxError).toBeLessThan(0.01);
|
|
160
|
+
}, 30000);
|
|
161
|
+
test('generateCode produces valid output', () => {
|
|
162
|
+
const meta = {
|
|
163
|
+
maxError: 0.00003,
|
|
164
|
+
mae: 0.000012,
|
|
165
|
+
rmse: 0.000015,
|
|
166
|
+
estimatedCmds: 38,
|
|
167
|
+
tuneDate: '2026-03-17',
|
|
168
|
+
budgetUsed: 5000,
|
|
169
|
+
};
|
|
170
|
+
const code = ln_polynomial_1.lnPolynomialAdapter.generateCode(defaultParams, meta);
|
|
171
|
+
expect(code).toContain('AUTO-GENERATED');
|
|
172
|
+
expect(code).toContain('ln-polynomial');
|
|
173
|
+
expect(code).toContain('fn ln');
|
|
174
|
+
expect(code).toContain('A1');
|
|
175
|
+
expect(code).toContain('A3');
|
|
176
|
+
expect(code).toContain('A5');
|
|
177
|
+
expect(code).toContain('2026-03-17');
|
|
178
|
+
});
|
|
179
|
+
test('searchSA achieves max_error < 0.001 on ln-polynomial', () => {
|
|
180
|
+
const result = (0, engine_1.searchSA)(ln_polynomial_1.lnPolynomialAdapter, 3000);
|
|
181
|
+
expect(result.maxError).toBeLessThan(0.001);
|
|
182
|
+
}, 30000);
|
|
183
|
+
});
|
|
184
|
+
// ─── sqrt-newton adapter tests ────────────────────────────────────────────────
|
|
185
|
+
describe('sqrt-newton adapter', () => {
|
|
186
|
+
test('simulate(10000, defaultParams) ≈ 10000 (sqrt(1.0)=1.0)', () => {
|
|
187
|
+
const result = sqrt_newton_1.sqrtNewtonAdapter.simulate(10000, sqrt_newton_1.defaultParams);
|
|
188
|
+
// sqrt(1.0) * 10000 = 10000
|
|
189
|
+
expect(Math.abs(result - 10000)).toBeLessThan(10);
|
|
190
|
+
});
|
|
191
|
+
test('simulate(40000, defaultParams) ≈ 20000 (sqrt(4.0)=2.0)', () => {
|
|
192
|
+
const result = sqrt_newton_1.sqrtNewtonAdapter.simulate(40000, sqrt_newton_1.defaultParams);
|
|
193
|
+
// sqrt(4.0) * 10000 = 20000
|
|
194
|
+
expect(Math.abs(result - 20000)).toBeLessThan(10);
|
|
195
|
+
});
|
|
196
|
+
test('simulate(0) returns 0', () => {
|
|
197
|
+
expect(sqrt_newton_1.sqrtNewtonAdapter.simulate(0, sqrt_newton_1.defaultParams)).toBe(0);
|
|
198
|
+
expect(sqrt_newton_1.sqrtNewtonAdapter.simulate(-1, sqrt_newton_1.defaultParams)).toBe(0);
|
|
199
|
+
});
|
|
200
|
+
test('simulate(250000, defaultParams) ≈ 50000 (sqrt(25.0)=5.0)', () => {
|
|
201
|
+
const result = sqrt_newton_1.sqrtNewtonAdapter.simulate(250000, sqrt_newton_1.defaultParams);
|
|
202
|
+
expect(Math.abs(result - 50000)).toBeLessThan(10);
|
|
203
|
+
});
|
|
204
|
+
test('sample inputs are all positive', () => {
|
|
205
|
+
const inputs = sqrt_newton_1.sqrtNewtonAdapter.sampleInputs();
|
|
206
|
+
expect(inputs.length).toBeGreaterThan(50);
|
|
207
|
+
expect(inputs.every(x => x > 0)).toBe(true);
|
|
208
|
+
});
|
|
209
|
+
test('reference matches Math.sqrt', () => {
|
|
210
|
+
const SCALE = 10000;
|
|
211
|
+
expect(sqrt_newton_1.sqrtNewtonAdapter.reference(SCALE)).toBe(SCALE); // sqrt(1.0)
|
|
212
|
+
expect(sqrt_newton_1.sqrtNewtonAdapter.reference(4 * SCALE)).toBe(2 * SCALE); // sqrt(4.0)
|
|
213
|
+
expect(sqrt_newton_1.sqrtNewtonAdapter.reference(9 * SCALE)).toBe(3 * SCALE); // sqrt(9.0)
|
|
214
|
+
expect(sqrt_newton_1.sqrtNewtonAdapter.reference(0)).toBe(0);
|
|
215
|
+
});
|
|
216
|
+
test('generateCode contains fn sqrt_fx', () => {
|
|
217
|
+
const meta = {
|
|
218
|
+
maxError: 1.5,
|
|
219
|
+
mae: 0.5,
|
|
220
|
+
rmse: 0.8,
|
|
221
|
+
estimatedCmds: 30,
|
|
222
|
+
tuneDate: '2026-03-17',
|
|
223
|
+
budgetUsed: 3000,
|
|
224
|
+
};
|
|
225
|
+
const code = sqrt_newton_1.sqrtNewtonAdapter.generateCode(sqrt_newton_1.defaultParams, meta);
|
|
226
|
+
expect(code).toContain('AUTO-GENERATED');
|
|
227
|
+
expect(code).toContain('sqrt-newton');
|
|
228
|
+
expect(code).toContain('fn sqrt_fx');
|
|
229
|
+
expect(code).toContain('2026-03-17');
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
//# sourceMappingURL=engine.test.js.map
|
package/dist/src/ast/types.d.ts
CHANGED
|
@@ -228,6 +228,13 @@ export type Expr = {
|
|
|
228
228
|
obj: Expr;
|
|
229
229
|
index: Expr;
|
|
230
230
|
span?: Span;
|
|
231
|
+
} | {
|
|
232
|
+
kind: 'index_assign';
|
|
233
|
+
obj: Expr;
|
|
234
|
+
index: Expr;
|
|
235
|
+
op: AssignOp;
|
|
236
|
+
value: Expr;
|
|
237
|
+
span?: Span;
|
|
231
238
|
} | {
|
|
232
239
|
kind: 'array_lit';
|
|
233
240
|
elements: Expr[];
|
package/dist/src/emit/modules.js
CHANGED
|
@@ -465,6 +465,11 @@ function rewriteExpr(expr, symbolMap) {
|
|
|
465
465
|
rewriteExpr(expr.obj, symbolMap);
|
|
466
466
|
rewriteExpr(expr.index, symbolMap);
|
|
467
467
|
break;
|
|
468
|
+
case 'index_assign':
|
|
469
|
+
rewriteExpr(expr.obj, symbolMap);
|
|
470
|
+
rewriteExpr(expr.index, symbolMap);
|
|
471
|
+
rewriteExpr(expr.value, symbolMap);
|
|
472
|
+
break;
|
|
468
473
|
case 'array_lit':
|
|
469
474
|
for (const el of expr.elements)
|
|
470
475
|
rewriteExpr(el, symbolMap);
|