redscript-mc 1.2.25 → 1.2.27
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/README.md +67 -9
- package/README.zh.md +61 -4
- package/dist/__tests__/cli.test.js +1 -1
- package/dist/__tests__/codegen.test.js +12 -6
- package/dist/__tests__/e2e.test.js +6 -6
- package/dist/__tests__/lowering.test.js +8 -8
- package/dist/__tests__/optimizer.test.js +31 -0
- package/dist/__tests__/stdlib-advanced.test.d.ts +4 -0
- package/dist/__tests__/stdlib-advanced.test.js +378 -0
- package/dist/__tests__/stdlib-bigint.test.d.ts +7 -0
- package/dist/__tests__/stdlib-bigint.test.js +428 -0
- package/dist/__tests__/stdlib-math.test.d.ts +7 -0
- package/dist/__tests__/stdlib-math.test.js +352 -0
- package/dist/__tests__/stdlib-vec.test.d.ts +4 -0
- package/dist/__tests__/stdlib-vec.test.js +264 -0
- package/dist/ast/types.d.ts +17 -1
- package/dist/codegen/mcfunction/index.js +154 -18
- package/dist/codegen/var-allocator.d.ts +17 -0
- package/dist/codegen/var-allocator.js +26 -0
- package/dist/compile.d.ts +14 -0
- package/dist/compile.js +62 -5
- package/dist/data/arena/function/__load.mcfunction +6 -0
- package/dist/data/arena/function/__tick.mcfunction +2 -0
- package/dist/data/arena/function/announce_leaders/else_1.mcfunction +3 -0
- package/dist/data/arena/function/announce_leaders/foreach_0/merge_2.mcfunction +1 -0
- package/dist/data/arena/function/announce_leaders/foreach_0/then_0.mcfunction +3 -0
- package/dist/data/arena/function/announce_leaders/foreach_0.mcfunction +7 -0
- package/dist/data/arena/function/announce_leaders/foreach_1/merge_2.mcfunction +1 -0
- package/dist/data/arena/function/announce_leaders/foreach_1/then_0.mcfunction +4 -0
- package/dist/data/arena/function/announce_leaders/foreach_1.mcfunction +6 -0
- package/dist/data/arena/function/announce_leaders/merge_2.mcfunction +1 -0
- package/dist/data/arena/function/announce_leaders/then_0.mcfunction +4 -0
- package/dist/data/arena/function/announce_leaders.mcfunction +6 -0
- package/dist/data/arena/function/arena_tick/merge_2.mcfunction +1 -0
- package/dist/data/arena/function/arena_tick/then_0.mcfunction +4 -0
- package/dist/data/arena/function/arena_tick.mcfunction +11 -0
- package/dist/data/counter/function/__load.mcfunction +5 -0
- package/dist/data/counter/function/__tick.mcfunction +2 -0
- package/dist/data/counter/function/counter_tick/merge_2.mcfunction +1 -0
- package/dist/data/counter/function/counter_tick/then_0.mcfunction +3 -0
- package/dist/data/counter/function/counter_tick.mcfunction +11 -0
- package/dist/data/gcd2/function/__load.mcfunction +3 -0
- package/dist/data/gcd2/function/abs/merge_2.mcfunction +3 -0
- package/dist/data/gcd2/function/abs/then_0.mcfunction +5 -0
- package/dist/data/gcd2/function/abs.mcfunction +7 -0
- package/dist/data/gcd2/function/gcd/loop_body_1.mcfunction +7 -0
- package/dist/data/gcd2/function/gcd/loop_check_0.mcfunction +5 -0
- package/dist/data/gcd2/function/gcd/loop_exit_2.mcfunction +3 -0
- package/dist/data/gcd2/function/gcd.mcfunction +14 -0
- package/dist/data/gcd3/function/__load.mcfunction +3 -0
- package/dist/data/gcd3/function/abs/merge_2.mcfunction +3 -0
- package/dist/data/gcd3/function/abs/then_0.mcfunction +5 -0
- package/dist/data/gcd3/function/abs.mcfunction +7 -0
- package/dist/data/gcd3/function/gcd/loop_body_1.mcfunction +7 -0
- package/dist/data/gcd3/function/gcd/loop_check_0.mcfunction +5 -0
- package/dist/data/gcd3/function/gcd/loop_exit_2.mcfunction +3 -0
- package/dist/data/gcd3/function/gcd.mcfunction +14 -0
- package/dist/data/gcd3/function/test.mcfunction +7 -0
- package/dist/data/gcd3nm/function/__load.mcfunction +3 -0
- package/dist/data/gcd3nm/function/abs/merge_2.mcfunction +3 -0
- package/dist/data/gcd3nm/function/abs/then_0.mcfunction +5 -0
- package/dist/data/gcd3nm/function/abs.mcfunction +7 -0
- package/dist/data/gcd3nm/function/gcd/loop_body_1.mcfunction +7 -0
- package/dist/data/gcd3nm/function/gcd/loop_check_0.mcfunction +5 -0
- package/dist/data/gcd3nm/function/gcd/loop_exit_2.mcfunction +3 -0
- package/dist/data/gcd3nm/function/gcd.mcfunction +14 -0
- package/dist/data/gcd3nm/function/test.mcfunction +7 -0
- package/dist/data/gcd_test/function/__load.mcfunction +3 -0
- package/dist/data/gcd_test/function/abs/merge_2.mcfunction +3 -0
- package/dist/data/gcd_test/function/abs/then_0.mcfunction +5 -0
- package/dist/data/gcd_test/function/abs.mcfunction +7 -0
- package/dist/data/gcd_test/function/gcd/loop_body_1.mcfunction +7 -0
- package/dist/data/gcd_test/function/gcd/loop_check_0.mcfunction +5 -0
- package/dist/data/gcd_test/function/gcd/loop_exit_2.mcfunction +3 -0
- package/dist/data/gcd_test/function/gcd.mcfunction +14 -0
- package/dist/data/isqrttest/function/__load.mcfunction +6 -0
- package/dist/data/isqrttest/function/isqrt/loop_body_4.mcfunction +12 -0
- package/dist/data/isqrttest/function/isqrt/loop_check_3.mcfunction +5 -0
- package/dist/data/isqrttest/function/isqrt/loop_exit_5.mcfunction +3 -0
- package/dist/data/isqrttest/function/isqrt/merge_2.mcfunction +4 -0
- package/dist/data/isqrttest/function/isqrt/merge_8.mcfunction +6 -0
- package/dist/data/isqrttest/function/isqrt/then_0.mcfunction +3 -0
- package/dist/data/isqrttest/function/isqrt/then_6.mcfunction +3 -0
- package/dist/data/isqrttest/function/isqrt.mcfunction +7 -0
- package/dist/data/isqrttest/function/test.mcfunction +6 -0
- package/dist/data/mathtest/function/__load.mcfunction +3 -0
- package/dist/data/mathtest/function/abs/merge_2.mcfunction +3 -0
- package/dist/data/mathtest/function/abs/then_0.mcfunction +5 -0
- package/dist/data/mathtest/function/abs.mcfunction +6 -0
- package/dist/data/mathtest/function/test.mcfunction +5 -0
- package/dist/data/minecraft/tags/function/load.json +5 -0
- package/dist/data/minecraft/tags/function/tick.json +5 -0
- package/dist/data/mypack/function/__load.mcfunction +13 -0
- package/dist/data/mypack/function/_atan_init.mcfunction +2 -0
- package/dist/data/mypack/function/abs/merge_2.mcfunction +3 -0
- package/dist/data/mypack/function/abs/then_0.mcfunction +5 -0
- package/dist/data/mypack/function/abs.mcfunction +6 -0
- package/dist/data/mypack/function/atan2_fixed/__sgi_1.mcfunction +2 -0
- package/dist/data/mypack/function/atan2_fixed/else_34.mcfunction +3 -0
- package/dist/data/mypack/function/atan2_fixed/loop_body_31.mcfunction +19 -0
- package/dist/data/mypack/function/atan2_fixed/loop_check_30.mcfunction +5 -0
- package/dist/data/mypack/function/atan2_fixed/loop_exit_32.mcfunction +6 -0
- package/dist/data/mypack/function/atan2_fixed/merge_11.mcfunction +6 -0
- package/dist/data/mypack/function/atan2_fixed/merge_14.mcfunction +3 -0
- package/dist/data/mypack/function/atan2_fixed/merge_17.mcfunction +6 -0
- package/dist/data/mypack/function/atan2_fixed/merge_2.mcfunction +5 -0
- package/dist/data/mypack/function/atan2_fixed/merge_20.mcfunction +5 -0
- package/dist/data/mypack/function/atan2_fixed/merge_23.mcfunction +5 -0
- package/dist/data/mypack/function/atan2_fixed/merge_26.mcfunction +6 -0
- package/dist/data/mypack/function/atan2_fixed/merge_29.mcfunction +4 -0
- package/dist/data/mypack/function/atan2_fixed/merge_38.mcfunction +5 -0
- package/dist/data/mypack/function/atan2_fixed/merge_41.mcfunction +5 -0
- package/dist/data/mypack/function/atan2_fixed/merge_44.mcfunction +5 -0
- package/dist/data/mypack/function/atan2_fixed/merge_47.mcfunction +5 -0
- package/dist/data/mypack/function/atan2_fixed/merge_5.mcfunction +5 -0
- package/dist/data/mypack/function/atan2_fixed/merge_8.mcfunction +3 -0
- package/dist/data/mypack/function/atan2_fixed/then_0.mcfunction +5 -0
- package/dist/data/mypack/function/atan2_fixed/then_12.mcfunction +3 -0
- package/dist/data/mypack/function/atan2_fixed/then_15.mcfunction +5 -0
- package/dist/data/mypack/function/atan2_fixed/then_18.mcfunction +5 -0
- package/dist/data/mypack/function/atan2_fixed/then_21.mcfunction +3 -0
- package/dist/data/mypack/function/atan2_fixed/then_24.mcfunction +3 -0
- package/dist/data/mypack/function/atan2_fixed/then_27.mcfunction +6 -0
- package/dist/data/mypack/function/atan2_fixed/then_3.mcfunction +3 -0
- package/dist/data/mypack/function/atan2_fixed/then_33.mcfunction +5 -0
- package/dist/data/mypack/function/atan2_fixed/then_36.mcfunction +5 -0
- package/dist/data/mypack/function/atan2_fixed/then_39.mcfunction +5 -0
- package/dist/data/mypack/function/atan2_fixed/then_42.mcfunction +3 -0
- package/dist/data/mypack/function/atan2_fixed/then_45.mcfunction +5 -0
- package/dist/data/mypack/function/atan2_fixed/then_6.mcfunction +3 -0
- package/dist/data/mypack/function/atan2_fixed/then_9.mcfunction +5 -0
- package/dist/data/mypack/function/atan2_fixed.mcfunction +7 -0
- package/dist/data/mypack/function/my_game.mcfunction +10 -0
- package/dist/data/quiz/function/__load.mcfunction +16 -0
- package/dist/data/quiz/function/__tick.mcfunction +6 -0
- package/dist/data/quiz/function/__trigger_quiz_a_dispatch.mcfunction +4 -0
- package/dist/data/quiz/function/__trigger_quiz_b_dispatch.mcfunction +4 -0
- package/dist/data/quiz/function/__trigger_quiz_c_dispatch.mcfunction +4 -0
- package/dist/data/quiz/function/__trigger_quiz_start_dispatch.mcfunction +4 -0
- package/dist/data/quiz/function/answer_a.mcfunction +4 -0
- package/dist/data/quiz/function/answer_b.mcfunction +4 -0
- package/dist/data/quiz/function/answer_c.mcfunction +4 -0
- package/dist/data/quiz/function/ask_question/else_1.mcfunction +5 -0
- package/dist/data/quiz/function/ask_question/else_4.mcfunction +5 -0
- package/dist/data/quiz/function/ask_question/else_7.mcfunction +4 -0
- package/dist/data/quiz/function/ask_question/merge_2.mcfunction +1 -0
- package/dist/data/quiz/function/ask_question/merge_5.mcfunction +2 -0
- package/dist/data/quiz/function/ask_question/merge_8.mcfunction +2 -0
- package/dist/data/quiz/function/ask_question/then_0.mcfunction +4 -0
- package/dist/data/quiz/function/ask_question/then_3.mcfunction +4 -0
- package/dist/data/quiz/function/ask_question/then_6.mcfunction +4 -0
- package/dist/data/quiz/function/ask_question.mcfunction +7 -0
- package/dist/data/quiz/function/finish_quiz.mcfunction +6 -0
- package/dist/data/quiz/function/handle_answer/else_1.mcfunction +5 -0
- package/dist/data/quiz/function/handle_answer/else_10.mcfunction +3 -0
- package/dist/data/quiz/function/handle_answer/else_16.mcfunction +3 -0
- package/dist/data/quiz/function/handle_answer/else_4.mcfunction +3 -0
- package/dist/data/quiz/function/handle_answer/else_7.mcfunction +5 -0
- package/dist/data/quiz/function/handle_answer/merge_11.mcfunction +2 -0
- package/dist/data/quiz/function/handle_answer/merge_14.mcfunction +2 -0
- package/dist/data/quiz/function/handle_answer/merge_17.mcfunction +2 -0
- package/dist/data/quiz/function/handle_answer/merge_2.mcfunction +8 -0
- package/dist/data/quiz/function/handle_answer/merge_5.mcfunction +2 -0
- package/dist/data/quiz/function/handle_answer/merge_8.mcfunction +2 -0
- package/dist/data/quiz/function/handle_answer/then_0.mcfunction +5 -0
- package/dist/data/quiz/function/handle_answer/then_12.mcfunction +5 -0
- package/dist/data/quiz/function/handle_answer/then_15.mcfunction +6 -0
- package/dist/data/quiz/function/handle_answer/then_3.mcfunction +6 -0
- package/dist/data/quiz/function/handle_answer/then_6.mcfunction +5 -0
- package/dist/data/quiz/function/handle_answer/then_9.mcfunction +6 -0
- package/dist/data/quiz/function/handle_answer.mcfunction +11 -0
- package/dist/data/quiz/function/start_quiz.mcfunction +5 -0
- package/dist/data/reqtest/function/__load.mcfunction +4 -0
- package/dist/data/reqtest/function/_table_init.mcfunction +2 -0
- package/dist/data/reqtest/function/no_trig.mcfunction +3 -0
- package/dist/data/reqtest/function/use_table.mcfunction +4 -0
- package/dist/data/reqtest2/function/__load.mcfunction +3 -0
- package/dist/data/reqtest2/function/no_trig.mcfunction +3 -0
- package/dist/data/runtime/function/__load.mcfunction +5 -0
- package/dist/data/runtime/function/__tick.mcfunction +2 -0
- package/dist/data/runtime/function/counter_tick/then_0.mcfunction +3 -0
- package/dist/data/runtime/function/counter_tick.mcfunction +13 -0
- package/dist/data/shop/function/__load.mcfunction +7 -0
- package/dist/data/shop/function/__tick.mcfunction +3 -0
- package/dist/data/shop/function/__trigger_shop_buy_dispatch.mcfunction +4 -0
- package/dist/data/shop/function/complete_purchase/else_1.mcfunction +5 -0
- package/dist/data/shop/function/complete_purchase/else_4.mcfunction +5 -0
- package/dist/data/shop/function/complete_purchase/else_7.mcfunction +3 -0
- package/dist/data/shop/function/complete_purchase/merge_2.mcfunction +2 -0
- package/dist/data/shop/function/complete_purchase/merge_5.mcfunction +2 -0
- package/dist/data/shop/function/complete_purchase/merge_8.mcfunction +2 -0
- package/dist/data/shop/function/complete_purchase/then_0.mcfunction +4 -0
- package/dist/data/shop/function/complete_purchase/then_3.mcfunction +4 -0
- package/dist/data/shop/function/complete_purchase/then_6.mcfunction +4 -0
- package/dist/data/shop/function/complete_purchase.mcfunction +7 -0
- package/dist/data/shop/function/handle_shop_trigger.mcfunction +3 -0
- package/dist/data/swap_test/function/__load.mcfunction +3 -0
- package/dist/data/swap_test/function/gcd_old/loop_body_1.mcfunction +7 -0
- package/dist/data/swap_test/function/gcd_old/loop_check_0.mcfunction +5 -0
- package/dist/data/swap_test/function/gcd_old/loop_exit_2.mcfunction +3 -0
- package/dist/data/swap_test/function/gcd_old.mcfunction +8 -0
- package/dist/data/turret/function/__load.mcfunction +5 -0
- package/dist/data/turret/function/__tick.mcfunction +4 -0
- package/dist/data/turret/function/__trigger_deploy_turret_dispatch.mcfunction +4 -0
- package/dist/data/turret/function/deploy_turret.mcfunction +8 -0
- package/dist/data/turret/function/turret_tick/at_1.mcfunction +2 -0
- package/dist/data/turret/function/turret_tick/foreach_0.mcfunction +2 -0
- package/dist/data/turret/function/turret_tick/foreach_2.mcfunction +2 -0
- package/dist/data/turret/function/turret_tick/tick_body.mcfunction +3 -0
- package/dist/data/turret/function/turret_tick/tick_skip.mcfunction +1 -0
- package/dist/data/turret/function/turret_tick.mcfunction +5 -0
- package/dist/gcd2.map.json +15 -0
- package/dist/gcd3.map.json +17 -0
- package/dist/gcd_test.map.json +15 -0
- package/dist/index.js +20 -1
- package/dist/ir/types.d.ts +4 -0
- package/dist/isqrttest.map.json +15 -0
- package/dist/lexer/index.d.ts +1 -1
- package/dist/lexer/index.js +1 -0
- package/dist/lowering/index.d.ts +5 -0
- package/dist/lowering/index.js +154 -14
- package/dist/mathtest.map.json +6 -0
- package/dist/mypack.map.json +27 -0
- package/dist/optimizer/dce.js +21 -5
- package/dist/optimizer/passes.js +18 -6
- package/dist/optimizer/structure.js +7 -0
- package/dist/pack.mcmeta +6 -0
- package/dist/parser/index.d.ts +5 -0
- package/dist/parser/index.js +43 -2
- package/dist/reqtest.map.json +4 -0
- package/dist/reqtest2.map.json +4 -0
- package/dist/runtime/index.d.ts +6 -0
- package/dist/runtime/index.js +130 -9
- package/dist/runtime.map.json +7 -0
- package/dist/swap_test.map.json +14 -0
- package/editors/vscode/package-lock.json +3 -3
- package/editors/vscode/package.json +1 -1
- package/examples/showcase.mcrs +505 -0
- package/package.json +1 -1
- package/src/__tests__/cli.test.ts +1 -1
- package/src/__tests__/codegen.test.ts +12 -6
- package/src/__tests__/e2e.test.ts +6 -6
- package/src/__tests__/lowering.test.ts +8 -8
- package/src/__tests__/optimizer.test.ts +33 -0
- package/src/__tests__/stdlib-advanced.test.ts +379 -0
- package/src/__tests__/stdlib-bigint.test.ts +427 -0
- package/src/__tests__/stdlib-math.test.ts +374 -0
- package/src/__tests__/stdlib-vec.test.ts +259 -0
- package/src/ast/types.ts +11 -1
- package/src/codegen/mcfunction/index.ts +143 -19
- package/src/codegen/var-allocator.ts +29 -0
- package/src/compile.ts +72 -5
- package/src/index.ts +21 -1
- package/src/ir/types.ts +2 -0
- package/src/lexer/index.ts +2 -1
- package/src/lowering/index.ts +171 -14
- package/src/optimizer/dce.ts +22 -5
- package/src/optimizer/passes.ts +18 -5
- package/src/optimizer/structure.ts +6 -1
- package/src/parser/index.ts +47 -2
- package/src/runtime/index.ts +130 -10
- package/src/stdlib/advanced.mcrs +330 -0
- package/src/stdlib/bigint.mcrs +205 -0
- package/src/stdlib/math.mcrs +274 -21
- package/src/stdlib/vec.mcrs +246 -0
package/src/lowering/index.ts
CHANGED
|
@@ -106,6 +106,9 @@ const BUILTINS: Record<string, (args: string[]) => string | null> = {
|
|
|
106
106
|
setTimeout: () => null, // Special handling
|
|
107
107
|
setInterval: () => null, // Special handling
|
|
108
108
|
clearInterval: () => null, // Special handling
|
|
109
|
+
storage_get_int: () => null, // Special handling (dynamic NBT array read via macro)
|
|
110
|
+
storage_set_array: () => null, // Special handling (write literal NBT array to storage)
|
|
111
|
+
storage_set_int: () => null, // Special handling (dynamic NBT array write via macro)
|
|
109
112
|
}
|
|
110
113
|
|
|
111
114
|
export interface Warning {
|
|
@@ -215,6 +218,14 @@ export class Lowering {
|
|
|
215
218
|
private implMethods: Map<string, Map<string, { fn: FnDecl; loweredName: string }>> = new Map()
|
|
216
219
|
private specializedFunctions: Map<string, string> = new Map()
|
|
217
220
|
private currentFn: string = ''
|
|
221
|
+
|
|
222
|
+
/** Unique IR variable name for a local variable, scoped to the current function.
|
|
223
|
+
* Prevents cross-function scoreboard slot collisions: $fn_x ≠ $gn_x.
|
|
224
|
+
* Only applies to user-defined locals/params; internal slots ($p0, $ret) are
|
|
225
|
+
* intentionally global (calling convention). */
|
|
226
|
+
private fnVar(name: string): string {
|
|
227
|
+
return `$${this.currentFn}_${name}`
|
|
228
|
+
}
|
|
218
229
|
private currentStdlibCallSite?: StdlibCallSiteContext
|
|
219
230
|
private foreachCounter: number = 0
|
|
220
231
|
private lambdaCounter: number = 0
|
|
@@ -354,12 +365,24 @@ export class Lowering {
|
|
|
354
365
|
|
|
355
366
|
private preScanExpr(expr: Expr, paramNames: Set<string>, macroParams: Set<string>): void {
|
|
356
367
|
if (expr.kind === 'call' && BUILTINS[expr.fn] !== undefined) {
|
|
357
|
-
//
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
368
|
+
// Only trigger macro param detection for builtins that actually emit
|
|
369
|
+
// MC commands with $(param) inline in the current function body.
|
|
370
|
+
// Special-handled builtins (storage_get_int / storage_set_int / etc.) are
|
|
371
|
+
// declared as `() => null` — they create their own sub-functions for macro
|
|
372
|
+
// indirection and do NOT require the surrounding function to be a macro.
|
|
373
|
+
const handler = BUILTINS[expr.fn]!
|
|
374
|
+
const isSpecialHandled: boolean = (() => {
|
|
375
|
+
try { return (handler as () => string | null)() === null } catch { return false }
|
|
376
|
+
})()
|
|
377
|
+
if (!isSpecialHandled) {
|
|
378
|
+
for (const arg of expr.args) {
|
|
379
|
+
if (arg.kind === 'ident' && paramNames.has(arg.name)) {
|
|
380
|
+
macroParams.add(arg.name)
|
|
381
|
+
}
|
|
361
382
|
}
|
|
362
383
|
}
|
|
384
|
+
// Always recurse into args for nested calls/expressions
|
|
385
|
+
for (const arg of expr.args) this.preScanExpr(arg, paramNames, macroParams)
|
|
363
386
|
return
|
|
364
387
|
}
|
|
365
388
|
// Recurse into sub-expressions for other call types
|
|
@@ -617,12 +640,12 @@ export class Lowering {
|
|
|
617
640
|
continue
|
|
618
641
|
}
|
|
619
642
|
|
|
620
|
-
this.varMap.set(param.name,
|
|
643
|
+
this.varMap.set(param.name, this.fnVar(param.name))
|
|
621
644
|
}
|
|
622
645
|
} else {
|
|
623
646
|
for (const param of runtimeParams) {
|
|
624
647
|
const paramName = param.name
|
|
625
|
-
this.varMap.set(paramName,
|
|
648
|
+
this.varMap.set(paramName, this.fnVar(paramName))
|
|
626
649
|
this.varTypes.set(paramName, this.normalizeType(param.type))
|
|
627
650
|
}
|
|
628
651
|
}
|
|
@@ -635,11 +658,15 @@ export class Lowering {
|
|
|
635
658
|
// Start entry block
|
|
636
659
|
this.builder.startBlock('entry')
|
|
637
660
|
|
|
638
|
-
// Copy params from
|
|
661
|
+
// Copy params from the parameter-passing slots to named local variables.
|
|
662
|
+
// Use { kind: 'param', index: i } so the codegen resolves to
|
|
663
|
+
// alloc.internal('p{i}') consistently in both mangle and no-mangle modes,
|
|
664
|
+
// avoiding the slot-collision between the internal register and a user variable
|
|
665
|
+
// named 'p0'/'p1' that occurred with { kind: 'var', name: '$p0' }.
|
|
639
666
|
for (let i = 0; i < runtimeParams.length; i++) {
|
|
640
667
|
const paramName = runtimeParams[i].name
|
|
641
|
-
const varName =
|
|
642
|
-
this.builder.emitAssign(varName, { kind: '
|
|
668
|
+
const varName = this.fnVar(paramName)
|
|
669
|
+
this.builder.emitAssign(varName, { kind: 'param', index: i })
|
|
643
670
|
}
|
|
644
671
|
|
|
645
672
|
if (staticEventDec) {
|
|
@@ -647,7 +674,7 @@ export class Lowering {
|
|
|
647
674
|
const param = fn.params[i]
|
|
648
675
|
const expected = eventParamSpecs[i]
|
|
649
676
|
if (expected?.type.kind === 'named' && expected.type.name !== 'string') {
|
|
650
|
-
this.builder.emitAssign(
|
|
677
|
+
this.builder.emitAssign(this.fnVar(param.name), { kind: 'const', value: 0 })
|
|
651
678
|
}
|
|
652
679
|
}
|
|
653
680
|
}
|
|
@@ -716,6 +743,23 @@ export class Lowering {
|
|
|
716
743
|
irFn.isLoadInit = true
|
|
717
744
|
}
|
|
718
745
|
|
|
746
|
+
// @requires("dep_fn") — when this function is compiled in, dep_fn is also
|
|
747
|
+
// called from __load. The dep_fn itself does NOT need @load; it can be a
|
|
748
|
+
// private (_) function that only runs at load time when this fn is used.
|
|
749
|
+
const requiredLoads: string[] = []
|
|
750
|
+
for (const d of fn.decorators) {
|
|
751
|
+
if (d.name === 'require_on_load') {
|
|
752
|
+
for (const arg of d.rawArgs ?? []) {
|
|
753
|
+
if (arg.kind === 'string') {
|
|
754
|
+
requiredLoads.push(arg.value)
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
if (requiredLoads.length > 0) {
|
|
760
|
+
irFn.requiredLoads = requiredLoads
|
|
761
|
+
}
|
|
762
|
+
|
|
719
763
|
// Handle tick rate counter if needed
|
|
720
764
|
if (tickRate && tickRate > 1) {
|
|
721
765
|
this.wrapWithTickRate(irFn, tickRate)
|
|
@@ -860,7 +904,7 @@ export class Lowering {
|
|
|
860
904
|
)
|
|
861
905
|
}
|
|
862
906
|
|
|
863
|
-
const varName =
|
|
907
|
+
const varName = this.fnVar(stmt.name)
|
|
864
908
|
this.varMap.set(stmt.name, varName)
|
|
865
909
|
|
|
866
910
|
// Track variable type
|
|
@@ -1196,7 +1240,7 @@ export class Lowering {
|
|
|
1196
1240
|
}
|
|
1197
1241
|
|
|
1198
1242
|
private lowerForRangeStmt(stmt: Extract<Stmt, { kind: 'for_range' }>): void {
|
|
1199
|
-
const loopVar =
|
|
1243
|
+
const loopVar = this.fnVar(stmt.varName)
|
|
1200
1244
|
const subFnName = `${this.currentFn}/__for_${this.foreachCounter++}`
|
|
1201
1245
|
|
|
1202
1246
|
// Initialize loop variable
|
|
@@ -1385,7 +1429,7 @@ export class Lowering {
|
|
|
1385
1429
|
}
|
|
1386
1430
|
|
|
1387
1431
|
const arrayType = this.inferExprType(stmt.iterable)
|
|
1388
|
-
const bindingVar =
|
|
1432
|
+
const bindingVar = this.fnVar(stmt.binding)
|
|
1389
1433
|
const indexVar = this.builder.freshTemp()
|
|
1390
1434
|
const lengthVar = this.builder.freshTemp()
|
|
1391
1435
|
const condVar = this.builder.freshTemp()
|
|
@@ -2543,6 +2587,119 @@ export class Lowering {
|
|
|
2543
2587
|
return { kind: 'var', name: dst }
|
|
2544
2588
|
}
|
|
2545
2589
|
|
|
2590
|
+
// storage_get_int(storage_ns, array_key, index) -> int
|
|
2591
|
+
// Reads one element from an NBT int-array stored in data storage.
|
|
2592
|
+
// storage_ns : e.g. "math:tables"
|
|
2593
|
+
// array_key : e.g. "sin"
|
|
2594
|
+
// index : integer index (const or runtime)
|
|
2595
|
+
//
|
|
2596
|
+
// Const index: execute store result score $dst rs run data get storage math:tables sin[N] 1
|
|
2597
|
+
// Runtime index: macro sub-function via rs:heap, mirrors readArrayElement.
|
|
2598
|
+
if (name === 'storage_get_int') {
|
|
2599
|
+
const storageNs = this.exprToString(args[0]) // "math:tables"
|
|
2600
|
+
const arrayKey = this.exprToString(args[1]) // "sin"
|
|
2601
|
+
const indexOperand = this.lowerExpr(args[2])
|
|
2602
|
+
const dst = this.builder.freshTemp()
|
|
2603
|
+
|
|
2604
|
+
if (indexOperand.kind === 'const') {
|
|
2605
|
+
this.builder.emitRaw(
|
|
2606
|
+
`execute store result score ${dst} rs run data get storage ${storageNs} ${arrayKey}[${indexOperand.value}] 1`
|
|
2607
|
+
)
|
|
2608
|
+
} else {
|
|
2609
|
+
// Runtime index: store the index into rs:heap under a unique key,
|
|
2610
|
+
// then call a macro sub-function that uses $(key) to index the array.
|
|
2611
|
+
const macroKey = `__sgi_${this.foreachCounter++}`
|
|
2612
|
+
const subFnName = `${this.currentFn}/__sgi_${this.foreachCounter++}`
|
|
2613
|
+
const indexVar = indexOperand.kind === 'var'
|
|
2614
|
+
? indexOperand.name
|
|
2615
|
+
: this.operandToVar(indexOperand)
|
|
2616
|
+
this.builder.emitRaw(
|
|
2617
|
+
`execute store result storage rs:heap ${macroKey} int 1 run scoreboard players get ${indexVar} rs`
|
|
2618
|
+
)
|
|
2619
|
+
this.builder.emitRaw(`function ${this.namespace}:${subFnName} with storage rs:heap`)
|
|
2620
|
+
// Prefix \x01 is a sentinel for the MC macro '$' line-start marker.
|
|
2621
|
+
// We avoid using literal '$execute' here so the pre-alloc pass
|
|
2622
|
+
// doesn't mistakenly register 'execute' as a scoreboard variable.
|
|
2623
|
+
// Codegen replaces \x01 → '$' when emitting the mc function file.
|
|
2624
|
+
this.emitRawSubFunction(
|
|
2625
|
+
subFnName,
|
|
2626
|
+
`\x01execute store result score ${dst} rs run data get storage ${storageNs} ${arrayKey}[$(${macroKey})] 1`
|
|
2627
|
+
)
|
|
2628
|
+
}
|
|
2629
|
+
return { kind: 'var', name: dst }
|
|
2630
|
+
}
|
|
2631
|
+
|
|
2632
|
+
// storage_set_array(storage_ns, array_key, nbt_array_literal)
|
|
2633
|
+
// Writes a literal NBT int array to data storage (used in @load for tables).
|
|
2634
|
+
// storage_set_array("math:tables", "sin", "[0, 17, 35, ...]")
|
|
2635
|
+
if (name === 'storage_set_array') {
|
|
2636
|
+
const storageNs = this.exprToString(args[0])
|
|
2637
|
+
const arrayKey = this.exprToString(args[1])
|
|
2638
|
+
const nbtLiteral = this.exprToString(args[2])
|
|
2639
|
+
this.builder.emitRaw(
|
|
2640
|
+
`data modify storage ${storageNs} ${arrayKey} set value ${nbtLiteral}`
|
|
2641
|
+
)
|
|
2642
|
+
return { kind: 'const', value: 0 }
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
// storage_set_int(storage_ns, array_key, index, value) -> void
|
|
2646
|
+
// Writes one integer element into an NBT int-array stored in data storage.
|
|
2647
|
+
// storage_ns : e.g. "rs:bigint"
|
|
2648
|
+
// array_key : e.g. "a"
|
|
2649
|
+
// index : element index (const or runtime)
|
|
2650
|
+
// value : integer value to write (const or runtime)
|
|
2651
|
+
//
|
|
2652
|
+
// Const index + const value:
|
|
2653
|
+
// execute store result storage <ns> <key>[N] int 1 run scoreboard players set $const_V rs V
|
|
2654
|
+
// Runtime index or value: macro sub-function via rs:heap
|
|
2655
|
+
if (name === 'storage_set_int') {
|
|
2656
|
+
const storageNs = this.exprToString(args[0])
|
|
2657
|
+
const arrayKey = this.exprToString(args[1])
|
|
2658
|
+
const indexOperand = this.lowerExpr(args[2])
|
|
2659
|
+
const valueOperand = this.lowerExpr(args[3])
|
|
2660
|
+
|
|
2661
|
+
if (indexOperand.kind === 'const') {
|
|
2662
|
+
// Static index — use execute store result to write to the fixed slot
|
|
2663
|
+
const valVar = valueOperand.kind === 'var'
|
|
2664
|
+
? valueOperand.name
|
|
2665
|
+
: this.operandToVar(valueOperand)
|
|
2666
|
+
this.builder.emitRaw(
|
|
2667
|
+
`execute store result storage ${storageNs} ${arrayKey}[${indexOperand.value}] int 1 run scoreboard players get ${valVar} rs`
|
|
2668
|
+
)
|
|
2669
|
+
} else {
|
|
2670
|
+
// Runtime index: we need a macro sub-function.
|
|
2671
|
+
// Store index + value into rs:heap, call macro that does:
|
|
2672
|
+
// $data modify storage <ns> <key>[$(idx_key)] set value $(val_key)
|
|
2673
|
+
const macroIdxKey = `__ssi_i_${this.foreachCounter++}`
|
|
2674
|
+
const macroValKey = `__ssi_v_${this.foreachCounter++}` // kept to pin valVar in optimizer
|
|
2675
|
+
const subFnName = `${this.currentFn}/__ssi_${this.foreachCounter++}`
|
|
2676
|
+
const indexVar = indexOperand.kind === 'var'
|
|
2677
|
+
? indexOperand.name
|
|
2678
|
+
: this.operandToVar(indexOperand)
|
|
2679
|
+
const valVar = valueOperand.kind === 'var'
|
|
2680
|
+
? valueOperand.name
|
|
2681
|
+
: this.operandToVar(valueOperand)
|
|
2682
|
+
this.builder.emitRaw(
|
|
2683
|
+
`execute store result storage rs:heap ${macroIdxKey} int 1 run scoreboard players get ${indexVar} rs`
|
|
2684
|
+
)
|
|
2685
|
+
// Pin valVar in the optimizer's read-set so the assignment is not dead-code-eliminated.
|
|
2686
|
+
// The value is stored to rs:heap but NOT used by the macro (the macro reads the scoreboard
|
|
2687
|
+
// slot directly to avoid the MC 'data modify set value $(n)' macro substitution bug).
|
|
2688
|
+
this.builder.emitRaw(
|
|
2689
|
+
`execute store result storage rs:heap ${macroValKey} int 1 run scoreboard players get ${valVar} rs`
|
|
2690
|
+
)
|
|
2691
|
+
this.builder.emitRaw(`function ${this.namespace}:${subFnName} with storage rs:heap`)
|
|
2692
|
+
// Use execute store result (not 'data modify set value $(val)') to avoid MC macro
|
|
2693
|
+
// substitution bugs with numeric values. The scoreboard slot ${valVar} is hardcoded
|
|
2694
|
+
// into the macro sub-function at compile time — only the array index is macro-substituted.
|
|
2695
|
+
this.emitRawSubFunction(
|
|
2696
|
+
subFnName,
|
|
2697
|
+
`\x01execute store result storage ${storageNs} ${arrayKey}[$(${macroIdxKey})] int 1 run scoreboard players get ${valVar} rs`
|
|
2698
|
+
)
|
|
2699
|
+
}
|
|
2700
|
+
return { kind: 'const', value: 0 }
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2546
2703
|
// data_merge(target, nbt) — merge NBT into entity/block/storage
|
|
2547
2704
|
// data_merge(@s, { Invisible: 1b, Silent: 1b })
|
|
2548
2705
|
if (name === 'data_merge') {
|
|
@@ -3447,7 +3604,7 @@ export class Lowering {
|
|
|
3447
3604
|
this.builder.emitRaw(`function ${this.namespace}:${subFnName} with storage rs:heap`)
|
|
3448
3605
|
this.emitRawSubFunction(
|
|
3449
3606
|
subFnName,
|
|
3450
|
-
|
|
3607
|
+
`\x01execute store result score ${dst} rs run data get storage rs:heap ${arrayName}[$(${macroKey})]`
|
|
3451
3608
|
)
|
|
3452
3609
|
return { kind: 'var', name: dst }
|
|
3453
3610
|
}
|
package/src/optimizer/dce.ts
CHANGED
|
@@ -134,13 +134,18 @@ export class DeadCodeEliminator {
|
|
|
134
134
|
const entries = new Set<string>()
|
|
135
135
|
|
|
136
136
|
for (const fn of program.declarations) {
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
|
|
140
|
-
|
|
137
|
+
// Library functions (from `module library;` or `librarySources`) are
|
|
138
|
+
// NOT MC entry points — they're only kept if reachable from user code.
|
|
139
|
+
// Exception: decorators like @tick / @load / @on / @keep always force inclusion.
|
|
140
|
+
if (!fn.isLibraryFn) {
|
|
141
|
+
// All top-level non-library functions are entry points (callable via /function)
|
|
142
|
+
// Exception: functions starting with _ are considered private/internal
|
|
143
|
+
if (!fn.name.startsWith('_')) {
|
|
144
|
+
entries.add(fn.name)
|
|
145
|
+
}
|
|
141
146
|
}
|
|
142
147
|
|
|
143
|
-
// Decorated functions are always entry points
|
|
148
|
+
// Decorated functions are always entry points regardless of library mode or _ prefix
|
|
144
149
|
if (fn.decorators.some(decorator => [
|
|
145
150
|
'tick',
|
|
146
151
|
'load',
|
|
@@ -172,6 +177,18 @@ export class DeadCodeEliminator {
|
|
|
172
177
|
|
|
173
178
|
this.reachableFunctions.add(fnName)
|
|
174
179
|
this.collectFunctionRefs(fn)
|
|
180
|
+
|
|
181
|
+
// @requires("dep") — when fn is reachable, its required dependencies are
|
|
182
|
+
// also pulled into the reachable set so they survive DCE.
|
|
183
|
+
for (const decorator of fn.decorators) {
|
|
184
|
+
if (decorator.name === 'require_on_load') {
|
|
185
|
+
for (const arg of decorator.rawArgs ?? []) {
|
|
186
|
+
if (arg.kind === 'string') {
|
|
187
|
+
this.markReachable(arg.value)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
175
192
|
}
|
|
176
193
|
|
|
177
194
|
private collectFunctionRefs(fn: FnDecl): void {
|
package/src/optimizer/passes.ts
CHANGED
|
@@ -95,30 +95,43 @@ export function copyPropagation(fn: IRFunction): IRFunction {
|
|
|
95
95
|
return copies.get(op.name) ?? op
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Invalidate all copies that became stale because `written` was modified.
|
|
100
|
+
* When $y is overwritten, any mapping copies[$tmp] = $y is now stale:
|
|
101
|
+
* reading $tmp would return the OLD $y value via the copy, but $y now holds
|
|
102
|
+
* a different value. Remove both the direct entry (copies[$y]) and every
|
|
103
|
+
* reverse entry that points at $y.
|
|
104
|
+
*/
|
|
105
|
+
function invalidate(written: string): void {
|
|
106
|
+
copies.delete(written)
|
|
107
|
+
for (const [k, v] of copies) {
|
|
108
|
+
if (v.kind === 'var' && v.name === written) copies.delete(k)
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
98
112
|
const newInstrs: IRInstr[] = []
|
|
99
113
|
for (const instr of block.instrs) {
|
|
100
114
|
switch (instr.op) {
|
|
101
115
|
case 'assign': {
|
|
102
116
|
const src = resolve(instr.src)
|
|
117
|
+
invalidate(instr.dst)
|
|
103
118
|
// Only propagate scalars (var or const), not storage
|
|
104
119
|
if (src.kind === 'var' || src.kind === 'const') {
|
|
105
120
|
copies.set(instr.dst, src)
|
|
106
|
-
} else {
|
|
107
|
-
copies.delete(instr.dst)
|
|
108
121
|
}
|
|
109
122
|
newInstrs.push({ ...instr, src })
|
|
110
123
|
break
|
|
111
124
|
}
|
|
112
125
|
case 'binop':
|
|
113
|
-
|
|
126
|
+
invalidate(instr.dst)
|
|
114
127
|
newInstrs.push({ ...instr, lhs: resolve(instr.lhs), rhs: resolve(instr.rhs) })
|
|
115
128
|
break
|
|
116
129
|
case 'cmp':
|
|
117
|
-
|
|
130
|
+
invalidate(instr.dst)
|
|
118
131
|
newInstrs.push({ ...instr, lhs: resolve(instr.lhs), rhs: resolve(instr.rhs) })
|
|
119
132
|
break
|
|
120
133
|
case 'call':
|
|
121
|
-
if (instr.dst)
|
|
134
|
+
if (instr.dst) invalidate(instr.dst)
|
|
122
135
|
newInstrs.push({ ...instr, args: instr.args.map(resolve) })
|
|
123
136
|
break
|
|
124
137
|
default:
|
|
@@ -24,7 +24,8 @@ function varRef(name: string): string {
|
|
|
24
24
|
function operandToScore(op: Operand): string {
|
|
25
25
|
if (op.kind === 'var') return `${varRef(op.name)} ${OBJ}`
|
|
26
26
|
if (op.kind === 'const') return `$const_${op.value} ${OBJ}`
|
|
27
|
-
|
|
27
|
+
if (op.kind === 'param') return `$p${op.index} ${OBJ}`
|
|
28
|
+
throw new Error(`Cannot convert storage operand to score: ${(op as any).path}`)
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
function emitInstr(instr: IRInstr, namespace: string): IRCommand[] {
|
|
@@ -38,6 +39,10 @@ function emitInstr(instr: IRInstr, namespace: string): IRCommand[] {
|
|
|
38
39
|
commands.push({
|
|
39
40
|
cmd: `scoreboard players operation ${varRef(instr.dst)} ${OBJ} = ${varRef(instr.src.name)} ${OBJ}`,
|
|
40
41
|
})
|
|
42
|
+
} else if (instr.src.kind === 'param') {
|
|
43
|
+
commands.push({
|
|
44
|
+
cmd: `scoreboard players operation ${varRef(instr.dst)} ${OBJ} = $p${instr.src.index} ${OBJ}`,
|
|
45
|
+
})
|
|
41
46
|
} else {
|
|
42
47
|
commands.push({
|
|
43
48
|
cmd: `execute store result score ${varRef(instr.dst)} ${OBJ} run data get storage ${instr.src.path}`,
|
package/src/parser/index.ts
CHANGED
|
@@ -76,6 +76,11 @@ export class Parser {
|
|
|
76
76
|
private pos: number = 0
|
|
77
77
|
private sourceLines: string[]
|
|
78
78
|
private filePath?: string
|
|
79
|
+
/** Set to true once `module library;` is seen — all subsequent fn declarations
|
|
80
|
+
* will be marked isLibraryFn=true. When library sources are parsed via the
|
|
81
|
+
* `librarySources` compile option, each source is parsed by its own fresh
|
|
82
|
+
* Parser instance, so this flag never bleeds into user code. */
|
|
83
|
+
private inLibraryMode: boolean = false
|
|
79
84
|
|
|
80
85
|
constructor(tokens: Token[], source?: string, filePath?: string) {
|
|
81
86
|
this.tokens = tokens
|
|
@@ -169,6 +174,7 @@ export class Parser {
|
|
|
169
174
|
const implBlocks: ImplBlock[] = []
|
|
170
175
|
const enums: EnumDecl[] = []
|
|
171
176
|
const consts: ConstDecl[] = []
|
|
177
|
+
let isLibrary = false
|
|
172
178
|
|
|
173
179
|
// Check for namespace declaration
|
|
174
180
|
if (this.check('namespace')) {
|
|
@@ -178,6 +184,20 @@ export class Parser {
|
|
|
178
184
|
this.expect(';')
|
|
179
185
|
}
|
|
180
186
|
|
|
187
|
+
// Check for module declaration: `module library;`
|
|
188
|
+
// Library-mode: all functions parsed from this point are marked isLibraryFn=true.
|
|
189
|
+
// When using the `librarySources` compile option, each library source is parsed
|
|
190
|
+
// by its own fresh Parser — so this flag never bleeds into user code.
|
|
191
|
+
if (this.check('module')) {
|
|
192
|
+
this.advance()
|
|
193
|
+
const modKind = this.expect('ident')
|
|
194
|
+
if (modKind.value === 'library') {
|
|
195
|
+
isLibrary = true
|
|
196
|
+
this.inLibraryMode = true
|
|
197
|
+
}
|
|
198
|
+
this.expect(';')
|
|
199
|
+
}
|
|
200
|
+
|
|
181
201
|
// Parse struct and function declarations
|
|
182
202
|
while (!this.check('eof')) {
|
|
183
203
|
if (this.check('let')) {
|
|
@@ -199,7 +219,7 @@ export class Parser {
|
|
|
199
219
|
}
|
|
200
220
|
}
|
|
201
221
|
|
|
202
|
-
return { namespace, globals, declarations, structs, implBlocks, enums, consts }
|
|
222
|
+
return { namespace, globals, declarations, structs, implBlocks, enums, consts, isLibrary }
|
|
203
223
|
}
|
|
204
224
|
|
|
205
225
|
// -------------------------------------------------------------------------
|
|
@@ -322,7 +342,11 @@ export class Parser {
|
|
|
322
342
|
|
|
323
343
|
const body = this.parseBlock()
|
|
324
344
|
|
|
325
|
-
|
|
345
|
+
const fn: import('../ast/types').FnDecl = this.withLoc(
|
|
346
|
+
{ name, params, returnType, decorators, body, isLibraryFn: this.inLibraryMode || undefined },
|
|
347
|
+
fnToken,
|
|
348
|
+
)
|
|
349
|
+
return fn
|
|
326
350
|
}
|
|
327
351
|
|
|
328
352
|
/** Parse a `declare fn name(params): returnType;` stub — no body, just discard. */
|
|
@@ -397,6 +421,27 @@ export class Parser {
|
|
|
397
421
|
}
|
|
398
422
|
}
|
|
399
423
|
|
|
424
|
+
// @require_on_load(fn_name) — when this fn is used, fn_name is called from __load.
|
|
425
|
+
// Accepts bare identifiers (with optional leading _) or quoted strings.
|
|
426
|
+
if (name === 'require_on_load') {
|
|
427
|
+
const rawArgs: NonNullable<Decorator['rawArgs']> = []
|
|
428
|
+
for (const part of argsStr.split(',')) {
|
|
429
|
+
const trimmed = part.trim()
|
|
430
|
+
// Bare identifier: @require_on_load(_math_init)
|
|
431
|
+
const identMatch = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)$/)
|
|
432
|
+
if (identMatch) {
|
|
433
|
+
rawArgs.push({ kind: 'string', value: identMatch[1] })
|
|
434
|
+
} else {
|
|
435
|
+
// Quoted string fallback: @require_on_load("_math_init")
|
|
436
|
+
const strMatch = trimmed.match(/^"([^"]*)"$/)
|
|
437
|
+
if (strMatch) {
|
|
438
|
+
rawArgs.push({ kind: 'string', value: strMatch[1] })
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return { name, rawArgs }
|
|
443
|
+
}
|
|
444
|
+
|
|
400
445
|
// Handle key=value format (e.g., rate=20)
|
|
401
446
|
for (const part of argsStr.split(',')) {
|
|
402
447
|
const [key, val] = part.split('=').map(s => s.trim())
|