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
package/dist/src/mir/lower.js
CHANGED
|
@@ -48,15 +48,16 @@ function lowerToMIR(hir, sourceFile) {
|
|
|
48
48
|
fnParamInfo.set(`${ib.typeName}::${m.name}`, m.params);
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
|
+
const timerCounter = { count: 0, timerId: 0 };
|
|
51
52
|
const allFunctions = [];
|
|
52
53
|
for (const f of hir.functions) {
|
|
53
|
-
const { fn, helpers } = lowerFunction(f, hir.namespace, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, sourceFile);
|
|
54
|
+
const { fn, helpers } = lowerFunction(f, hir.namespace, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, sourceFile, timerCounter);
|
|
54
55
|
allFunctions.push(fn, ...helpers);
|
|
55
56
|
}
|
|
56
57
|
// Lower impl block methods
|
|
57
58
|
for (const ib of hir.implBlocks) {
|
|
58
59
|
for (const m of ib.methods) {
|
|
59
|
-
const { fn, helpers } = lowerImplMethod(m, ib.typeName, hir.namespace, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, sourceFile);
|
|
60
|
+
const { fn, helpers } = lowerImplMethod(m, ib.typeName, hir.namespace, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, sourceFile, timerCounter);
|
|
60
61
|
allFunctions.push(fn, ...helpers);
|
|
61
62
|
}
|
|
62
63
|
}
|
|
@@ -70,7 +71,7 @@ function lowerToMIR(hir, sourceFile) {
|
|
|
70
71
|
// Function lowering context
|
|
71
72
|
// ---------------------------------------------------------------------------
|
|
72
73
|
class FnContext {
|
|
73
|
-
constructor(namespace, fnName, structDefs = new Map(), implMethods = new Map(), macroInfo = new Map(), fnParamInfo = new Map(), enumDefs = new Map()) {
|
|
74
|
+
constructor(namespace, fnName, structDefs = new Map(), implMethods = new Map(), macroInfo = new Map(), fnParamInfo = new Map(), enumDefs = new Map(), timerCounter = { count: 0, timerId: 0 }) {
|
|
74
75
|
this.tempCounter = 0;
|
|
75
76
|
this.blockCounter = 0;
|
|
76
77
|
this.blocks = [];
|
|
@@ -82,10 +83,14 @@ class FnContext {
|
|
|
82
83
|
this.structVars = new Map();
|
|
83
84
|
/** Tuple variable tracking: varName → array of element temps (index = slot) */
|
|
84
85
|
this.tupleVars = new Map();
|
|
86
|
+
/** Array variable tracking: varName → { ns, pathPrefix } for NBT-backed int[] */
|
|
87
|
+
this.arrayVars = new Map();
|
|
85
88
|
/** Current source location (set during statement lowering) */
|
|
86
89
|
this.currentSourceLoc = undefined;
|
|
87
90
|
/** Source file path for the module being compiled */
|
|
88
91
|
this.sourceFile = undefined;
|
|
92
|
+
/** Tracks temps whose values are known compile-time constants (for Timer static ID propagation) */
|
|
93
|
+
this.constTemps = new Map();
|
|
89
94
|
this.namespace = namespace;
|
|
90
95
|
this.fnName = fnName;
|
|
91
96
|
this.structDefs = structDefs;
|
|
@@ -94,6 +99,7 @@ class FnContext {
|
|
|
94
99
|
this.fnParamInfo = fnParamInfo;
|
|
95
100
|
this.currentMacroParams = macroInfo.get(fnName)?.macroParams ?? new Set();
|
|
96
101
|
this.enumDefs = enumDefs;
|
|
102
|
+
this.timerCounter = timerCounter;
|
|
97
103
|
const entry = this.makeBlock('entry');
|
|
98
104
|
this.currentBlock = entry;
|
|
99
105
|
}
|
|
@@ -150,8 +156,8 @@ class FnContext {
|
|
|
150
156
|
// ---------------------------------------------------------------------------
|
|
151
157
|
// Function lowering
|
|
152
158
|
// ---------------------------------------------------------------------------
|
|
153
|
-
function lowerFunction(fn, namespace, structDefs = new Map(), implMethods = new Map(), macroInfo = new Map(), fnParamInfo = new Map(), enumDefs = new Map(), sourceFile) {
|
|
154
|
-
const ctx = new FnContext(namespace, fn.name, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs);
|
|
159
|
+
function lowerFunction(fn, namespace, structDefs = new Map(), implMethods = new Map(), macroInfo = new Map(), fnParamInfo = new Map(), enumDefs = new Map(), sourceFile, timerCounter = { count: 0, timerId: 0 }) {
|
|
160
|
+
const ctx = new FnContext(namespace, fn.name, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, timerCounter);
|
|
155
161
|
ctx.sourceFile = sourceFile;
|
|
156
162
|
const fnMacroInfo = macroInfo.get(fn.name);
|
|
157
163
|
// Create temps for parameters
|
|
@@ -184,9 +190,9 @@ function lowerFunction(fn, namespace, structDefs = new Map(), implMethods = new
|
|
|
184
190
|
};
|
|
185
191
|
return { fn: result, helpers: ctx.helperFunctions };
|
|
186
192
|
}
|
|
187
|
-
function lowerImplMethod(method, typeName, namespace, structDefs, implMethods, macroInfo = new Map(), fnParamInfo = new Map(), enumDefs = new Map(), sourceFile) {
|
|
193
|
+
function lowerImplMethod(method, typeName, namespace, structDefs, implMethods, macroInfo = new Map(), fnParamInfo = new Map(), enumDefs = new Map(), sourceFile, timerCounter = { count: 0, timerId: 0 }) {
|
|
188
194
|
const fnName = `${typeName}::${method.name}`;
|
|
189
|
-
const ctx = new FnContext(namespace, fnName, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs);
|
|
195
|
+
const ctx = new FnContext(namespace, fnName, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, timerCounter);
|
|
190
196
|
ctx.sourceFile = sourceFile;
|
|
191
197
|
const fields = structDefs.get(typeName) ?? [];
|
|
192
198
|
const hasSelf = method.params.length > 0 && method.params[0].name === 'self';
|
|
@@ -345,6 +351,12 @@ function lowerStmt(stmt, ctx, scope) {
|
|
|
345
351
|
const t = ctx.freshTemp();
|
|
346
352
|
ctx.emit({ kind: 'copy', dst: t, src: { kind: 'temp', name: `__rf_${fieldName}` } });
|
|
347
353
|
fieldTemps.set(fieldName, t);
|
|
354
|
+
// Propagate compile-time constants from return slots (e.g. Timer._id from Timer::new)
|
|
355
|
+
const rfSlot = `__rf_${fieldName}`;
|
|
356
|
+
const constVal = ctx.constTemps.get(rfSlot);
|
|
357
|
+
if (constVal !== undefined) {
|
|
358
|
+
ctx.constTemps.set(t, constVal);
|
|
359
|
+
}
|
|
348
360
|
}
|
|
349
361
|
ctx.structVars.set(stmt.name, { typeName: stmt.type.name, fields: fieldTemps });
|
|
350
362
|
}
|
|
@@ -355,6 +367,35 @@ function lowerStmt(stmt, ctx, scope) {
|
|
|
355
367
|
scope.set(stmt.name, t);
|
|
356
368
|
}
|
|
357
369
|
}
|
|
370
|
+
else if (stmt.init.kind === 'array_lit') {
|
|
371
|
+
// Array literal: write to NBT storage, track the var for index access
|
|
372
|
+
const ns = `${ctx.getNamespace()}:arrays`;
|
|
373
|
+
const pathPrefix = stmt.name;
|
|
374
|
+
ctx.arrayVars.set(stmt.name, { ns, pathPrefix });
|
|
375
|
+
const elems = stmt.init.elements;
|
|
376
|
+
// Check if all elements are pure integer literals (no side-effects)
|
|
377
|
+
const allConst = elems.every(e => e.kind === 'int_lit');
|
|
378
|
+
if (allConst) {
|
|
379
|
+
// Emit a single raw 'data modify ... set value [...]' to initialize the whole list
|
|
380
|
+
const vals = elems.map(e => e.value).join(', ');
|
|
381
|
+
ctx.emit({ kind: 'call', dst: null, fn: `__raw:data modify storage ${ns} ${pathPrefix} set value [${vals}]`, args: [] });
|
|
382
|
+
}
|
|
383
|
+
else {
|
|
384
|
+
// Initialize with zeros, then overwrite dynamic elements
|
|
385
|
+
const zeros = elems.map(() => '0').join(', ');
|
|
386
|
+
ctx.emit({ kind: 'call', dst: null, fn: `__raw:data modify storage ${ns} ${pathPrefix} set value [${zeros}]`, args: [] });
|
|
387
|
+
for (let i = 0; i < elems.length; i++) {
|
|
388
|
+
const elemOp = lowerExpr(elems[i], ctx, scope);
|
|
389
|
+
if (elemOp.kind !== 'const' || (elems[i].kind !== 'int_lit')) {
|
|
390
|
+
ctx.emit({ kind: 'nbt_write', ns, path: `${pathPrefix}[${i}]`, type: 'int', scale: 1, src: elemOp });
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
// Store array length as a temp in scope (for .len access)
|
|
395
|
+
const lenTemp = ctx.freshTemp();
|
|
396
|
+
ctx.emit({ kind: 'const', dst: lenTemp, value: elems.length });
|
|
397
|
+
scope.set(stmt.name, lenTemp);
|
|
398
|
+
}
|
|
358
399
|
else {
|
|
359
400
|
const valOp = lowerExpr(stmt.init, ctx, scope);
|
|
360
401
|
const t = ctx.freshTemp();
|
|
@@ -616,6 +657,41 @@ function lowerStmt(stmt, ctx, scope) {
|
|
|
616
657
|
ctx.terminate({ kind: 'jump', target: mergeBlock.id });
|
|
617
658
|
}
|
|
618
659
|
}
|
|
660
|
+
else if (arm.pattern.kind === 'range_lit') {
|
|
661
|
+
// Range pattern: e.g. 0..59 => emit ge/le comparisons
|
|
662
|
+
const range = arm.pattern.range;
|
|
663
|
+
const armBody = ctx.newBlock('match_arm');
|
|
664
|
+
const nextArm = ctx.newBlock('match_next');
|
|
665
|
+
// Chain checks: if min defined, check matchVal >= min; if max defined, check matchVal <= max
|
|
666
|
+
// Each failed check jumps to nextArm
|
|
667
|
+
const checks = [];
|
|
668
|
+
if (range.min !== undefined)
|
|
669
|
+
checks.push({ op: 'ge', bound: range.min });
|
|
670
|
+
if (range.max !== undefined)
|
|
671
|
+
checks.push({ op: 'le', bound: range.max });
|
|
672
|
+
if (checks.length === 0) {
|
|
673
|
+
// Open range — always matches
|
|
674
|
+
ctx.terminate({ kind: 'jump', target: armBody.id });
|
|
675
|
+
}
|
|
676
|
+
else {
|
|
677
|
+
// Emit checks sequentially; each check passes → continue to next or armBody
|
|
678
|
+
for (let ci = 0; ci < checks.length; ci++) {
|
|
679
|
+
const { op, bound } = checks[ci];
|
|
680
|
+
const cmpTemp = ctx.freshTemp();
|
|
681
|
+
ctx.emit({ kind: 'cmp', dst: cmpTemp, op, a: matchVal, b: { kind: 'const', value: bound } });
|
|
682
|
+
const passBlock = ci === checks.length - 1 ? armBody : ctx.newBlock('match_range_check');
|
|
683
|
+
ctx.terminate({ kind: 'branch', cond: { kind: 'temp', name: cmpTemp }, then: passBlock.id, else: nextArm.id });
|
|
684
|
+
if (ci < checks.length - 1)
|
|
685
|
+
ctx.switchTo(passBlock);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
ctx.switchTo(armBody);
|
|
689
|
+
lowerBlock(arm.body, ctx, new Map(scope));
|
|
690
|
+
if (isPlaceholderTerm(ctx.current().term)) {
|
|
691
|
+
ctx.terminate({ kind: 'jump', target: mergeBlock.id });
|
|
692
|
+
}
|
|
693
|
+
ctx.switchTo(nextArm);
|
|
694
|
+
}
|
|
619
695
|
else {
|
|
620
696
|
const patOp = lowerExpr(arm.pattern, ctx, scope);
|
|
621
697
|
const cmpTemp = ctx.freshTemp();
|
|
@@ -853,13 +929,97 @@ function lowerExpr(expr, ctx, scope) {
|
|
|
853
929
|
return { kind: 'temp', name: t };
|
|
854
930
|
}
|
|
855
931
|
case 'index': {
|
|
932
|
+
// Check if obj is a tracked array variable with a constant index
|
|
933
|
+
if (expr.obj.kind === 'ident') {
|
|
934
|
+
const arrInfo = ctx.arrayVars.get(expr.obj.name);
|
|
935
|
+
if (arrInfo && expr.index.kind === 'int_lit') {
|
|
936
|
+
const t = ctx.freshTemp();
|
|
937
|
+
ctx.emit({ kind: 'nbt_read', dst: t, ns: arrInfo.ns, path: `${arrInfo.pathPrefix}[${expr.index.value}]`, scale: 1 });
|
|
938
|
+
return { kind: 'temp', name: t };
|
|
939
|
+
}
|
|
940
|
+
}
|
|
856
941
|
const obj = lowerExpr(expr.obj, ctx, scope);
|
|
857
|
-
|
|
942
|
+
lowerExpr(expr.index, ctx, scope);
|
|
858
943
|
const t = ctx.freshTemp();
|
|
859
944
|
ctx.emit({ kind: 'copy', dst: t, src: obj });
|
|
860
945
|
return { kind: 'temp', name: t };
|
|
861
946
|
}
|
|
862
947
|
case 'call': {
|
|
948
|
+
// Handle scoreboard_get / score — read from vanilla MC scoreboard
|
|
949
|
+
if (expr.fn === 'scoreboard_get' || expr.fn === 'score') {
|
|
950
|
+
const player = hirExprToStringLiteral(expr.args[0]);
|
|
951
|
+
const obj = hirExprToStringLiteral(expr.args[1]);
|
|
952
|
+
const t = ctx.freshTemp();
|
|
953
|
+
ctx.emit({ kind: 'score_read', dst: t, player, obj });
|
|
954
|
+
return { kind: 'temp', name: t };
|
|
955
|
+
}
|
|
956
|
+
// Handle scoreboard_set — write to vanilla MC scoreboard
|
|
957
|
+
if (expr.fn === 'scoreboard_set') {
|
|
958
|
+
const player = hirExprToStringLiteral(expr.args[0]);
|
|
959
|
+
const obj = hirExprToStringLiteral(expr.args[1]);
|
|
960
|
+
const src = lowerExpr(expr.args[2], ctx, scope);
|
|
961
|
+
ctx.emit({ kind: 'score_write', player, obj, src });
|
|
962
|
+
const t = ctx.freshTemp();
|
|
963
|
+
ctx.emit({ kind: 'const', dst: t, value: 0 });
|
|
964
|
+
return { kind: 'temp', name: t };
|
|
965
|
+
}
|
|
966
|
+
// Handle setTimeout/setInterval: lift lambda arg to a named helper function
|
|
967
|
+
if ((expr.fn === 'setTimeout' || expr.fn === 'setInterval') && expr.args.length === 2) {
|
|
968
|
+
const ticksArg = expr.args[0];
|
|
969
|
+
const callbackArg = expr.args[1];
|
|
970
|
+
const ns = ctx.getNamespace();
|
|
971
|
+
const id = ctx.timerCounter.count++;
|
|
972
|
+
const callbackName = `__timeout_callback_${id}`;
|
|
973
|
+
// Extract ticks value for the schedule command
|
|
974
|
+
let ticksLiteral = null;
|
|
975
|
+
if (ticksArg.kind === 'int_lit') {
|
|
976
|
+
ticksLiteral = ticksArg.value;
|
|
977
|
+
}
|
|
978
|
+
// Build the callback MIRFunction from the lambda body
|
|
979
|
+
if (callbackArg.kind === 'lambda') {
|
|
980
|
+
const cbCtx = new FnContext(ns, callbackName, ctx.structDefs, ctx.implMethods, ctx.macroInfo, ctx.fnParamInfo, ctx.enumDefs, ctx.timerCounter);
|
|
981
|
+
cbCtx.sourceFile = ctx.sourceFile;
|
|
982
|
+
const cbBody = Array.isArray(callbackArg.body) ? callbackArg.body : [{ kind: 'expr', expr: callbackArg.body }];
|
|
983
|
+
// For setInterval: reschedule at end of body
|
|
984
|
+
const bodyStmts = [...cbBody];
|
|
985
|
+
if (expr.fn === 'setInterval' && ticksLiteral !== null) {
|
|
986
|
+
// Append: raw `schedule function ns:callbackName ticksT`
|
|
987
|
+
bodyStmts.push({
|
|
988
|
+
kind: 'raw',
|
|
989
|
+
cmd: `schedule function ${ns}:${callbackName} ${ticksLiteral}t`,
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
lowerBlock(bodyStmts, cbCtx, new Map());
|
|
993
|
+
const cbCur = cbCtx.current();
|
|
994
|
+
if (isPlaceholderTerm(cbCur.term)) {
|
|
995
|
+
cbCtx.terminate({ kind: 'return', value: null });
|
|
996
|
+
}
|
|
997
|
+
const cbReachable = computeReachable(cbCtx.blocks, 'entry');
|
|
998
|
+
const cbLiveBlocks = cbCtx.blocks.filter(b => cbReachable.has(b.id));
|
|
999
|
+
computePreds(cbLiveBlocks);
|
|
1000
|
+
const cbFn = {
|
|
1001
|
+
name: callbackName,
|
|
1002
|
+
params: [],
|
|
1003
|
+
blocks: cbLiveBlocks,
|
|
1004
|
+
entry: 'entry',
|
|
1005
|
+
isMacro: false,
|
|
1006
|
+
};
|
|
1007
|
+
ctx.helperFunctions.push(cbFn, ...cbCtx.helperFunctions);
|
|
1008
|
+
}
|
|
1009
|
+
// Emit: schedule function ns:callbackName ticksT
|
|
1010
|
+
if (ticksLiteral !== null) {
|
|
1011
|
+
ctx.emit({ kind: 'call', dst: null, fn: `__raw:schedule function ${ns}:${callbackName} ${ticksLiteral}t`, args: [] });
|
|
1012
|
+
}
|
|
1013
|
+
else {
|
|
1014
|
+
// Dynamic ticks: lower ticks operand and emit a raw schedule (best-effort)
|
|
1015
|
+
const ticksOp = lowerExpr(ticksArg, ctx, scope);
|
|
1016
|
+
ctx.emit({ kind: 'call', dst: null, fn: `__raw:schedule function ${ns}:${callbackName} 1t`, args: [ticksOp] });
|
|
1017
|
+
}
|
|
1018
|
+
// setTimeout returns void (0), setInterval returns an int ID (0 for now)
|
|
1019
|
+
const t = ctx.freshTemp();
|
|
1020
|
+
ctx.emit({ kind: 'const', dst: t, value: 0 });
|
|
1021
|
+
return { kind: 'temp', name: t };
|
|
1022
|
+
}
|
|
863
1023
|
// Handle builtin calls → raw MC commands
|
|
864
1024
|
if (macro_1.BUILTIN_SET.has(expr.fn)) {
|
|
865
1025
|
const cmd = formatBuiltinCall(expr.fn, expr.args, ctx.currentMacroParams);
|
|
@@ -872,6 +1032,14 @@ function lowerExpr(expr, ctx, scope) {
|
|
|
872
1032
|
if (expr.args.length > 0 && expr.args[0].kind === 'ident') {
|
|
873
1033
|
const sv = ctx.structVars.get(expr.args[0].name);
|
|
874
1034
|
if (sv) {
|
|
1035
|
+
// Intercept Timer method calls when _id is a known compile-time constant
|
|
1036
|
+
if (sv.typeName === 'Timer') {
|
|
1037
|
+
const idTemp = sv.fields.get('_id');
|
|
1038
|
+
const timerId = idTemp !== undefined ? ctx.constTemps.get(idTemp) : undefined;
|
|
1039
|
+
if (timerId !== undefined) {
|
|
1040
|
+
return lowerTimerMethod(expr.fn, timerId, sv, ctx, scope, expr.args.slice(1));
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
875
1043
|
const methodInfo = ctx.implMethods.get(sv.typeName)?.get(expr.fn);
|
|
876
1044
|
if (methodInfo?.hasSelf) {
|
|
877
1045
|
// Build args: self fields first, then remaining explicit args
|
|
@@ -921,6 +1089,14 @@ function lowerExpr(expr, ctx, scope) {
|
|
|
921
1089
|
if (expr.callee.kind === 'member' && expr.callee.obj.kind === 'ident') {
|
|
922
1090
|
const sv = ctx.structVars.get(expr.callee.obj.name);
|
|
923
1091
|
if (sv) {
|
|
1092
|
+
// Intercept Timer method calls when _id is a known compile-time constant
|
|
1093
|
+
if (sv.typeName === 'Timer') {
|
|
1094
|
+
const idTemp = sv.fields.get('_id');
|
|
1095
|
+
const timerId = idTemp !== undefined ? ctx.constTemps.get(idTemp) : undefined;
|
|
1096
|
+
if (timerId !== undefined) {
|
|
1097
|
+
return lowerTimerMethod(expr.callee.field, timerId, sv, ctx, scope, expr.args);
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
924
1100
|
const methodInfo = ctx.implMethods.get(sv.typeName)?.get(expr.callee.field);
|
|
925
1101
|
if (methodInfo?.hasSelf) {
|
|
926
1102
|
// Build args: self fields first, then explicit args
|
|
@@ -945,6 +1121,24 @@ function lowerExpr(expr, ctx, scope) {
|
|
|
945
1121
|
return { kind: 'temp', name: t };
|
|
946
1122
|
}
|
|
947
1123
|
case 'static_call': {
|
|
1124
|
+
// Intercept Timer::new() to statically allocate a unique ID
|
|
1125
|
+
if (expr.type === 'Timer' && expr.method === 'new' && expr.args.length === 1) {
|
|
1126
|
+
const id = ctx.timerCounter.timerId++;
|
|
1127
|
+
const ns = ctx.getNamespace();
|
|
1128
|
+
const playerName = `__timer_${id}`;
|
|
1129
|
+
// Emit scoreboard initialization: ticks=0, active=0
|
|
1130
|
+
ctx.emit({ kind: 'score_write', player: `${playerName}_ticks`, obj: ns, src: { kind: 'const', value: 0 } });
|
|
1131
|
+
ctx.emit({ kind: 'score_write', player: `${playerName}_active`, obj: ns, src: { kind: 'const', value: 0 } });
|
|
1132
|
+
// Lower the duration argument
|
|
1133
|
+
const durationOp = lowerExpr(expr.args[0], ctx, scope);
|
|
1134
|
+
// Return fields via __rf_ slots (Timer has fields: _id, _duration)
|
|
1135
|
+
ctx.emit({ kind: 'const', dst: '__rf__id', value: id });
|
|
1136
|
+
ctx.constTemps.set('__rf__id', id);
|
|
1137
|
+
ctx.emit({ kind: 'copy', dst: '__rf__duration', src: durationOp });
|
|
1138
|
+
const t = ctx.freshTemp();
|
|
1139
|
+
ctx.emit({ kind: 'const', dst: t, value: 0 });
|
|
1140
|
+
return { kind: 'temp', name: t };
|
|
1141
|
+
}
|
|
948
1142
|
const args = expr.args.map(a => lowerExpr(a, ctx, scope));
|
|
949
1143
|
const t = ctx.freshTemp();
|
|
950
1144
|
ctx.emit({ kind: 'call', dst: t, fn: `${expr.type}::${expr.method}`, args });
|
|
@@ -1024,6 +1218,98 @@ function lowerShortCircuitOr(expr, ctx, scope) {
|
|
|
1024
1218
|
return { kind: 'temp', name: result };
|
|
1025
1219
|
}
|
|
1026
1220
|
// ---------------------------------------------------------------------------
|
|
1221
|
+
// Timer method inlining
|
|
1222
|
+
// ---------------------------------------------------------------------------
|
|
1223
|
+
/**
|
|
1224
|
+
* Inline a Timer instance method call using the statically-assigned timer ID.
|
|
1225
|
+
* Emits scoreboard operations directly, bypassing the Timer::* function calls.
|
|
1226
|
+
*/
|
|
1227
|
+
function lowerTimerMethod(method, timerId, sv, ctx, scope, extraArgs) {
|
|
1228
|
+
const ns = ctx.getNamespace();
|
|
1229
|
+
const player = `__timer_${timerId}`;
|
|
1230
|
+
const t = ctx.freshTemp();
|
|
1231
|
+
if (method === 'start') {
|
|
1232
|
+
ctx.emit({ kind: 'score_write', player: `${player}_active`, obj: ns, src: { kind: 'const', value: 1 } });
|
|
1233
|
+
ctx.emit({ kind: 'const', dst: t, value: 0 });
|
|
1234
|
+
}
|
|
1235
|
+
else if (method === 'pause') {
|
|
1236
|
+
ctx.emit({ kind: 'score_write', player: `${player}_active`, obj: ns, src: { kind: 'const', value: 0 } });
|
|
1237
|
+
ctx.emit({ kind: 'const', dst: t, value: 0 });
|
|
1238
|
+
}
|
|
1239
|
+
else if (method === 'reset') {
|
|
1240
|
+
ctx.emit({ kind: 'score_write', player: `${player}_ticks`, obj: ns, src: { kind: 'const', value: 0 } });
|
|
1241
|
+
ctx.emit({ kind: 'const', dst: t, value: 0 });
|
|
1242
|
+
}
|
|
1243
|
+
else if (method === 'tick') {
|
|
1244
|
+
const durationTemp = sv.fields.get('_duration');
|
|
1245
|
+
const activeTemp = ctx.freshTemp();
|
|
1246
|
+
const ticksTemp = ctx.freshTemp();
|
|
1247
|
+
ctx.emit({ kind: 'score_read', dst: activeTemp, player: `${player}_active`, obj: ns });
|
|
1248
|
+
ctx.emit({ kind: 'score_read', dst: ticksTemp, player: `${player}_ticks`, obj: ns });
|
|
1249
|
+
const innerThen = ctx.newBlock('timer_tick_inner');
|
|
1250
|
+
const innerMerge = ctx.newBlock('timer_tick_after_lt');
|
|
1251
|
+
const outerMerge = ctx.newBlock('timer_tick_done');
|
|
1252
|
+
const activeCheck = ctx.freshTemp();
|
|
1253
|
+
ctx.emit({ kind: 'cmp', op: 'eq', dst: activeCheck, a: { kind: 'temp', name: activeTemp }, b: { kind: 'const', value: 1 } });
|
|
1254
|
+
ctx.terminate({ kind: 'branch', cond: { kind: 'temp', name: activeCheck }, then: innerThen.id, else: outerMerge.id });
|
|
1255
|
+
ctx.switchTo(innerThen);
|
|
1256
|
+
const lessCheck = ctx.freshTemp();
|
|
1257
|
+
if (durationTemp) {
|
|
1258
|
+
ctx.emit({ kind: 'cmp', op: 'lt', dst: lessCheck, a: { kind: 'temp', name: ticksTemp }, b: { kind: 'temp', name: durationTemp } });
|
|
1259
|
+
}
|
|
1260
|
+
else {
|
|
1261
|
+
ctx.emit({ kind: 'const', dst: lessCheck, value: 0 });
|
|
1262
|
+
}
|
|
1263
|
+
const doIncBlock = ctx.newBlock('timer_tick_inc');
|
|
1264
|
+
ctx.terminate({ kind: 'branch', cond: { kind: 'temp', name: lessCheck }, then: doIncBlock.id, else: innerMerge.id });
|
|
1265
|
+
ctx.switchTo(doIncBlock);
|
|
1266
|
+
const newTicks = ctx.freshTemp();
|
|
1267
|
+
ctx.emit({ kind: 'add', dst: newTicks, a: { kind: 'temp', name: ticksTemp }, b: { kind: 'const', value: 1 } });
|
|
1268
|
+
ctx.emit({ kind: 'score_write', player: `${player}_ticks`, obj: ns, src: { kind: 'temp', name: newTicks } });
|
|
1269
|
+
ctx.terminate({ kind: 'jump', target: innerMerge.id });
|
|
1270
|
+
ctx.switchTo(innerMerge);
|
|
1271
|
+
ctx.terminate({ kind: 'jump', target: outerMerge.id });
|
|
1272
|
+
ctx.switchTo(outerMerge);
|
|
1273
|
+
ctx.emit({ kind: 'const', dst: t, value: 0 });
|
|
1274
|
+
}
|
|
1275
|
+
else if (method === 'done') {
|
|
1276
|
+
const durationTemp = sv.fields.get('_duration');
|
|
1277
|
+
const ticksTemp = ctx.freshTemp();
|
|
1278
|
+
ctx.emit({ kind: 'score_read', dst: ticksTemp, player: `${player}_ticks`, obj: ns });
|
|
1279
|
+
if (durationTemp) {
|
|
1280
|
+
ctx.emit({ kind: 'cmp', op: 'ge', dst: t, a: { kind: 'temp', name: ticksTemp }, b: { kind: 'temp', name: durationTemp } });
|
|
1281
|
+
}
|
|
1282
|
+
else {
|
|
1283
|
+
ctx.emit({ kind: 'const', dst: t, value: 0 });
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
else if (method === 'elapsed') {
|
|
1287
|
+
ctx.emit({ kind: 'score_read', dst: t, player: `${player}_ticks`, obj: ns });
|
|
1288
|
+
}
|
|
1289
|
+
else if (method === 'remaining') {
|
|
1290
|
+
const durationTemp = sv.fields.get('_duration');
|
|
1291
|
+
const ticksTemp = ctx.freshTemp();
|
|
1292
|
+
ctx.emit({ kind: 'score_read', dst: ticksTemp, player: `${player}_ticks`, obj: ns });
|
|
1293
|
+
if (durationTemp) {
|
|
1294
|
+
ctx.emit({ kind: 'sub', dst: t, a: { kind: 'temp', name: durationTemp }, b: { kind: 'temp', name: ticksTemp } });
|
|
1295
|
+
}
|
|
1296
|
+
else {
|
|
1297
|
+
ctx.emit({ kind: 'const', dst: t, value: 0 });
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
else {
|
|
1301
|
+
// Unknown Timer method — emit regular call
|
|
1302
|
+
const fields = ['_id', '_duration'];
|
|
1303
|
+
const selfArgs = fields.map(f => {
|
|
1304
|
+
const temp = sv.fields.get(f);
|
|
1305
|
+
return temp ? { kind: 'temp', name: temp } : { kind: 'const', value: 0 };
|
|
1306
|
+
});
|
|
1307
|
+
const explicitArgs = extraArgs.map(a => lowerExpr(a, ctx, scope));
|
|
1308
|
+
ctx.emit({ kind: 'call', dst: t, fn: `Timer::${method}`, args: [...selfArgs, ...explicitArgs] });
|
|
1309
|
+
}
|
|
1310
|
+
return { kind: 'temp', name: t };
|
|
1311
|
+
}
|
|
1312
|
+
// ---------------------------------------------------------------------------
|
|
1027
1313
|
// Execute subcommand lowering
|
|
1028
1314
|
// ---------------------------------------------------------------------------
|
|
1029
1315
|
function lowerExecuteSubcmd(sub) {
|
|
@@ -1128,13 +1414,38 @@ function formatBuiltinCall(fn, args, macroParams) {
|
|
|
1128
1414
|
break;
|
|
1129
1415
|
}
|
|
1130
1416
|
case 'setblock': {
|
|
1131
|
-
|
|
1132
|
-
|
|
1417
|
+
// args: blockpos, block — expand blockpos to x y z
|
|
1418
|
+
const [posOrX, blockOrY] = args;
|
|
1419
|
+
if (posOrX?.kind === 'blockpos') {
|
|
1420
|
+
const px = coordStr(posOrX.x);
|
|
1421
|
+
const py = coordStr(posOrX.y);
|
|
1422
|
+
const pz = coordStr(posOrX.z);
|
|
1423
|
+
const blk = exprToCommandArg(blockOrY, macroParams).str;
|
|
1424
|
+
cmd = `setblock ${px} ${py} ${pz} ${blk}`;
|
|
1425
|
+
}
|
|
1426
|
+
else {
|
|
1427
|
+
const [x, y, z, block] = strs;
|
|
1428
|
+
cmd = `setblock ${x} ${y} ${z} ${block}`;
|
|
1429
|
+
}
|
|
1133
1430
|
break;
|
|
1134
1431
|
}
|
|
1135
1432
|
case 'fill': {
|
|
1136
|
-
|
|
1137
|
-
|
|
1433
|
+
// args: blockpos1, blockpos2, block — expand both blockpos
|
|
1434
|
+
const [p1, p2, blkArg] = args;
|
|
1435
|
+
if (p1?.kind === 'blockpos' && p2?.kind === 'blockpos') {
|
|
1436
|
+
const x1 = coordStr(p1.x);
|
|
1437
|
+
const y1 = coordStr(p1.y);
|
|
1438
|
+
const z1 = coordStr(p1.z);
|
|
1439
|
+
const x2 = coordStr(p2.x);
|
|
1440
|
+
const y2 = coordStr(p2.y);
|
|
1441
|
+
const z2 = coordStr(p2.z);
|
|
1442
|
+
const blk = exprToCommandArg(blkArg, macroParams).str;
|
|
1443
|
+
cmd = `fill ${x1} ${y1} ${z1} ${x2} ${y2} ${z2} ${blk}`;
|
|
1444
|
+
}
|
|
1445
|
+
else {
|
|
1446
|
+
const [x1, y1, z1, x2, y2, z2, block] = strs;
|
|
1447
|
+
cmd = `fill ${x1} ${y1} ${z1} ${x2} ${y2} ${z2} ${block}`;
|
|
1448
|
+
}
|
|
1138
1449
|
break;
|
|
1139
1450
|
}
|
|
1140
1451
|
case 'say':
|
|
@@ -1217,6 +1528,14 @@ function formatBuiltinCall(fn, args, macroParams) {
|
|
|
1217
1528
|
return hasMacro ? `${MACRO_SENTINEL}${cmd}` : cmd;
|
|
1218
1529
|
}
|
|
1219
1530
|
/** Convert an HIR expression to its MC command string representation */
|
|
1531
|
+
/** Convert a CoordComponent to a MC coordinate string */
|
|
1532
|
+
function coordStr(c) {
|
|
1533
|
+
switch (c.kind) {
|
|
1534
|
+
case 'absolute': return String(c.value);
|
|
1535
|
+
case 'relative': return c.offset === 0 ? '~' : `~${c.offset}`;
|
|
1536
|
+
case 'local': return c.offset === 0 ? '^' : `^${c.offset}`;
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1220
1539
|
function exprToCommandArg(expr, macroParams) {
|
|
1221
1540
|
switch (expr.kind) {
|
|
1222
1541
|
case 'int_lit': return { str: String(expr.value), isMacro: false };
|
|
@@ -1261,4 +1580,14 @@ function exprToCommandArg(expr, macroParams) {
|
|
|
1261
1580
|
return { str: '~', isMacro: false };
|
|
1262
1581
|
}
|
|
1263
1582
|
}
|
|
1583
|
+
/** Extract a string literal from a HIR expression for use in MC commands */
|
|
1584
|
+
function hirExprToStringLiteral(expr) {
|
|
1585
|
+
switch (expr.kind) {
|
|
1586
|
+
case 'str_lit': return expr.value;
|
|
1587
|
+
case 'mc_name': return expr.value;
|
|
1588
|
+
case 'selector': return expr.raw;
|
|
1589
|
+
case 'int_lit': return String(expr.value);
|
|
1590
|
+
default: return '';
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1264
1593
|
//# sourceMappingURL=lower.js.map
|
package/dist/src/mir/types.d.ts
CHANGED
|
@@ -137,6 +137,16 @@ export type MIRInstr = MIRInstrBase & ({
|
|
|
137
137
|
type: NBTType;
|
|
138
138
|
scale: number;
|
|
139
139
|
src: Operand;
|
|
140
|
+
} | {
|
|
141
|
+
kind: 'score_read';
|
|
142
|
+
dst: Temp;
|
|
143
|
+
player: string;
|
|
144
|
+
obj: string;
|
|
145
|
+
} | {
|
|
146
|
+
kind: 'score_write';
|
|
147
|
+
player: string;
|
|
148
|
+
obj: string;
|
|
149
|
+
src: Operand;
|
|
140
150
|
} | {
|
|
141
151
|
kind: 'call';
|
|
142
152
|
dst: Temp | null;
|
|
@@ -83,6 +83,8 @@ function rewriteUses(instr, copies) {
|
|
|
83
83
|
return { ...instr, cond: resolve(instr.cond, copies) };
|
|
84
84
|
case 'return':
|
|
85
85
|
return { ...instr, value: instr.value ? resolve(instr.value, copies) : null };
|
|
86
|
+
case 'score_write':
|
|
87
|
+
return { ...instr, src: resolve(instr.src, copies) };
|
|
86
88
|
default:
|
|
87
89
|
return instr;
|
|
88
90
|
}
|
|
@@ -106,6 +108,8 @@ function getDst(instr) {
|
|
|
106
108
|
case 'call':
|
|
107
109
|
case 'call_macro':
|
|
108
110
|
return instr.dst;
|
|
111
|
+
case 'score_read':
|
|
112
|
+
return instr.dst;
|
|
109
113
|
default:
|
|
110
114
|
return null;
|
|
111
115
|
}
|
|
@@ -25,6 +25,8 @@ export interface CoroutineResult {
|
|
|
25
25
|
module: MIRModule;
|
|
26
26
|
/** Names of generated @tick dispatcher functions (caller must add to tick list). */
|
|
27
27
|
generatedTickFunctions: string[];
|
|
28
|
+
/** Warning messages for skipped transforms. */
|
|
29
|
+
warnings: string[];
|
|
28
30
|
}
|
|
29
31
|
/**
|
|
30
32
|
* Apply the coroutine transform to all functions in `infos`.
|
|
@@ -25,16 +25,25 @@ exports.coroutineTransform = coroutineTransform;
|
|
|
25
25
|
*/
|
|
26
26
|
function coroutineTransform(mod, infos) {
|
|
27
27
|
if (infos.length === 0)
|
|
28
|
-
return { module: mod, generatedTickFunctions: [] };
|
|
28
|
+
return { module: mod, generatedTickFunctions: [], warnings: [] };
|
|
29
29
|
const infoMap = new Map(infos.map(i => [i.fnName, i]));
|
|
30
30
|
const newFunctions = [];
|
|
31
31
|
const tickFns = [];
|
|
32
|
+
const warnings = [];
|
|
32
33
|
for (const fn of mod.functions) {
|
|
33
34
|
const info = infoMap.get(fn.name);
|
|
34
35
|
if (!info) {
|
|
35
36
|
newFunctions.push(fn);
|
|
36
37
|
continue;
|
|
37
38
|
}
|
|
39
|
+
// Skip transform if function contains macro calls — continuations are called
|
|
40
|
+
// directly (not via `function ... with storage`) so macro variables like
|
|
41
|
+
// ${px} would not be substituted, causing MC parse errors.
|
|
42
|
+
if (fnContainsMacroCalls(fn)) {
|
|
43
|
+
warnings.push(`@coroutine cannot be applied to functions containing macro calls (skipped: ${fn.name})`);
|
|
44
|
+
newFunctions.push(fn);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
38
47
|
const transformed = transformCoroutine(fn, info, mod.objective);
|
|
39
48
|
newFunctions.push(transformed.initFn);
|
|
40
49
|
newFunctions.push(...transformed.continuations);
|
|
@@ -44,8 +53,31 @@ function coroutineTransform(mod, infos) {
|
|
|
44
53
|
return {
|
|
45
54
|
module: { ...mod, functions: newFunctions },
|
|
46
55
|
generatedTickFunctions: tickFns,
|
|
56
|
+
warnings,
|
|
47
57
|
};
|
|
48
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Returns true if any instruction in the function requires macro processing.
|
|
61
|
+
* This includes:
|
|
62
|
+
* - call_macro: explicit macro function invocations
|
|
63
|
+
* - call with fn = '__raw:\x01...': builtin calls (particle, summon, etc.) with macro params
|
|
64
|
+
* - call with fn = '__raw:<cmd>' where cmd contains '${': raw() commands with variable interpolation
|
|
65
|
+
*/
|
|
66
|
+
function fnContainsMacroCalls(fn) {
|
|
67
|
+
for (const block of fn.blocks) {
|
|
68
|
+
for (const instr of [...block.instrs, block.term]) {
|
|
69
|
+
if (instr.kind === 'call_macro')
|
|
70
|
+
return true;
|
|
71
|
+
if (instr.kind === 'call' && instr.fn.startsWith('__raw:')) {
|
|
72
|
+
const cmd = instr.fn.slice(6);
|
|
73
|
+
// \x01 sentinel: builtin with macro params; '${': raw() with variable interpolation
|
|
74
|
+
if (cmd.startsWith('\x01') || cmd.includes('${'))
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
49
81
|
function transformCoroutine(fn, info, objective) {
|
|
50
82
|
const prefix = `_coro_${fn.name}`;
|
|
51
83
|
const pcTemp = `${prefix}_pc`;
|
|
@@ -74,7 +74,8 @@ function recomputePreds(blocks) {
|
|
|
74
74
|
}
|
|
75
75
|
function hasSideEffects(instr) {
|
|
76
76
|
if (instr.kind === 'call' || instr.kind === 'call_macro' ||
|
|
77
|
-
instr.kind === 'call_context' || instr.kind === 'nbt_write'
|
|
77
|
+
instr.kind === 'call_context' || instr.kind === 'nbt_write' ||
|
|
78
|
+
instr.kind === 'score_write')
|
|
78
79
|
return true;
|
|
79
80
|
// Return field temps (__rf_) write to global return slots — not dead even if unused locally
|
|
80
81
|
// Option slot temps (__opt_) write observable scoreboard state — preserve even if var unused
|
|
@@ -109,6 +110,8 @@ function getDst(instr) {
|
|
|
109
110
|
case 'call':
|
|
110
111
|
case 'call_macro':
|
|
111
112
|
return instr.dst;
|
|
113
|
+
case 'score_read':
|
|
114
|
+
return instr.dst;
|
|
112
115
|
default:
|
|
113
116
|
return null;
|
|
114
117
|
}
|
|
@@ -150,6 +153,9 @@ function getUsedTemps(instr) {
|
|
|
150
153
|
if (instr.value)
|
|
151
154
|
addOp(instr.value);
|
|
152
155
|
break;
|
|
156
|
+
case 'score_write':
|
|
157
|
+
addOp(instr.src);
|
|
158
|
+
break;
|
|
153
159
|
}
|
|
154
160
|
return temps;
|
|
155
161
|
}
|
|
@@ -27,7 +27,7 @@ function countSlotUses(instrs, target) {
|
|
|
27
27
|
}
|
|
28
28
|
function extractSlotsFromRaw(cmd) {
|
|
29
29
|
const slots = [];
|
|
30
|
-
const re = /(\$[\w
|
|
30
|
+
const re = /(\$[\w.:]+)\s+(\S+)/g;
|
|
31
31
|
let m;
|
|
32
32
|
while ((m = re.exec(cmd)) !== null) {
|
|
33
33
|
slots.push({ player: m[1], obj: m[2] });
|
|
@@ -24,7 +24,7 @@ function slotKey(s) {
|
|
|
24
24
|
function extractSlotsFromRaw(cmd) {
|
|
25
25
|
const slots = [];
|
|
26
26
|
// Match $<player> <obj> patterns (scoreboard slot references)
|
|
27
|
-
const re = /(\$[\w
|
|
27
|
+
const re = /(\$[\w.:]+)\s+(\S+)/g;
|
|
28
28
|
let m;
|
|
29
29
|
while ((m = re.exec(cmd)) !== null) {
|
|
30
30
|
slots.push({ player: m[1], obj: m[2] });
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ln(x) polynomial approximation adapter — atanh series form.
|
|
3
|
+
*
|
|
4
|
+
* Algorithm:
|
|
5
|
+
* Input x: fixed-point integer (×10000), e.g. 10000 = 1.0
|
|
6
|
+
* 1. Range reduction: find k s.t. xr ∈ [10000, 20000)
|
|
7
|
+
* 2. s = (xr - 10000) * 10000 / (xr + 10000) → s ∈ [0, 3333]
|
|
8
|
+
* 3. ln(xr/10000) ≈ A1*s/SCALE + A3*s³/SCALE² + A5*s⁵/SCALE³
|
|
9
|
+
* (coefficients absorb the factor of 2; theoretical: A1=20000, A3=6667, A5=4000)
|
|
10
|
+
* 4. ln(x/10000) = k * LN2 + ln(xr/10000)
|
|
11
|
+
*
|
|
12
|
+
* Intermediate overflow analysis (s ≤ 3333, SCALE = 10000):
|
|
13
|
+
* s² = s*s ≤ 11M — fits int32 (max ~2.1B)
|
|
14
|
+
* s2 = s²/SCALE ≤ 1111
|
|
15
|
+
* s3 = s*s2 ≤ 3.7M — fits int32
|
|
16
|
+
* s5 = s3*s2 ≤ 4.1M — fits int32
|
|
17
|
+
* A1*s ≤ 22000*3333 ≤ 73M — fits int32
|
|
18
|
+
* A3*s3 ≤ 7000*3703 ≤ 26M — fits int32
|
|
19
|
+
* A5*s5 ≤ 5000*4115 ≤ 21M — fits int32
|
|
20
|
+
*/
|
|
21
|
+
import { TunerAdapter } from '../types';
|
|
22
|
+
export declare const defaultParams: Record<string, number>;
|
|
23
|
+
export declare const lnPolynomialAdapter: TunerAdapter;
|