redscript-mc 1.2.30 → 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 (269) 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/demo.gif +0 -0
  8. package/dist/cli.js +2 -554
  9. package/dist/compile.js +2 -266
  10. package/dist/index.js +2 -159
  11. package/dist/lowering/index.js +5 -3
  12. package/dist/src/__tests__/cli.test.d.ts +1 -0
  13. package/dist/src/__tests__/cli.test.js +104 -0
  14. package/dist/src/__tests__/codegen.test.d.ts +1 -0
  15. package/dist/src/__tests__/codegen.test.js +152 -0
  16. package/dist/src/__tests__/compile-all.test.d.ts +10 -0
  17. package/dist/src/__tests__/compile-all.test.js +108 -0
  18. package/dist/src/__tests__/dce.test.d.ts +1 -0
  19. package/dist/src/__tests__/dce.test.js +102 -0
  20. package/dist/src/__tests__/diagnostics.test.d.ts +4 -0
  21. package/dist/src/__tests__/diagnostics.test.js +177 -0
  22. package/dist/src/__tests__/e2e.test.d.ts +6 -0
  23. package/dist/src/__tests__/e2e.test.js +1789 -0
  24. package/dist/src/__tests__/entity-types.test.d.ts +1 -0
  25. package/dist/src/__tests__/entity-types.test.js +203 -0
  26. package/dist/src/__tests__/formatter.test.d.ts +1 -0
  27. package/dist/src/__tests__/formatter.test.js +40 -0
  28. package/dist/src/__tests__/lexer.test.d.ts +1 -0
  29. package/dist/src/__tests__/lexer.test.js +343 -0
  30. package/dist/src/__tests__/lowering.test.d.ts +1 -0
  31. package/dist/src/__tests__/lowering.test.js +1015 -0
  32. package/dist/src/__tests__/macro.test.d.ts +8 -0
  33. package/dist/src/__tests__/macro.test.js +306 -0
  34. package/dist/src/__tests__/mc-integration.test.d.ts +12 -0
  35. package/dist/src/__tests__/mc-integration.test.js +817 -0
  36. package/dist/src/__tests__/mc-syntax.test.d.ts +1 -0
  37. package/dist/src/__tests__/mc-syntax.test.js +124 -0
  38. package/dist/src/__tests__/nbt.test.d.ts +1 -0
  39. package/dist/src/__tests__/nbt.test.js +82 -0
  40. package/dist/src/__tests__/optimizer-advanced.test.d.ts +1 -0
  41. package/dist/src/__tests__/optimizer-advanced.test.js +124 -0
  42. package/dist/src/__tests__/optimizer.test.d.ts +1 -0
  43. package/dist/src/__tests__/optimizer.test.js +149 -0
  44. package/dist/src/__tests__/parser.test.d.ts +1 -0
  45. package/dist/src/__tests__/parser.test.js +807 -0
  46. package/dist/src/__tests__/repl.test.d.ts +1 -0
  47. package/dist/src/__tests__/repl.test.js +27 -0
  48. package/dist/src/__tests__/runtime.test.d.ts +1 -0
  49. package/dist/src/__tests__/runtime.test.js +289 -0
  50. package/dist/src/__tests__/stdlib-advanced.test.d.ts +4 -0
  51. package/dist/src/__tests__/stdlib-advanced.test.js +374 -0
  52. package/dist/src/__tests__/stdlib-bigint.test.d.ts +7 -0
  53. package/dist/src/__tests__/stdlib-bigint.test.js +426 -0
  54. package/dist/src/__tests__/stdlib-math.test.d.ts +7 -0
  55. package/dist/src/__tests__/stdlib-math.test.js +351 -0
  56. package/dist/src/__tests__/stdlib-vec.test.d.ts +4 -0
  57. package/dist/src/__tests__/stdlib-vec.test.js +263 -0
  58. package/dist/src/__tests__/structure-optimizer.test.d.ts +1 -0
  59. package/dist/src/__tests__/structure-optimizer.test.js +33 -0
  60. package/dist/src/__tests__/typechecker.test.d.ts +1 -0
  61. package/dist/src/__tests__/typechecker.test.js +552 -0
  62. package/dist/src/__tests__/var-allocator.test.d.ts +1 -0
  63. package/dist/src/__tests__/var-allocator.test.js +69 -0
  64. package/dist/src/ast/types.d.ts +515 -0
  65. package/dist/src/ast/types.js +9 -0
  66. package/dist/src/builtins/metadata.d.ts +36 -0
  67. package/dist/src/builtins/metadata.js +1014 -0
  68. package/dist/src/cli.d.ts +11 -0
  69. package/dist/src/cli.js +443 -0
  70. package/dist/src/codegen/cmdblock/index.d.ts +26 -0
  71. package/dist/src/codegen/cmdblock/index.js +45 -0
  72. package/dist/src/codegen/mcfunction/index.d.ts +40 -0
  73. package/dist/src/codegen/mcfunction/index.js +606 -0
  74. package/dist/src/codegen/structure/index.d.ts +24 -0
  75. package/dist/src/codegen/structure/index.js +279 -0
  76. package/dist/src/codegen/var-allocator.d.ts +45 -0
  77. package/dist/src/codegen/var-allocator.js +104 -0
  78. package/dist/src/compile.d.ts +37 -0
  79. package/dist/src/compile.js +165 -0
  80. package/dist/src/diagnostics/index.d.ts +44 -0
  81. package/dist/src/diagnostics/index.js +140 -0
  82. package/dist/src/events/types.d.ts +35 -0
  83. package/dist/src/events/types.js +59 -0
  84. package/dist/src/formatter/index.d.ts +1 -0
  85. package/dist/src/formatter/index.js +26 -0
  86. package/dist/src/index.d.ts +22 -0
  87. package/dist/src/index.js +45 -0
  88. package/dist/src/ir/builder.d.ts +33 -0
  89. package/dist/src/ir/builder.js +99 -0
  90. package/dist/src/ir/types.d.ts +132 -0
  91. package/dist/src/ir/types.js +15 -0
  92. package/dist/src/lexer/index.d.ts +37 -0
  93. package/dist/src/lexer/index.js +569 -0
  94. package/dist/src/lowering/index.d.ts +188 -0
  95. package/dist/src/lowering/index.js +3405 -0
  96. package/dist/src/mc-test/client.d.ts +128 -0
  97. package/dist/src/mc-test/client.js +174 -0
  98. package/dist/src/mc-test/runner.d.ts +28 -0
  99. package/dist/src/mc-test/runner.js +151 -0
  100. package/dist/src/mc-test/setup.d.ts +11 -0
  101. package/dist/src/mc-test/setup.js +98 -0
  102. package/dist/src/mc-validator/index.d.ts +17 -0
  103. package/dist/src/mc-validator/index.js +322 -0
  104. package/dist/src/nbt/index.d.ts +86 -0
  105. package/dist/src/nbt/index.js +250 -0
  106. package/dist/src/optimizer/commands.d.ts +38 -0
  107. package/dist/src/optimizer/commands.js +451 -0
  108. package/dist/src/optimizer/dce.d.ts +34 -0
  109. package/dist/src/optimizer/dce.js +639 -0
  110. package/dist/src/optimizer/passes.d.ts +34 -0
  111. package/dist/src/optimizer/passes.js +243 -0
  112. package/dist/src/optimizer/structure.d.ts +9 -0
  113. package/dist/src/optimizer/structure.js +356 -0
  114. package/dist/src/parser/index.d.ts +93 -0
  115. package/dist/src/parser/index.js +1687 -0
  116. package/dist/src/repl.d.ts +16 -0
  117. package/dist/src/repl.js +165 -0
  118. package/dist/src/runtime/index.d.ts +107 -0
  119. package/dist/src/runtime/index.js +1409 -0
  120. package/dist/src/typechecker/index.d.ts +61 -0
  121. package/dist/src/typechecker/index.js +1034 -0
  122. package/dist/src/types/entity-hierarchy.d.ts +29 -0
  123. package/dist/src/types/entity-hierarchy.js +107 -0
  124. package/dist/src2/__tests__/e2e/basic.test.d.ts +8 -0
  125. package/dist/src2/__tests__/e2e/basic.test.js +140 -0
  126. package/dist/src2/__tests__/e2e/macros.test.d.ts +9 -0
  127. package/dist/src2/__tests__/e2e/macros.test.js +182 -0
  128. package/dist/src2/__tests__/e2e/migrate.test.d.ts +13 -0
  129. package/dist/src2/__tests__/e2e/migrate.test.js +2739 -0
  130. package/dist/src2/__tests__/hir/desugar.test.d.ts +1 -0
  131. package/dist/src2/__tests__/hir/desugar.test.js +234 -0
  132. package/dist/src2/__tests__/lir/lower.test.d.ts +1 -0
  133. package/dist/src2/__tests__/lir/lower.test.js +559 -0
  134. package/dist/src2/__tests__/lir/types.test.d.ts +1 -0
  135. package/dist/src2/__tests__/lir/types.test.js +185 -0
  136. package/dist/src2/__tests__/lir/verify.test.d.ts +1 -0
  137. package/dist/src2/__tests__/lir/verify.test.js +221 -0
  138. package/dist/src2/__tests__/mir/arithmetic.test.d.ts +1 -0
  139. package/dist/src2/__tests__/mir/arithmetic.test.js +130 -0
  140. package/dist/src2/__tests__/mir/control-flow.test.d.ts +1 -0
  141. package/dist/src2/__tests__/mir/control-flow.test.js +205 -0
  142. package/dist/src2/__tests__/mir/verify.test.d.ts +1 -0
  143. package/dist/src2/__tests__/mir/verify.test.js +223 -0
  144. package/dist/src2/__tests__/optimizer/block_merge.test.d.ts +1 -0
  145. package/dist/src2/__tests__/optimizer/block_merge.test.js +78 -0
  146. package/dist/src2/__tests__/optimizer/branch_simplify.test.d.ts +1 -0
  147. package/dist/src2/__tests__/optimizer/branch_simplify.test.js +58 -0
  148. package/dist/src2/__tests__/optimizer/constant_fold.test.d.ts +1 -0
  149. package/dist/src2/__tests__/optimizer/constant_fold.test.js +131 -0
  150. package/dist/src2/__tests__/optimizer/copy_prop.test.d.ts +1 -0
  151. package/dist/src2/__tests__/optimizer/copy_prop.test.js +91 -0
  152. package/dist/src2/__tests__/optimizer/dce.test.d.ts +1 -0
  153. package/dist/src2/__tests__/optimizer/dce.test.js +76 -0
  154. package/dist/src2/__tests__/optimizer/pipeline.test.d.ts +1 -0
  155. package/dist/src2/__tests__/optimizer/pipeline.test.js +102 -0
  156. package/dist/src2/emit/compile.d.ts +19 -0
  157. package/dist/src2/emit/compile.js +80 -0
  158. package/dist/src2/emit/index.d.ts +17 -0
  159. package/dist/src2/emit/index.js +172 -0
  160. package/dist/src2/hir/lower.d.ts +15 -0
  161. package/dist/src2/hir/lower.js +378 -0
  162. package/dist/src2/hir/types.d.ts +373 -0
  163. package/dist/src2/hir/types.js +16 -0
  164. package/dist/src2/lir/lower.d.ts +15 -0
  165. package/dist/src2/lir/lower.js +453 -0
  166. package/dist/src2/lir/types.d.ts +136 -0
  167. package/dist/src2/lir/types.js +11 -0
  168. package/dist/src2/lir/verify.d.ts +14 -0
  169. package/dist/src2/lir/verify.js +113 -0
  170. package/dist/src2/mir/lower.d.ts +9 -0
  171. package/dist/src2/mir/lower.js +1030 -0
  172. package/dist/src2/mir/macro.d.ts +22 -0
  173. package/dist/src2/mir/macro.js +168 -0
  174. package/dist/src2/mir/types.d.ts +183 -0
  175. package/dist/src2/mir/types.js +11 -0
  176. package/dist/src2/mir/verify.d.ts +16 -0
  177. package/dist/src2/mir/verify.js +216 -0
  178. package/dist/src2/optimizer/block_merge.d.ts +12 -0
  179. package/dist/src2/optimizer/block_merge.js +84 -0
  180. package/dist/src2/optimizer/branch_simplify.d.ts +9 -0
  181. package/dist/src2/optimizer/branch_simplify.js +28 -0
  182. package/dist/src2/optimizer/constant_fold.d.ts +10 -0
  183. package/dist/src2/optimizer/constant_fold.js +85 -0
  184. package/dist/src2/optimizer/copy_prop.d.ts +9 -0
  185. package/dist/src2/optimizer/copy_prop.js +113 -0
  186. package/dist/src2/optimizer/dce.d.ts +8 -0
  187. package/dist/src2/optimizer/dce.js +155 -0
  188. package/dist/src2/optimizer/pipeline.d.ts +10 -0
  189. package/dist/src2/optimizer/pipeline.js +42 -0
  190. package/dist/tsconfig.tsbuildinfo +1 -0
  191. package/docs/compiler-pipeline-redesign.md +2243 -0
  192. package/docs/optimization-ideas.md +1076 -0
  193. package/editors/vscode/package-lock.json +3 -3
  194. package/editors/vscode/package.json +1 -1
  195. package/jest.config.js +1 -1
  196. package/package.json +6 -5
  197. package/scripts/postbuild.js +15 -0
  198. package/src/__tests__/cli.test.ts +8 -220
  199. package/src/__tests__/dce.test.ts +11 -56
  200. package/src/__tests__/diagnostics.test.ts +59 -38
  201. package/src/__tests__/mc-integration.test.ts +1 -2
  202. package/src/ast/types.ts +6 -1
  203. package/src/cli.ts +29 -156
  204. package/src/compile.ts +6 -162
  205. package/src/index.ts +14 -178
  206. package/src/mc-test/runner.ts +4 -3
  207. package/src/parser/index.ts +1 -1
  208. package/src/repl.ts +1 -1
  209. package/src/runtime/index.ts +1 -1
  210. package/src2/__tests__/e2e/basic.test.ts +154 -0
  211. package/src2/__tests__/e2e/macros.test.ts +199 -0
  212. package/src2/__tests__/e2e/migrate.test.ts +3008 -0
  213. package/src2/__tests__/hir/desugar.test.ts +263 -0
  214. package/src2/__tests__/lir/lower.test.ts +619 -0
  215. package/src2/__tests__/lir/types.test.ts +207 -0
  216. package/src2/__tests__/lir/verify.test.ts +249 -0
  217. package/src2/__tests__/mir/arithmetic.test.ts +156 -0
  218. package/src2/__tests__/mir/control-flow.test.ts +242 -0
  219. package/src2/__tests__/mir/verify.test.ts +254 -0
  220. package/src2/__tests__/optimizer/block_merge.test.ts +84 -0
  221. package/src2/__tests__/optimizer/branch_simplify.test.ts +64 -0
  222. package/src2/__tests__/optimizer/constant_fold.test.ts +145 -0
  223. package/src2/__tests__/optimizer/copy_prop.test.ts +99 -0
  224. package/src2/__tests__/optimizer/dce.test.ts +83 -0
  225. package/src2/__tests__/optimizer/pipeline.test.ts +116 -0
  226. package/src2/emit/compile.ts +99 -0
  227. package/src2/emit/index.ts +222 -0
  228. package/src2/hir/lower.ts +428 -0
  229. package/src2/hir/types.ts +216 -0
  230. package/src2/lir/lower.ts +556 -0
  231. package/src2/lir/types.ts +109 -0
  232. package/src2/lir/verify.ts +129 -0
  233. package/src2/mir/lower.ts +1160 -0
  234. package/src2/mir/macro.ts +167 -0
  235. package/src2/mir/types.ts +106 -0
  236. package/src2/mir/verify.ts +218 -0
  237. package/src2/optimizer/block_merge.ts +93 -0
  238. package/src2/optimizer/branch_simplify.ts +27 -0
  239. package/src2/optimizer/constant_fold.ts +88 -0
  240. package/src2/optimizer/copy_prop.ts +106 -0
  241. package/src2/optimizer/dce.ts +133 -0
  242. package/src2/optimizer/pipeline.ts +44 -0
  243. package/tsconfig.json +2 -2
  244. package/src/__tests__/codegen.test.ts +0 -161
  245. package/src/__tests__/e2e.test.ts +0 -2039
  246. package/src/__tests__/entity-types.test.ts +0 -236
  247. package/src/__tests__/lowering.test.ts +0 -1185
  248. package/src/__tests__/macro.test.ts +0 -343
  249. package/src/__tests__/nbt.test.ts +0 -58
  250. package/src/__tests__/optimizer-advanced.test.ts +0 -144
  251. package/src/__tests__/optimizer.test.ts +0 -162
  252. package/src/__tests__/runtime.test.ts +0 -305
  253. package/src/__tests__/stdlib-advanced.test.ts +0 -379
  254. package/src/__tests__/stdlib-bigint.test.ts +0 -427
  255. package/src/__tests__/stdlib-math.test.ts +0 -374
  256. package/src/__tests__/stdlib-vec.test.ts +0 -259
  257. package/src/__tests__/structure-optimizer.test.ts +0 -38
  258. package/src/__tests__/var-allocator.test.ts +0 -75
  259. package/src/codegen/cmdblock/index.ts +0 -63
  260. package/src/codegen/mcfunction/index.ts +0 -662
  261. package/src/codegen/structure/index.ts +0 -346
  262. package/src/codegen/var-allocator.ts +0 -104
  263. package/src/ir/builder.ts +0 -116
  264. package/src/ir/types.ts +0 -134
  265. package/src/lowering/index.ts +0 -3876
  266. package/src/optimizer/commands.ts +0 -534
  267. package/src/optimizer/dce.ts +0 -679
  268. package/src/optimizer/passes.ts +0 -250
  269. package/src/optimizer/structure.ts +0 -450
