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
@@ -0,0 +1,263 @@
1
+ import { Lexer } from '../../../src/lexer'
2
+ import { Parser } from '../../../src/parser'
3
+ import { lowerToHIR } from '../../hir/lower'
4
+ import type { HIRStmt, HIRExpr, HIRModule } from '../../hir/types'
5
+
6
+ function parse(source: string): HIRModule {
7
+ const tokens = new Lexer(source).tokenize()
8
+ const ast = new Parser(tokens).parse('test')
9
+ return lowerToHIR(ast)
10
+ }
11
+
12
+ function getBody(source: string): HIRStmt[] {
13
+ const mod = parse(source)
14
+ return mod.functions[0].body
15
+ }
16
+
17
+ describe('HIR lowering — for loop desugaring', () => {
18
+ test('for(init;cond;step) → let + while with step in separate block', () => {
19
+ const body = getBody('fn f() { for (let i: int = 0; i < 10; i = i + 1) { let x: int = i; } }')
20
+ // Should produce: let i = 0; while(i < 10) { body } step { i = i + 1 }
21
+ expect(body).toHaveLength(2)
22
+ expect(body[0].kind).toBe('let')
23
+ const letStmt = body[0] as Extract<HIRStmt, { kind: 'let' }>
24
+ expect(letStmt.name).toBe('i')
25
+
26
+ expect(body[1].kind).toBe('while')
27
+ const whileStmt = body[1] as Extract<HIRStmt, { kind: 'while' }>
28
+ expect(whileStmt.cond.kind).toBe('binary')
29
+
30
+ // while body: only the original body (step is separate)
31
+ expect(whileStmt.body).toHaveLength(1)
32
+ expect(whileStmt.body[0].kind).toBe('let') // let x = i
33
+
34
+ // step block: i = i + 1 (separate so continue still increments)
35
+ expect(whileStmt.step).toBeDefined()
36
+ expect(whileStmt.step).toHaveLength(1)
37
+ expect(whileStmt.step![0].kind).toBe('expr') // i = i + 1
38
+ })
39
+
40
+ test('for loop without init', () => {
41
+ const body = getBody('fn f() { let i: int = 0; for (; i < 5; i = i + 1) {} }')
42
+ // let i = 0; while(i < 5) { i = i + 1 }
43
+ expect(body).toHaveLength(2)
44
+ expect(body[0].kind).toBe('let')
45
+ expect(body[1].kind).toBe('while')
46
+ })
47
+ })
48
+
49
+ describe('HIR lowering — for_range desugaring', () => {
50
+ test('for i in 0..10 → let + while with step block', () => {
51
+ const body = getBody('fn f() { for i in 0..10 { let x: int = i; } }')
52
+ expect(body).toHaveLength(2)
53
+
54
+ const letStmt = body[0] as Extract<HIRStmt, { kind: 'let' }>
55
+ expect(letStmt.kind).toBe('let')
56
+ expect(letStmt.name).toBe('i')
57
+ expect(letStmt.init.kind).toBe('int_lit')
58
+
59
+ const whileStmt = body[1] as Extract<HIRStmt, { kind: 'while' }>
60
+ expect(whileStmt.kind).toBe('while')
61
+ // cond: i < 10
62
+ expect(whileStmt.cond.kind).toBe('binary')
63
+ const cond = whileStmt.cond as Extract<HIRExpr, { kind: 'binary' }>
64
+ expect(cond.op).toBe('<')
65
+ // body: only user body (step is separate)
66
+ expect(whileStmt.body).toHaveLength(1)
67
+ expect(whileStmt.body[0].kind).toBe('let')
68
+ // step: i = i + 1 (in separate block so continue still increments)
69
+ expect(whileStmt.step).toBeDefined()
70
+ expect(whileStmt.step).toHaveLength(1)
71
+ expect(whileStmt.step![0].kind).toBe('expr')
72
+ })
73
+ })
74
+
75
+ describe('HIR lowering — compound assignment desugaring', () => {
76
+ test('x += 1 → x = x + 1', () => {
77
+ const body = getBody('fn f() { let x: int = 0; x += 1; }')
78
+ const exprStmt = body[1] as Extract<HIRStmt, { kind: 'expr' }>
79
+ expect(exprStmt.kind).toBe('expr')
80
+ const assign = exprStmt.expr as Extract<HIRExpr, { kind: 'assign' }>
81
+ expect(assign.kind).toBe('assign')
82
+ expect(assign.target).toBe('x')
83
+ // value should be x + 1
84
+ const binExpr = assign.value as Extract<HIRExpr, { kind: 'binary' }>
85
+ expect(binExpr.kind).toBe('binary')
86
+ expect(binExpr.op).toBe('+')
87
+ expect((binExpr.left as any).name).toBe('x')
88
+ expect((binExpr.right as any).value).toBe(1)
89
+ })
90
+
91
+ test('x -= 5 → x = x - 5', () => {
92
+ const body = getBody('fn f() { let x: int = 10; x -= 5; }')
93
+ const exprStmt = body[1] as Extract<HIRStmt, { kind: 'expr' }>
94
+ const assign = exprStmt.expr as Extract<HIRExpr, { kind: 'assign' }>
95
+ expect(assign.kind).toBe('assign')
96
+ const bin = assign.value as Extract<HIRExpr, { kind: 'binary' }>
97
+ expect(bin.op).toBe('-')
98
+ })
99
+
100
+ test('x *= 2 → x = x * 2', () => {
101
+ const body = getBody('fn f() { let x: int = 1; x *= 2; }')
102
+ const exprStmt = body[1] as Extract<HIRStmt, { kind: 'expr' }>
103
+ const assign = exprStmt.expr as Extract<HIRExpr, { kind: 'assign' }>
104
+ const bin = assign.value as Extract<HIRExpr, { kind: 'binary' }>
105
+ expect(bin.op).toBe('*')
106
+ })
107
+
108
+ test('x /= 3 → x = x / 3', () => {
109
+ const body = getBody('fn f() { let x: int = 9; x /= 3; }')
110
+ const exprStmt = body[1] as Extract<HIRStmt, { kind: 'expr' }>
111
+ const assign = exprStmt.expr as Extract<HIRExpr, { kind: 'assign' }>
112
+ const bin = assign.value as Extract<HIRExpr, { kind: 'binary' }>
113
+ expect(bin.op).toBe('/')
114
+ })
115
+
116
+ test('x %= 3 → x = x % 3', () => {
117
+ const body = getBody('fn f() { let x: int = 10; x %= 3; }')
118
+ const exprStmt = body[1] as Extract<HIRStmt, { kind: 'expr' }>
119
+ const assign = exprStmt.expr as Extract<HIRExpr, { kind: 'assign' }>
120
+ const bin = assign.value as Extract<HIRExpr, { kind: 'binary' }>
121
+ expect(bin.op).toBe('%')
122
+ })
123
+ })
124
+
125
+ describe('HIR lowering — execute block unification', () => {
126
+ test('as_block → execute [as]', () => {
127
+ const body = getBody('fn f() { as @e[tag=foo] { } }')
128
+ expect(body).toHaveLength(1)
129
+ const exec = body[0] as Extract<HIRStmt, { kind: 'execute' }>
130
+ expect(exec.kind).toBe('execute')
131
+ expect(exec.subcommands).toHaveLength(1)
132
+ expect(exec.subcommands[0].kind).toBe('as')
133
+ })
134
+
135
+ test('foreach with at context preserved', () => {
136
+ const body = getBody('fn f() { foreach (p in @a) at @s { } }')
137
+ expect(body).toHaveLength(1)
138
+ expect(body[0].kind).toBe('foreach')
139
+ const fe = body[0] as Extract<HIRStmt, { kind: 'foreach' }>
140
+ expect(fe.executeContext).toBe('at @s')
141
+ })
142
+ })
143
+
144
+ describe('HIR lowering — && and || preservation', () => {
145
+ test('&& preserved as binary op', () => {
146
+ const body = getBody('fn f() { let x: bool = true && false; }')
147
+ const letStmt = body[0] as Extract<HIRStmt, { kind: 'let' }>
148
+ const bin = letStmt.init as Extract<HIRExpr, { kind: 'binary' }>
149
+ expect(bin.kind).toBe('binary')
150
+ expect(bin.op).toBe('&&')
151
+ })
152
+
153
+ test('|| preserved as binary op', () => {
154
+ const body = getBody('fn f() { let x: bool = true || false; }')
155
+ const letStmt = body[0] as Extract<HIRStmt, { kind: 'let' }>
156
+ const bin = letStmt.init as Extract<HIRExpr, { kind: 'binary' }>
157
+ expect(bin.kind).toBe('binary')
158
+ expect(bin.op).toBe('||')
159
+ })
160
+ })
161
+
162
+ describe('HIR lowering — pass-through constructs', () => {
163
+ test('while loop passes through', () => {
164
+ const body = getBody('fn f() { while (true) { } }')
165
+ expect(body).toHaveLength(1)
166
+ expect(body[0].kind).toBe('while')
167
+ })
168
+
169
+ test('if/else passes through', () => {
170
+ const body = getBody('fn f() { if (true) { } else { } }')
171
+ expect(body).toHaveLength(1)
172
+ expect(body[0].kind).toBe('if')
173
+ const ifStmt = body[0] as Extract<HIRStmt, { kind: 'if' }>
174
+ expect(ifStmt.then).toBeDefined()
175
+ expect(ifStmt.else_).toBeDefined()
176
+ })
177
+
178
+ test('return passes through', () => {
179
+ const body = getBody('fn f(): int { return 42; }')
180
+ expect(body).toHaveLength(1)
181
+ expect(body[0].kind).toBe('return')
182
+ const ret = body[0] as Extract<HIRStmt, { kind: 'return' }>
183
+ expect(ret.value).toBeDefined()
184
+ expect((ret.value as any).value).toBe(42)
185
+ })
186
+
187
+ test('function call passes through', () => {
188
+ const body = getBody('fn f() { foo(1, 2); }')
189
+ expect(body).toHaveLength(1)
190
+ const exprStmt = body[0] as Extract<HIRStmt, { kind: 'expr' }>
191
+ const call = exprStmt.expr as Extract<HIRExpr, { kind: 'call' }>
192
+ expect(call.kind).toBe('call')
193
+ expect(call.fn).toBe('foo')
194
+ expect(call.args).toHaveLength(2)
195
+ })
196
+
197
+ test('break and continue pass through', () => {
198
+ const body = getBody('fn f() { while (true) { break; continue; } }')
199
+ const whileStmt = body[0] as Extract<HIRStmt, { kind: 'while' }>
200
+ expect(whileStmt.body[0].kind).toBe('break')
201
+ expect(whileStmt.body[1].kind).toBe('continue')
202
+ })
203
+
204
+ test('raw command passes through', () => {
205
+ const body = getBody('fn f() { raw("say hello"); }')
206
+ expect(body).toHaveLength(1)
207
+ expect(body[0].kind).toBe('raw')
208
+ })
209
+ })
210
+
211
+ describe('HIR lowering — module structure', () => {
212
+ test('struct declarations preserved', () => {
213
+ const mod = parse('struct Vec2 { x: int, y: int }')
214
+ expect(mod.structs).toHaveLength(1)
215
+ expect(mod.structs[0].name).toBe('Vec2')
216
+ expect(mod.structs[0].fields).toHaveLength(2)
217
+ })
218
+
219
+ test('const declarations preserved', () => {
220
+ const mod = parse('const MAX: int = 100;')
221
+ expect(mod.consts).toHaveLength(1)
222
+ expect(mod.consts[0].name).toBe('MAX')
223
+ })
224
+
225
+ test('namespace preserved', () => {
226
+ const mod = parse('fn f() {}')
227
+ expect(mod.namespace).toBe('test')
228
+ })
229
+
230
+ test('decorators preserved on functions', () => {
231
+ const mod = parse('@tick fn tick_fn() {}')
232
+ expect(mod.functions).toHaveLength(1)
233
+ expect(mod.functions[0].decorators).toHaveLength(1)
234
+ expect(mod.functions[0].decorators[0].name).toBe('tick')
235
+ })
236
+ })
237
+
238
+ describe('HIR lowering — member compound assignment', () => {
239
+ test('obj.field += 1 → obj.field = obj.field + 1', () => {
240
+ const mod = parse('struct S { x: int } fn f() { let s: S = { x: 0 }; s.x += 1; }')
241
+ const body = mod.functions[0].body
242
+ const exprStmt = body[1] as Extract<HIRStmt, { kind: 'expr' }>
243
+ const assign = exprStmt.expr as Extract<HIRExpr, { kind: 'member_assign' }>
244
+ expect(assign.kind).toBe('member_assign')
245
+ expect(assign.field).toBe('x')
246
+ const bin = assign.value as Extract<HIRExpr, { kind: 'binary' }>
247
+ expect(bin.kind).toBe('binary')
248
+ expect(bin.op).toBe('+')
249
+ expect((bin.left as any).kind).toBe('member')
250
+ expect((bin.left as any).field).toBe('x')
251
+ })
252
+ })
253
+
254
+ describe('HIR lowering — plain assignment passes through', () => {
255
+ test('x = 5 stays as x = 5', () => {
256
+ const body = getBody('fn f() { let x: int = 0; x = 5; }')
257
+ const exprStmt = body[1] as Extract<HIRStmt, { kind: 'expr' }>
258
+ const assign = exprStmt.expr as Extract<HIRExpr, { kind: 'assign' }>
259
+ expect(assign.kind).toBe('assign')
260
+ expect(assign.target).toBe('x')
261
+ expect((assign.value as any).value).toBe(5)
262
+ })
263
+ })