redscript-mc 2.3.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/dist/src/__tests__/array-dynamic.test.d.ts +12 -0
- package/dist/src/__tests__/array-dynamic.test.js +131 -0
- package/dist/src/__tests__/array-write.test.d.ts +11 -0
- package/dist/src/__tests__/array-write.test.js +149 -0
- package/dist/src/ast/types.d.ts +7 -0
- package/dist/src/emit/modules.js +5 -0
- package/dist/src/hir/lower.js +29 -0
- package/dist/src/hir/monomorphize.js +2 -0
- package/dist/src/hir/types.d.ts +9 -2
- package/dist/src/lir/lower.js +131 -0
- package/dist/src/mir/lower.js +73 -3
- package/dist/src/mir/macro.js +5 -0
- package/dist/src/mir/types.d.ts +12 -0
- package/dist/src/mir/verify.js +7 -0
- package/dist/src/optimizer/copy_prop.js +5 -0
- package/dist/src/optimizer/coroutine.js +12 -0
- package/dist/src/optimizer/dce.js +9 -0
- package/dist/src/optimizer/unroll.js +3 -0
- package/dist/src/parser/index.js +5 -0
- package/dist/src/typechecker/index.js +5 -0
- package/editors/vscode/package-lock.json +3 -3
- package/editors/vscode/package.json +1 -1
- package/package.json +1 -1
- package/src/__tests__/array-dynamic.test.ts +147 -0
- package/src/__tests__/array-write.test.ts +169 -0
- package/src/ast/types.ts +1 -0
- package/src/emit/modules.ts +5 -0
- package/src/hir/lower.ts +30 -0
- package/src/hir/monomorphize.ts +2 -0
- package/src/hir/types.ts +3 -1
- package/src/lir/lower.ts +151 -0
- package/src/mir/lower.ts +75 -3
- package/src/mir/macro.ts +5 -0
- package/src/mir/types.ts +2 -0
- package/src/mir/verify.ts +7 -0
- package/src/optimizer/copy_prop.ts +5 -0
- package/src/optimizer/coroutine.ts +9 -0
- package/src/optimizer/dce.ts +6 -0
- package/src/optimizer/unroll.ts +3 -0
- package/src/parser/index.ts +9 -0
- package/src/stdlib/list.mcrs +43 -72
- package/src/stdlib/math.mcrs +137 -0
- package/src/stdlib/timer.mcrs +32 -0
- package/src/typechecker/index.ts +6 -0
package/src/hir/lower.ts
CHANGED
|
@@ -409,6 +409,36 @@ function lowerExpr(expr: Expr): HIRExpr {
|
|
|
409
409
|
case 'index':
|
|
410
410
|
return { kind: 'index', obj: lowerExpr(expr.obj), index: lowerExpr(expr.index), span: expr.span }
|
|
411
411
|
|
|
412
|
+
// --- Desugaring: compound index_assign → plain index_assign ---
|
|
413
|
+
case 'index_assign':
|
|
414
|
+
if (expr.op !== '=') {
|
|
415
|
+
const binOp = COMPOUND_TO_BINOP[expr.op]
|
|
416
|
+
const obj = lowerExpr(expr.obj)
|
|
417
|
+
const index = lowerExpr(expr.index)
|
|
418
|
+
return {
|
|
419
|
+
kind: 'index_assign',
|
|
420
|
+
obj,
|
|
421
|
+
index,
|
|
422
|
+
op: '=' as const,
|
|
423
|
+
value: {
|
|
424
|
+
kind: 'binary',
|
|
425
|
+
op: binOp as any,
|
|
426
|
+
left: { kind: 'index', obj, index },
|
|
427
|
+
right: lowerExpr(expr.value),
|
|
428
|
+
span: expr.span,
|
|
429
|
+
},
|
|
430
|
+
span: expr.span,
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return {
|
|
434
|
+
kind: 'index_assign',
|
|
435
|
+
obj: lowerExpr(expr.obj),
|
|
436
|
+
index: lowerExpr(expr.index),
|
|
437
|
+
op: expr.op,
|
|
438
|
+
value: lowerExpr(expr.value),
|
|
439
|
+
span: expr.span,
|
|
440
|
+
}
|
|
441
|
+
|
|
412
442
|
case 'call':
|
|
413
443
|
return { kind: 'call', fn: expr.fn, args: expr.args.map(lowerExpr), typeArgs: expr.typeArgs, span: expr.span }
|
|
414
444
|
|
package/src/hir/monomorphize.ts
CHANGED
|
@@ -312,6 +312,8 @@ class Monomorphizer {
|
|
|
312
312
|
return { ...expr, value: this.rewriteExpr(expr.value, ctx) }
|
|
313
313
|
case 'member_assign':
|
|
314
314
|
return { ...expr, obj: this.rewriteExpr(expr.obj, ctx), value: this.rewriteExpr(expr.value, ctx) }
|
|
315
|
+
case 'index_assign':
|
|
316
|
+
return { ...expr, obj: this.rewriteExpr(expr.obj, ctx), index: this.rewriteExpr(expr.index, ctx), value: this.rewriteExpr(expr.value, ctx) }
|
|
315
317
|
case 'member':
|
|
316
318
|
return { ...expr, obj: this.rewriteExpr(expr.obj, ctx) }
|
|
317
319
|
case 'index':
|
package/src/hir/types.ts
CHANGED
|
@@ -24,7 +24,7 @@ import type {
|
|
|
24
24
|
EntityTypeName,
|
|
25
25
|
LambdaParam,
|
|
26
26
|
} from '../ast/types'
|
|
27
|
-
import type { BinOp, CmpOp } from '../ast/types'
|
|
27
|
+
import type { BinOp, CmpOp, AssignOp } from '../ast/types'
|
|
28
28
|
|
|
29
29
|
// Re-export types that HIR shares with AST unchanged
|
|
30
30
|
export type {
|
|
@@ -40,6 +40,7 @@ export type {
|
|
|
40
40
|
LambdaParam,
|
|
41
41
|
BinOp,
|
|
42
42
|
CmpOp,
|
|
43
|
+
AssignOp,
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
// ---------------------------------------------------------------------------
|
|
@@ -77,6 +78,7 @@ export type HIRExpr =
|
|
|
77
78
|
// Assignment — only plain '=' (compound ops desugared)
|
|
78
79
|
| { kind: 'assign'; target: string; value: HIRExpr; span?: Span }
|
|
79
80
|
| { kind: 'member_assign'; obj: HIRExpr; field: string; value: HIRExpr; span?: Span }
|
|
81
|
+
| { kind: 'index_assign'; obj: HIRExpr; index: HIRExpr; op: AssignOp; value: HIRExpr; span?: Span }
|
|
80
82
|
// Access
|
|
81
83
|
| { kind: 'member'; obj: HIRExpr; field: string; span?: Span }
|
|
82
84
|
| { kind: 'index'; obj: HIRExpr; index: HIRExpr; span?: Span }
|
package/src/lir/lower.ts
CHANGED
|
@@ -50,6 +50,10 @@ class LoweringContext {
|
|
|
50
50
|
private currentMIRFn: MIRFunction | null = null
|
|
51
51
|
/** Block map for quick lookup */
|
|
52
52
|
private blockMap = new Map<BlockId, MIRBlock>()
|
|
53
|
+
/** Track generated dynamic array macro helper functions to avoid duplicates: key → fn name */
|
|
54
|
+
private dynIdxHelpers = new Map<string, string>()
|
|
55
|
+
/** Track generated dynamic array write helper functions: key → fn name */
|
|
56
|
+
private dynWrtHelpers = new Map<string, string>()
|
|
53
57
|
|
|
54
58
|
constructor(namespace: string, objective: string) {
|
|
55
59
|
this.namespace = namespace
|
|
@@ -77,6 +81,78 @@ class LoweringContext {
|
|
|
77
81
|
this.functions.push(fn)
|
|
78
82
|
}
|
|
79
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Get or create a macro helper function for dynamic array index reads.
|
|
86
|
+
* The helper function is: $return run data get storage <ns> <pathPrefix>[$(arr_idx)] 1
|
|
87
|
+
* Returns the qualified MC function name (namespace:fnName).
|
|
88
|
+
*/
|
|
89
|
+
getDynIdxHelper(ns: string, pathPrefix: string): string {
|
|
90
|
+
const key = `${ns}\0${pathPrefix}`
|
|
91
|
+
const existing = this.dynIdxHelpers.get(key)
|
|
92
|
+
if (existing) return existing
|
|
93
|
+
|
|
94
|
+
// Generate deterministic name from ns and pathPrefix
|
|
95
|
+
const sanitize = (s: string) => s.replace(/[^a-z0-9_]/gi, '_').toLowerCase()
|
|
96
|
+
// Extract just the storage name part from ns (e.g. "myns:arrays" → "myns_arrays")
|
|
97
|
+
const nsStr = sanitize(ns)
|
|
98
|
+
const prefixStr = sanitize(pathPrefix)
|
|
99
|
+
const helperName = `__dyn_idx_${nsStr}_${prefixStr}`
|
|
100
|
+
|
|
101
|
+
// The helper is placed in the current namespace
|
|
102
|
+
const qualifiedName = `${this.namespace}:${helperName}`
|
|
103
|
+
|
|
104
|
+
// Generate the macro function content:
|
|
105
|
+
// $return run data get storage <ns> <pathPrefix>[$(arr_idx)] 1
|
|
106
|
+
const macroLine: LIRInstr = {
|
|
107
|
+
kind: 'macro_line',
|
|
108
|
+
template: `return run data get storage ${ns} ${pathPrefix}[$(arr_idx)] 1`,
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
this.addFunction({
|
|
112
|
+
name: helperName,
|
|
113
|
+
instructions: [macroLine],
|
|
114
|
+
isMacro: true,
|
|
115
|
+
macroParams: ['arr_idx'],
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
this.dynIdxHelpers.set(key, qualifiedName)
|
|
119
|
+
return qualifiedName
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Get or create a macro helper function for dynamic array index writes.
|
|
124
|
+
* The helper function: $data modify storage <ns> <pathPrefix>[$(arr_idx)] set value $(arr_val)
|
|
125
|
+
* Returns the qualified MC function name.
|
|
126
|
+
*/
|
|
127
|
+
getDynWrtHelper(ns: string, pathPrefix: string): string {
|
|
128
|
+
const key = `${ns}\0${pathPrefix}`
|
|
129
|
+
const existing = this.dynWrtHelpers.get(key)
|
|
130
|
+
if (existing) return existing
|
|
131
|
+
|
|
132
|
+
const sanitize = (s: string) => s.replace(/[^a-z0-9_]/gi, '_').toLowerCase()
|
|
133
|
+
const nsStr = sanitize(ns)
|
|
134
|
+
const prefixStr = sanitize(pathPrefix)
|
|
135
|
+
const helperName = `__dyn_wrt_${nsStr}_${prefixStr}`
|
|
136
|
+
|
|
137
|
+
const qualifiedName = `${this.namespace}:${helperName}`
|
|
138
|
+
|
|
139
|
+
// Macro line: $data modify storage <ns> <pathPrefix>[$(arr_idx)] set value $(arr_val)
|
|
140
|
+
const macroLine: LIRInstr = {
|
|
141
|
+
kind: 'macro_line',
|
|
142
|
+
template: `data modify storage ${ns} ${pathPrefix}[$(arr_idx)] set value $(arr_val)`,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
this.addFunction({
|
|
146
|
+
name: helperName,
|
|
147
|
+
instructions: [macroLine],
|
|
148
|
+
isMacro: true,
|
|
149
|
+
macroParams: ['arr_idx', 'arr_val'],
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
this.dynWrtHelpers.set(key, qualifiedName)
|
|
153
|
+
return qualifiedName
|
|
154
|
+
}
|
|
155
|
+
|
|
80
156
|
/** Attach sourceLoc to newly added instructions (from the given start index onward) */
|
|
81
157
|
tagSourceLoc(instrs: LIRInstr[], fromIndex: number, sourceLoc: SourceLoc | undefined): void {
|
|
82
158
|
if (!sourceLoc) return
|
|
@@ -321,6 +397,46 @@ function lowerInstrInner(
|
|
|
321
397
|
break
|
|
322
398
|
}
|
|
323
399
|
|
|
400
|
+
case 'nbt_read_dynamic': {
|
|
401
|
+
// Strategy:
|
|
402
|
+
// 1. Store the index value into rs:macro_args __arr_idx (int)
|
|
403
|
+
// 2. Call the per-array macro helper function with 'with storage rs:macro_args'
|
|
404
|
+
// 3. Result comes back via $ret scoreboard slot (the macro uses $return)
|
|
405
|
+
|
|
406
|
+
const dst = ctx.slot(instr.dst)
|
|
407
|
+
const idxSlot = operandToSlot(instr.indexSrc, ctx, instrs)
|
|
408
|
+
|
|
409
|
+
// Step 1: store index score → rs:macro_args arr_idx (int, scale 1)
|
|
410
|
+
instrs.push({
|
|
411
|
+
kind: 'store_score_to_nbt',
|
|
412
|
+
ns: 'rs:macro_args',
|
|
413
|
+
path: 'arr_idx',
|
|
414
|
+
type: 'int',
|
|
415
|
+
scale: 1,
|
|
416
|
+
src: idxSlot,
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
// Step 2: get or create the macro helper function, then call it
|
|
420
|
+
const helperFn = ctx.getDynIdxHelper(instr.ns, instr.pathPrefix)
|
|
421
|
+
instrs.push({ kind: 'call_macro', fn: helperFn, storage: 'rs:macro_args' })
|
|
422
|
+
|
|
423
|
+
// Step 3: the macro uses $return which sets the MC return value.
|
|
424
|
+
// We need to capture that. In MC, $return run ... returns the result
|
|
425
|
+
// to the caller via the execute store mechanism.
|
|
426
|
+
// Use store_cmd_to_score to capture the return value of the macro call.
|
|
427
|
+
// Actually, the call_macro instruction above already ran the function.
|
|
428
|
+
// The $return run data get ... sets the scoreboard return value for the
|
|
429
|
+
// *calling* function context. We need to use execute store result score.
|
|
430
|
+
// Rewrite: use raw command instead:
|
|
431
|
+
instrs.pop() // remove the call_macro we just added
|
|
432
|
+
instrs.push({
|
|
433
|
+
kind: 'store_cmd_to_score',
|
|
434
|
+
dst,
|
|
435
|
+
cmd: { kind: 'call_macro', fn: helperFn, storage: 'rs:macro_args' },
|
|
436
|
+
})
|
|
437
|
+
break
|
|
438
|
+
}
|
|
439
|
+
|
|
324
440
|
case 'nbt_write': {
|
|
325
441
|
const srcSlot = operandToSlot(instr.src, ctx, instrs)
|
|
326
442
|
instrs.push({
|
|
@@ -334,6 +450,41 @@ function lowerInstrInner(
|
|
|
334
450
|
break
|
|
335
451
|
}
|
|
336
452
|
|
|
453
|
+
case 'nbt_write_dynamic': {
|
|
454
|
+
// Strategy:
|
|
455
|
+
// 1. Store index score → rs:macro_args arr_idx (int)
|
|
456
|
+
// 2. Store value score → rs:macro_args arr_val (int)
|
|
457
|
+
// 3. Call macro helper: $data modify storage <ns> <pathPrefix>[$(arr_idx)] set value $(arr_val)
|
|
458
|
+
|
|
459
|
+
const idxSlot = operandToSlot(instr.indexSrc, ctx, instrs)
|
|
460
|
+
const valSlot = operandToSlot(instr.valueSrc, ctx, instrs)
|
|
461
|
+
|
|
462
|
+
// Store index
|
|
463
|
+
instrs.push({
|
|
464
|
+
kind: 'store_score_to_nbt',
|
|
465
|
+
ns: 'rs:macro_args',
|
|
466
|
+
path: 'arr_idx',
|
|
467
|
+
type: 'int',
|
|
468
|
+
scale: 1,
|
|
469
|
+
src: idxSlot,
|
|
470
|
+
})
|
|
471
|
+
|
|
472
|
+
// Store value
|
|
473
|
+
instrs.push({
|
|
474
|
+
kind: 'store_score_to_nbt',
|
|
475
|
+
ns: 'rs:macro_args',
|
|
476
|
+
path: 'arr_val',
|
|
477
|
+
type: 'int',
|
|
478
|
+
scale: 1,
|
|
479
|
+
src: valSlot,
|
|
480
|
+
})
|
|
481
|
+
|
|
482
|
+
// Call macro helper function
|
|
483
|
+
const helperFn = ctx.getDynWrtHelper(instr.ns, instr.pathPrefix)
|
|
484
|
+
instrs.push({ kind: 'call_macro', fn: helperFn, storage: 'rs:macro_args' })
|
|
485
|
+
break
|
|
486
|
+
}
|
|
487
|
+
|
|
337
488
|
case 'score_read': {
|
|
338
489
|
// execute store result score $dst __obj run scoreboard players get <player> <obj>
|
|
339
490
|
const dst = ctx.slot(instr.dst)
|
package/src/mir/lower.ts
CHANGED
|
@@ -1075,12 +1075,19 @@ function lowerExpr(
|
|
|
1075
1075
|
}
|
|
1076
1076
|
|
|
1077
1077
|
case 'index': {
|
|
1078
|
-
// Check if obj is a tracked array variable
|
|
1078
|
+
// Check if obj is a tracked array variable
|
|
1079
1079
|
if (expr.obj.kind === 'ident') {
|
|
1080
1080
|
const arrInfo = ctx.arrayVars.get(expr.obj.name)
|
|
1081
|
-
if (arrInfo
|
|
1081
|
+
if (arrInfo) {
|
|
1082
1082
|
const t = ctx.freshTemp()
|
|
1083
|
-
|
|
1083
|
+
if (expr.index.kind === 'int_lit') {
|
|
1084
|
+
// Constant index: direct NBT read
|
|
1085
|
+
ctx.emit({ kind: 'nbt_read', dst: t, ns: arrInfo.ns, path: `${arrInfo.pathPrefix}[${expr.index.value}]`, scale: 1 })
|
|
1086
|
+
} else {
|
|
1087
|
+
// Dynamic index: emit nbt_read_dynamic
|
|
1088
|
+
const idxOp = lowerExpr(expr.index, ctx, scope)
|
|
1089
|
+
ctx.emit({ kind: 'nbt_read_dynamic', dst: t, ns: arrInfo.ns, pathPrefix: arrInfo.pathPrefix, indexSrc: idxOp })
|
|
1090
|
+
}
|
|
1084
1091
|
return { kind: 'temp', name: t }
|
|
1085
1092
|
}
|
|
1086
1093
|
}
|
|
@@ -1091,6 +1098,25 @@ function lowerExpr(
|
|
|
1091
1098
|
return { kind: 'temp', name: t }
|
|
1092
1099
|
}
|
|
1093
1100
|
|
|
1101
|
+
case 'index_assign': {
|
|
1102
|
+
const valOp = lowerExpr(expr.value, ctx, scope)
|
|
1103
|
+
if (expr.obj.kind === 'ident') {
|
|
1104
|
+
const arrInfo = ctx.arrayVars.get(expr.obj.name)
|
|
1105
|
+
if (arrInfo) {
|
|
1106
|
+
if (expr.index.kind === 'int_lit') {
|
|
1107
|
+
// constant index → direct nbt_write
|
|
1108
|
+
ctx.emit({ kind: 'nbt_write', ns: arrInfo.ns, path: `${arrInfo.pathPrefix}[${expr.index.value}]`, type: 'int', scale: 1, src: valOp })
|
|
1109
|
+
} else {
|
|
1110
|
+
// dynamic index → nbt_write_dynamic
|
|
1111
|
+
const idxOp = lowerExpr(expr.index, ctx, scope)
|
|
1112
|
+
ctx.emit({ kind: 'nbt_write_dynamic', ns: arrInfo.ns, pathPrefix: arrInfo.pathPrefix, indexSrc: idxOp, valueSrc: valOp })
|
|
1113
|
+
}
|
|
1114
|
+
return valOp
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
return valOp
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1094
1120
|
case 'call': {
|
|
1095
1121
|
// Handle scoreboard_get / score — read from vanilla MC scoreboard
|
|
1096
1122
|
if (expr.fn === 'scoreboard_get' || expr.fn === 'score') {
|
|
@@ -1112,6 +1138,52 @@ function lowerExpr(
|
|
|
1112
1138
|
return { kind: 'temp', name: t }
|
|
1113
1139
|
}
|
|
1114
1140
|
|
|
1141
|
+
// Handle list_push(arr_name, val) — append an int to an NBT int array
|
|
1142
|
+
// list_push("rs:lists", "mylist", val) or simpler: uses the array's storage path
|
|
1143
|
+
if (expr.fn === 'list_push') {
|
|
1144
|
+
// list_push(array_var, value)
|
|
1145
|
+
// 1. Append a placeholder 0
|
|
1146
|
+
// 2. Overwrite [-1] with the actual value
|
|
1147
|
+
if (expr.args[0].kind === 'ident') {
|
|
1148
|
+
const arrInfo = ctx.arrayVars.get((expr.args[0] as { kind: 'ident'; name: string }).name)
|
|
1149
|
+
if (arrInfo) {
|
|
1150
|
+
const valOp = lowerExpr(expr.args[1], ctx, scope)
|
|
1151
|
+
// Step 1: append placeholder
|
|
1152
|
+
ctx.emit({ kind: 'call', dst: null, fn: `__raw:data modify storage ${arrInfo.ns} ${arrInfo.pathPrefix} append value 0`, args: [] })
|
|
1153
|
+
// Step 2: overwrite last element with actual value
|
|
1154
|
+
ctx.emit({ kind: 'nbt_write', ns: arrInfo.ns, path: `${arrInfo.pathPrefix}[-1]`, type: 'int', scale: 1, src: valOp })
|
|
1155
|
+
const t = ctx.freshTemp()
|
|
1156
|
+
ctx.emit({ kind: 'const', dst: t, value: 0 })
|
|
1157
|
+
return { kind: 'temp', name: t }
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
// Handle list_pop(arr_var) — remove last element from NBT int array
|
|
1163
|
+
if (expr.fn === 'list_pop') {
|
|
1164
|
+
if (expr.args[0].kind === 'ident') {
|
|
1165
|
+
const arrInfo = ctx.arrayVars.get((expr.args[0] as { kind: 'ident'; name: string }).name)
|
|
1166
|
+
if (arrInfo) {
|
|
1167
|
+
ctx.emit({ kind: 'call', dst: null, fn: `__raw:data remove storage ${arrInfo.ns} ${arrInfo.pathPrefix}[-1]`, args: [] })
|
|
1168
|
+
const t = ctx.freshTemp()
|
|
1169
|
+
ctx.emit({ kind: 'const', dst: t, value: 0 })
|
|
1170
|
+
return { kind: 'temp', name: t }
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// Handle list_len(arr_var) — get length of NBT int array
|
|
1176
|
+
if (expr.fn === 'list_len') {
|
|
1177
|
+
if (expr.args[0].kind === 'ident') {
|
|
1178
|
+
const arrInfo = ctx.arrayVars.get((expr.args[0] as { kind: 'ident'; name: string }).name)
|
|
1179
|
+
if (arrInfo) {
|
|
1180
|
+
const t = ctx.freshTemp()
|
|
1181
|
+
ctx.emit({ kind: 'nbt_read', dst: t, ns: arrInfo.ns, path: `${arrInfo.pathPrefix}`, scale: 1 })
|
|
1182
|
+
return { kind: 'temp', name: t }
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1115
1187
|
// Handle setTimeout/setInterval: lift lambda arg to a named helper function
|
|
1116
1188
|
if ((expr.fn === 'setTimeout' || expr.fn === 'setInterval') && expr.args.length === 2) {
|
|
1117
1189
|
const ticksArg = expr.args[0]
|
package/src/mir/macro.ts
CHANGED
|
@@ -140,6 +140,11 @@ function scanExpr(expr: HIRExpr, paramNames: Set<string>, macroParams: Set<strin
|
|
|
140
140
|
scanExpr(expr.obj, paramNames, macroParams)
|
|
141
141
|
scanExpr(expr.value, paramNames, macroParams)
|
|
142
142
|
break
|
|
143
|
+
case 'index_assign':
|
|
144
|
+
scanExpr(expr.obj, paramNames, macroParams)
|
|
145
|
+
scanExpr(expr.index, paramNames, macroParams)
|
|
146
|
+
scanExpr(expr.value, paramNames, macroParams)
|
|
147
|
+
break
|
|
143
148
|
case 'member':
|
|
144
149
|
scanExpr(expr.obj, paramNames, macroParams)
|
|
145
150
|
break
|
package/src/mir/types.ts
CHANGED
|
@@ -78,7 +78,9 @@ export type MIRInstr = MIRInstrBase & (
|
|
|
78
78
|
|
|
79
79
|
// ── NBT storage ──────────────────────────────────────────────────────────
|
|
80
80
|
| { kind: 'nbt_read'; dst: Temp; ns: string; path: string; scale: number }
|
|
81
|
+
| { kind: 'nbt_read_dynamic'; dst: Temp; ns: string; pathPrefix: string; indexSrc: Operand }
|
|
81
82
|
| { kind: 'nbt_write'; ns: string; path: string; type: NBTType; scale: number; src: Operand }
|
|
83
|
+
| { kind: 'nbt_write_dynamic'; ns: string; pathPrefix: string; indexSrc: Operand; valueSrc: Operand }
|
|
82
84
|
|
|
83
85
|
// ── Vanilla scoreboard interop ────────────────────────────────────────────
|
|
84
86
|
| { kind: 'score_read'; dst: Temp; player: string; obj: string }
|
package/src/mir/verify.ts
CHANGED
|
@@ -164,6 +164,7 @@ function getDst(instr: MIRInstr): Temp | null {
|
|
|
164
164
|
case 'cmp':
|
|
165
165
|
case 'and': case 'or': case 'not':
|
|
166
166
|
case 'nbt_read':
|
|
167
|
+
case 'nbt_read_dynamic':
|
|
167
168
|
return instr.dst
|
|
168
169
|
case 'call':
|
|
169
170
|
case 'call_macro':
|
|
@@ -194,9 +195,15 @@ function getUsedTemps(instr: MIRInstr): Temp[] {
|
|
|
194
195
|
break
|
|
195
196
|
case 'nbt_read':
|
|
196
197
|
break
|
|
198
|
+
case 'nbt_read_dynamic':
|
|
199
|
+
temps.push(...getOperandTemps(instr.indexSrc))
|
|
200
|
+
break
|
|
197
201
|
case 'nbt_write':
|
|
198
202
|
temps.push(...getOperandTemps(instr.src))
|
|
199
203
|
break
|
|
204
|
+
case 'nbt_write_dynamic':
|
|
205
|
+
temps.push(...getOperandTemps(instr.indexSrc), ...getOperandTemps(instr.valueSrc))
|
|
206
|
+
break
|
|
200
207
|
case 'call':
|
|
201
208
|
for (const arg of instr.args) temps.push(...getOperandTemps(arg))
|
|
202
209
|
break
|
|
@@ -77,6 +77,10 @@ function rewriteUses(instr: MIRInstr, copies: Map<Temp, Operand>): MIRInstr {
|
|
|
77
77
|
return { ...instr, a: resolve(instr.a, copies), b: resolve(instr.b, copies) }
|
|
78
78
|
case 'nbt_write':
|
|
79
79
|
return { ...instr, src: resolve(instr.src, copies) }
|
|
80
|
+
case 'nbt_write_dynamic':
|
|
81
|
+
return { ...instr, indexSrc: resolve(instr.indexSrc, copies), valueSrc: resolve(instr.valueSrc, copies) }
|
|
82
|
+
case 'nbt_read_dynamic':
|
|
83
|
+
return { ...instr, indexSrc: resolve(instr.indexSrc, copies) }
|
|
80
84
|
case 'call':
|
|
81
85
|
return { ...instr, args: instr.args.map(a => resolve(a, copies)) }
|
|
82
86
|
case 'call_macro':
|
|
@@ -99,6 +103,7 @@ function getDst(instr: MIRInstr): Temp | null {
|
|
|
99
103
|
case 'neg': case 'cmp':
|
|
100
104
|
case 'and': case 'or': case 'not':
|
|
101
105
|
case 'nbt_read':
|
|
106
|
+
case 'nbt_read_dynamic':
|
|
102
107
|
return instr.dst
|
|
103
108
|
case 'call': case 'call_macro':
|
|
104
109
|
return instr.dst
|
|
@@ -946,8 +946,12 @@ function rewriteInstr(instr: MIRInstr, promoted: Map<Temp, Temp>): MIRInstr {
|
|
|
946
946
|
return { ...instr, dst: rTemp(instr.dst), src: rOp(instr.src) }
|
|
947
947
|
case 'nbt_read':
|
|
948
948
|
return { ...instr, dst: rTemp(instr.dst) }
|
|
949
|
+
case 'nbt_read_dynamic':
|
|
950
|
+
return { ...instr, dst: rTemp(instr.dst), indexSrc: rOp(instr.indexSrc) }
|
|
949
951
|
case 'nbt_write':
|
|
950
952
|
return { ...instr, src: rOp(instr.src) }
|
|
953
|
+
case 'nbt_write_dynamic':
|
|
954
|
+
return { ...instr, indexSrc: rOp(instr.indexSrc), valueSrc: rOp(instr.valueSrc) }
|
|
951
955
|
case 'call':
|
|
952
956
|
return { ...instr, dst: instr.dst ? rTemp(instr.dst) : null, args: instr.args.map(rOp) }
|
|
953
957
|
case 'call_macro':
|
|
@@ -999,6 +1003,7 @@ function getDst(instr: MIRInstr): Temp | null {
|
|
|
999
1003
|
case 'neg': case 'cmp':
|
|
1000
1004
|
case 'and': case 'or': case 'not':
|
|
1001
1005
|
case 'nbt_read':
|
|
1006
|
+
case 'nbt_read_dynamic':
|
|
1002
1007
|
return instr.dst
|
|
1003
1008
|
case 'call': case 'call_macro':
|
|
1004
1009
|
return instr.dst
|
|
@@ -1019,6 +1024,10 @@ function getUsedTemps(instr: MIRInstr): Temp[] {
|
|
|
1019
1024
|
addOp(instr.a); addOp(instr.b); break
|
|
1020
1025
|
case 'nbt_write':
|
|
1021
1026
|
addOp(instr.src); break
|
|
1027
|
+
case 'nbt_write_dynamic':
|
|
1028
|
+
addOp(instr.indexSrc); addOp(instr.valueSrc); break
|
|
1029
|
+
case 'nbt_read_dynamic':
|
|
1030
|
+
addOp(instr.indexSrc); break
|
|
1022
1031
|
case 'call':
|
|
1023
1032
|
instr.args.forEach(addOp); break
|
|
1024
1033
|
case 'call_macro':
|
package/src/optimizer/dce.ts
CHANGED
|
@@ -79,6 +79,7 @@ function recomputePreds(blocks: MIRBlock[]): MIRBlock[] {
|
|
|
79
79
|
function hasSideEffects(instr: MIRInstr): boolean {
|
|
80
80
|
if (instr.kind === 'call' || instr.kind === 'call_macro' ||
|
|
81
81
|
instr.kind === 'call_context' || instr.kind === 'nbt_write' ||
|
|
82
|
+
instr.kind === 'nbt_write_dynamic' ||
|
|
82
83
|
instr.kind === 'score_write') return true
|
|
83
84
|
// Return field temps (__rf_) write to global return slots — not dead even if unused locally
|
|
84
85
|
// Option slot temps (__opt_) write observable scoreboard state — preserve even if var unused
|
|
@@ -102,6 +103,7 @@ function getDst(instr: MIRInstr): Temp | null {
|
|
|
102
103
|
case 'neg': case 'cmp':
|
|
103
104
|
case 'and': case 'or': case 'not':
|
|
104
105
|
case 'nbt_read':
|
|
106
|
+
case 'nbt_read_dynamic':
|
|
105
107
|
return instr.dst
|
|
106
108
|
case 'call': case 'call_macro':
|
|
107
109
|
return instr.dst
|
|
@@ -124,6 +126,10 @@ function getUsedTemps(instr: MIRInstr): Temp[] {
|
|
|
124
126
|
addOp(instr.a); addOp(instr.b); break
|
|
125
127
|
case 'nbt_write':
|
|
126
128
|
addOp(instr.src); break
|
|
129
|
+
case 'nbt_write_dynamic':
|
|
130
|
+
addOp(instr.indexSrc); addOp(instr.valueSrc); break
|
|
131
|
+
case 'nbt_read_dynamic':
|
|
132
|
+
addOp(instr.indexSrc); break
|
|
127
133
|
case 'call':
|
|
128
134
|
instr.args.forEach(addOp); break
|
|
129
135
|
case 'call_macro':
|
package/src/optimizer/unroll.ts
CHANGED
|
@@ -334,6 +334,8 @@ function substituteInstr(instr: MIRInstr, sub: Map<Temp, Operand>): MIRInstr {
|
|
|
334
334
|
return { ...instr, a: substituteOp(instr.a, sub), b: substituteOp(instr.b, sub) }
|
|
335
335
|
case 'nbt_write':
|
|
336
336
|
return { ...instr, src: substituteOp(instr.src, sub) }
|
|
337
|
+
case 'nbt_write_dynamic':
|
|
338
|
+
return { ...instr, indexSrc: substituteOp(instr.indexSrc, sub), valueSrc: substituteOp(instr.valueSrc, sub) }
|
|
337
339
|
case 'call':
|
|
338
340
|
return { ...instr, args: instr.args.map(a => substituteOp(a, sub)) }
|
|
339
341
|
case 'call_macro':
|
|
@@ -357,6 +359,7 @@ function getInstrDst(instr: MIRInstr): Temp | null {
|
|
|
357
359
|
case 'add': case 'sub': case 'mul': case 'div': case 'mod':
|
|
358
360
|
case 'neg': case 'cmp': case 'and': case 'or': case 'not':
|
|
359
361
|
case 'nbt_read':
|
|
362
|
+
case 'nbt_read_dynamic':
|
|
360
363
|
return instr.dst
|
|
361
364
|
case 'call': case 'call_macro':
|
|
362
365
|
return instr.dst
|
package/src/parser/index.ts
CHANGED
|
@@ -1162,6 +1162,15 @@ export class Parser {
|
|
|
1162
1162
|
this.getLocToken(left) ?? token
|
|
1163
1163
|
)
|
|
1164
1164
|
}
|
|
1165
|
+
|
|
1166
|
+
// Index assignment: arr[0] = val, arr[i] = val
|
|
1167
|
+
if (left.kind === 'index') {
|
|
1168
|
+
const value = this.parseAssignment()
|
|
1169
|
+
return this.withLoc(
|
|
1170
|
+
{ kind: 'index_assign', obj: left.obj, index: left.index, op, value },
|
|
1171
|
+
this.getLocToken(left) ?? token
|
|
1172
|
+
)
|
|
1173
|
+
}
|
|
1165
1174
|
}
|
|
1166
1175
|
|
|
1167
1176
|
return left
|
package/src/stdlib/list.mcrs
CHANGED
|
@@ -1,45 +1,29 @@
|
|
|
1
|
-
// list.mcrs —
|
|
1
|
+
// list.mcrs — List and array utilities for RedScript datapacks.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
3
|
+
// NOTE: Array parameters cannot be passed to functions by reference in
|
|
4
|
+
// the current RedScript implementation. Functions that operate on arrays
|
|
5
|
+
// must be inlined by the user, OR use fixed-size static helpers below.
|
|
5
6
|
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// The caller must pass the list path as a namespace:path string.
|
|
7
|
+
// For dynamic array operations (sort, search, sum over variable-length arrays),
|
|
8
|
+
// write the loop directly in your code:
|
|
9
9
|
//
|
|
10
|
-
//
|
|
11
|
-
// //
|
|
12
|
-
//
|
|
10
|
+
// let nums: int[] = [30, 10, 20];
|
|
11
|
+
// // Manual bubble sort (2 elements):
|
|
12
|
+
// if (nums[0] > nums[1]) {
|
|
13
|
+
// let tmp: int = nums[0];
|
|
14
|
+
// nums[0] = nums[1];
|
|
15
|
+
// nums[1] = tmp;
|
|
16
|
+
// }
|
|
13
17
|
//
|
|
14
|
-
//
|
|
15
|
-
// // Note: push/pop/len use raw() NBT commands directly.
|
|
16
|
-
//
|
|
17
|
-
// For fixed-size arrays, use int[] literals directly.
|
|
18
|
-
// For dynamic lists, use the functions below.
|
|
19
|
-
//
|
|
20
|
-
// Storage convention: lists live at storage rs:lists <varname>
|
|
18
|
+
// The static helpers below work on up to 5 discrete values passed as arguments.
|
|
21
19
|
|
|
22
20
|
module library;
|
|
23
21
|
|
|
24
|
-
//
|
|
25
|
-
// Returns the count stored in a scoreboard temp.
|
|
26
|
-
// Use: raw("execute store result score $len __ns run data get storage ns:name path")
|
|
27
|
-
// This is a convenience wrapper — in practice use raw() directly.
|
|
28
|
-
|
|
29
|
-
// list_sum(a, b, c, d, e): sum of up to 5 int values (utility)
|
|
30
|
-
fn list_sum5(a: int, b: int, c: int, d: int, e: int): int {
|
|
31
|
-
return a + b + c + d + e;
|
|
32
|
-
}
|
|
22
|
+
// ─── Static min/max/avg ──────────────────────────────────────────────────────
|
|
33
23
|
|
|
34
|
-
fn
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
fn list_sum3(a: int, b: int, c: int): int {
|
|
39
|
-
return a + b + c;
|
|
40
|
-
}
|
|
24
|
+
fn sort2_min(a: int, b: int): int { if (a <= b) { return a; } return b; }
|
|
25
|
+
fn sort2_max(a: int, b: int): int { if (a >= b) { return a; } return b; }
|
|
41
26
|
|
|
42
|
-
// list_min3 / list_max3: min/max over 3 values (useful for array-derived work)
|
|
43
27
|
fn list_min3(a: int, b: int, c: int): int {
|
|
44
28
|
let m: int = a;
|
|
45
29
|
if (b < m) { m = b; }
|
|
@@ -72,54 +56,41 @@ fn list_max5(a: int, b: int, c: int, d: int, e: int): int {
|
|
|
72
56
|
return m;
|
|
73
57
|
}
|
|
74
58
|
|
|
75
|
-
|
|
59
|
+
fn list_sum5(a: int, b: int, c: int, d: int, e: int): int { return a + b + c + d + e; }
|
|
60
|
+
fn list_sum4(a: int, b: int, c: int, d: int): int { return a + b + c + d; }
|
|
61
|
+
fn list_sum3(a: int, b: int, c: int): int { return a + b + c; }
|
|
62
|
+
fn avg3(a: int, b: int, c: int): int { return (a + b + c) / 3; }
|
|
63
|
+
fn avg5(a: int, b: int, c: int, d: int, e: int): int { return (a + b + c + d + e) / 5; }
|
|
76
64
|
|
|
77
|
-
//
|
|
78
|
-
// side-effect scoreboard. Caller reads $sort3_a, $sort3_b, $sort3_c.
|
|
79
|
-
// (This is a placeholder — proper NBT list sort requires runtime support.)
|
|
65
|
+
// ─── Static sort (sorting network) ───────────────────────────────────────────
|
|
80
66
|
|
|
81
|
-
//
|
|
82
|
-
fn sort2_min(a: int, b: int): int {
|
|
83
|
-
if (a <= b) { return a; }
|
|
84
|
-
return b;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
fn sort2_max(a: int, b: int): int {
|
|
88
|
-
if (a >= b) { return a; }
|
|
89
|
-
return b;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// bubble_sort3: returns sorted value at position pos (0=min, 1=mid, 2=max)
|
|
67
|
+
// sort3(a, b, c, pos): return sorted value at position pos (0=min, 1=mid, 2=max)
|
|
93
68
|
fn sort3(a: int, b: int, c: int, pos: int): int {
|
|
94
|
-
|
|
95
|
-
let
|
|
96
|
-
let
|
|
97
|
-
let
|
|
98
|
-
// Step 1: compare-swap (x, y)
|
|
99
|
-
if (x > y) { let tmp: int = x; x = y; y = tmp; }
|
|
100
|
-
// Step 2: compare-swap (y, z)
|
|
101
|
-
if (y > z) { let tmp: int = y; y = z; z = tmp; }
|
|
102
|
-
// Step 3: compare-swap (x, y)
|
|
103
|
-
if (x > y) { let tmp: int = x; x = y; y = tmp; }
|
|
69
|
+
let x: int = a; let y: int = b; let z: int = c;
|
|
70
|
+
if (x > y) { let t: int = x; x = y; y = t; }
|
|
71
|
+
if (y > z) { let t: int = y; y = z; z = t; }
|
|
72
|
+
if (x > y) { let t: int = x; x = y; y = t; }
|
|
104
73
|
if (pos == 0) { return x; }
|
|
105
74
|
if (pos == 1) { return y; }
|
|
106
75
|
return z;
|
|
107
76
|
}
|
|
108
77
|
|
|
109
|
-
// ───
|
|
110
|
-
|
|
111
|
-
// avg3(a, b, c): integer average (truncated)
|
|
112
|
-
fn avg3(a: int, b: int, c: int): int {
|
|
113
|
-
return (a + b + c) / 3;
|
|
114
|
-
}
|
|
78
|
+
// ─── Weighted random choice ───────────────────────────────────────────────────
|
|
115
79
|
|
|
116
|
-
fn
|
|
117
|
-
|
|
80
|
+
fn weighted2(seed: int, w0: int, w1: int): int {
|
|
81
|
+
let total: int = w0 + w1;
|
|
82
|
+
let r: int = seed * 1664525 + 1013904223;
|
|
83
|
+
if (r < 0) { r = 0 - r; }
|
|
84
|
+
if (r % total < w0) { return 0; }
|
|
85
|
+
return 1;
|
|
118
86
|
}
|
|
119
87
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (
|
|
124
|
-
|
|
88
|
+
fn weighted3(seed: int, w0: int, w1: int, w2: int): int {
|
|
89
|
+
let total: int = w0 + w1 + w2;
|
|
90
|
+
let r: int = seed * 1664525 + 1013904223;
|
|
91
|
+
if (r < 0) { r = 0 - r; }
|
|
92
|
+
let v: int = r % total;
|
|
93
|
+
if (v < w0) { return 0; }
|
|
94
|
+
if (v < w0 + w1) { return 1; }
|
|
95
|
+
return 2;
|
|
125
96
|
}
|