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.
Files changed (265) hide show
  1. package/README.md +67 -9
  2. package/README.zh.md +61 -4
  3. package/dist/__tests__/cli.test.js +1 -1
  4. package/dist/__tests__/codegen.test.js +12 -6
  5. package/dist/__tests__/e2e.test.js +6 -6
  6. package/dist/__tests__/lowering.test.js +8 -8
  7. package/dist/__tests__/optimizer.test.js +31 -0
  8. package/dist/__tests__/stdlib-advanced.test.d.ts +4 -0
  9. package/dist/__tests__/stdlib-advanced.test.js +378 -0
  10. package/dist/__tests__/stdlib-bigint.test.d.ts +7 -0
  11. package/dist/__tests__/stdlib-bigint.test.js +428 -0
  12. package/dist/__tests__/stdlib-math.test.d.ts +7 -0
  13. package/dist/__tests__/stdlib-math.test.js +352 -0
  14. package/dist/__tests__/stdlib-vec.test.d.ts +4 -0
  15. package/dist/__tests__/stdlib-vec.test.js +264 -0
  16. package/dist/ast/types.d.ts +17 -1
  17. package/dist/codegen/mcfunction/index.js +154 -18
  18. package/dist/codegen/var-allocator.d.ts +17 -0
  19. package/dist/codegen/var-allocator.js +26 -0
  20. package/dist/compile.d.ts +14 -0
  21. package/dist/compile.js +62 -5
  22. package/dist/data/arena/function/__load.mcfunction +6 -0
  23. package/dist/data/arena/function/__tick.mcfunction +2 -0
  24. package/dist/data/arena/function/announce_leaders/else_1.mcfunction +3 -0
  25. package/dist/data/arena/function/announce_leaders/foreach_0/merge_2.mcfunction +1 -0
  26. package/dist/data/arena/function/announce_leaders/foreach_0/then_0.mcfunction +3 -0
  27. package/dist/data/arena/function/announce_leaders/foreach_0.mcfunction +7 -0
  28. package/dist/data/arena/function/announce_leaders/foreach_1/merge_2.mcfunction +1 -0
  29. package/dist/data/arena/function/announce_leaders/foreach_1/then_0.mcfunction +4 -0
  30. package/dist/data/arena/function/announce_leaders/foreach_1.mcfunction +6 -0
  31. package/dist/data/arena/function/announce_leaders/merge_2.mcfunction +1 -0
  32. package/dist/data/arena/function/announce_leaders/then_0.mcfunction +4 -0
  33. package/dist/data/arena/function/announce_leaders.mcfunction +6 -0
  34. package/dist/data/arena/function/arena_tick/merge_2.mcfunction +1 -0
  35. package/dist/data/arena/function/arena_tick/then_0.mcfunction +4 -0
  36. package/dist/data/arena/function/arena_tick.mcfunction +11 -0
  37. package/dist/data/counter/function/__load.mcfunction +5 -0
  38. package/dist/data/counter/function/__tick.mcfunction +2 -0
  39. package/dist/data/counter/function/counter_tick/merge_2.mcfunction +1 -0
  40. package/dist/data/counter/function/counter_tick/then_0.mcfunction +3 -0
  41. package/dist/data/counter/function/counter_tick.mcfunction +11 -0
  42. package/dist/data/gcd2/function/__load.mcfunction +3 -0
  43. package/dist/data/gcd2/function/abs/merge_2.mcfunction +3 -0
  44. package/dist/data/gcd2/function/abs/then_0.mcfunction +5 -0
  45. package/dist/data/gcd2/function/abs.mcfunction +7 -0
  46. package/dist/data/gcd2/function/gcd/loop_body_1.mcfunction +7 -0
  47. package/dist/data/gcd2/function/gcd/loop_check_0.mcfunction +5 -0
  48. package/dist/data/gcd2/function/gcd/loop_exit_2.mcfunction +3 -0
  49. package/dist/data/gcd2/function/gcd.mcfunction +14 -0
  50. package/dist/data/gcd3/function/__load.mcfunction +3 -0
  51. package/dist/data/gcd3/function/abs/merge_2.mcfunction +3 -0
  52. package/dist/data/gcd3/function/abs/then_0.mcfunction +5 -0
  53. package/dist/data/gcd3/function/abs.mcfunction +7 -0
  54. package/dist/data/gcd3/function/gcd/loop_body_1.mcfunction +7 -0
  55. package/dist/data/gcd3/function/gcd/loop_check_0.mcfunction +5 -0
  56. package/dist/data/gcd3/function/gcd/loop_exit_2.mcfunction +3 -0
  57. package/dist/data/gcd3/function/gcd.mcfunction +14 -0
  58. package/dist/data/gcd3/function/test.mcfunction +7 -0
  59. package/dist/data/gcd3nm/function/__load.mcfunction +3 -0
  60. package/dist/data/gcd3nm/function/abs/merge_2.mcfunction +3 -0
  61. package/dist/data/gcd3nm/function/abs/then_0.mcfunction +5 -0
  62. package/dist/data/gcd3nm/function/abs.mcfunction +7 -0
  63. package/dist/data/gcd3nm/function/gcd/loop_body_1.mcfunction +7 -0
  64. package/dist/data/gcd3nm/function/gcd/loop_check_0.mcfunction +5 -0
  65. package/dist/data/gcd3nm/function/gcd/loop_exit_2.mcfunction +3 -0
  66. package/dist/data/gcd3nm/function/gcd.mcfunction +14 -0
  67. package/dist/data/gcd3nm/function/test.mcfunction +7 -0
  68. package/dist/data/gcd_test/function/__load.mcfunction +3 -0
  69. package/dist/data/gcd_test/function/abs/merge_2.mcfunction +3 -0
  70. package/dist/data/gcd_test/function/abs/then_0.mcfunction +5 -0
  71. package/dist/data/gcd_test/function/abs.mcfunction +7 -0
  72. package/dist/data/gcd_test/function/gcd/loop_body_1.mcfunction +7 -0
  73. package/dist/data/gcd_test/function/gcd/loop_check_0.mcfunction +5 -0
  74. package/dist/data/gcd_test/function/gcd/loop_exit_2.mcfunction +3 -0
  75. package/dist/data/gcd_test/function/gcd.mcfunction +14 -0
  76. package/dist/data/isqrttest/function/__load.mcfunction +6 -0
  77. package/dist/data/isqrttest/function/isqrt/loop_body_4.mcfunction +12 -0
  78. package/dist/data/isqrttest/function/isqrt/loop_check_3.mcfunction +5 -0
  79. package/dist/data/isqrttest/function/isqrt/loop_exit_5.mcfunction +3 -0
  80. package/dist/data/isqrttest/function/isqrt/merge_2.mcfunction +4 -0
  81. package/dist/data/isqrttest/function/isqrt/merge_8.mcfunction +6 -0
  82. package/dist/data/isqrttest/function/isqrt/then_0.mcfunction +3 -0
  83. package/dist/data/isqrttest/function/isqrt/then_6.mcfunction +3 -0
  84. package/dist/data/isqrttest/function/isqrt.mcfunction +7 -0
  85. package/dist/data/isqrttest/function/test.mcfunction +6 -0
  86. package/dist/data/mathtest/function/__load.mcfunction +3 -0
  87. package/dist/data/mathtest/function/abs/merge_2.mcfunction +3 -0
  88. package/dist/data/mathtest/function/abs/then_0.mcfunction +5 -0
  89. package/dist/data/mathtest/function/abs.mcfunction +6 -0
  90. package/dist/data/mathtest/function/test.mcfunction +5 -0
  91. package/dist/data/minecraft/tags/function/load.json +5 -0
  92. package/dist/data/minecraft/tags/function/tick.json +5 -0
  93. package/dist/data/mypack/function/__load.mcfunction +13 -0
  94. package/dist/data/mypack/function/_atan_init.mcfunction +2 -0
  95. package/dist/data/mypack/function/abs/merge_2.mcfunction +3 -0
  96. package/dist/data/mypack/function/abs/then_0.mcfunction +5 -0
  97. package/dist/data/mypack/function/abs.mcfunction +6 -0
  98. package/dist/data/mypack/function/atan2_fixed/__sgi_1.mcfunction +2 -0
  99. package/dist/data/mypack/function/atan2_fixed/else_34.mcfunction +3 -0
  100. package/dist/data/mypack/function/atan2_fixed/loop_body_31.mcfunction +19 -0
  101. package/dist/data/mypack/function/atan2_fixed/loop_check_30.mcfunction +5 -0
  102. package/dist/data/mypack/function/atan2_fixed/loop_exit_32.mcfunction +6 -0
  103. package/dist/data/mypack/function/atan2_fixed/merge_11.mcfunction +6 -0
  104. package/dist/data/mypack/function/atan2_fixed/merge_14.mcfunction +3 -0
  105. package/dist/data/mypack/function/atan2_fixed/merge_17.mcfunction +6 -0
  106. package/dist/data/mypack/function/atan2_fixed/merge_2.mcfunction +5 -0
  107. package/dist/data/mypack/function/atan2_fixed/merge_20.mcfunction +5 -0
  108. package/dist/data/mypack/function/atan2_fixed/merge_23.mcfunction +5 -0
  109. package/dist/data/mypack/function/atan2_fixed/merge_26.mcfunction +6 -0
  110. package/dist/data/mypack/function/atan2_fixed/merge_29.mcfunction +4 -0
  111. package/dist/data/mypack/function/atan2_fixed/merge_38.mcfunction +5 -0
  112. package/dist/data/mypack/function/atan2_fixed/merge_41.mcfunction +5 -0
  113. package/dist/data/mypack/function/atan2_fixed/merge_44.mcfunction +5 -0
  114. package/dist/data/mypack/function/atan2_fixed/merge_47.mcfunction +5 -0
  115. package/dist/data/mypack/function/atan2_fixed/merge_5.mcfunction +5 -0
  116. package/dist/data/mypack/function/atan2_fixed/merge_8.mcfunction +3 -0
  117. package/dist/data/mypack/function/atan2_fixed/then_0.mcfunction +5 -0
  118. package/dist/data/mypack/function/atan2_fixed/then_12.mcfunction +3 -0
  119. package/dist/data/mypack/function/atan2_fixed/then_15.mcfunction +5 -0
  120. package/dist/data/mypack/function/atan2_fixed/then_18.mcfunction +5 -0
  121. package/dist/data/mypack/function/atan2_fixed/then_21.mcfunction +3 -0
  122. package/dist/data/mypack/function/atan2_fixed/then_24.mcfunction +3 -0
  123. package/dist/data/mypack/function/atan2_fixed/then_27.mcfunction +6 -0
  124. package/dist/data/mypack/function/atan2_fixed/then_3.mcfunction +3 -0
  125. package/dist/data/mypack/function/atan2_fixed/then_33.mcfunction +5 -0
  126. package/dist/data/mypack/function/atan2_fixed/then_36.mcfunction +5 -0
  127. package/dist/data/mypack/function/atan2_fixed/then_39.mcfunction +5 -0
  128. package/dist/data/mypack/function/atan2_fixed/then_42.mcfunction +3 -0
  129. package/dist/data/mypack/function/atan2_fixed/then_45.mcfunction +5 -0
  130. package/dist/data/mypack/function/atan2_fixed/then_6.mcfunction +3 -0
  131. package/dist/data/mypack/function/atan2_fixed/then_9.mcfunction +5 -0
  132. package/dist/data/mypack/function/atan2_fixed.mcfunction +7 -0
  133. package/dist/data/mypack/function/my_game.mcfunction +10 -0
  134. package/dist/data/quiz/function/__load.mcfunction +16 -0
  135. package/dist/data/quiz/function/__tick.mcfunction +6 -0
  136. package/dist/data/quiz/function/__trigger_quiz_a_dispatch.mcfunction +4 -0
  137. package/dist/data/quiz/function/__trigger_quiz_b_dispatch.mcfunction +4 -0
  138. package/dist/data/quiz/function/__trigger_quiz_c_dispatch.mcfunction +4 -0
  139. package/dist/data/quiz/function/__trigger_quiz_start_dispatch.mcfunction +4 -0
  140. package/dist/data/quiz/function/answer_a.mcfunction +4 -0
  141. package/dist/data/quiz/function/answer_b.mcfunction +4 -0
  142. package/dist/data/quiz/function/answer_c.mcfunction +4 -0
  143. package/dist/data/quiz/function/ask_question/else_1.mcfunction +5 -0
  144. package/dist/data/quiz/function/ask_question/else_4.mcfunction +5 -0
  145. package/dist/data/quiz/function/ask_question/else_7.mcfunction +4 -0
  146. package/dist/data/quiz/function/ask_question/merge_2.mcfunction +1 -0
  147. package/dist/data/quiz/function/ask_question/merge_5.mcfunction +2 -0
  148. package/dist/data/quiz/function/ask_question/merge_8.mcfunction +2 -0
  149. package/dist/data/quiz/function/ask_question/then_0.mcfunction +4 -0
  150. package/dist/data/quiz/function/ask_question/then_3.mcfunction +4 -0
  151. package/dist/data/quiz/function/ask_question/then_6.mcfunction +4 -0
  152. package/dist/data/quiz/function/ask_question.mcfunction +7 -0
  153. package/dist/data/quiz/function/finish_quiz.mcfunction +6 -0
  154. package/dist/data/quiz/function/handle_answer/else_1.mcfunction +5 -0
  155. package/dist/data/quiz/function/handle_answer/else_10.mcfunction +3 -0
  156. package/dist/data/quiz/function/handle_answer/else_16.mcfunction +3 -0
  157. package/dist/data/quiz/function/handle_answer/else_4.mcfunction +3 -0
  158. package/dist/data/quiz/function/handle_answer/else_7.mcfunction +5 -0
  159. package/dist/data/quiz/function/handle_answer/merge_11.mcfunction +2 -0
  160. package/dist/data/quiz/function/handle_answer/merge_14.mcfunction +2 -0
  161. package/dist/data/quiz/function/handle_answer/merge_17.mcfunction +2 -0
  162. package/dist/data/quiz/function/handle_answer/merge_2.mcfunction +8 -0
  163. package/dist/data/quiz/function/handle_answer/merge_5.mcfunction +2 -0
  164. package/dist/data/quiz/function/handle_answer/merge_8.mcfunction +2 -0
  165. package/dist/data/quiz/function/handle_answer/then_0.mcfunction +5 -0
  166. package/dist/data/quiz/function/handle_answer/then_12.mcfunction +5 -0
  167. package/dist/data/quiz/function/handle_answer/then_15.mcfunction +6 -0
  168. package/dist/data/quiz/function/handle_answer/then_3.mcfunction +6 -0
  169. package/dist/data/quiz/function/handle_answer/then_6.mcfunction +5 -0
  170. package/dist/data/quiz/function/handle_answer/then_9.mcfunction +6 -0
  171. package/dist/data/quiz/function/handle_answer.mcfunction +11 -0
  172. package/dist/data/quiz/function/start_quiz.mcfunction +5 -0
  173. package/dist/data/reqtest/function/__load.mcfunction +4 -0
  174. package/dist/data/reqtest/function/_table_init.mcfunction +2 -0
  175. package/dist/data/reqtest/function/no_trig.mcfunction +3 -0
  176. package/dist/data/reqtest/function/use_table.mcfunction +4 -0
  177. package/dist/data/reqtest2/function/__load.mcfunction +3 -0
  178. package/dist/data/reqtest2/function/no_trig.mcfunction +3 -0
  179. package/dist/data/runtime/function/__load.mcfunction +5 -0
  180. package/dist/data/runtime/function/__tick.mcfunction +2 -0
  181. package/dist/data/runtime/function/counter_tick/then_0.mcfunction +3 -0
  182. package/dist/data/runtime/function/counter_tick.mcfunction +13 -0
  183. package/dist/data/shop/function/__load.mcfunction +7 -0
  184. package/dist/data/shop/function/__tick.mcfunction +3 -0
  185. package/dist/data/shop/function/__trigger_shop_buy_dispatch.mcfunction +4 -0
  186. package/dist/data/shop/function/complete_purchase/else_1.mcfunction +5 -0
  187. package/dist/data/shop/function/complete_purchase/else_4.mcfunction +5 -0
  188. package/dist/data/shop/function/complete_purchase/else_7.mcfunction +3 -0
  189. package/dist/data/shop/function/complete_purchase/merge_2.mcfunction +2 -0
  190. package/dist/data/shop/function/complete_purchase/merge_5.mcfunction +2 -0
  191. package/dist/data/shop/function/complete_purchase/merge_8.mcfunction +2 -0
  192. package/dist/data/shop/function/complete_purchase/then_0.mcfunction +4 -0
  193. package/dist/data/shop/function/complete_purchase/then_3.mcfunction +4 -0
  194. package/dist/data/shop/function/complete_purchase/then_6.mcfunction +4 -0
  195. package/dist/data/shop/function/complete_purchase.mcfunction +7 -0
  196. package/dist/data/shop/function/handle_shop_trigger.mcfunction +3 -0
  197. package/dist/data/swap_test/function/__load.mcfunction +3 -0
  198. package/dist/data/swap_test/function/gcd_old/loop_body_1.mcfunction +7 -0
  199. package/dist/data/swap_test/function/gcd_old/loop_check_0.mcfunction +5 -0
  200. package/dist/data/swap_test/function/gcd_old/loop_exit_2.mcfunction +3 -0
  201. package/dist/data/swap_test/function/gcd_old.mcfunction +8 -0
  202. package/dist/data/turret/function/__load.mcfunction +5 -0
  203. package/dist/data/turret/function/__tick.mcfunction +4 -0
  204. package/dist/data/turret/function/__trigger_deploy_turret_dispatch.mcfunction +4 -0
  205. package/dist/data/turret/function/deploy_turret.mcfunction +8 -0
  206. package/dist/data/turret/function/turret_tick/at_1.mcfunction +2 -0
  207. package/dist/data/turret/function/turret_tick/foreach_0.mcfunction +2 -0
  208. package/dist/data/turret/function/turret_tick/foreach_2.mcfunction +2 -0
  209. package/dist/data/turret/function/turret_tick/tick_body.mcfunction +3 -0
  210. package/dist/data/turret/function/turret_tick/tick_skip.mcfunction +1 -0
  211. package/dist/data/turret/function/turret_tick.mcfunction +5 -0
  212. package/dist/gcd2.map.json +15 -0
  213. package/dist/gcd3.map.json +17 -0
  214. package/dist/gcd_test.map.json +15 -0
  215. package/dist/index.js +20 -1
  216. package/dist/ir/types.d.ts +4 -0
  217. package/dist/isqrttest.map.json +15 -0
  218. package/dist/lexer/index.d.ts +1 -1
  219. package/dist/lexer/index.js +1 -0
  220. package/dist/lowering/index.d.ts +5 -0
  221. package/dist/lowering/index.js +154 -14
  222. package/dist/mathtest.map.json +6 -0
  223. package/dist/mypack.map.json +27 -0
  224. package/dist/optimizer/dce.js +21 -5
  225. package/dist/optimizer/passes.js +18 -6
  226. package/dist/optimizer/structure.js +7 -0
  227. package/dist/pack.mcmeta +6 -0
  228. package/dist/parser/index.d.ts +5 -0
  229. package/dist/parser/index.js +43 -2
  230. package/dist/reqtest.map.json +4 -0
  231. package/dist/reqtest2.map.json +4 -0
  232. package/dist/runtime/index.d.ts +6 -0
  233. package/dist/runtime/index.js +130 -9
  234. package/dist/runtime.map.json +7 -0
  235. package/dist/swap_test.map.json +14 -0
  236. package/editors/vscode/package-lock.json +3 -3
  237. package/editors/vscode/package.json +1 -1
  238. package/examples/showcase.mcrs +505 -0
  239. package/package.json +1 -1
  240. package/src/__tests__/cli.test.ts +1 -1
  241. package/src/__tests__/codegen.test.ts +12 -6
  242. package/src/__tests__/e2e.test.ts +6 -6
  243. package/src/__tests__/lowering.test.ts +8 -8
  244. package/src/__tests__/optimizer.test.ts +33 -0
  245. package/src/__tests__/stdlib-advanced.test.ts +379 -0
  246. package/src/__tests__/stdlib-bigint.test.ts +427 -0
  247. package/src/__tests__/stdlib-math.test.ts +374 -0
  248. package/src/__tests__/stdlib-vec.test.ts +259 -0
  249. package/src/ast/types.ts +11 -1
  250. package/src/codegen/mcfunction/index.ts +143 -19
  251. package/src/codegen/var-allocator.ts +29 -0
  252. package/src/compile.ts +72 -5
  253. package/src/index.ts +21 -1
  254. package/src/ir/types.ts +2 -0
  255. package/src/lexer/index.ts +2 -1
  256. package/src/lowering/index.ts +171 -14
  257. package/src/optimizer/dce.ts +22 -5
  258. package/src/optimizer/passes.ts +18 -5
  259. package/src/optimizer/structure.ts +6 -1
  260. package/src/parser/index.ts +47 -2
  261. package/src/runtime/index.ts +130 -10
  262. package/src/stdlib/advanced.mcrs +330 -0
  263. package/src/stdlib/bigint.mcrs +205 -0
  264. package/src/stdlib/math.mcrs +274 -21
  265. package/src/stdlib/vec.mcrs +246 -0
@@ -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
- // All ident args to macro-aware builtins that are params → macro params
358
- for (const arg of expr.args) {
359
- if (arg.kind === 'ident' && paramNames.has(arg.name)) {
360
- macroParams.add(arg.name)
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, `$${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, `$${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 $p0, $p1, ... to named variables
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 = `$${paramName}`
642
- this.builder.emitAssign(varName, { kind: 'var', name: `$p${i}` })
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(`$${param.name}`, { kind: 'const', value: 0 })
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 = `$${stmt.name}`
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 = `$${stmt.varName}`
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 = `$${stmt.binding}`
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
- `$execute store result score ${dst} rs run data get storage rs:heap ${arrayName}[$(${macroKey})]`
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
  }
@@ -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
- // All top-level functions are entry points (callable via /function)
138
- // Exception: functions starting with _ are considered private/internal
139
- if (!fn.name.startsWith('_')) {
140
- entries.add(fn.name)
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 (even if prefixed with _)
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 {
@@ -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
- copies.delete(instr.dst)
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
- copies.delete(instr.dst)
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) copies.delete(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
- throw new Error(`Cannot convert storage operand to score: ${op.path}`)
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}`,
@@ -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
- return this.withLoc({ name, params, returnType, decorators, body }, fnToken)
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())