redscript-mc 2.1.1 → 2.3.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 +66 -21
- package/README.zh.md +61 -61
- package/dist/src/__tests__/e2e/basic.test.js +25 -0
- package/dist/src/__tests__/e2e/coroutine.test.js +22 -0
- package/dist/src/__tests__/mc-integration.test.js +25 -13
- package/dist/src/__tests__/schedule.test.js +105 -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/__tests__/typechecker.test.js +63 -0
- package/dist/src/emit/compile.js +1 -0
- package/dist/src/emit/index.js +3 -1
- package/dist/src/lir/lower.js +26 -0
- package/dist/src/mir/lower.js +341 -12
- package/dist/src/mir/types.d.ts +10 -0
- package/dist/src/optimizer/copy_prop.js +4 -0
- package/dist/src/optimizer/coroutine.d.ts +2 -0
- package/dist/src/optimizer/coroutine.js +33 -1
- package/dist/src/optimizer/dce.js +7 -1
- package/dist/src/optimizer/lir/const_imm.js +1 -1
- package/dist/src/optimizer/lir/dead_slot.js +1 -1
- 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.d.ts +2 -0
- package/dist/src/typechecker/index.js +29 -0
- package/docs/ROADMAP.md +35 -0
- package/docs/STDLIB_ROADMAP.md +142 -0
- package/editors/vscode/package-lock.json +3 -3
- package/editors/vscode/package.json +1 -1
- package/examples/coroutine-demo.mcrs +11 -10
- package/jest.config.js +19 -0
- package/package.json +1 -1
- package/src/__tests__/e2e/basic.test.ts +27 -0
- package/src/__tests__/e2e/coroutine.test.ts +23 -0
- package/src/__tests__/fixtures/array-test.mcrs +21 -22
- package/src/__tests__/fixtures/counter.mcrs +17 -0
- package/src/__tests__/fixtures/foreach-at-test.mcrs +9 -10
- package/src/__tests__/mc-integration.test.ts +25 -13
- package/src/__tests__/schedule.test.ts +112 -0
- package/src/__tests__/tuner/engine.test.ts +260 -0
- package/src/__tests__/typechecker.test.ts +68 -0
- package/src/emit/compile.ts +1 -0
- package/src/emit/index.ts +3 -1
- package/src/lir/lower.ts +27 -0
- package/src/mir/lower.ts +355 -9
- package/src/mir/types.ts +4 -0
- package/src/optimizer/copy_prop.ts +4 -0
- package/src/optimizer/coroutine.ts +37 -1
- package/src/optimizer/dce.ts +6 -1
- package/src/optimizer/lir/const_imm.ts +1 -1
- package/src/optimizer/lir/dead_slot.ts +1 -1
- 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 +125 -0
- package/src/stdlib/math.mcrs +90 -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 +10 -5
- 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 +39 -0
- package/docs/ARCHITECTURE.zh.md +0 -1088
- package/docs/COMPILATION_STATS.md +0 -142
- package/docs/IMPLEMENTATION_GUIDE.md +0 -512
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the redscript tuner engine, simulator, and ln-polynomial adapter.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { search, searchSA } from '../../tuner/engine';
|
|
6
|
+
import { i32, fixedMul, isOverflow } from '../../tuner/simulator';
|
|
7
|
+
import { evaluate } from '../../tuner/metrics';
|
|
8
|
+
import { lnPolynomialAdapter, defaultParams as lnDefaultParams } from '../../tuner/adapters/ln-polynomial';
|
|
9
|
+
import { sqrtNewtonAdapter, defaultParams as sqrtDefaultParams } from '../../tuner/adapters/sqrt-newton';
|
|
10
|
+
import { TunerAdapter, ParamSpec } from '../../tuner/types';
|
|
11
|
+
|
|
12
|
+
// ─── simulator tests ──────────────────────────────────────────────────────────
|
|
13
|
+
|
|
14
|
+
describe('simulator', () => {
|
|
15
|
+
test('i32 truncates to int32', () => {
|
|
16
|
+
expect(i32(3.7)).toBe(3);
|
|
17
|
+
expect(i32(-3.7)).toBe(-3);
|
|
18
|
+
expect(i32(2147483648)).toBe(-2147483648); // overflow wraps
|
|
19
|
+
expect(i32(0)).toBe(0);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('fixedMul basic', () => {
|
|
23
|
+
// 10000 * 10000 / 10000 = 10000
|
|
24
|
+
expect(fixedMul(10000, 10000, 10000)).toBe(10000);
|
|
25
|
+
// 5000 * 2 / 10000 = 1
|
|
26
|
+
expect(fixedMul(5000, 2, 10000)).toBe(1);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('fixedMul returns Infinity on overflow', () => {
|
|
30
|
+
expect(fixedMul(2147483647, 2147483647, 1)).toBe(Infinity);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('isOverflow detects out-of-range', () => {
|
|
34
|
+
expect(isOverflow(2147483648)).toBe(true);
|
|
35
|
+
expect(isOverflow(-2147483649)).toBe(true);
|
|
36
|
+
expect(isOverflow(Infinity)).toBe(true);
|
|
37
|
+
expect(isOverflow(NaN)).toBe(true);
|
|
38
|
+
expect(isOverflow(0)).toBe(false);
|
|
39
|
+
expect(isOverflow(2147483647)).toBe(false);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// ─── Nelder-Mead convergence test ────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
describe('Nelder-Mead engine', () => {
|
|
46
|
+
test('converges to minimum of (x-3)^2', () => {
|
|
47
|
+
// Simple 1D minimization: minimize (x-3)^2
|
|
48
|
+
const mockAdapter: TunerAdapter = {
|
|
49
|
+
name: 'test-quadratic',
|
|
50
|
+
description: 'Minimize (x-3)^2',
|
|
51
|
+
params: [
|
|
52
|
+
{ name: 'x', range: [-10, 10], integer: false } as ParamSpec,
|
|
53
|
+
],
|
|
54
|
+
simulate(input: number, params: Record<string, number>): number {
|
|
55
|
+
// Return the residual as a scaled integer
|
|
56
|
+
const x = params['x'];
|
|
57
|
+
return Math.round(x * 10000);
|
|
58
|
+
},
|
|
59
|
+
reference(_input: number): number {
|
|
60
|
+
// Target: x = 3 → value 30000
|
|
61
|
+
return 30000;
|
|
62
|
+
},
|
|
63
|
+
sampleInputs(): number[] {
|
|
64
|
+
return [1]; // single input, target value is 3.0 (×10000 = 30000)
|
|
65
|
+
},
|
|
66
|
+
generateCode(params: Record<string, number>): string {
|
|
67
|
+
return `// x = ${params['x']}`;
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const result = search(mockAdapter, 5000);
|
|
72
|
+
// Should converge close to x=3
|
|
73
|
+
expect(result.params['x']).toBeCloseTo(3.0, 1);
|
|
74
|
+
expect(result.maxError).toBeLessThan(0.1);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('handles integer constraints', () => {
|
|
78
|
+
const mockAdapter: TunerAdapter = {
|
|
79
|
+
name: 'test-integer',
|
|
80
|
+
description: 'Integer parameter test',
|
|
81
|
+
params: [
|
|
82
|
+
{ name: 'n', range: [0, 10], integer: true } as ParamSpec,
|
|
83
|
+
],
|
|
84
|
+
simulate(input: number, params: Record<string, number>): number {
|
|
85
|
+
// Should snap to integer 7
|
|
86
|
+
return Math.round(params['n'] * 10000);
|
|
87
|
+
},
|
|
88
|
+
reference(_input: number): number {
|
|
89
|
+
return 70000; // 7.0 × 10000
|
|
90
|
+
},
|
|
91
|
+
sampleInputs(): number[] {
|
|
92
|
+
return [1];
|
|
93
|
+
},
|
|
94
|
+
generateCode(): string {
|
|
95
|
+
return '';
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const result = search(mockAdapter, 2000);
|
|
100
|
+
// Should find n close to 7
|
|
101
|
+
expect(Math.round(result.params['n'])).toBe(7);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test('i32 overflow penalization', () => {
|
|
105
|
+
const mockAdapter: TunerAdapter = {
|
|
106
|
+
name: 'test-overflow',
|
|
107
|
+
description: 'Test overflow penalization',
|
|
108
|
+
params: [
|
|
109
|
+
{ name: 'scale', range: [1, 1000], integer: true } as ParamSpec,
|
|
110
|
+
],
|
|
111
|
+
simulate(_input: number, params: Record<string, number>): number {
|
|
112
|
+
// Always overflow for any scale >= 500
|
|
113
|
+
if (params['scale'] >= 500) return Infinity;
|
|
114
|
+
return params['scale'] * 10000;
|
|
115
|
+
},
|
|
116
|
+
reference(_input: number): number {
|
|
117
|
+
return 2000000; // target: scale=200 → 2000000
|
|
118
|
+
},
|
|
119
|
+
sampleInputs(): number[] {
|
|
120
|
+
return [1];
|
|
121
|
+
},
|
|
122
|
+
generateCode(): string {
|
|
123
|
+
return '';
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const { maxError, mae, rmse } = evaluate(mockAdapter, { scale: 2147483647 });
|
|
128
|
+
expect(maxError).toBe(Infinity);
|
|
129
|
+
expect(mae).toBe(Infinity);
|
|
130
|
+
expect(rmse).toBe(Infinity);
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// ─── ln-polynomial adapter tests ─────────────────────────────────────────────
|
|
135
|
+
|
|
136
|
+
describe('ln-polynomial adapter', () => {
|
|
137
|
+
const defaultParams = lnDefaultParams; // { A1: 20000, A3: 6667, A5: 4000 }
|
|
138
|
+
|
|
139
|
+
test('sample inputs cover the valid range', () => {
|
|
140
|
+
const inputs = lnPolynomialAdapter.sampleInputs();
|
|
141
|
+
expect(inputs.length).toBeGreaterThan(50);
|
|
142
|
+
// All inputs should be positive
|
|
143
|
+
expect(inputs.every(x => x > 0)).toBe(true);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('reference matches Math.log', () => {
|
|
147
|
+
const SCALE = 10000;
|
|
148
|
+
// ln(1.0) = 0
|
|
149
|
+
expect(lnPolynomialAdapter.reference(SCALE)).toBeCloseTo(0, 5);
|
|
150
|
+
// ln(2.0) ≈ 0.6931 → 6931
|
|
151
|
+
expect(lnPolynomialAdapter.reference(2 * SCALE)).toBeCloseTo(6931.47, 0);
|
|
152
|
+
// ln(0.5) ≈ -0.6931 → -6931
|
|
153
|
+
expect(lnPolynomialAdapter.reference(5000)).toBeCloseTo(-6931.47, 0);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test('simulate produces reasonable output for x=1 (no error)', () => {
|
|
157
|
+
const result = lnPolynomialAdapter.simulate(10000, defaultParams);
|
|
158
|
+
// ln(1.0) = 0; allow some approximation error
|
|
159
|
+
expect(Math.abs(result)).toBeLessThan(500); // within 0.05
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test('simulate returns Infinity for invalid input', () => {
|
|
163
|
+
const result = lnPolynomialAdapter.simulate(0, defaultParams);
|
|
164
|
+
expect(result).toBeLessThan(0); // negative sentinel or -MAX_INT
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('max_error < 0.001 with default atanh coefficients', () => {
|
|
168
|
+
const metrics = evaluate(lnPolynomialAdapter, defaultParams);
|
|
169
|
+
expect(metrics.maxError).toBeLessThan(0.001);
|
|
170
|
+
}, 10000);
|
|
171
|
+
|
|
172
|
+
test('search improves over default params', () => {
|
|
173
|
+
// Run a short search and confirm it doesn't get worse
|
|
174
|
+
const baseMetrics = evaluate(lnPolynomialAdapter, defaultParams);
|
|
175
|
+
const result = search(lnPolynomialAdapter, 500); // short budget for test speed
|
|
176
|
+
// Either same or better
|
|
177
|
+
expect(result.maxError).toBeLessThanOrEqual(baseMetrics.maxError * 2);
|
|
178
|
+
expect(result.maxError).toBeLessThan(0.01);
|
|
179
|
+
}, 30000);
|
|
180
|
+
|
|
181
|
+
test('generateCode produces valid output', () => {
|
|
182
|
+
const meta = {
|
|
183
|
+
maxError: 0.00003,
|
|
184
|
+
mae: 0.000012,
|
|
185
|
+
rmse: 0.000015,
|
|
186
|
+
estimatedCmds: 38,
|
|
187
|
+
tuneDate: '2026-03-17',
|
|
188
|
+
budgetUsed: 5000,
|
|
189
|
+
};
|
|
190
|
+
const code = lnPolynomialAdapter.generateCode(defaultParams, meta);
|
|
191
|
+
expect(code).toContain('AUTO-GENERATED');
|
|
192
|
+
expect(code).toContain('ln-polynomial');
|
|
193
|
+
expect(code).toContain('fn ln');
|
|
194
|
+
expect(code).toContain('A1');
|
|
195
|
+
expect(code).toContain('A3');
|
|
196
|
+
expect(code).toContain('A5');
|
|
197
|
+
expect(code).toContain('2026-03-17');
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test('searchSA achieves max_error < 0.001 on ln-polynomial', () => {
|
|
201
|
+
const result = searchSA(lnPolynomialAdapter, 3000);
|
|
202
|
+
expect(result.maxError).toBeLessThan(0.001);
|
|
203
|
+
}, 30000);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// ─── sqrt-newton adapter tests ────────────────────────────────────────────────
|
|
207
|
+
|
|
208
|
+
describe('sqrt-newton adapter', () => {
|
|
209
|
+
test('simulate(10000, defaultParams) ≈ 10000 (sqrt(1.0)=1.0)', () => {
|
|
210
|
+
const result = sqrtNewtonAdapter.simulate(10000, sqrtDefaultParams);
|
|
211
|
+
// sqrt(1.0) * 10000 = 10000
|
|
212
|
+
expect(Math.abs(result - 10000)).toBeLessThan(10);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test('simulate(40000, defaultParams) ≈ 20000 (sqrt(4.0)=2.0)', () => {
|
|
216
|
+
const result = sqrtNewtonAdapter.simulate(40000, sqrtDefaultParams);
|
|
217
|
+
// sqrt(4.0) * 10000 = 20000
|
|
218
|
+
expect(Math.abs(result - 20000)).toBeLessThan(10);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test('simulate(0) returns 0', () => {
|
|
222
|
+
expect(sqrtNewtonAdapter.simulate(0, sqrtDefaultParams)).toBe(0);
|
|
223
|
+
expect(sqrtNewtonAdapter.simulate(-1, sqrtDefaultParams)).toBe(0);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test('simulate(250000, defaultParams) ≈ 50000 (sqrt(25.0)=5.0)', () => {
|
|
227
|
+
const result = sqrtNewtonAdapter.simulate(250000, sqrtDefaultParams);
|
|
228
|
+
expect(Math.abs(result - 50000)).toBeLessThan(10);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test('sample inputs are all positive', () => {
|
|
232
|
+
const inputs = sqrtNewtonAdapter.sampleInputs();
|
|
233
|
+
expect(inputs.length).toBeGreaterThan(50);
|
|
234
|
+
expect(inputs.every(x => x > 0)).toBe(true);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test('reference matches Math.sqrt', () => {
|
|
238
|
+
const SCALE = 10000;
|
|
239
|
+
expect(sqrtNewtonAdapter.reference(SCALE)).toBe(SCALE); // sqrt(1.0)
|
|
240
|
+
expect(sqrtNewtonAdapter.reference(4 * SCALE)).toBe(2 * SCALE); // sqrt(4.0)
|
|
241
|
+
expect(sqrtNewtonAdapter.reference(9 * SCALE)).toBe(3 * SCALE); // sqrt(9.0)
|
|
242
|
+
expect(sqrtNewtonAdapter.reference(0)).toBe(0);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
test('generateCode contains fn sqrt_fx', () => {
|
|
246
|
+
const meta = {
|
|
247
|
+
maxError: 1.5,
|
|
248
|
+
mae: 0.5,
|
|
249
|
+
rmse: 0.8,
|
|
250
|
+
estimatedCmds: 30,
|
|
251
|
+
tuneDate: '2026-03-17',
|
|
252
|
+
budgetUsed: 3000,
|
|
253
|
+
};
|
|
254
|
+
const code = sqrtNewtonAdapter.generateCode(sqrtDefaultParams, meta);
|
|
255
|
+
expect(code).toContain('AUTO-GENERATED');
|
|
256
|
+
expect(code).toContain('sqrt-newton');
|
|
257
|
+
expect(code).toContain('fn sqrt_fx');
|
|
258
|
+
expect(code).toContain('2026-03-17');
|
|
259
|
+
});
|
|
260
|
+
});
|
|
@@ -270,6 +270,74 @@ fn test() {
|
|
|
270
270
|
expect(errors[0].message).toContain('Return type mismatch: expected void, got int')
|
|
271
271
|
})
|
|
272
272
|
|
|
273
|
+
it('rejects setTimeout inside a loop', () => {
|
|
274
|
+
const errors = typeCheck(`
|
|
275
|
+
fn test() {
|
|
276
|
+
while (true) {
|
|
277
|
+
setTimeout(20, () => { say("x"); });
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
`)
|
|
281
|
+
expect(errors.length).toBeGreaterThan(0)
|
|
282
|
+
expect(errors[0].message).toContain('cannot be called inside a loop')
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
it('rejects setTimeout inside an if body', () => {
|
|
286
|
+
const errors = typeCheck(`
|
|
287
|
+
fn test() {
|
|
288
|
+
if (true) {
|
|
289
|
+
setTimeout(20, () => { say("x"); });
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
`)
|
|
293
|
+
expect(errors.length).toBeGreaterThan(0)
|
|
294
|
+
expect(errors[0].message).toContain('cannot be called inside an if/else body')
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
it('rejects setInterval inside a loop', () => {
|
|
298
|
+
const errors = typeCheck(`
|
|
299
|
+
fn test() {
|
|
300
|
+
while (true) {
|
|
301
|
+
setInterval(20, () => { say("x"); });
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
`)
|
|
305
|
+
expect(errors.length).toBeGreaterThan(0)
|
|
306
|
+
expect(errors[0].message).toContain('cannot be called inside a loop')
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
it('rejects Timer::new() inside a loop', () => {
|
|
310
|
+
const errors = typeCheck(`
|
|
311
|
+
struct Timer { _id: int, _duration: int }
|
|
312
|
+
impl Timer {
|
|
313
|
+
fn new(duration: int) -> Timer { return { _id: 0, _duration: duration }; }
|
|
314
|
+
}
|
|
315
|
+
fn test() {
|
|
316
|
+
while (true) {
|
|
317
|
+
let t: Timer = Timer::new(10);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
`)
|
|
321
|
+
expect(errors.length).toBeGreaterThan(0)
|
|
322
|
+
expect(errors[0].message).toContain('Timer::new() cannot be called inside a loop')
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
it('rejects Timer::new() inside an if body', () => {
|
|
326
|
+
const errors = typeCheck(`
|
|
327
|
+
struct Timer { _id: int, _duration: int }
|
|
328
|
+
impl Timer {
|
|
329
|
+
fn new(duration: int) -> Timer { return { _id: 0, _duration: duration }; }
|
|
330
|
+
}
|
|
331
|
+
fn test() {
|
|
332
|
+
if (true) {
|
|
333
|
+
let t: Timer = Timer::new(10);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
`)
|
|
337
|
+
expect(errors.length).toBeGreaterThan(0)
|
|
338
|
+
expect(errors[0].message).toContain('Timer::new() cannot be called inside an if/else body')
|
|
339
|
+
})
|
|
340
|
+
|
|
273
341
|
it('allows impl instance methods with inferred self type', () => {
|
|
274
342
|
const errors = typeCheck(`
|
|
275
343
|
struct Timer { duration: int }
|
package/src/emit/compile.ts
CHANGED
|
@@ -146,6 +146,7 @@ export function compile(source: string, options: CompileOptions = {}): CompileRe
|
|
|
146
146
|
const coroResult = coroutineTransform(mirOpt, coroutineInfos)
|
|
147
147
|
const mirFinal = coroResult.module
|
|
148
148
|
tickFunctions.push(...coroResult.generatedTickFunctions)
|
|
149
|
+
warnings.push(...coroResult.warnings)
|
|
149
150
|
|
|
150
151
|
// Stage 5: MIR → LIR
|
|
151
152
|
const lir = lowerToLIR(mirFinal)
|
package/src/emit/index.ts
CHANGED
|
@@ -208,7 +208,9 @@ function emitInstr(instr: LIRInstr, ns: string, obj: string, mcVersion: McVersio
|
|
|
208
208
|
|
|
209
209
|
case 'call_context': {
|
|
210
210
|
const subcmds = instr.subcommands.map(emitSubcmd).join(' ')
|
|
211
|
-
return
|
|
211
|
+
return subcmds
|
|
212
|
+
? `execute ${subcmds} run function ${instr.fn}`
|
|
213
|
+
: `function ${instr.fn}`
|
|
212
214
|
}
|
|
213
215
|
|
|
214
216
|
case 'return_value':
|
package/src/lir/lower.ts
CHANGED
|
@@ -334,6 +334,33 @@ function lowerInstrInner(
|
|
|
334
334
|
break
|
|
335
335
|
}
|
|
336
336
|
|
|
337
|
+
case 'score_read': {
|
|
338
|
+
// execute store result score $dst __obj run scoreboard players get <player> <obj>
|
|
339
|
+
const dst = ctx.slot(instr.dst)
|
|
340
|
+
instrs.push({
|
|
341
|
+
kind: 'store_cmd_to_score',
|
|
342
|
+
dst,
|
|
343
|
+
cmd: { kind: 'raw', cmd: `scoreboard players get ${instr.player} ${instr.obj}` },
|
|
344
|
+
})
|
|
345
|
+
break
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
case 'score_write': {
|
|
349
|
+
// Write a value to a vanilla MC scoreboard objective
|
|
350
|
+
if (instr.src.kind === 'const') {
|
|
351
|
+
instrs.push({ kind: 'raw', cmd: `scoreboard players set ${instr.player} ${instr.obj} ${instr.src.value}` })
|
|
352
|
+
} else {
|
|
353
|
+
// execute store result score <player> <obj> run scoreboard players get $src __ns
|
|
354
|
+
const srcSlot = operandToSlot(instr.src, ctx, instrs)
|
|
355
|
+
instrs.push({
|
|
356
|
+
kind: 'store_cmd_to_score',
|
|
357
|
+
dst: { player: instr.player, obj: instr.obj },
|
|
358
|
+
cmd: { kind: 'raw', cmd: `scoreboard players get ${srcSlot.player} ${srcSlot.obj}` },
|
|
359
|
+
})
|
|
360
|
+
}
|
|
361
|
+
break
|
|
362
|
+
}
|
|
363
|
+
|
|
337
364
|
case 'call': {
|
|
338
365
|
// Set parameter slots $p0, $p1, ...
|
|
339
366
|
for (let i = 0; i < instr.args.length; i++) {
|