redscript-mc 1.2.29 → 2.0.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.
Files changed (274) hide show
  1. package/.claude/commands/build-test.md +10 -0
  2. package/.claude/commands/deploy-demo.md +12 -0
  3. package/.claude/commands/stage-status.md +13 -0
  4. package/.claude/settings.json +12 -0
  5. package/.github/workflows/ci.yml +1 -0
  6. package/CLAUDE.md +231 -0
  7. package/README.md +29 -28
  8. package/README.zh.md +28 -28
  9. package/demo.gif +0 -0
  10. package/dist/cli.js +2 -554
  11. package/dist/compile.js +2 -266
  12. package/dist/index.js +2 -159
  13. package/dist/lexer/index.js +9 -1
  14. package/dist/lowering/index.js +22 -5
  15. package/dist/src/__tests__/cli.test.d.ts +1 -0
  16. package/dist/src/__tests__/cli.test.js +104 -0
  17. package/dist/src/__tests__/codegen.test.d.ts +1 -0
  18. package/dist/src/__tests__/codegen.test.js +152 -0
  19. package/dist/src/__tests__/compile-all.test.d.ts +10 -0
  20. package/dist/src/__tests__/compile-all.test.js +108 -0
  21. package/dist/src/__tests__/dce.test.d.ts +1 -0
  22. package/dist/src/__tests__/dce.test.js +102 -0
  23. package/dist/src/__tests__/diagnostics.test.d.ts +4 -0
  24. package/dist/src/__tests__/diagnostics.test.js +177 -0
  25. package/dist/src/__tests__/e2e.test.d.ts +6 -0
  26. package/dist/src/__tests__/e2e.test.js +1789 -0
  27. package/dist/src/__tests__/entity-types.test.d.ts +1 -0
  28. package/dist/src/__tests__/entity-types.test.js +203 -0
  29. package/dist/src/__tests__/formatter.test.d.ts +1 -0
  30. package/dist/src/__tests__/formatter.test.js +40 -0
  31. package/dist/src/__tests__/lexer.test.d.ts +1 -0
  32. package/dist/src/__tests__/lexer.test.js +343 -0
  33. package/dist/src/__tests__/lowering.test.d.ts +1 -0
  34. package/dist/src/__tests__/lowering.test.js +1015 -0
  35. package/dist/src/__tests__/macro.test.d.ts +8 -0
  36. package/dist/src/__tests__/macro.test.js +306 -0
  37. package/dist/src/__tests__/mc-integration.test.d.ts +12 -0
  38. package/dist/src/__tests__/mc-integration.test.js +817 -0
  39. package/dist/src/__tests__/mc-syntax.test.d.ts +1 -0
  40. package/dist/src/__tests__/mc-syntax.test.js +124 -0
  41. package/dist/src/__tests__/nbt.test.d.ts +1 -0
  42. package/dist/src/__tests__/nbt.test.js +82 -0
  43. package/dist/src/__tests__/optimizer-advanced.test.d.ts +1 -0
  44. package/dist/src/__tests__/optimizer-advanced.test.js +124 -0
  45. package/dist/src/__tests__/optimizer.test.d.ts +1 -0
  46. package/dist/src/__tests__/optimizer.test.js +149 -0
  47. package/dist/src/__tests__/parser.test.d.ts +1 -0
  48. package/dist/src/__tests__/parser.test.js +807 -0
  49. package/dist/src/__tests__/repl.test.d.ts +1 -0
  50. package/dist/src/__tests__/repl.test.js +27 -0
  51. package/dist/src/__tests__/runtime.test.d.ts +1 -0
  52. package/dist/src/__tests__/runtime.test.js +289 -0
  53. package/dist/src/__tests__/stdlib-advanced.test.d.ts +4 -0
  54. package/dist/src/__tests__/stdlib-advanced.test.js +374 -0
  55. package/dist/src/__tests__/stdlib-bigint.test.d.ts +7 -0
  56. package/dist/src/__tests__/stdlib-bigint.test.js +426 -0
  57. package/dist/src/__tests__/stdlib-math.test.d.ts +7 -0
  58. package/dist/src/__tests__/stdlib-math.test.js +351 -0
  59. package/dist/src/__tests__/stdlib-vec.test.d.ts +4 -0
  60. package/dist/src/__tests__/stdlib-vec.test.js +263 -0
  61. package/dist/src/__tests__/structure-optimizer.test.d.ts +1 -0
  62. package/dist/src/__tests__/structure-optimizer.test.js +33 -0
  63. package/dist/src/__tests__/typechecker.test.d.ts +1 -0
  64. package/dist/src/__tests__/typechecker.test.js +552 -0
  65. package/dist/src/__tests__/var-allocator.test.d.ts +1 -0
  66. package/dist/src/__tests__/var-allocator.test.js +69 -0
  67. package/dist/src/ast/types.d.ts +515 -0
  68. package/dist/src/ast/types.js +9 -0
  69. package/dist/src/builtins/metadata.d.ts +36 -0
  70. package/dist/src/builtins/metadata.js +1014 -0
  71. package/dist/src/cli.d.ts +11 -0
  72. package/dist/src/cli.js +443 -0
  73. package/dist/src/codegen/cmdblock/index.d.ts +26 -0
  74. package/dist/src/codegen/cmdblock/index.js +45 -0
  75. package/dist/src/codegen/mcfunction/index.d.ts +40 -0
  76. package/dist/src/codegen/mcfunction/index.js +606 -0
  77. package/dist/src/codegen/structure/index.d.ts +24 -0
  78. package/dist/src/codegen/structure/index.js +279 -0
  79. package/dist/src/codegen/var-allocator.d.ts +45 -0
  80. package/dist/src/codegen/var-allocator.js +104 -0
  81. package/dist/src/compile.d.ts +37 -0
  82. package/dist/src/compile.js +165 -0
  83. package/dist/src/diagnostics/index.d.ts +44 -0
  84. package/dist/src/diagnostics/index.js +140 -0
  85. package/dist/src/events/types.d.ts +35 -0
  86. package/dist/src/events/types.js +59 -0
  87. package/dist/src/formatter/index.d.ts +1 -0
  88. package/dist/src/formatter/index.js +26 -0
  89. package/dist/src/index.d.ts +22 -0
  90. package/dist/src/index.js +45 -0
  91. package/dist/src/ir/builder.d.ts +33 -0
  92. package/dist/src/ir/builder.js +99 -0
  93. package/dist/src/ir/types.d.ts +132 -0
  94. package/dist/src/ir/types.js +15 -0
  95. package/dist/src/lexer/index.d.ts +37 -0
  96. package/dist/src/lexer/index.js +569 -0
  97. package/dist/src/lowering/index.d.ts +188 -0
  98. package/dist/src/lowering/index.js +3405 -0
  99. package/dist/src/mc-test/client.d.ts +128 -0
  100. package/dist/src/mc-test/client.js +174 -0
  101. package/dist/src/mc-test/runner.d.ts +28 -0
  102. package/dist/src/mc-test/runner.js +151 -0
  103. package/dist/src/mc-test/setup.d.ts +11 -0
  104. package/dist/src/mc-test/setup.js +98 -0
  105. package/dist/src/mc-validator/index.d.ts +17 -0
  106. package/dist/src/mc-validator/index.js +322 -0
  107. package/dist/src/nbt/index.d.ts +86 -0
  108. package/dist/src/nbt/index.js +250 -0
  109. package/dist/src/optimizer/commands.d.ts +38 -0
  110. package/dist/src/optimizer/commands.js +451 -0
  111. package/dist/src/optimizer/dce.d.ts +34 -0
  112. package/dist/src/optimizer/dce.js +639 -0
  113. package/dist/src/optimizer/passes.d.ts +34 -0
  114. package/dist/src/optimizer/passes.js +243 -0
  115. package/dist/src/optimizer/structure.d.ts +9 -0
  116. package/dist/src/optimizer/structure.js +356 -0
  117. package/dist/src/parser/index.d.ts +93 -0
  118. package/dist/src/parser/index.js +1687 -0
  119. package/dist/src/repl.d.ts +16 -0
  120. package/dist/src/repl.js +165 -0
  121. package/dist/src/runtime/index.d.ts +107 -0
  122. package/dist/src/runtime/index.js +1409 -0
  123. package/dist/src/typechecker/index.d.ts +61 -0
  124. package/dist/src/typechecker/index.js +1034 -0
  125. package/dist/src/types/entity-hierarchy.d.ts +29 -0
  126. package/dist/src/types/entity-hierarchy.js +107 -0
  127. package/dist/src2/__tests__/e2e/basic.test.d.ts +8 -0
  128. package/dist/src2/__tests__/e2e/basic.test.js +140 -0
  129. package/dist/src2/__tests__/e2e/macros.test.d.ts +9 -0
  130. package/dist/src2/__tests__/e2e/macros.test.js +182 -0
  131. package/dist/src2/__tests__/e2e/migrate.test.d.ts +13 -0
  132. package/dist/src2/__tests__/e2e/migrate.test.js +2739 -0
  133. package/dist/src2/__tests__/hir/desugar.test.d.ts +1 -0
  134. package/dist/src2/__tests__/hir/desugar.test.js +234 -0
  135. package/dist/src2/__tests__/lir/lower.test.d.ts +1 -0
  136. package/dist/src2/__tests__/lir/lower.test.js +559 -0
  137. package/dist/src2/__tests__/lir/types.test.d.ts +1 -0
  138. package/dist/src2/__tests__/lir/types.test.js +185 -0
  139. package/dist/src2/__tests__/lir/verify.test.d.ts +1 -0
  140. package/dist/src2/__tests__/lir/verify.test.js +221 -0
  141. package/dist/src2/__tests__/mir/arithmetic.test.d.ts +1 -0
  142. package/dist/src2/__tests__/mir/arithmetic.test.js +130 -0
  143. package/dist/src2/__tests__/mir/control-flow.test.d.ts +1 -0
  144. package/dist/src2/__tests__/mir/control-flow.test.js +205 -0
  145. package/dist/src2/__tests__/mir/verify.test.d.ts +1 -0
  146. package/dist/src2/__tests__/mir/verify.test.js +223 -0
  147. package/dist/src2/__tests__/optimizer/block_merge.test.d.ts +1 -0
  148. package/dist/src2/__tests__/optimizer/block_merge.test.js +78 -0
  149. package/dist/src2/__tests__/optimizer/branch_simplify.test.d.ts +1 -0
  150. package/dist/src2/__tests__/optimizer/branch_simplify.test.js +58 -0
  151. package/dist/src2/__tests__/optimizer/constant_fold.test.d.ts +1 -0
  152. package/dist/src2/__tests__/optimizer/constant_fold.test.js +131 -0
  153. package/dist/src2/__tests__/optimizer/copy_prop.test.d.ts +1 -0
  154. package/dist/src2/__tests__/optimizer/copy_prop.test.js +91 -0
  155. package/dist/src2/__tests__/optimizer/dce.test.d.ts +1 -0
  156. package/dist/src2/__tests__/optimizer/dce.test.js +76 -0
  157. package/dist/src2/__tests__/optimizer/pipeline.test.d.ts +1 -0
  158. package/dist/src2/__tests__/optimizer/pipeline.test.js +102 -0
  159. package/dist/src2/emit/compile.d.ts +19 -0
  160. package/dist/src2/emit/compile.js +80 -0
  161. package/dist/src2/emit/index.d.ts +17 -0
  162. package/dist/src2/emit/index.js +172 -0
  163. package/dist/src2/hir/lower.d.ts +15 -0
  164. package/dist/src2/hir/lower.js +378 -0
  165. package/dist/src2/hir/types.d.ts +373 -0
  166. package/dist/src2/hir/types.js +16 -0
  167. package/dist/src2/lir/lower.d.ts +15 -0
  168. package/dist/src2/lir/lower.js +453 -0
  169. package/dist/src2/lir/types.d.ts +136 -0
  170. package/dist/src2/lir/types.js +11 -0
  171. package/dist/src2/lir/verify.d.ts +14 -0
  172. package/dist/src2/lir/verify.js +113 -0
  173. package/dist/src2/mir/lower.d.ts +9 -0
  174. package/dist/src2/mir/lower.js +1030 -0
  175. package/dist/src2/mir/macro.d.ts +22 -0
  176. package/dist/src2/mir/macro.js +168 -0
  177. package/dist/src2/mir/types.d.ts +183 -0
  178. package/dist/src2/mir/types.js +11 -0
  179. package/dist/src2/mir/verify.d.ts +16 -0
  180. package/dist/src2/mir/verify.js +216 -0
  181. package/dist/src2/optimizer/block_merge.d.ts +12 -0
  182. package/dist/src2/optimizer/block_merge.js +84 -0
  183. package/dist/src2/optimizer/branch_simplify.d.ts +9 -0
  184. package/dist/src2/optimizer/branch_simplify.js +28 -0
  185. package/dist/src2/optimizer/constant_fold.d.ts +10 -0
  186. package/dist/src2/optimizer/constant_fold.js +85 -0
  187. package/dist/src2/optimizer/copy_prop.d.ts +9 -0
  188. package/dist/src2/optimizer/copy_prop.js +113 -0
  189. package/dist/src2/optimizer/dce.d.ts +8 -0
  190. package/dist/src2/optimizer/dce.js +155 -0
  191. package/dist/src2/optimizer/pipeline.d.ts +10 -0
  192. package/dist/src2/optimizer/pipeline.js +42 -0
  193. package/dist/tsconfig.tsbuildinfo +1 -0
  194. package/docs/compiler-pipeline-redesign.md +2243 -0
  195. package/docs/optimization-ideas.md +1076 -0
  196. package/editors/vscode/package-lock.json +3 -3
  197. package/editors/vscode/package.json +1 -1
  198. package/examples/readme-demo.mcrs +44 -66
  199. package/jest.config.js +1 -1
  200. package/package.json +6 -5
  201. package/scripts/postbuild.js +15 -0
  202. package/src/__tests__/cli.test.ts +8 -220
  203. package/src/__tests__/dce.test.ts +11 -56
  204. package/src/__tests__/diagnostics.test.ts +59 -38
  205. package/src/__tests__/mc-integration.test.ts +1 -2
  206. package/src/ast/types.ts +6 -1
  207. package/src/cli.ts +29 -156
  208. package/src/compile.ts +6 -162
  209. package/src/index.ts +14 -178
  210. package/src/lexer/index.ts +9 -1
  211. package/src/mc-test/runner.ts +4 -3
  212. package/src/parser/index.ts +1 -1
  213. package/src/repl.ts +1 -1
  214. package/src/runtime/index.ts +1 -1
  215. package/src2/__tests__/e2e/basic.test.ts +154 -0
  216. package/src2/__tests__/e2e/macros.test.ts +199 -0
  217. package/src2/__tests__/e2e/migrate.test.ts +3008 -0
  218. package/src2/__tests__/hir/desugar.test.ts +263 -0
  219. package/src2/__tests__/lir/lower.test.ts +619 -0
  220. package/src2/__tests__/lir/types.test.ts +207 -0
  221. package/src2/__tests__/lir/verify.test.ts +249 -0
  222. package/src2/__tests__/mir/arithmetic.test.ts +156 -0
  223. package/src2/__tests__/mir/control-flow.test.ts +242 -0
  224. package/src2/__tests__/mir/verify.test.ts +254 -0
  225. package/src2/__tests__/optimizer/block_merge.test.ts +84 -0
  226. package/src2/__tests__/optimizer/branch_simplify.test.ts +64 -0
  227. package/src2/__tests__/optimizer/constant_fold.test.ts +145 -0
  228. package/src2/__tests__/optimizer/copy_prop.test.ts +99 -0
  229. package/src2/__tests__/optimizer/dce.test.ts +83 -0
  230. package/src2/__tests__/optimizer/pipeline.test.ts +116 -0
  231. package/src2/emit/compile.ts +99 -0
  232. package/src2/emit/index.ts +222 -0
  233. package/src2/hir/lower.ts +428 -0
  234. package/src2/hir/types.ts +216 -0
  235. package/src2/lir/lower.ts +556 -0
  236. package/src2/lir/types.ts +109 -0
  237. package/src2/lir/verify.ts +129 -0
  238. package/src2/mir/lower.ts +1160 -0
  239. package/src2/mir/macro.ts +167 -0
  240. package/src2/mir/types.ts +106 -0
  241. package/src2/mir/verify.ts +218 -0
  242. package/src2/optimizer/block_merge.ts +93 -0
  243. package/src2/optimizer/branch_simplify.ts +27 -0
  244. package/src2/optimizer/constant_fold.ts +88 -0
  245. package/src2/optimizer/copy_prop.ts +106 -0
  246. package/src2/optimizer/dce.ts +133 -0
  247. package/src2/optimizer/pipeline.ts +44 -0
  248. package/tsconfig.json +2 -2
  249. package/src/__tests__/codegen.test.ts +0 -161
  250. package/src/__tests__/e2e.test.ts +0 -2039
  251. package/src/__tests__/entity-types.test.ts +0 -236
  252. package/src/__tests__/lowering.test.ts +0 -1185
  253. package/src/__tests__/macro.test.ts +0 -343
  254. package/src/__tests__/nbt.test.ts +0 -58
  255. package/src/__tests__/optimizer-advanced.test.ts +0 -144
  256. package/src/__tests__/optimizer.test.ts +0 -162
  257. package/src/__tests__/runtime.test.ts +0 -305
  258. package/src/__tests__/stdlib-advanced.test.ts +0 -379
  259. package/src/__tests__/stdlib-bigint.test.ts +0 -427
  260. package/src/__tests__/stdlib-math.test.ts +0 -374
  261. package/src/__tests__/stdlib-vec.test.ts +0 -259
  262. package/src/__tests__/structure-optimizer.test.ts +0 -38
  263. package/src/__tests__/var-allocator.test.ts +0 -75
  264. package/src/codegen/cmdblock/index.ts +0 -63
  265. package/src/codegen/mcfunction/index.ts +0 -662
  266. package/src/codegen/structure/index.ts +0 -346
  267. package/src/codegen/var-allocator.ts +0 -104
  268. package/src/ir/builder.ts +0 -116
  269. package/src/ir/types.ts +0 -134
  270. package/src/lowering/index.ts +0 -3860
  271. package/src/optimizer/commands.ts +0 -534
  272. package/src/optimizer/dce.ts +0 -679
  273. package/src/optimizer/passes.ts +0 -250
  274. package/src/optimizer/structure.ts +0 -450
