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/src/mir/lower.ts
CHANGED
|
@@ -61,16 +61,18 @@ export function lowerToMIR(hir: HIRModule, sourceFile?: string): MIRModule {
|
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
const timerCounter = { count: 0, timerId: 0 }
|
|
65
|
+
|
|
64
66
|
const allFunctions: MIRFunction[] = []
|
|
65
67
|
for (const f of hir.functions) {
|
|
66
|
-
const { fn, helpers } = lowerFunction(f, hir.namespace, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, sourceFile)
|
|
68
|
+
const { fn, helpers } = lowerFunction(f, hir.namespace, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, sourceFile, timerCounter)
|
|
67
69
|
allFunctions.push(fn, ...helpers)
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
// Lower impl block methods
|
|
71
73
|
for (const ib of hir.implBlocks) {
|
|
72
74
|
for (const m of ib.methods) {
|
|
73
|
-
const { fn, helpers } = lowerImplMethod(m, ib.typeName, hir.namespace, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, sourceFile)
|
|
75
|
+
const { fn, helpers } = lowerImplMethod(m, ib.typeName, hir.namespace, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, sourceFile, timerCounter)
|
|
74
76
|
allFunctions.push(fn, ...helpers)
|
|
75
77
|
}
|
|
76
78
|
}
|
|
@@ -105,6 +107,8 @@ class FnContext {
|
|
|
105
107
|
readonly structVars = new Map<string, { typeName: string; fields: Map<string, Temp> }>()
|
|
106
108
|
/** Tuple variable tracking: varName → array of element temps (index = slot) */
|
|
107
109
|
readonly tupleVars = new Map<string, Temp[]>()
|
|
110
|
+
/** Array variable tracking: varName → { ns, pathPrefix } for NBT-backed int[] */
|
|
111
|
+
readonly arrayVars = new Map<string, { ns: string; pathPrefix: string }>()
|
|
108
112
|
/** Macro function info for all functions in the module */
|
|
109
113
|
readonly macroInfo: Map<string, MacroFunctionInfo>
|
|
110
114
|
/** Function parameter info for call_macro generation */
|
|
@@ -117,6 +121,10 @@ class FnContext {
|
|
|
117
121
|
currentSourceLoc: SourceLoc | undefined = undefined
|
|
118
122
|
/** Source file path for the module being compiled */
|
|
119
123
|
sourceFile: string | undefined = undefined
|
|
124
|
+
/** Shared counter for setTimeout/setInterval callback naming and Timer static IDs (module-wide) */
|
|
125
|
+
readonly timerCounter: { count: number; timerId: number }
|
|
126
|
+
/** Tracks temps whose values are known compile-time constants (for Timer static ID propagation) */
|
|
127
|
+
readonly constTemps = new Map<Temp, number>()
|
|
120
128
|
|
|
121
129
|
constructor(
|
|
122
130
|
namespace: string,
|
|
@@ -126,6 +134,7 @@ class FnContext {
|
|
|
126
134
|
macroInfo: Map<string, MacroFunctionInfo> = new Map(),
|
|
127
135
|
fnParamInfo: Map<string, HIRParam[]> = new Map(),
|
|
128
136
|
enumDefs: Map<string, Map<string, number>> = new Map(),
|
|
137
|
+
timerCounter: { count: number; timerId: number } = { count: 0, timerId: 0 },
|
|
129
138
|
) {
|
|
130
139
|
this.namespace = namespace
|
|
131
140
|
this.fnName = fnName
|
|
@@ -135,6 +144,7 @@ class FnContext {
|
|
|
135
144
|
this.fnParamInfo = fnParamInfo
|
|
136
145
|
this.currentMacroParams = macroInfo.get(fnName)?.macroParams ?? new Set()
|
|
137
146
|
this.enumDefs = enumDefs
|
|
147
|
+
this.timerCounter = timerCounter
|
|
138
148
|
const entry = this.makeBlock('entry')
|
|
139
149
|
this.currentBlock = entry
|
|
140
150
|
}
|
|
@@ -214,8 +224,9 @@ function lowerFunction(
|
|
|
214
224
|
fnParamInfo: Map<string, HIRParam[]> = new Map(),
|
|
215
225
|
enumDefs: Map<string, Map<string, number>> = new Map(),
|
|
216
226
|
sourceFile?: string,
|
|
227
|
+
timerCounter: { count: number; timerId: number } = { count: 0, timerId: 0 },
|
|
217
228
|
): { fn: MIRFunction; helpers: MIRFunction[] } {
|
|
218
|
-
const ctx = new FnContext(namespace, fn.name, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs)
|
|
229
|
+
const ctx = new FnContext(namespace, fn.name, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, timerCounter)
|
|
219
230
|
ctx.sourceFile = sourceFile
|
|
220
231
|
const fnMacroInfo = macroInfo.get(fn.name)
|
|
221
232
|
|
|
@@ -267,9 +278,10 @@ function lowerImplMethod(
|
|
|
267
278
|
fnParamInfo: Map<string, HIRParam[]> = new Map(),
|
|
268
279
|
enumDefs: Map<string, Map<string, number>> = new Map(),
|
|
269
280
|
sourceFile?: string,
|
|
281
|
+
timerCounter: { count: number; timerId: number } = { count: 0, timerId: 0 },
|
|
270
282
|
): { fn: MIRFunction; helpers: MIRFunction[] } {
|
|
271
283
|
const fnName = `${typeName}::${method.name}`
|
|
272
|
-
const ctx = new FnContext(namespace, fnName, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs)
|
|
284
|
+
const ctx = new FnContext(namespace, fnName, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, timerCounter)
|
|
273
285
|
ctx.sourceFile = sourceFile
|
|
274
286
|
const fields = structDefs.get(typeName) ?? []
|
|
275
287
|
const hasSelf = method.params.length > 0 && method.params[0].name === 'self'
|
|
@@ -444,6 +456,12 @@ function lowerStmt(
|
|
|
444
456
|
const t = ctx.freshTemp()
|
|
445
457
|
ctx.emit({ kind: 'copy', dst: t, src: { kind: 'temp', name: `__rf_${fieldName}` } })
|
|
446
458
|
fieldTemps.set(fieldName, t)
|
|
459
|
+
// Propagate compile-time constants from return slots (e.g. Timer._id from Timer::new)
|
|
460
|
+
const rfSlot = `__rf_${fieldName}`
|
|
461
|
+
const constVal = ctx.constTemps.get(rfSlot)
|
|
462
|
+
if (constVal !== undefined) {
|
|
463
|
+
ctx.constTemps.set(t, constVal)
|
|
464
|
+
}
|
|
447
465
|
}
|
|
448
466
|
ctx.structVars.set(stmt.name, { typeName: stmt.type.name, fields: fieldTemps })
|
|
449
467
|
} else {
|
|
@@ -452,6 +470,33 @@ function lowerStmt(
|
|
|
452
470
|
ctx.emit({ kind: 'copy', dst: t, src: valOp })
|
|
453
471
|
scope.set(stmt.name, t)
|
|
454
472
|
}
|
|
473
|
+
} else if (stmt.init.kind === 'array_lit') {
|
|
474
|
+
// Array literal: write to NBT storage, track the var for index access
|
|
475
|
+
const ns = `${ctx.getNamespace()}:arrays`
|
|
476
|
+
const pathPrefix = stmt.name
|
|
477
|
+
ctx.arrayVars.set(stmt.name, { ns, pathPrefix })
|
|
478
|
+
const elems = stmt.init.elements
|
|
479
|
+
// Check if all elements are pure integer literals (no side-effects)
|
|
480
|
+
const allConst = elems.every(e => e.kind === 'int_lit')
|
|
481
|
+
if (allConst) {
|
|
482
|
+
// Emit a single raw 'data modify ... set value [...]' to initialize the whole list
|
|
483
|
+
const vals = elems.map(e => (e as { kind: 'int_lit'; value: number }).value).join(', ')
|
|
484
|
+
ctx.emit({ kind: 'call', dst: null, fn: `__raw:data modify storage ${ns} ${pathPrefix} set value [${vals}]`, args: [] })
|
|
485
|
+
} else {
|
|
486
|
+
// Initialize with zeros, then overwrite dynamic elements
|
|
487
|
+
const zeros = elems.map(() => '0').join(', ')
|
|
488
|
+
ctx.emit({ kind: 'call', dst: null, fn: `__raw:data modify storage ${ns} ${pathPrefix} set value [${zeros}]`, args: [] })
|
|
489
|
+
for (let i = 0; i < elems.length; i++) {
|
|
490
|
+
const elemOp = lowerExpr(elems[i], ctx, scope)
|
|
491
|
+
if (elemOp.kind !== 'const' || (elems[i].kind !== 'int_lit')) {
|
|
492
|
+
ctx.emit({ kind: 'nbt_write', ns, path: `${pathPrefix}[${i}]`, type: 'int', scale: 1, src: elemOp })
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
// Store array length as a temp in scope (for .len access)
|
|
497
|
+
const lenTemp = ctx.freshTemp()
|
|
498
|
+
ctx.emit({ kind: 'const', dst: lenTemp, value: elems.length })
|
|
499
|
+
scope.set(stmt.name, lenTemp)
|
|
455
500
|
} else {
|
|
456
501
|
const valOp = lowerExpr(stmt.init, ctx, scope)
|
|
457
502
|
const t = ctx.freshTemp()
|
|
@@ -730,6 +775,40 @@ function lowerStmt(
|
|
|
730
775
|
if (isPlaceholderTerm(ctx.current().term)) {
|
|
731
776
|
ctx.terminate({ kind: 'jump', target: mergeBlock.id })
|
|
732
777
|
}
|
|
778
|
+
} else if (arm.pattern.kind === 'range_lit') {
|
|
779
|
+
// Range pattern: e.g. 0..59 => emit ge/le comparisons
|
|
780
|
+
const range = arm.pattern.range
|
|
781
|
+
const armBody = ctx.newBlock('match_arm')
|
|
782
|
+
const nextArm = ctx.newBlock('match_next')
|
|
783
|
+
|
|
784
|
+
// Chain checks: if min defined, check matchVal >= min; if max defined, check matchVal <= max
|
|
785
|
+
// Each failed check jumps to nextArm
|
|
786
|
+
const checks: Array<{ op: 'ge' | 'le'; bound: number }> = []
|
|
787
|
+
if (range.min !== undefined) checks.push({ op: 'ge', bound: range.min })
|
|
788
|
+
if (range.max !== undefined) checks.push({ op: 'le', bound: range.max })
|
|
789
|
+
|
|
790
|
+
if (checks.length === 0) {
|
|
791
|
+
// Open range — always matches
|
|
792
|
+
ctx.terminate({ kind: 'jump', target: armBody.id })
|
|
793
|
+
} else {
|
|
794
|
+
// Emit checks sequentially; each check passes → continue to next or armBody
|
|
795
|
+
for (let ci = 0; ci < checks.length; ci++) {
|
|
796
|
+
const { op, bound } = checks[ci]
|
|
797
|
+
const cmpTemp = ctx.freshTemp()
|
|
798
|
+
ctx.emit({ kind: 'cmp', dst: cmpTemp, op, a: matchVal, b: { kind: 'const', value: bound } })
|
|
799
|
+
const passBlock = ci === checks.length - 1 ? armBody : ctx.newBlock('match_range_check')
|
|
800
|
+
ctx.terminate({ kind: 'branch', cond: { kind: 'temp', name: cmpTemp }, then: passBlock.id, else: nextArm.id })
|
|
801
|
+
if (ci < checks.length - 1) ctx.switchTo(passBlock)
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
ctx.switchTo(armBody)
|
|
806
|
+
lowerBlock(arm.body, ctx, new Map(scope))
|
|
807
|
+
if (isPlaceholderTerm(ctx.current().term)) {
|
|
808
|
+
ctx.terminate({ kind: 'jump', target: mergeBlock.id })
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
ctx.switchTo(nextArm)
|
|
733
812
|
} else {
|
|
734
813
|
const patOp = lowerExpr(arm.pattern, ctx, scope)
|
|
735
814
|
const cmpTemp = ctx.freshTemp()
|
|
@@ -996,14 +1075,116 @@ function lowerExpr(
|
|
|
996
1075
|
}
|
|
997
1076
|
|
|
998
1077
|
case 'index': {
|
|
1078
|
+
// Check if obj is a tracked array variable with a constant index
|
|
1079
|
+
if (expr.obj.kind === 'ident') {
|
|
1080
|
+
const arrInfo = ctx.arrayVars.get(expr.obj.name)
|
|
1081
|
+
if (arrInfo && expr.index.kind === 'int_lit') {
|
|
1082
|
+
const t = ctx.freshTemp()
|
|
1083
|
+
ctx.emit({ kind: 'nbt_read', dst: t, ns: arrInfo.ns, path: `${arrInfo.pathPrefix}[${expr.index.value}]`, scale: 1 })
|
|
1084
|
+
return { kind: 'temp', name: t }
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
999
1087
|
const obj = lowerExpr(expr.obj, ctx, scope)
|
|
1000
|
-
|
|
1088
|
+
lowerExpr(expr.index, ctx, scope)
|
|
1001
1089
|
const t = ctx.freshTemp()
|
|
1002
1090
|
ctx.emit({ kind: 'copy', dst: t, src: obj })
|
|
1003
1091
|
return { kind: 'temp', name: t }
|
|
1004
1092
|
}
|
|
1005
1093
|
|
|
1006
1094
|
case 'call': {
|
|
1095
|
+
// Handle scoreboard_get / score — read from vanilla MC scoreboard
|
|
1096
|
+
if (expr.fn === 'scoreboard_get' || expr.fn === 'score') {
|
|
1097
|
+
const player = hirExprToStringLiteral(expr.args[0])
|
|
1098
|
+
const obj = hirExprToStringLiteral(expr.args[1])
|
|
1099
|
+
const t = ctx.freshTemp()
|
|
1100
|
+
ctx.emit({ kind: 'score_read', dst: t, player, obj })
|
|
1101
|
+
return { kind: 'temp', name: t }
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
// Handle scoreboard_set — write to vanilla MC scoreboard
|
|
1105
|
+
if (expr.fn === 'scoreboard_set') {
|
|
1106
|
+
const player = hirExprToStringLiteral(expr.args[0])
|
|
1107
|
+
const obj = hirExprToStringLiteral(expr.args[1])
|
|
1108
|
+
const src = lowerExpr(expr.args[2], ctx, scope)
|
|
1109
|
+
ctx.emit({ kind: 'score_write', player, obj, src })
|
|
1110
|
+
const t = ctx.freshTemp()
|
|
1111
|
+
ctx.emit({ kind: 'const', dst: t, value: 0 })
|
|
1112
|
+
return { kind: 'temp', name: t }
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
// Handle setTimeout/setInterval: lift lambda arg to a named helper function
|
|
1116
|
+
if ((expr.fn === 'setTimeout' || expr.fn === 'setInterval') && expr.args.length === 2) {
|
|
1117
|
+
const ticksArg = expr.args[0]
|
|
1118
|
+
const callbackArg = expr.args[1]
|
|
1119
|
+
const ns = ctx.getNamespace()
|
|
1120
|
+
const id = ctx.timerCounter.count++
|
|
1121
|
+
const callbackName = `__timeout_callback_${id}`
|
|
1122
|
+
|
|
1123
|
+
// Extract ticks value for the schedule command
|
|
1124
|
+
let ticksLiteral: number | null = null
|
|
1125
|
+
if (ticksArg.kind === 'int_lit') {
|
|
1126
|
+
ticksLiteral = ticksArg.value
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
// Build the callback MIRFunction from the lambda body
|
|
1130
|
+
if (callbackArg.kind === 'lambda') {
|
|
1131
|
+
const cbCtx = new FnContext(
|
|
1132
|
+
ns,
|
|
1133
|
+
callbackName,
|
|
1134
|
+
ctx.structDefs,
|
|
1135
|
+
ctx.implMethods,
|
|
1136
|
+
ctx.macroInfo,
|
|
1137
|
+
ctx.fnParamInfo,
|
|
1138
|
+
ctx.enumDefs,
|
|
1139
|
+
ctx.timerCounter,
|
|
1140
|
+
)
|
|
1141
|
+
cbCtx.sourceFile = ctx.sourceFile
|
|
1142
|
+
|
|
1143
|
+
const cbBody = Array.isArray(callbackArg.body) ? callbackArg.body : [{ kind: 'expr' as const, expr: callbackArg.body }]
|
|
1144
|
+
|
|
1145
|
+
// For setInterval: reschedule at end of body
|
|
1146
|
+
const bodyStmts: typeof cbBody = [...cbBody]
|
|
1147
|
+
if (expr.fn === 'setInterval' && ticksLiteral !== null) {
|
|
1148
|
+
// Append: raw `schedule function ns:callbackName ticksT`
|
|
1149
|
+
bodyStmts.push({
|
|
1150
|
+
kind: 'raw' as const,
|
|
1151
|
+
cmd: `schedule function ${ns}:${callbackName} ${ticksLiteral}t`,
|
|
1152
|
+
} as any)
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
lowerBlock(bodyStmts, cbCtx, new Map())
|
|
1156
|
+
const cbCur = cbCtx.current()
|
|
1157
|
+
if (isPlaceholderTerm(cbCur.term)) {
|
|
1158
|
+
cbCtx.terminate({ kind: 'return', value: null })
|
|
1159
|
+
}
|
|
1160
|
+
const cbReachable = computeReachable(cbCtx.blocks, 'entry')
|
|
1161
|
+
const cbLiveBlocks = cbCtx.blocks.filter(b => cbReachable.has(b.id))
|
|
1162
|
+
computePreds(cbLiveBlocks)
|
|
1163
|
+
const cbFn: MIRFunction = {
|
|
1164
|
+
name: callbackName,
|
|
1165
|
+
params: [],
|
|
1166
|
+
blocks: cbLiveBlocks,
|
|
1167
|
+
entry: 'entry',
|
|
1168
|
+
isMacro: false,
|
|
1169
|
+
}
|
|
1170
|
+
ctx.helperFunctions.push(cbFn, ...cbCtx.helperFunctions)
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
// Emit: schedule function ns:callbackName ticksT
|
|
1174
|
+
if (ticksLiteral !== null) {
|
|
1175
|
+
ctx.emit({ kind: 'call', dst: null, fn: `__raw:schedule function ${ns}:${callbackName} ${ticksLiteral}t`, args: [] })
|
|
1176
|
+
} else {
|
|
1177
|
+
// Dynamic ticks: lower ticks operand and emit a raw schedule (best-effort)
|
|
1178
|
+
const ticksOp = lowerExpr(ticksArg, ctx, scope)
|
|
1179
|
+
ctx.emit({ kind: 'call', dst: null, fn: `__raw:schedule function ${ns}:${callbackName} 1t`, args: [ticksOp] })
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
// setTimeout returns void (0), setInterval returns an int ID (0 for now)
|
|
1183
|
+
const t = ctx.freshTemp()
|
|
1184
|
+
ctx.emit({ kind: 'const', dst: t, value: 0 })
|
|
1185
|
+
return { kind: 'temp', name: t }
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1007
1188
|
// Handle builtin calls → raw MC commands
|
|
1008
1189
|
if (BUILTIN_SET.has(expr.fn)) {
|
|
1009
1190
|
const cmd = formatBuiltinCall(expr.fn, expr.args, ctx.currentMacroParams)
|
|
@@ -1017,6 +1198,14 @@ function lowerExpr(
|
|
|
1017
1198
|
if (expr.args.length > 0 && expr.args[0].kind === 'ident') {
|
|
1018
1199
|
const sv = ctx.structVars.get(expr.args[0].name)
|
|
1019
1200
|
if (sv) {
|
|
1201
|
+
// Intercept Timer method calls when _id is a known compile-time constant
|
|
1202
|
+
if (sv.typeName === 'Timer') {
|
|
1203
|
+
const idTemp = sv.fields.get('_id')
|
|
1204
|
+
const timerId = idTemp !== undefined ? ctx.constTemps.get(idTemp) : undefined
|
|
1205
|
+
if (timerId !== undefined) {
|
|
1206
|
+
return lowerTimerMethod(expr.fn, timerId, sv, ctx, scope, expr.args.slice(1))
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1020
1209
|
const methodInfo = ctx.implMethods.get(sv.typeName)?.get(expr.fn)
|
|
1021
1210
|
if (methodInfo?.hasSelf) {
|
|
1022
1211
|
// Build args: self fields first, then remaining explicit args
|
|
@@ -1069,6 +1258,14 @@ function lowerExpr(
|
|
|
1069
1258
|
if (expr.callee.kind === 'member' && expr.callee.obj.kind === 'ident') {
|
|
1070
1259
|
const sv = ctx.structVars.get(expr.callee.obj.name)
|
|
1071
1260
|
if (sv) {
|
|
1261
|
+
// Intercept Timer method calls when _id is a known compile-time constant
|
|
1262
|
+
if (sv.typeName === 'Timer') {
|
|
1263
|
+
const idTemp = sv.fields.get('_id')
|
|
1264
|
+
const timerId = idTemp !== undefined ? ctx.constTemps.get(idTemp) : undefined
|
|
1265
|
+
if (timerId !== undefined) {
|
|
1266
|
+
return lowerTimerMethod(expr.callee.field, timerId, sv, ctx, scope, expr.args)
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1072
1269
|
const methodInfo = ctx.implMethods.get(sv.typeName)?.get(expr.callee.field)
|
|
1073
1270
|
if (methodInfo?.hasSelf) {
|
|
1074
1271
|
// Build args: self fields first, then explicit args
|
|
@@ -1094,6 +1291,24 @@ function lowerExpr(
|
|
|
1094
1291
|
}
|
|
1095
1292
|
|
|
1096
1293
|
case 'static_call': {
|
|
1294
|
+
// Intercept Timer::new() to statically allocate a unique ID
|
|
1295
|
+
if (expr.type === 'Timer' && expr.method === 'new' && expr.args.length === 1) {
|
|
1296
|
+
const id = ctx.timerCounter.timerId++
|
|
1297
|
+
const ns = ctx.getNamespace()
|
|
1298
|
+
const playerName = `__timer_${id}`
|
|
1299
|
+
// Emit scoreboard initialization: ticks=0, active=0
|
|
1300
|
+
ctx.emit({ kind: 'score_write', player: `${playerName}_ticks`, obj: ns, src: { kind: 'const', value: 0 } })
|
|
1301
|
+
ctx.emit({ kind: 'score_write', player: `${playerName}_active`, obj: ns, src: { kind: 'const', value: 0 } })
|
|
1302
|
+
// Lower the duration argument
|
|
1303
|
+
const durationOp = lowerExpr(expr.args[0], ctx, scope)
|
|
1304
|
+
// Return fields via __rf_ slots (Timer has fields: _id, _duration)
|
|
1305
|
+
ctx.emit({ kind: 'const', dst: '__rf__id', value: id })
|
|
1306
|
+
ctx.constTemps.set('__rf__id', id)
|
|
1307
|
+
ctx.emit({ kind: 'copy', dst: '__rf__duration', src: durationOp })
|
|
1308
|
+
const t = ctx.freshTemp()
|
|
1309
|
+
ctx.emit({ kind: 'const', dst: t, value: 0 })
|
|
1310
|
+
return { kind: 'temp', name: t }
|
|
1311
|
+
}
|
|
1097
1312
|
const args = expr.args.map(a => lowerExpr(a, ctx, scope))
|
|
1098
1313
|
const t = ctx.freshTemp()
|
|
1099
1314
|
ctx.emit({ kind: 'call', dst: t, fn: `${expr.type}::${expr.method}`, args })
|
|
@@ -1198,6 +1413,98 @@ function lowerShortCircuitOr(
|
|
|
1198
1413
|
return { kind: 'temp', name: result }
|
|
1199
1414
|
}
|
|
1200
1415
|
|
|
1416
|
+
// ---------------------------------------------------------------------------
|
|
1417
|
+
// Timer method inlining
|
|
1418
|
+
// ---------------------------------------------------------------------------
|
|
1419
|
+
|
|
1420
|
+
/**
|
|
1421
|
+
* Inline a Timer instance method call using the statically-assigned timer ID.
|
|
1422
|
+
* Emits scoreboard operations directly, bypassing the Timer::* function calls.
|
|
1423
|
+
*/
|
|
1424
|
+
function lowerTimerMethod(
|
|
1425
|
+
method: string,
|
|
1426
|
+
timerId: number,
|
|
1427
|
+
sv: { typeName: string; fields: Map<string, Temp> },
|
|
1428
|
+
ctx: FnContext,
|
|
1429
|
+
scope: Map<string, Temp>,
|
|
1430
|
+
extraArgs: HIRExpr[],
|
|
1431
|
+
): Operand {
|
|
1432
|
+
const ns = ctx.getNamespace()
|
|
1433
|
+
const player = `__timer_${timerId}`
|
|
1434
|
+
const t = ctx.freshTemp()
|
|
1435
|
+
|
|
1436
|
+
if (method === 'start') {
|
|
1437
|
+
ctx.emit({ kind: 'score_write', player: `${player}_active`, obj: ns, src: { kind: 'const', value: 1 } })
|
|
1438
|
+
ctx.emit({ kind: 'const', dst: t, value: 0 })
|
|
1439
|
+
} else if (method === 'pause') {
|
|
1440
|
+
ctx.emit({ kind: 'score_write', player: `${player}_active`, obj: ns, src: { kind: 'const', value: 0 } })
|
|
1441
|
+
ctx.emit({ kind: 'const', dst: t, value: 0 })
|
|
1442
|
+
} else if (method === 'reset') {
|
|
1443
|
+
ctx.emit({ kind: 'score_write', player: `${player}_ticks`, obj: ns, src: { kind: 'const', value: 0 } })
|
|
1444
|
+
ctx.emit({ kind: 'const', dst: t, value: 0 })
|
|
1445
|
+
} else if (method === 'tick') {
|
|
1446
|
+
const durationTemp = sv.fields.get('_duration')
|
|
1447
|
+
const activeTemp = ctx.freshTemp()
|
|
1448
|
+
const ticksTemp = ctx.freshTemp()
|
|
1449
|
+
ctx.emit({ kind: 'score_read', dst: activeTemp, player: `${player}_active`, obj: ns })
|
|
1450
|
+
ctx.emit({ kind: 'score_read', dst: ticksTemp, player: `${player}_ticks`, obj: ns })
|
|
1451
|
+
const innerThen = ctx.newBlock('timer_tick_inner')
|
|
1452
|
+
const innerMerge = ctx.newBlock('timer_tick_after_lt')
|
|
1453
|
+
const outerMerge = ctx.newBlock('timer_tick_done')
|
|
1454
|
+
const activeCheck = ctx.freshTemp()
|
|
1455
|
+
ctx.emit({ kind: 'cmp', op: 'eq', dst: activeCheck, a: { kind: 'temp', name: activeTemp }, b: { kind: 'const', value: 1 } })
|
|
1456
|
+
ctx.terminate({ kind: 'branch', cond: { kind: 'temp', name: activeCheck }, then: innerThen.id, else: outerMerge.id })
|
|
1457
|
+
ctx.switchTo(innerThen)
|
|
1458
|
+
const lessCheck = ctx.freshTemp()
|
|
1459
|
+
if (durationTemp) {
|
|
1460
|
+
ctx.emit({ kind: 'cmp', op: 'lt', dst: lessCheck, a: { kind: 'temp', name: ticksTemp }, b: { kind: 'temp', name: durationTemp } })
|
|
1461
|
+
} else {
|
|
1462
|
+
ctx.emit({ kind: 'const', dst: lessCheck, value: 0 })
|
|
1463
|
+
}
|
|
1464
|
+
const doIncBlock = ctx.newBlock('timer_tick_inc')
|
|
1465
|
+
ctx.terminate({ kind: 'branch', cond: { kind: 'temp', name: lessCheck }, then: doIncBlock.id, else: innerMerge.id })
|
|
1466
|
+
ctx.switchTo(doIncBlock)
|
|
1467
|
+
const newTicks = ctx.freshTemp()
|
|
1468
|
+
ctx.emit({ kind: 'add', dst: newTicks, a: { kind: 'temp', name: ticksTemp }, b: { kind: 'const', value: 1 } })
|
|
1469
|
+
ctx.emit({ kind: 'score_write', player: `${player}_ticks`, obj: ns, src: { kind: 'temp', name: newTicks } })
|
|
1470
|
+
ctx.terminate({ kind: 'jump', target: innerMerge.id })
|
|
1471
|
+
ctx.switchTo(innerMerge)
|
|
1472
|
+
ctx.terminate({ kind: 'jump', target: outerMerge.id })
|
|
1473
|
+
ctx.switchTo(outerMerge)
|
|
1474
|
+
ctx.emit({ kind: 'const', dst: t, value: 0 })
|
|
1475
|
+
} else if (method === 'done') {
|
|
1476
|
+
const durationTemp = sv.fields.get('_duration')
|
|
1477
|
+
const ticksTemp = ctx.freshTemp()
|
|
1478
|
+
ctx.emit({ kind: 'score_read', dst: ticksTemp, player: `${player}_ticks`, obj: ns })
|
|
1479
|
+
if (durationTemp) {
|
|
1480
|
+
ctx.emit({ kind: 'cmp', op: 'ge', dst: t, a: { kind: 'temp', name: ticksTemp }, b: { kind: 'temp', name: durationTemp } })
|
|
1481
|
+
} else {
|
|
1482
|
+
ctx.emit({ kind: 'const', dst: t, value: 0 })
|
|
1483
|
+
}
|
|
1484
|
+
} else if (method === 'elapsed') {
|
|
1485
|
+
ctx.emit({ kind: 'score_read', dst: t, player: `${player}_ticks`, obj: ns })
|
|
1486
|
+
} else if (method === 'remaining') {
|
|
1487
|
+
const durationTemp = sv.fields.get('_duration')
|
|
1488
|
+
const ticksTemp = ctx.freshTemp()
|
|
1489
|
+
ctx.emit({ kind: 'score_read', dst: ticksTemp, player: `${player}_ticks`, obj: ns })
|
|
1490
|
+
if (durationTemp) {
|
|
1491
|
+
ctx.emit({ kind: 'sub', dst: t, a: { kind: 'temp', name: durationTemp }, b: { kind: 'temp', name: ticksTemp } })
|
|
1492
|
+
} else {
|
|
1493
|
+
ctx.emit({ kind: 'const', dst: t, value: 0 })
|
|
1494
|
+
}
|
|
1495
|
+
} else {
|
|
1496
|
+
// Unknown Timer method — emit regular call
|
|
1497
|
+
const fields = ['_id', '_duration']
|
|
1498
|
+
const selfArgs: Operand[] = fields.map(f => {
|
|
1499
|
+
const temp = sv.fields.get(f)
|
|
1500
|
+
return temp ? { kind: 'temp' as const, name: temp } : { kind: 'const' as const, value: 0 }
|
|
1501
|
+
})
|
|
1502
|
+
const explicitArgs = extraArgs.map(a => lowerExpr(a, ctx, scope))
|
|
1503
|
+
ctx.emit({ kind: 'call', dst: t, fn: `Timer::${method}`, args: [...selfArgs, ...explicitArgs] })
|
|
1504
|
+
}
|
|
1505
|
+
return { kind: 'temp', name: t }
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1201
1508
|
// ---------------------------------------------------------------------------
|
|
1202
1509
|
// Execute subcommand lowering
|
|
1203
1510
|
// ---------------------------------------------------------------------------
|
|
@@ -1310,13 +1617,32 @@ function formatBuiltinCall(
|
|
|
1310
1617
|
break
|
|
1311
1618
|
}
|
|
1312
1619
|
case 'setblock': {
|
|
1313
|
-
|
|
1314
|
-
|
|
1620
|
+
// args: blockpos, block — expand blockpos to x y z
|
|
1621
|
+
const [posOrX, blockOrY] = args
|
|
1622
|
+
if (posOrX?.kind === 'blockpos') {
|
|
1623
|
+
const px = coordStr(posOrX.x)
|
|
1624
|
+
const py = coordStr(posOrX.y)
|
|
1625
|
+
const pz = coordStr(posOrX.z)
|
|
1626
|
+
const blk = exprToCommandArg(blockOrY, macroParams).str
|
|
1627
|
+
cmd = `setblock ${px} ${py} ${pz} ${blk}`
|
|
1628
|
+
} else {
|
|
1629
|
+
const [x, y, z, block] = strs
|
|
1630
|
+
cmd = `setblock ${x} ${y} ${z} ${block}`
|
|
1631
|
+
}
|
|
1315
1632
|
break
|
|
1316
1633
|
}
|
|
1317
1634
|
case 'fill': {
|
|
1318
|
-
|
|
1319
|
-
|
|
1635
|
+
// args: blockpos1, blockpos2, block — expand both blockpos
|
|
1636
|
+
const [p1, p2, blkArg] = args
|
|
1637
|
+
if (p1?.kind === 'blockpos' && p2?.kind === 'blockpos') {
|
|
1638
|
+
const x1 = coordStr(p1.x); const y1 = coordStr(p1.y); const z1 = coordStr(p1.z)
|
|
1639
|
+
const x2 = coordStr(p2.x); const y2 = coordStr(p2.y); const z2 = coordStr(p2.z)
|
|
1640
|
+
const blk = exprToCommandArg(blkArg, macroParams).str
|
|
1641
|
+
cmd = `fill ${x1} ${y1} ${z1} ${x2} ${y2} ${z2} ${blk}`
|
|
1642
|
+
} else {
|
|
1643
|
+
const [x1, y1, z1, x2, y2, z2, block] = strs
|
|
1644
|
+
cmd = `fill ${x1} ${y1} ${z1} ${x2} ${y2} ${z2} ${block}`
|
|
1645
|
+
}
|
|
1320
1646
|
break
|
|
1321
1647
|
}
|
|
1322
1648
|
case 'say': cmd = `say ${strs[0] ?? ''}`; break
|
|
@@ -1355,6 +1681,15 @@ function formatBuiltinCall(
|
|
|
1355
1681
|
}
|
|
1356
1682
|
|
|
1357
1683
|
/** Convert an HIR expression to its MC command string representation */
|
|
1684
|
+
/** Convert a CoordComponent to a MC coordinate string */
|
|
1685
|
+
function coordStr(c: import('../ast/types').CoordComponent): string {
|
|
1686
|
+
switch (c.kind) {
|
|
1687
|
+
case 'absolute': return String(c.value)
|
|
1688
|
+
case 'relative': return c.offset === 0 ? '~' : `~${c.offset}`
|
|
1689
|
+
case 'local': return c.offset === 0 ? '^' : `^${c.offset}`
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1358
1693
|
function exprToCommandArg(
|
|
1359
1694
|
expr: HIRExpr,
|
|
1360
1695
|
macroParams: Set<string>,
|
|
@@ -1401,3 +1736,14 @@ function exprToCommandArg(
|
|
|
1401
1736
|
return { str: '~', isMacro: false }
|
|
1402
1737
|
}
|
|
1403
1738
|
}
|
|
1739
|
+
|
|
1740
|
+
/** Extract a string literal from a HIR expression for use in MC commands */
|
|
1741
|
+
function hirExprToStringLiteral(expr: HIRExpr): string {
|
|
1742
|
+
switch (expr.kind) {
|
|
1743
|
+
case 'str_lit': return expr.value
|
|
1744
|
+
case 'mc_name': return expr.value
|
|
1745
|
+
case 'selector': return expr.raw
|
|
1746
|
+
case 'int_lit': return String(expr.value)
|
|
1747
|
+
default: return ''
|
|
1748
|
+
}
|
|
1749
|
+
}
|
package/src/mir/types.ts
CHANGED
|
@@ -80,6 +80,10 @@ export type MIRInstr = MIRInstrBase & (
|
|
|
80
80
|
| { kind: 'nbt_read'; dst: Temp; ns: string; path: string; scale: number }
|
|
81
81
|
| { kind: 'nbt_write'; ns: string; path: string; type: NBTType; scale: number; src: Operand }
|
|
82
82
|
|
|
83
|
+
// ── Vanilla scoreboard interop ────────────────────────────────────────────
|
|
84
|
+
| { kind: 'score_read'; dst: Temp; player: string; obj: string }
|
|
85
|
+
| { kind: 'score_write'; player: string; obj: string; src: Operand }
|
|
86
|
+
|
|
83
87
|
// ── Function calls ────────────────────────────────────────────────────────
|
|
84
88
|
| { kind: 'call'; dst: Temp | null; fn: string; args: Operand[] }
|
|
85
89
|
| { kind: 'call_macro'; dst: Temp | null; fn: string; args: { name: string; value: Operand; type: NBTType; scale: number }[] }
|
|
@@ -85,6 +85,8 @@ function rewriteUses(instr: MIRInstr, copies: Map<Temp, Operand>): MIRInstr {
|
|
|
85
85
|
return { ...instr, cond: resolve(instr.cond, copies) }
|
|
86
86
|
case 'return':
|
|
87
87
|
return { ...instr, value: instr.value ? resolve(instr.value, copies) : null }
|
|
88
|
+
case 'score_write':
|
|
89
|
+
return { ...instr, src: resolve(instr.src, copies) }
|
|
88
90
|
default:
|
|
89
91
|
return instr
|
|
90
92
|
}
|
|
@@ -100,6 +102,8 @@ function getDst(instr: MIRInstr): Temp | null {
|
|
|
100
102
|
return instr.dst
|
|
101
103
|
case 'call': case 'call_macro':
|
|
102
104
|
return instr.dst
|
|
105
|
+
case 'score_read':
|
|
106
|
+
return instr.dst
|
|
103
107
|
default:
|
|
104
108
|
return null
|
|
105
109
|
}
|
|
@@ -40,6 +40,8 @@ export interface CoroutineResult {
|
|
|
40
40
|
module: MIRModule
|
|
41
41
|
/** Names of generated @tick dispatcher functions (caller must add to tick list). */
|
|
42
42
|
generatedTickFunctions: string[]
|
|
43
|
+
/** Warning messages for skipped transforms. */
|
|
44
|
+
warnings: string[]
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
/**
|
|
@@ -51,11 +53,12 @@ export function coroutineTransform(
|
|
|
51
53
|
mod: MIRModule,
|
|
52
54
|
infos: CoroutineInfo[],
|
|
53
55
|
): CoroutineResult {
|
|
54
|
-
if (infos.length === 0) return { module: mod, generatedTickFunctions: [] }
|
|
56
|
+
if (infos.length === 0) return { module: mod, generatedTickFunctions: [], warnings: [] }
|
|
55
57
|
|
|
56
58
|
const infoMap = new Map(infos.map(i => [i.fnName, i]))
|
|
57
59
|
const newFunctions: MIRFunction[] = []
|
|
58
60
|
const tickFns: string[] = []
|
|
61
|
+
const warnings: string[] = []
|
|
59
62
|
|
|
60
63
|
for (const fn of mod.functions) {
|
|
61
64
|
const info = infoMap.get(fn.name)
|
|
@@ -64,6 +67,17 @@ export function coroutineTransform(
|
|
|
64
67
|
continue
|
|
65
68
|
}
|
|
66
69
|
|
|
70
|
+
// Skip transform if function contains macro calls — continuations are called
|
|
71
|
+
// directly (not via `function ... with storage`) so macro variables like
|
|
72
|
+
// ${px} would not be substituted, causing MC parse errors.
|
|
73
|
+
if (fnContainsMacroCalls(fn)) {
|
|
74
|
+
warnings.push(
|
|
75
|
+
`@coroutine cannot be applied to functions containing macro calls (skipped: ${fn.name})`,
|
|
76
|
+
)
|
|
77
|
+
newFunctions.push(fn)
|
|
78
|
+
continue
|
|
79
|
+
}
|
|
80
|
+
|
|
67
81
|
const transformed = transformCoroutine(fn, info, mod.objective)
|
|
68
82
|
newFunctions.push(transformed.initFn)
|
|
69
83
|
newFunctions.push(...transformed.continuations)
|
|
@@ -74,7 +88,29 @@ export function coroutineTransform(
|
|
|
74
88
|
return {
|
|
75
89
|
module: { ...mod, functions: newFunctions },
|
|
76
90
|
generatedTickFunctions: tickFns,
|
|
91
|
+
warnings,
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Returns true if any instruction in the function requires macro processing.
|
|
97
|
+
* This includes:
|
|
98
|
+
* - call_macro: explicit macro function invocations
|
|
99
|
+
* - call with fn = '__raw:\x01...': builtin calls (particle, summon, etc.) with macro params
|
|
100
|
+
* - call with fn = '__raw:<cmd>' where cmd contains '${': raw() commands with variable interpolation
|
|
101
|
+
*/
|
|
102
|
+
function fnContainsMacroCalls(fn: MIRFunction): boolean {
|
|
103
|
+
for (const block of fn.blocks) {
|
|
104
|
+
for (const instr of [...block.instrs, block.term]) {
|
|
105
|
+
if (instr.kind === 'call_macro') return true
|
|
106
|
+
if (instr.kind === 'call' && instr.fn.startsWith('__raw:')) {
|
|
107
|
+
const cmd = instr.fn.slice(6)
|
|
108
|
+
// \x01 sentinel: builtin with macro params; '${': raw() with variable interpolation
|
|
109
|
+
if (cmd.startsWith('\x01') || cmd.includes('${')) return true
|
|
110
|
+
}
|
|
111
|
+
}
|
|
77
112
|
}
|
|
113
|
+
return false
|
|
78
114
|
}
|
|
79
115
|
|
|
80
116
|
// ---------------------------------------------------------------------------
|
package/src/optimizer/dce.ts
CHANGED
|
@@ -78,7 +78,8 @@ function recomputePreds(blocks: MIRBlock[]): MIRBlock[] {
|
|
|
78
78
|
|
|
79
79
|
function hasSideEffects(instr: MIRInstr): boolean {
|
|
80
80
|
if (instr.kind === 'call' || instr.kind === 'call_macro' ||
|
|
81
|
-
instr.kind === 'call_context' || instr.kind === 'nbt_write'
|
|
81
|
+
instr.kind === 'call_context' || instr.kind === 'nbt_write' ||
|
|
82
|
+
instr.kind === 'score_write') return true
|
|
82
83
|
// Return field temps (__rf_) write to global return slots — not dead even if unused locally
|
|
83
84
|
// Option slot temps (__opt_) write observable scoreboard state — preserve even if var unused
|
|
84
85
|
const dst = getDst(instr)
|
|
@@ -104,6 +105,8 @@ function getDst(instr: MIRInstr): Temp | null {
|
|
|
104
105
|
return instr.dst
|
|
105
106
|
case 'call': case 'call_macro':
|
|
106
107
|
return instr.dst
|
|
108
|
+
case 'score_read':
|
|
109
|
+
return instr.dst
|
|
107
110
|
default:
|
|
108
111
|
return null
|
|
109
112
|
}
|
|
@@ -129,6 +132,8 @@ function getUsedTemps(instr: MIRInstr): Temp[] {
|
|
|
129
132
|
addOp(instr.cond); break
|
|
130
133
|
case 'return':
|
|
131
134
|
if (instr.value) addOp(instr.value); break
|
|
135
|
+
case 'score_write':
|
|
136
|
+
addOp(instr.src); break
|
|
132
137
|
}
|
|
133
138
|
return temps
|
|
134
139
|
}
|
|
@@ -28,7 +28,7 @@ function countSlotUses(instrs: LIRInstr[], target: string): number {
|
|
|
28
28
|
|
|
29
29
|
function extractSlotsFromRaw(cmd: string): Slot[] {
|
|
30
30
|
const slots: Slot[] = []
|
|
31
|
-
const re = /(\$[\w
|
|
31
|
+
const re = /(\$[\w.:]+)\s+(\S+)/g
|
|
32
32
|
let m
|
|
33
33
|
while ((m = re.exec(cmd)) !== null) {
|
|
34
34
|
slots.push({ player: m[1], obj: m[2] })
|
|
@@ -24,7 +24,7 @@ function slotKey(s: Slot): string {
|
|
|
24
24
|
function extractSlotsFromRaw(cmd: string): Slot[] {
|
|
25
25
|
const slots: Slot[] = []
|
|
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] })
|