@@ -0,0 +1,145 @@
1
+ import { constantFold } from '../../optimizer/constant_fold'
2
+ import type { MIRFunction, MIRBlock, MIRInstr, Operand } from '../../mir/types'
3
+
4
+ function mkFn(blocks: MIRBlock[]): MIRFunction {
5
+ return { name: 'test', params: [], blocks, entry: 'entry', isMacro: false }
6
+ }
7
+
8
+ function mkBlock(id: string, instrs: MIRInstr[], term: MIRInstr): MIRBlock {
9
+ return { id, instrs, term, preds: [] }
10
+ }
11
+
12
+ const c = (v: number): Operand => ({ kind: 'const', value: v })
13
+ const t = (n: string): Operand => ({ kind: 'temp', name: n })
14
+
15
+ describe('constant folding', () => {
16
+ test('folds add(const, const)', () => {
17
+ const fn = mkFn([
18
+ mkBlock('entry', [
19
+ { kind: 'add', dst: 't0', a: c(3), b: c(4) },
20
+ ], { kind: 'return', value: t('t0') }),
21
+ ])
22
+ const result = constantFold(fn)
23
+ expect(result.blocks[0].instrs[0]).toEqual({ kind: 'const', dst: 't0', value: 7 })
24
+ })
25
+
26
+ test('folds sub(const, const)', () => {
27
+ const fn = mkFn([
28
+ mkBlock('entry', [
29
+ { kind: 'sub', dst: 't0', a: c(10), b: c(3) },
30
+ ], { kind: 'return', value: t('t0') }),
31
+ ])
32
+ const result = constantFold(fn)
33
+ expect(result.blocks[0].instrs[0]).toEqual({ kind: 'const', dst: 't0', value: 7 })
34
+ })
35
+
36
+ test('folds mul(const, const)', () => {
37
+ const fn = mkFn([
38
+ mkBlock('entry', [
39
+ { kind: 'mul', dst: 't0', a: c(3), b: c(5) },
40
+ ], { kind: 'return', value: t('t0') }),
41
+ ])
42
+ const result = constantFold(fn)
43
+ expect(result.blocks[0].instrs[0]).toEqual({ kind: 'const', dst: 't0', value: 15 })
44
+ })
45
+
46
+ test('folds div(const, const) with truncation', () => {
47
+ const fn = mkFn([
48
+ mkBlock('entry', [
49
+ { kind: 'div', dst: 't0', a: c(7), b: c(2) },
50
+ ], { kind: 'return', value: t('t0') }),
51
+ ])
52
+ const result = constantFold(fn)
53
+ expect(result.blocks[0].instrs[0]).toEqual({ kind: 'const', dst: 't0', value: 3 })
54
+ })
55
+
56
+ test('does not fold div by zero', () => {
57
+ const fn = mkFn([
58
+ mkBlock('entry', [
59
+ { kind: 'div', dst: 't0', a: c(7), b: c(0) },
60
+ ], { kind: 'return', value: t('t0') }),
61
+ ])
62
+ const result = constantFold(fn)
63
+ expect(result.blocks[0].instrs[0].kind).toBe('div')
64
+ })
65
+
66
+ test('folds mod(const, const)', () => {
67
+ const fn = mkFn([
68
+ mkBlock('entry', [
69
+ { kind: 'mod', dst: 't0', a: c(7), b: c(3) },
70
+ ], { kind: 'return', value: t('t0') }),
71
+ ])
72
+ const result = constantFold(fn)
73
+ expect(result.blocks[0].instrs[0]).toEqual({ kind: 'const', dst: 't0', value: 1 })
74
+ })
75
+
76
+ test('folds neg(const)', () => {
77
+ const fn = mkFn([
78
+ mkBlock('entry', [
79
+ { kind: 'neg', dst: 't0', src: c(5) },
80
+ ], { kind: 'return', value: t('t0') }),
81
+ ])
82
+ const result = constantFold(fn)
83
+ expect(result.blocks[0].instrs[0]).toEqual({ kind: 'const', dst: 't0', value: -5 })
84
+ })
85
+
86
+ test('folds not(0) → 1', () => {
87
+ const fn = mkFn([
88
+ mkBlock('entry', [
89
+ { kind: 'not', dst: 't0', src: c(0) },
90
+ ], { kind: 'return', value: t('t0') }),
91
+ ])
92
+ const result = constantFold(fn)
93
+ expect(result.blocks[0].instrs[0]).toEqual({ kind: 'const', dst: 't0', value: 1 })
94
+ })
95
+
96
+ test('folds cmp(lt, 3, 4) → 1', () => {
97
+ const fn = mkFn([
98
+ mkBlock('entry', [
99
+ { kind: 'cmp', dst: 't0', op: 'lt', a: c(3), b: c(4) },
100
+ ], { kind: 'return', value: t('t0') }),
101
+ ])
102
+ const result = constantFold(fn)
103
+ expect(result.blocks[0].instrs[0]).toEqual({ kind: 'const', dst: 't0', value: 1 })
104
+ })
105
+
106
+ test('folds cmp(eq, 5, 5) → 1', () => {
107
+ const fn = mkFn([
108
+ mkBlock('entry', [
109
+ { kind: 'cmp', dst: 't0', op: 'eq', a: c(5), b: c(5) },
110
+ ], { kind: 'return', value: t('t0') }),
111
+ ])
112
+ const result = constantFold(fn)
113
+ expect(result.blocks[0].instrs[0]).toEqual({ kind: 'const', dst: 't0', value: 1 })
114
+ })
115
+
116
+ test('folds and(1, 0) → 0', () => {
117
+ const fn = mkFn([
118
+ mkBlock('entry', [
119
+ { kind: 'and', dst: 't0', a: c(1), b: c(0) },
120
+ ], { kind: 'return', value: t('t0') }),
121
+ ])
122
+ const result = constantFold(fn)
123
+ expect(result.blocks[0].instrs[0]).toEqual({ kind: 'const', dst: 't0', value: 0 })
124
+ })
125
+
126
+ test('folds or(0, 1) → 1', () => {
127
+ const fn = mkFn([
128
+ mkBlock('entry', [
129
+ { kind: 'or', dst: 't0', a: c(0), b: c(1) },
130
+ ], { kind: 'return', value: t('t0') }),
131
+ ])
132
+ const result = constantFold(fn)
133
+ expect(result.blocks[0].instrs[0]).toEqual({ kind: 'const', dst: 't0', value: 1 })
134
+ })
135
+
136
+ test('does not fold when operand is temp', () => {
137
+ const fn = mkFn([
138
+ mkBlock('entry', [
139
+ { kind: 'add', dst: 't0', a: t('a'), b: c(4) },
140
+ ], { kind: 'return', value: t('t0') }),
141
+ ])
142
+ const result = constantFold(fn)
143
+ expect(result.blocks[0].instrs[0].kind).toBe('add')
144
+ })
145
+ })
@@ -0,0 +1,99 @@
1
+ import { copyProp } from '../../optimizer/copy_prop'
2
+ import type { MIRFunction, MIRBlock, MIRInstr, Operand } from '../../mir/types'
3
+
4
+ function mkFn(blocks: MIRBlock[]): MIRFunction {
5
+ return { name: 'test', params: [], blocks, entry: 'entry', isMacro: false }
6
+ }
7
+
8
+ function mkBlock(id: string, instrs: MIRInstr[], term: MIRInstr): MIRBlock {
9
+ return { id, instrs, term, preds: [] }
10
+ }
11
+
12
+ const c = (v: number): Operand => ({ kind: 'const', value: v })
13
+ const t = (n: string): Operand => ({ kind: 'temp', name: n })
14
+
15
+ describe('copy propagation', () => {
16
+ test('propagates copy into subsequent use', () => {
17
+ const fn = mkFn([
18
+ mkBlock('entry', [
19
+ { kind: 'copy', dst: 'x', src: t('y') },
20
+ { kind: 'add', dst: 'z', a: t('x'), b: c(1) },
21
+ ], { kind: 'return', value: t('z') }),
22
+ ])
23
+ const result = copyProp(fn)
24
+ const add = result.blocks[0].instrs[1]
25
+ expect(add.kind).toBe('add')
26
+ expect((add as any).a).toEqual(t('y'))
27
+ })
28
+
29
+ test('propagates into terminator', () => {
30
+ const fn = mkFn([
31
+ mkBlock('entry', [
32
+ { kind: 'copy', dst: 'x', src: t('y') },
33
+ ], { kind: 'return', value: t('x') }),
34
+ ])
35
+ const result = copyProp(fn)
36
+ expect(result.blocks[0].term).toEqual({ kind: 'return', value: t('y') })
37
+ })
38
+
39
+ test('invalidates mapping when source is redefined', () => {
40
+ const fn = mkFn([
41
+ mkBlock('entry', [
42
+ { kind: 'copy', dst: 'x', src: t('y') },
43
+ { kind: 'const', dst: 'y', value: 99 },
44
+ { kind: 'add', dst: 'z', a: t('x'), b: c(1) },
45
+ ], { kind: 'return', value: t('z') }),
46
+ ])
47
+ const result = copyProp(fn)
48
+ // x's mapping was invalidated because y was redefined
49
+ const add = result.blocks[0].instrs[2]
50
+ expect((add as any).a).toEqual(t('x'))
51
+ })
52
+
53
+ test('propagates const definitions into uses', () => {
54
+ const fn = mkFn([
55
+ mkBlock('entry', [
56
+ { kind: 'const', dst: 'x', value: 42 },
57
+ { kind: 'add', dst: 'z', a: t('x'), b: c(1) },
58
+ ], { kind: 'return', value: t('z') }),
59
+ ])
60
+ const result = copyProp(fn)
61
+ const add = result.blocks[0].instrs[1]
62
+ expect((add as any).a).toEqual(c(42))
63
+ })
64
+
65
+ test('propagates copy-of-const into uses', () => {
66
+ const fn = mkFn([
67
+ mkBlock('entry', [
68
+ { kind: 'copy', dst: 'x', src: c(42) },
69
+ { kind: 'add', dst: 'z', a: t('x'), b: c(1) },
70
+ ], { kind: 'return', value: t('z') }),
71
+ ])
72
+ const result = copyProp(fn)
73
+ const add = result.blocks[0].instrs[1]
74
+ expect((add as any).a).toEqual(c(42))
75
+ })
76
+
77
+ test('chains propagation: x=y, z=x → z uses y', () => {
78
+ const fn = mkFn([
79
+ mkBlock('entry', [
80
+ { kind: 'copy', dst: 'x', src: t('y') },
81
+ { kind: 'copy', dst: 'z', src: t('x') },
82
+ ], { kind: 'return', value: t('z') }),
83
+ ])
84
+ const result = copyProp(fn)
85
+ // z = copy x → rewritten to z = copy y
86
+ // then return z → rewritten to return y
87
+ expect(result.blocks[0].term).toEqual({ kind: 'return', value: t('y') })
88
+ })
89
+
90
+ test('propagates into branch condition', () => {
91
+ const fn = mkFn([
92
+ mkBlock('entry', [
93
+ { kind: 'copy', dst: 'c', src: t('flag') },
94
+ ], { kind: 'branch', cond: t('c'), then: 'b1', else: 'b2' }),
95
+ ])
96
+ const result = copyProp(fn)
97
+ expect(result.blocks[0].term).toEqual({ kind: 'branch', cond: t('flag'), then: 'b1', else: 'b2' })
98
+ })
99
+ })
@@ -0,0 +1,83 @@
1
+ import { dce } from '../../optimizer/dce'
2
+ import type { MIRFunction, MIRBlock, MIRInstr, Operand } from '../../mir/types'
3
+
4
+ function mkFn(blocks: MIRBlock[], entry = 'entry'): MIRFunction {
5
+ return { name: 'test', params: [], blocks, entry, isMacro: false }
6
+ }
7
+
8
+ function mkBlock(id: string, instrs: MIRInstr[], term: MIRInstr, preds: string[] = []): MIRBlock {
9
+ return { id, instrs, term, preds }
10
+ }
11
+
12
+ const c = (v: number): Operand => ({ kind: 'const', value: v })
13
+ const t = (n: string): Operand => ({ kind: 'temp', name: n })
14
+
15
+ describe('dead code elimination', () => {
16
+ test('removes unused temp definition', () => {
17
+ const fn = mkFn([
18
+ mkBlock('entry', [
19
+ { kind: 'const', dst: 'dead', value: 42 },
20
+ { kind: 'const', dst: 'live', value: 1 },
21
+ ], { kind: 'return', value: t('live') }),
22
+ ])
23
+ const result = dce(fn)
24
+ expect(result.blocks[0].instrs).toHaveLength(1)
25
+ expect(result.blocks[0].instrs[0]).toEqual({ kind: 'const', dst: 'live', value: 1 })
26
+ })
27
+
28
+ test('keeps side-effectful instructions even if dst unused', () => {
29
+ const fn = mkFn([
30
+ mkBlock('entry', [
31
+ { kind: 'call', dst: 'unused', fn: 'sideEffect', args: [] },
32
+ ], { kind: 'return', value: null }),
33
+ ])
34
+ const result = dce(fn)
35
+ expect(result.blocks[0].instrs).toHaveLength(1)
36
+ })
37
+
38
+ test('removes unreachable blocks', () => {
39
+ const fn = mkFn([
40
+ mkBlock('entry', [], { kind: 'return', value: null }),
41
+ mkBlock('dead_block', [
42
+ { kind: 'const', dst: 't0', value: 99 },
43
+ ], { kind: 'return', value: t('t0') }),
44
+ ])
45
+ const result = dce(fn)
46
+ expect(result.blocks).toHaveLength(1)
47
+ expect(result.blocks[0].id).toBe('entry')
48
+ })
49
+
50
+ test('keeps reachable blocks', () => {
51
+ const fn = mkFn([
52
+ mkBlock('entry', [
53
+ { kind: 'const', dst: 't0', value: 1 },
54
+ ], { kind: 'branch', cond: t('t0'), then: 'b1', else: 'b2' }),
55
+ mkBlock('b1', [], { kind: 'return', value: null }, ['entry']),
56
+ mkBlock('b2', [], { kind: 'return', value: null }, ['entry']),
57
+ ])
58
+ const result = dce(fn)
59
+ expect(result.blocks).toHaveLength(3)
60
+ })
61
+
62
+ test('recomputes preds after block removal', () => {
63
+ const fn = mkFn([
64
+ mkBlock('entry', [], { kind: 'jump', target: 'b1' }),
65
+ mkBlock('b1', [], { kind: 'return', value: null }, ['entry']),
66
+ mkBlock('dead', [], { kind: 'jump', target: 'b1' }),
67
+ ])
68
+ const result = dce(fn)
69
+ expect(result.blocks).toHaveLength(2)
70
+ const b1 = result.blocks.find(b => b.id === 'b1')!
71
+ expect(b1.preds).toEqual(['entry'])
72
+ })
73
+
74
+ test('keeps nbt_write even though it has no dst', () => {
75
+ const fn = mkFn([
76
+ mkBlock('entry', [
77
+ { kind: 'nbt_write', ns: 'rs:data', path: 'x', type: 'int', scale: 1, src: c(5) },
78
+ ], { kind: 'return', value: null }),
79
+ ])
80
+ const result = dce(fn)
81
+ expect(result.blocks[0].instrs).toHaveLength(1)
82
+ })
83
+ })
@@ -0,0 +1,116 @@
1
+ import { optimizeFunction, optimizeModule } from '../../optimizer/pipeline'
2
+ import type { MIRFunction, MIRBlock, MIRInstr, MIRModule, Operand } from '../../mir/types'
3
+
4
+ function mkFn(blocks: MIRBlock[], entry = 'entry'): MIRFunction {
5
+ return { name: 'test', params: [], blocks, entry, isMacro: false }
6
+ }
7
+
8
+ function mkBlock(id: string, instrs: MIRInstr[], term: MIRInstr, preds: string[] = []): MIRBlock {
9
+ return { id, instrs, term, preds }
10
+ }
11
+
12
+ const c = (v: number): Operand => ({ kind: 'const', value: v })
13
+ const t = (n: string): Operand => ({ kind: 'temp', name: n })
14
+
15
+ describe('optimization pipeline', () => {
16
+ test('constant fold + branch simplify + DCE removes dead branch', () => {
17
+ // cmp(lt, 1, 2) → 1 → branch(1, then, else) → jump(then) → else is dead
18
+ const fn = mkFn([
19
+ mkBlock('entry', [
20
+ { kind: 'cmp', dst: 't0', op: 'lt', a: c(1), b: c(2) },
21
+ ], { kind: 'branch', cond: t('t0'), then: 'then', else: 'else' }),
22
+ mkBlock('then', [], { kind: 'return', value: c(1) }, ['entry']),
23
+ mkBlock('else', [], { kind: 'return', value: c(0) }, ['entry']),
24
+ ])
25
+
26
+ const result = optimizeFunction(fn)
27
+
28
+ // After optimization: entry should return 1 directly, else block removed
29
+ // The cmp folds to const 1, branch simplifies to jump(then),
30
+ // else block becomes unreachable and is removed,
31
+ // then block merges into entry
32
+ expect(result.blocks).toHaveLength(1)
33
+ expect(result.blocks[0].term).toEqual({ kind: 'return', value: c(1) })
34
+ })
35
+
36
+ test('copy prop + const fold + DCE eliminates dead copy and folds', () => {
37
+ const fn = mkFn([
38
+ mkBlock('entry', [
39
+ { kind: 'const', dst: 'a', value: 5 },
40
+ { kind: 'copy', dst: 'b', src: t('a') },
41
+ { kind: 'add', dst: 'c', a: t('b'), b: c(1) },
42
+ ], { kind: 'return', value: t('c') }),
43
+ ])
44
+
45
+ const result = optimizeFunction(fn)
46
+
47
+ // const a=5 propagated into copy and add, add(5,1) folded to 6
48
+ // all dead defs removed, returns const 6
49
+ expect(result.blocks[0].term).toEqual({ kind: 'return', value: c(6) })
50
+ })
51
+
52
+ test('full pipeline: fold + simplify + merge + dce', () => {
53
+ // if (3 > 2) { return 10 + 20; } else { return 0; }
54
+ const fn = mkFn([
55
+ mkBlock('entry', [
56
+ { kind: 'cmp', dst: 't0', op: 'gt', a: c(3), b: c(2) },
57
+ ], { kind: 'branch', cond: t('t0'), then: 'then', else: 'else' }),
58
+ mkBlock('then', [
59
+ { kind: 'add', dst: 't1', a: c(10), b: c(20) },
60
+ ], { kind: 'return', value: t('t1') }, ['entry']),
61
+ mkBlock('else', [], { kind: 'return', value: c(0) }, ['entry']),
62
+ ])
63
+
64
+ const result = optimizeFunction(fn)
65
+
66
+ // Everything folds away: single block returning const 30
67
+ expect(result.blocks).toHaveLength(1)
68
+ expect(result.blocks[0].term).toEqual({ kind: 'return', value: c(30) })
69
+ })
70
+
71
+ test('optimizeModule applies to all functions', () => {
72
+ const mod: MIRModule = {
73
+ namespace: 'test',
74
+ objective: '__test',
75
+ functions: [
76
+ mkFn([
77
+ mkBlock('entry', [
78
+ { kind: 'add', dst: 't0', a: c(1), b: c(2) },
79
+ ], { kind: 'return', value: t('t0') }),
80
+ ]),
81
+ mkFn([
82
+ mkBlock('entry', [
83
+ { kind: 'mul', dst: 't0', a: c(3), b: c(4) },
84
+ ], { kind: 'return', value: t('t0') }),
85
+ ]),
86
+ ],
87
+ }
88
+
89
+ const result = optimizeModule(mod)
90
+ // Both functions should have their constants folded
91
+ for (const fn of result.functions) {
92
+ const instrs = fn.blocks[0].instrs
93
+ const hasArith = instrs.some(i => i.kind === 'add' || i.kind === 'mul')
94
+ expect(hasArith).toBe(false)
95
+ }
96
+ })
97
+
98
+ test('fixpoint: multiple iterations needed', () => {
99
+ // First iteration: fold add → const, fold cmp → const
100
+ // Second iteration: branch simplify on newly-const cond
101
+ // Third iteration: DCE removes dead block, merge
102
+ const fn = mkFn([
103
+ mkBlock('entry', [
104
+ { kind: 'add', dst: 't0', a: c(1), b: c(1) },
105
+ { kind: 'cmp', dst: 't1', op: 'eq', a: t('t0'), b: c(2) },
106
+ ], { kind: 'branch', cond: t('t1'), then: 'yes', else: 'no' }),
107
+ mkBlock('yes', [], { kind: 'return', value: c(1) }, ['entry']),
108
+ mkBlock('no', [], { kind: 'return', value: c(0) }, ['entry']),
109
+ ])
110
+
111
+ const result = optimizeFunction(fn)
112
+ // 1+1=2, 2==2 → 1, branch(1) → jump(yes), dead block removed, merged
113
+ expect(result.blocks).toHaveLength(1)
114
+ expect(result.blocks[0].term).toEqual({ kind: 'return', value: c(1) })
115
+ })
116
+ })
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Top-level compile function for the v2 pipeline.
3
+ *
4
+ * Pipeline: source → Lexer → Parser → HIR → MIR → optimize → LIR → emit
5
+ */
6
+
7
+ import { Lexer } from '../../src/lexer'
8
+ import { Parser } from '../../src/parser'
9
+ import { preprocessSourceWithMetadata } from '../../src/compile'
10
+ import { lowerToHIR } from '../hir/lower'
11
+ import { lowerToMIR } from '../mir/lower'
12
+ import { optimizeModule } from '../optimizer/pipeline'
13
+ import { lowerToLIR } from '../lir/lower'
14
+ import { emit, type DatapackFile } from './index'
15
+
16
+ export interface CompileOptions {
17
+ namespace?: string
18
+ filePath?: string
19
+ /** v1 compat: inline library sources (treated as `module library;` imports) */
20
+ librarySources?: string[]
21
+ }
22
+
23
+ export interface CompileResult {
24
+ files: DatapackFile[]
25
+ warnings: string[]
26
+ /** Always true — v1 compat shim (compile() throws on error) */
27
+ readonly success: true
28
+ }
29
+
30
+ export function compile(source: string, options: CompileOptions = {}): CompileResult {
31
+ const { namespace = 'redscript', filePath } = options
32
+ const warnings: string[] = []
33
+
34
+ // Preprocess: resolve import directives, merge imported sources
35
+ const preprocessed = preprocessSourceWithMetadata(source, { filePath })
36
+ const processedSource = preprocessed.source
37
+
38
+ // Stage 1: Lex + Parse → AST
39
+ const lexer = new Lexer(processedSource)
40
+ const tokens = lexer.tokenize()
41
+ const parser = new Parser(tokens, processedSource, filePath)
42
+ const ast = parser.parse(namespace)
43
+
44
+ // Merge library imports (files with `module library;`) into AST
45
+ for (const li of preprocessed.libraryImports ?? []) {
46
+ const libPreprocessed = preprocessSourceWithMetadata(li.source, { filePath: li.filePath })
47
+ const libTokens = new Lexer(libPreprocessed.source, li.filePath).tokenize()
48
+ const libAst = new Parser(libTokens, libPreprocessed.source, li.filePath).parse(namespace)
49
+ for (const fn of libAst.declarations) fn.isLibraryFn = true
50
+ ast.declarations.push(...libAst.declarations)
51
+ ast.structs.push(...libAst.structs)
52
+ ast.implBlocks.push(...libAst.implBlocks)
53
+ ast.enums.push(...libAst.enums)
54
+ ast.consts.push(...libAst.consts)
55
+ ast.globals.push(...libAst.globals)
56
+ }
57
+
58
+ // Merge librarySources (v1 compat: inline library strings) before HIR
59
+ if (options.librarySources) {
60
+ for (const libSrc of options.librarySources) {
61
+ const libTokens = new Lexer(libSrc).tokenize()
62
+ const libAst = new Parser(libTokens, libSrc).parse(namespace)
63
+ for (const fn of libAst.declarations) fn.isLibraryFn = true
64
+ ast.declarations.push(...libAst.declarations)
65
+ ast.structs.push(...libAst.structs)
66
+ ast.implBlocks.push(...libAst.implBlocks)
67
+ ast.enums.push(...libAst.enums)
68
+ ast.consts.push(...libAst.consts)
69
+ ast.globals.push(...libAst.globals)
70
+ }
71
+ }
72
+
73
+ // Stage 2: AST → HIR
74
+ const hir = lowerToHIR(ast)
75
+
76
+ // Extract @tick and @load functions from HIR (before decorator info is lost)
77
+ const tickFunctions: string[] = []
78
+ const loadFunctions: string[] = []
79
+ for (const fn of hir.functions) {
80
+ for (const dec of fn.decorators) {
81
+ if (dec.name === 'tick') tickFunctions.push(fn.name)
82
+ if (dec.name === 'load') loadFunctions.push(fn.name)
83
+ }
84
+ }
85
+
86
+ // Stage 3: HIR → MIR
87
+ const mir = lowerToMIR(hir)
88
+
89
+ // Stage 4: MIR optimization
90
+ const mirOpt = optimizeModule(mir)
91
+
92
+ // Stage 5: MIR → LIR
93
+ const lir = lowerToLIR(mirOpt)
94
+
95
+ // Stage 7: LIR → .mcfunction
96
+ const files = emit(lir, { namespace, tickFunctions, loadFunctions })
97
+
98
+ return { files, warnings, success: true as const }
99
+ }