@@ -1,679 +0,0 @@
1
- import type { Block, Expr, FnDecl, Program, Stmt } from '../ast/types'
2
-
3
- interface ScopeEntry {
4
- id: string
5
- name: string
6
- }
7
-
8
- function copySpan<T extends object>(target: T, source: object): T {
9
- const descriptor = Object.getOwnPropertyDescriptor(source, 'span')
10
- if (descriptor) {
11
- Object.defineProperty(target, 'span', descriptor)
12
- }
13
- return target
14
- }
15
-
16
- function isConstantBoolean(expr: Expr): boolean | null {
17
- if (expr.kind === 'bool_lit') {
18
- return expr.value
19
- }
20
- return null
21
- }
22
-
23
- function isPureExpr(expr: Expr): boolean {
24
- switch (expr.kind) {
25
- case 'int_lit':
26
- case 'float_lit':
27
- case 'byte_lit':
28
- case 'short_lit':
29
- case 'long_lit':
30
- case 'double_lit':
31
- case 'rel_coord':
32
- case 'local_coord':
33
- case 'bool_lit':
34
- case 'str_lit':
35
- case 'mc_name':
36
- case 'range_lit':
37
- case 'selector':
38
- case 'ident':
39
- case 'blockpos':
40
- return true
41
- case 'str_interp':
42
- return expr.parts.every(part => typeof part === 'string' || isPureExpr(part))
43
- case 'f_string':
44
- return expr.parts.every(part => part.kind === 'text' || isPureExpr(part.expr))
45
- case 'binary':
46
- return isPureExpr(expr.left) && isPureExpr(expr.right)
47
- case 'is_check':
48
- return isPureExpr(expr.expr)
49
- case 'unary':
50
- return isPureExpr(expr.operand)
51
- case 'member':
52
- return isPureExpr(expr.obj)
53
- case 'index':
54
- return isPureExpr(expr.obj) && isPureExpr(expr.index)
55
- case 'array_lit':
56
- return expr.elements.every(isPureExpr)
57
- case 'struct_lit':
58
- return expr.fields.every(field => isPureExpr(field.value))
59
- case 'lambda':
60
- return true
61
- case 'assign':
62
- case 'member_assign':
63
- case 'call':
64
- case 'invoke':
65
- case 'static_call':
66
- return false
67
- }
68
- }
69
-
70
- export interface DCEWarning {
71
- message: string
72
- code: string
73
- line?: number
74
- col?: number
75
- filePath?: string
76
- }
77
-
78
- export class DeadCodeEliminator {
79
- private readonly functionMap = new Map<string, FnDecl>()
80
- private readonly reachableFunctions = new Set<string>()
81
- private readonly usedConstants = new Set<string>()
82
- private readonly localReads = new Set<string>()
83
- private readonly localDeclIds = new WeakMap<Stmt, string>()
84
- private localIdCounter = 0
85
- readonly warnings: DCEWarning[] = []
86
-
87
- eliminate(program: Program): Program {
88
- this.functionMap.clear()
89
- this.reachableFunctions.clear()
90
- this.usedConstants.clear()
91
- this.localReads.clear()
92
- this.localIdCounter = 0
93
- this.warnings.length = 0
94
-
95
- for (const fn of program.declarations) {
96
- this.functionMap.set(fn.name, fn)
97
- }
98
-
99
- const entryPoints = this.findEntryPoints(program)
100
- if (entryPoints.length === 0) {
101
- for (const fn of program.declarations) {
102
- this.markReachable(fn.name)
103
- }
104
- } else {
105
- for (const fnName of entryPoints) {
106
- this.markReachable(fnName)
107
- }
108
- }
109
-
110
- for (const global of program.globals) {
111
- this.collectExprRefs(global.init, [])
112
- }
113
-
114
- for (const implBlock of program.implBlocks) {
115
- for (const method of implBlock.methods) {
116
- this.collectFunctionRefs(method)
117
- }
118
- }
119
-
120
- return {
121
- ...program,
122
- declarations: program.declarations
123
- .filter(fn => this.reachableFunctions.has(fn.name))
124
- .map(fn => this.transformFunction(fn)),
125
- consts: program.consts.filter(constDecl => this.usedConstants.has(constDecl.name)),
126
- implBlocks: program.implBlocks.map(implBlock => ({
127
- ...implBlock,
128
- methods: implBlock.methods.map(method => this.transformFunction(method)),
129
- })),
130
- }
131
- }
132
-
133
- private findEntryPoints(program: Program): string[] {
134
- const entries = new Set<string>()
135
-
136
- for (const fn of program.declarations) {
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
- }
146
- }
147
-
148
- // Decorated functions are always entry points regardless of library mode or _ prefix
149
- if (fn.decorators.some(decorator => [
150
- 'tick',
151
- 'load',
152
- 'on',
153
- 'on_trigger',
154
- 'on_advancement',
155
- 'on_craft',
156
- 'on_death',
157
- 'on_login',
158
- 'on_join_team',
159
- 'keep',
160
- ].includes(decorator.name))) {
161
- entries.add(fn.name)
162
- }
163
- }
164
-
165
- return [...entries]
166
- }
167
-
168
- private markReachable(fnName: string): void {
169
- if (this.reachableFunctions.has(fnName)) {
170
- return
171
- }
172
-
173
- const fn = this.functionMap.get(fnName)
174
- if (!fn) {
175
- return
176
- }
177
-
178
- this.reachableFunctions.add(fnName)
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
- }
192
- }
193
-
194
- private collectFunctionRefs(fn: FnDecl): void {
195
- const scope: ScopeEntry[][] = [fn.params.map(param => ({ id: `param:${fn.name}:${param.name}`, name: param.name }))]
196
- for (const param of fn.params) {
197
- if (param.default) {
198
- this.collectExprRefs(param.default, scope)
199
- }
200
- }
201
- this.collectStmtRefs(fn.body, scope)
202
- }
203
-
204
- private collectStmtRefs(block: Block, scope: ScopeEntry[][]): void {
205
- scope.push([])
206
-
207
- for (const stmt of block) {
208
- this.collectStmtRef(stmt, scope)
209
- }
210
-
211
- scope.pop()
212
- }
213
-
214
- private collectStmtRef(stmt: Stmt, scope: ScopeEntry[][]): void {
215
- switch (stmt.kind) {
216
- case 'let': {
217
- this.collectExprRefs(stmt.init, scope)
218
- const id = `local:${stmt.name}:${this.localIdCounter++}:${(stmt.span?.line ?? 0)}:${(stmt.span?.col ?? 0)}`
219
- this.localDeclIds.set(stmt, id)
220
- scope[scope.length - 1].push({ id, name: stmt.name })
221
- break
222
- }
223
- case 'expr':
224
- this.collectExprRefs(stmt.expr, scope)
225
- break
226
- case 'return':
227
- if (stmt.value) {
228
- this.collectExprRefs(stmt.value, scope)
229
- }
230
- break
231
- case 'if': {
232
- this.collectExprRefs(stmt.cond, scope)
233
- const constant = isConstantBoolean(stmt.cond)
234
- if (constant === true) {
235
- this.collectStmtRefs(stmt.then, scope)
236
- } else if (constant === false) {
237
- if (stmt.else_) {
238
- this.collectStmtRefs(stmt.else_, scope)
239
- }
240
- } else {
241
- this.collectStmtRefs(stmt.then, scope)
242
- if (stmt.else_) {
243
- this.collectStmtRefs(stmt.else_, scope)
244
- }
245
- }
246
- break
247
- }
248
- case 'while':
249
- this.collectExprRefs(stmt.cond, scope)
250
- this.collectStmtRefs(stmt.body, scope)
251
- break
252
- case 'for':
253
- scope.push([])
254
- if (stmt.init) {
255
- this.collectStmtRef(stmt.init, scope)
256
- }
257
- this.collectExprRefs(stmt.cond, scope)
258
- this.collectExprRefs(stmt.step, scope)
259
- this.collectStmtRefs(stmt.body, scope)
260
- scope.pop()
261
- break
262
- case 'foreach':
263
- this.collectExprRefs(stmt.iterable, scope)
264
- scope.push([{ id: `foreach:${stmt.binding}:${stmt.span?.line ?? 0}:${stmt.span?.col ?? 0}`, name: stmt.binding }])
265
- this.collectStmtRefs(stmt.body, scope)
266
- scope.pop()
267
- break
268
- case 'for_range':
269
- this.collectExprRefs(stmt.start, scope)
270
- this.collectExprRefs(stmt.end, scope)
271
- scope.push([{ id: `range:${stmt.varName}:${stmt.span?.line ?? 0}:${stmt.span?.col ?? 0}`, name: stmt.varName }])
272
- this.collectStmtRefs(stmt.body, scope)
273
- scope.pop()
274
- break
275
- case 'match':
276
- this.collectExprRefs(stmt.expr, scope)
277
- for (const arm of stmt.arms) {
278
- if (arm.pattern) {
279
- this.collectExprRefs(arm.pattern, scope)
280
- }
281
- this.collectStmtRefs(arm.body, scope)
282
- }
283
- break
284
- case 'as_block':
285
- case 'at_block':
286
- case 'as_at':
287
- case 'execute':
288
- this.collectNestedStmtRefs(stmt, scope)
289
- break
290
- case 'raw':
291
- case 'break':
292
- case 'continue':
293
- break
294
- }
295
- }
296
-
297
- private collectNestedStmtRefs(
298
- stmt: Extract<Stmt, { kind: 'as_block' | 'at_block' | 'as_at' | 'execute' }>,
299
- scope: ScopeEntry[][]
300
- ): void {
301
- if (stmt.kind === 'execute') {
302
- for (const sub of stmt.subcommands) {
303
- if ('varName' in sub && sub.varName) {
304
- const resolved = this.resolveLocal(sub.varName, scope)
305
- if (resolved) {
306
- this.localReads.add(resolved.id)
307
- }
308
- }
309
- }
310
- }
311
- this.collectStmtRefs(stmt.body, scope)
312
- }
313
-
314
- private collectExprRefs(expr: Expr, scope: ScopeEntry[][]): void {
315
- switch (expr.kind) {
316
- case 'ident': {
317
- const resolved = this.resolveLocal(expr.name, scope)
318
- if (resolved) {
319
- this.localReads.add(resolved.id)
320
- } else {
321
- this.usedConstants.add(expr.name)
322
- }
323
- break
324
- }
325
- case 'call':
326
- {
327
- const resolved = this.resolveLocal(expr.fn, scope)
328
- if (resolved) {
329
- this.localReads.add(resolved.id)
330
- } else if (this.functionMap.has(expr.fn)) {
331
- this.markReachable(expr.fn)
332
- }
333
- }
334
- for (const arg of expr.args) {
335
- this.collectExprRefs(arg, scope)
336
- }
337
- break
338
- case 'static_call':
339
- for (const arg of expr.args) {
340
- this.collectExprRefs(arg, scope)
341
- }
342
- break
343
- case 'invoke':
344
- this.collectExprRefs(expr.callee, scope)
345
- for (const arg of expr.args) {
346
- this.collectExprRefs(arg, scope)
347
- }
348
- break
349
- case 'member':
350
- this.collectExprRefs(expr.obj, scope)
351
- break
352
- case 'member_assign':
353
- this.collectExprRefs(expr.obj, scope)
354
- this.collectExprRefs(expr.value, scope)
355
- break
356
- case 'index':
357
- this.collectExprRefs(expr.obj, scope)
358
- this.collectExprRefs(expr.index, scope)
359
- break
360
- case 'array_lit':
361
- expr.elements.forEach(element => this.collectExprRefs(element, scope))
362
- break
363
- case 'struct_lit':
364
- expr.fields.forEach(field => this.collectExprRefs(field.value, scope))
365
- break
366
- case 'binary':
367
- this.collectExprRefs(expr.left, scope)
368
- this.collectExprRefs(expr.right, scope)
369
- break
370
- case 'is_check':
371
- this.collectExprRefs(expr.expr, scope)
372
- break
373
- case 'unary':
374
- this.collectExprRefs(expr.operand, scope)
375
- break
376
- case 'assign': {
377
- this.collectExprRefs(expr.value, scope)
378
- break
379
- }
380
- case 'str_interp':
381
- expr.parts.forEach(part => {
382
- if (typeof part !== 'string') {
383
- this.collectExprRefs(part, scope)
384
- }
385
- })
386
- break
387
- case 'f_string':
388
- expr.parts.forEach(part => {
389
- if (part.kind === 'expr') {
390
- this.collectExprRefs(part.expr, scope)
391
- }
392
- })
393
- break
394
- case 'lambda': {
395
- const lambdaScope: ScopeEntry[][] = [
396
- ...scope.map(entries => [...entries]),
397
- expr.params.map(param => ({ id: `lambda:${param.name}:${expr.span?.line ?? 0}:${expr.span?.col ?? 0}`, name: param.name })),
398
- ]
399
- if (Array.isArray(expr.body)) {
400
- this.collectStmtRefs(expr.body, lambdaScope)
401
- } else {
402
- this.collectExprRefs(expr.body, lambdaScope)
403
- }
404
- break
405
- }
406
- case 'blockpos':
407
- case 'bool_lit':
408
- case 'byte_lit':
409
- case 'double_lit':
410
- case 'float_lit':
411
- case 'int_lit':
412
- case 'long_lit':
413
- case 'mc_name':
414
- case 'range_lit':
415
- case 'selector':
416
- case 'short_lit':
417
- case 'str_lit':
418
- break
419
- }
420
- }
421
-
422
- private resolveLocal(name: string, scope: ScopeEntry[][]): ScopeEntry | null {
423
- for (let i = scope.length - 1; i >= 0; i--) {
424
- for (let j = scope[i].length - 1; j >= 0; j--) {
425
- if (scope[i][j].name === name) {
426
- return scope[i][j]
427
- }
428
- }
429
- }
430
- return null
431
- }
432
-
433
- private transformFunction(fn: FnDecl): FnDecl {
434
- const scope: ScopeEntry[][] = [fn.params.map(param => ({ id: `param:${fn.name}:${param.name}`, name: param.name }))]
435
- const body = this.transformBlock(fn.body, scope)
436
- return body === fn.body ? fn : copySpan({ ...fn, body }, fn)
437
- }
438
-
439
- private transformBlock(block: Block, scope: ScopeEntry[][]): Block {
440
- scope.push([])
441
- const transformed: Stmt[] = []
442
-
443
- for (const stmt of block) {
444
- const next = this.transformStmt(stmt, scope)
445
- transformed.push(...next)
446
- }
447
-
448
- scope.pop()
449
- return transformed
450
- }
451
-
452
- private transformStmt(stmt: Stmt, scope: ScopeEntry[][]): Stmt[] {
453
- switch (stmt.kind) {
454
- case 'let': {
455
- const init = this.transformExpr(stmt.init, scope)
456
- const id = this.localDeclIds.get(stmt) ?? `local:${stmt.name}:${stmt.span?.line ?? 0}:${stmt.span?.col ?? 0}`
457
- scope[scope.length - 1].push({ id, name: stmt.name })
458
- if (this.localReads.has(id)) {
459
- if (init === stmt.init) {
460
- return [stmt]
461
- }
462
- return [copySpan({ ...stmt, init }, stmt)]
463
- }
464
- // Unused variable - emit warning
465
- this.warnings.push({
466
- message: `Unused variable '${stmt.name}'`,
467
- code: 'W_UNUSED_VAR',
468
- line: stmt.span?.line,
469
- col: stmt.span?.col,
470
- })
471
- if (isPureExpr(init)) {
472
- return []
473
- }
474
- return [copySpan({ kind: 'expr', expr: init }, stmt)]
475
- }
476
- case 'expr': {
477
- const expr = this.transformExpr(stmt.expr, scope)
478
- if (expr.kind === 'assign') {
479
- const resolved = this.resolveLocal(expr.target, scope)
480
- if (resolved && !this.localReads.has(resolved.id)) {
481
- if (isPureExpr(expr.value)) {
482
- return []
483
- }
484
- return [copySpan({ kind: 'expr', expr: expr.value }, stmt)]
485
- }
486
- }
487
- if (expr === stmt.expr) {
488
- return [stmt]
489
- }
490
- return [copySpan({ ...stmt, expr }, stmt)]
491
- }
492
- case 'return': {
493
- if (!stmt.value) {
494
- return [stmt]
495
- }
496
- const value = this.transformExpr(stmt.value, scope)
497
- if (value === stmt.value) {
498
- return [stmt]
499
- }
500
- return [copySpan({ ...stmt, value }, stmt)]
501
- }
502
- case 'if': {
503
- const cond = this.transformExpr(stmt.cond, scope)
504
- const constant = isConstantBoolean(cond)
505
- if (constant === true) {
506
- return this.transformBlock(stmt.then, scope)
507
- }
508
- if (constant === false) {
509
- return stmt.else_ ? this.transformBlock(stmt.else_, scope) : []
510
- }
511
- const thenBlock = this.transformBlock(stmt.then, scope)
512
- const elseBlock = stmt.else_ ? this.transformBlock(stmt.else_, scope) : undefined
513
- if (cond === stmt.cond && thenBlock === stmt.then && elseBlock === stmt.else_) {
514
- return [stmt]
515
- }
516
- return [copySpan({ ...stmt, cond, then: thenBlock, else_: elseBlock }, stmt)]
517
- }
518
- case 'while': {
519
- const cond = this.transformExpr(stmt.cond, scope)
520
- if (isConstantBoolean(cond) === false) {
521
- return []
522
- }
523
- const body = this.transformBlock(stmt.body, scope)
524
- return [copySpan({ ...stmt, cond, body }, stmt)]
525
- }
526
- case 'for': {
527
- const forScope: ScopeEntry[][] = [...scope, []]
528
- const init = stmt.init ? this.transformStmt(stmt.init, forScope)[0] : undefined
529
- const cond = this.transformExpr(stmt.cond, forScope)
530
- if (isConstantBoolean(cond) === false) {
531
- return init ? [init] : []
532
- }
533
- const step = this.transformExpr(stmt.step, forScope)
534
- const body = this.transformBlock(stmt.body, forScope)
535
- return [copySpan({ ...stmt, init, cond, step, body }, stmt)]
536
- }
537
- case 'foreach': {
538
- const iterable = this.transformExpr(stmt.iterable, scope)
539
- const foreachScope: ScopeEntry[][] = [...scope, [{ id: `foreach:${stmt.binding}:${stmt.span?.line ?? 0}:${stmt.span?.col ?? 0}`, name: stmt.binding }]]
540
- const body = this.transformBlock(stmt.body, foreachScope)
541
- return [copySpan({ ...stmt, iterable, body }, stmt)]
542
- }
543
- case 'for_range': {
544
- const start = this.transformExpr(stmt.start, scope)
545
- const end = this.transformExpr(stmt.end, scope)
546
- const rangeScope: ScopeEntry[][] = [...scope, [{ id: `range:${stmt.varName}:${stmt.span?.line ?? 0}:${stmt.span?.col ?? 0}`, name: stmt.varName }]]
547
- const body = this.transformBlock(stmt.body, rangeScope)
548
- return [copySpan({ ...stmt, start, end, body }, stmt)]
549
- }
550
- case 'match': {
551
- const expr = this.transformExpr(stmt.expr, scope)
552
- const arms = stmt.arms.map(arm => ({
553
- pattern: arm.pattern ? this.transformExpr(arm.pattern, scope) : null,
554
- body: this.transformBlock(arm.body, scope),
555
- }))
556
- return [copySpan({ ...stmt, expr, arms }, stmt)]
557
- }
558
- case 'as_block':
559
- return [copySpan({ ...stmt, body: this.transformBlock(stmt.body, scope) }, stmt)]
560
- case 'at_block':
561
- return [copySpan({ ...stmt, body: this.transformBlock(stmt.body, scope) }, stmt)]
562
- case 'as_at':
563
- return [copySpan({ ...stmt, body: this.transformBlock(stmt.body, scope) }, stmt)]
564
- case 'execute':
565
- return [copySpan({ ...stmt, body: this.transformBlock(stmt.body, scope) }, stmt)]
566
- case 'raw':
567
- return [stmt]
568
- case 'break':
569
- return [stmt]
570
- case 'continue':
571
- return [stmt]
572
- }
573
- }
574
-
575
- private transformExpr(expr: Expr, scope: ScopeEntry[][]): Expr {
576
- switch (expr.kind) {
577
- case 'call':
578
- return copySpan({ ...expr, args: expr.args.map(arg => this.transformExpr(arg, scope)) }, expr)
579
- case 'static_call':
580
- return copySpan({ ...expr, args: expr.args.map(arg => this.transformExpr(arg, scope)) }, expr)
581
- case 'invoke':
582
- return copySpan({
583
- ...expr,
584
- callee: this.transformExpr(expr.callee, scope),
585
- args: expr.args.map(arg => this.transformExpr(arg, scope)),
586
- }, expr)
587
- case 'binary':
588
- return copySpan({
589
- ...expr,
590
- left: this.transformExpr(expr.left, scope),
591
- right: this.transformExpr(expr.right, scope),
592
- }, expr)
593
- case 'is_check':
594
- return copySpan({ ...expr, expr: this.transformExpr(expr.expr, scope) }, expr)
595
- case 'unary':
596
- return copySpan({ ...expr, operand: this.transformExpr(expr.operand, scope) }, expr)
597
- case 'assign':
598
- return copySpan({ ...expr, value: this.transformExpr(expr.value, scope) }, expr)
599
- case 'member':
600
- return copySpan({ ...expr, obj: this.transformExpr(expr.obj, scope) }, expr)
601
- case 'member_assign':
602
- return copySpan({
603
- ...expr,
604
- obj: this.transformExpr(expr.obj, scope),
605
- value: this.transformExpr(expr.value, scope),
606
- }, expr)
607
- case 'index':
608
- return copySpan({
609
- ...expr,
610
- obj: this.transformExpr(expr.obj, scope),
611
- index: this.transformExpr(expr.index, scope),
612
- }, expr)
613
- case 'array_lit':
614
- return copySpan({ ...expr, elements: expr.elements.map(element => this.transformExpr(element, scope)) }, expr)
615
- case 'struct_lit':
616
- return copySpan({
617
- ...expr,
618
- fields: expr.fields.map(field => ({ ...field, value: this.transformExpr(field.value, scope) })),
619
- }, expr)
620
- case 'str_interp':
621
- return copySpan({
622
- ...expr,
623
- parts: expr.parts.map(part => typeof part === 'string' ? part : this.transformExpr(part, scope)),
624
- }, expr)
625
- case 'f_string':
626
- return copySpan({
627
- ...expr,
628
- parts: expr.parts.map(part => part.kind === 'text' ? part : { kind: 'expr', expr: this.transformExpr(part.expr, scope) }),
629
- }, expr)
630
- case 'lambda': {
631
- const lambdaScope: ScopeEntry[][] = [
632
- ...scope.map(entries => [...entries]),
633
- expr.params.map(param => ({ id: `lambda:${param.name}:${expr.span?.line ?? 0}:${expr.span?.col ?? 0}`, name: param.name })),
634
- ]
635
- const body = Array.isArray(expr.body)
636
- ? this.transformBlock(expr.body, lambdaScope)
637
- : this.transformExpr(expr.body, lambdaScope)
638
- return copySpan({ ...expr, body }, expr)
639
- }
640
- case 'blockpos':
641
- case 'bool_lit':
642
- case 'byte_lit':
643
- case 'double_lit':
644
- case 'float_lit':
645
- case 'ident':
646
- case 'int_lit':
647
- case 'long_lit':
648
- case 'mc_name':
649
- case 'range_lit':
650
- case 'rel_coord':
651
- case 'local_coord':
652
- case 'selector':
653
- case 'short_lit':
654
- case 'str_lit':
655
- return expr
656
- }
657
- }
658
- }
659
-
660
- export function eliminateDeadCode(
661
- program: Program,
662
- sourceRanges?: import('../compile').SourceRange[]
663
- ): { program: Program; warnings: DCEWarning[] } {
664
- const eliminator = new DeadCodeEliminator()
665
- const result = eliminator.eliminate(program)
666
- let warnings = eliminator.warnings
667
-
668
- // Resolve combined-source line numbers back to original file + line
669
- if (sourceRanges && sourceRanges.length > 0) {
670
- const { resolveSourceLine } = require('../compile') as typeof import('../compile')
671
- warnings = warnings.map(w => {
672
- if (w.line == null) return w
673
- const resolved = resolveSourceLine(w.line, sourceRanges)
674
- return { ...w, line: resolved.line, filePath: resolved.filePath ?? w.filePath }
675
- })
676
- }
677
-
678
- return { program: result, warnings }
679
- }