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,1015 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const lexer_1 = require("../lexer");
4
+ const parser_1 = require("../parser");
5
+ const lowering_1 = require("../lowering");
6
+ function compile(source, namespace = 'test') {
7
+ return compileWithWarnings(source, namespace).ir;
8
+ }
9
+ function compileWithWarnings(source, namespace = 'test') {
10
+ const tokens = new lexer_1.Lexer(source).tokenize();
11
+ const ast = new parser_1.Parser(tokens).parse(namespace);
12
+ const lowering = new lowering_1.Lowering(namespace);
13
+ return { ir: lowering.lower(ast), warnings: lowering.warnings };
14
+ }
15
+ function getFunction(module, name) {
16
+ return module.functions.find(f => f.name === name);
17
+ }
18
+ function getInstructions(fn) {
19
+ return fn.blocks.flatMap(b => b.instrs);
20
+ }
21
+ function getRawCommands(fn) {
22
+ return getInstructions(fn)
23
+ .filter((i) => i.op === 'raw')
24
+ .map(i => i.cmd);
25
+ }
26
+ describe('Lowering', () => {
27
+ describe('basic functions', () => {
28
+ it('lowers empty function', () => {
29
+ const ir = compile('fn empty() {}');
30
+ const fn = getFunction(ir, 'empty');
31
+ expect(fn).toBeDefined();
32
+ expect(fn?.blocks).toHaveLength(1);
33
+ expect(fn?.blocks[0].term.op).toBe('return');
34
+ });
35
+ it('lowers function with params', () => {
36
+ const ir = compile('fn add(a: int, b: int) -> int { return a + b; }');
37
+ const fn = getFunction(ir, 'add');
38
+ expect(fn).toBeDefined();
39
+ expect(fn?.params).toEqual(['$a', '$b']);
40
+ });
41
+ it('creates param copy instructions', () => {
42
+ const ir = compile('fn foo(x: int) {}');
43
+ const fn = getFunction(ir, 'foo');
44
+ const instrs = getInstructions(fn);
45
+ expect(instrs.some(i => i.op === 'assign' && i.dst === '$foo_x' && i.src.kind === 'param' && i.src.index === 0)).toBe(true);
46
+ });
47
+ it('fills in missing default arguments at call sites', () => {
48
+ const ir = compile(`
49
+ fn damage(amount: int, multiplier: int = 1) -> int {
50
+ return amount * multiplier;
51
+ }
52
+
53
+ fn test() -> int {
54
+ return damage(10);
55
+ }
56
+ `);
57
+ const fn = getFunction(ir, 'test');
58
+ const call = getInstructions(fn).find(i => i.op === 'call');
59
+ expect(call.args).toHaveLength(2);
60
+ expect(call.args[0]).toEqual({ kind: 'const', value: 10 });
61
+ expect(call.args[1]).toEqual({ kind: 'const', value: 1 });
62
+ });
63
+ it('specializes callback-accepting functions for lambda arguments', () => {
64
+ const ir = compile(`
65
+ fn apply(val: int, cb: (int) -> int) -> int {
66
+ return cb(val);
67
+ }
68
+
69
+ fn test() -> int {
70
+ return apply(5, (x: int) => x * 3);
71
+ }
72
+ `);
73
+ expect(getFunction(ir, '__lambda_0')).toBeDefined();
74
+ const specialized = ir.functions.find(fn => fn.name.startsWith('apply__cb___lambda_0'));
75
+ expect(specialized).toBeDefined();
76
+ expect(specialized?.params).toEqual(['$val']);
77
+ const call = getInstructions(specialized).find(i => i.op === 'call');
78
+ expect(call.fn).toBe('__lambda_0');
79
+ });
80
+ it('lowers impl methods to prefixed function names', () => {
81
+ const ir = compile(`
82
+ struct Timer { duration: int }
83
+
84
+ impl Timer {
85
+ fn elapsed(self) -> int {
86
+ return self.duration;
87
+ }
88
+ }
89
+ `);
90
+ expect(getFunction(ir, 'Timer_elapsed')).toBeDefined();
91
+ });
92
+ it('lowers impl instance and static method calls', () => {
93
+ const ir = compile(`
94
+ struct Timer { duration: int }
95
+
96
+ impl Timer {
97
+ fn new(duration: int) -> Timer {
98
+ return { duration: duration };
99
+ }
100
+
101
+ fn elapsed(self) -> int {
102
+ return self.duration;
103
+ }
104
+ }
105
+
106
+ fn test() -> int {
107
+ let timer: Timer = Timer::new(10);
108
+ return timer.elapsed();
109
+ }
110
+ `);
111
+ const fn = getFunction(ir, 'test');
112
+ const calls = getInstructions(fn).filter((instr) => instr.op === 'call');
113
+ expect(calls.map(call => call.fn)).toEqual(['Timer_new', 'Timer_elapsed']);
114
+ });
115
+ });
116
+ describe('let statements', () => {
117
+ it('inlines const values without allocating scoreboard variables', () => {
118
+ const ir = compile(`
119
+ const MAX_HP: int = 100
120
+
121
+ fn foo() {
122
+ let x: int = MAX_HP;
123
+ }
124
+ `);
125
+ const fn = getFunction(ir, 'foo');
126
+ const instrs = getInstructions(fn);
127
+ expect(instrs.some(i => i.op === 'assign' && i.dst === '$foo_x' && i.src.kind === 'const' && i.src.value === 100)).toBe(true);
128
+ expect(ir.globals).not.toContain('$MAX_HP');
129
+ });
130
+ it('lowers let with literal', () => {
131
+ const ir = compile('fn foo() { let x: int = 42; }');
132
+ const fn = getFunction(ir, 'foo');
133
+ const instrs = getInstructions(fn);
134
+ expect(instrs.some(i => i.op === 'assign' && i.dst === '$foo_x' && i.src.value === 42)).toBe(true);
135
+ });
136
+ it('lowers let with expression', () => {
137
+ const ir = compile('fn foo(a: int) { let x: int = a + 1; }');
138
+ const fn = getFunction(ir, 'foo');
139
+ const instrs = getInstructions(fn);
140
+ expect(instrs.some(i => i.op === 'binop')).toBe(true);
141
+ });
142
+ it('stores literal-backed string variables in storage for str_len', () => {
143
+ const ir = compile('fn foo() { let name: string = "Player"; let n: int = str_len(name); }');
144
+ const fn = getFunction(ir, 'foo');
145
+ const rawCmds = getRawCommands(fn);
146
+ expect(rawCmds).toContain('data modify storage rs:strings name set value "Player"');
147
+ expect(rawCmds.some(cmd => cmd.includes('execute store result score') && cmd.includes('run data get storage rs:strings name'))).toBe(true);
148
+ });
149
+ });
150
+ describe('return statements', () => {
151
+ it('lowers return with value', () => {
152
+ const ir = compile('fn foo() -> int { return 42; }');
153
+ const fn = getFunction(ir, 'foo');
154
+ const term = fn.blocks[0].term;
155
+ expect(term.op).toBe('return');
156
+ expect(term.value).toEqual({ kind: 'const', value: 42 });
157
+ });
158
+ it('lowers empty return', () => {
159
+ const ir = compile('fn foo() { return; }');
160
+ const fn = getFunction(ir, 'foo');
161
+ const term = fn.blocks[0].term;
162
+ expect(term.op).toBe('return');
163
+ expect(term.value).toBeUndefined();
164
+ });
165
+ });
166
+ describe('lambda lowering', () => {
167
+ it('lowers lambda variables to generated sub-functions', () => {
168
+ const ir = compile(`
169
+ fn test() {
170
+ let double: (int) -> int = (x: int) => x * 2;
171
+ let result: int = double(5);
172
+ }
173
+ `);
174
+ const lambdaFn = getFunction(ir, '__lambda_0');
175
+ expect(lambdaFn).toBeDefined();
176
+ const testFn = getFunction(ir, 'test');
177
+ const calls = getInstructions(testFn).filter((instr) => instr.op === 'call');
178
+ expect(calls.some(call => call.fn === '__lambda_0')).toBe(true);
179
+ });
180
+ it('inlines immediately-invoked expression-body lambdas', () => {
181
+ const ir = compile(`
182
+ fn test() -> int {
183
+ return ((x: int) => x * 2)(5);
184
+ }
185
+ `);
186
+ expect(ir.functions.find(fn => fn.name.startsWith('__lambda_'))).toBeUndefined();
187
+ const testFn = getFunction(ir, 'test');
188
+ expect(getInstructions(testFn).some(instr => instr.op === 'binop')).toBe(true);
189
+ });
190
+ });
191
+ describe('binary expressions', () => {
192
+ it('lowers arithmetic', () => {
193
+ const ir = compile('fn foo(a: int, b: int) -> int { return a + b; }');
194
+ const fn = getFunction(ir, 'foo');
195
+ const instrs = getInstructions(fn);
196
+ const binop = instrs.find(i => i.op === 'binop');
197
+ expect(binop).toBeDefined();
198
+ expect(binop.bop).toBe('+');
199
+ });
200
+ it('lowers comparison', () => {
201
+ const ir = compile('fn foo(a: int, b: int) -> bool { return a < b; }');
202
+ const fn = getFunction(ir, 'foo');
203
+ const instrs = getInstructions(fn);
204
+ const cmp = instrs.find(i => i.op === 'cmp');
205
+ expect(cmp).toBeDefined();
206
+ expect(cmp.cop).toBe('<');
207
+ });
208
+ });
209
+ describe('unary expressions', () => {
210
+ it('lowers negation', () => {
211
+ const ir = compile('fn foo(x: int) -> int { return -x; }');
212
+ const fn = getFunction(ir, 'foo');
213
+ const instrs = getInstructions(fn);
214
+ const binop = instrs.find(i => i.op === 'binop' && i.bop === '-');
215
+ expect(binop).toBeDefined();
216
+ // -x is lowered as 0 - x
217
+ expect(binop.lhs).toEqual({ kind: 'const', value: 0 });
218
+ });
219
+ it('lowers logical not', () => {
220
+ const ir = compile('fn foo(x: bool) -> bool { return !x; }');
221
+ const fn = getFunction(ir, 'foo');
222
+ const instrs = getInstructions(fn);
223
+ const cmp = instrs.find(i => i.op === 'cmp' && i.cop === '==');
224
+ expect(cmp).toBeDefined();
225
+ // !x is lowered as x == 0
226
+ expect(cmp.rhs).toEqual({ kind: 'const', value: 0 });
227
+ });
228
+ });
229
+ describe('if statements', () => {
230
+ it('creates conditional jump', () => {
231
+ const ir = compile('fn foo(x: int) { if (x > 0) { let y: int = 1; } }');
232
+ const fn = getFunction(ir, 'foo');
233
+ expect(fn.blocks.length).toBeGreaterThan(1);
234
+ const term = fn.blocks[0].term;
235
+ expect(term.op).toBe('jump_if');
236
+ });
237
+ it('creates else block', () => {
238
+ const ir = compile('fn foo(x: int) { if (x > 0) { let y: int = 1; } else { let y: int = 2; } }');
239
+ const fn = getFunction(ir, 'foo');
240
+ expect(fn.blocks.length).toBeGreaterThanOrEqual(3); // entry, then, else, merge
241
+ });
242
+ it('lowers entity is-checks to execute if entity type filters', () => {
243
+ const ir = compile(`
244
+ fn scan() {
245
+ foreach (e in @e) {
246
+ if (e is Player) {
247
+ kill(e);
248
+ }
249
+ }
250
+ }
251
+ `);
252
+ const foreachFn = ir.functions.find(fn => fn.name.includes('scan/foreach'));
253
+ const rawCmds = getRawCommands(foreachFn);
254
+ const isCheckCmd = rawCmds.find(cmd => cmd.startsWith('execute if entity @s[type=minecraft:player] run function test:scan/then_'));
255
+ expect(isCheckCmd).toBeDefined();
256
+ const thenFn = ir.functions.find(fn => fn.name.startsWith('scan/then_'));
257
+ expect(getRawCommands(thenFn)).toContain('kill @s');
258
+ });
259
+ });
260
+ describe('while statements', () => {
261
+ it('creates loop structure', () => {
262
+ const ir = compile('fn foo() { let i: int = 0; while (i < 10) { i = i + 1; } }');
263
+ const fn = getFunction(ir, 'foo');
264
+ // Should have: entry -> check -> body -> exit
265
+ expect(fn.blocks.length).toBeGreaterThanOrEqual(3);
266
+ // Find loop_check block
267
+ const checkBlock = fn.blocks.find(b => b.label.includes('loop_check'));
268
+ expect(checkBlock).toBeDefined();
269
+ });
270
+ });
271
+ describe('foreach statements', () => {
272
+ it('extracts body into sub-function', () => {
273
+ const ir = compile('fn kill_all() { foreach (e in @e[type=zombie]) { kill(e); } }');
274
+ expect(ir.functions.length).toBe(2); // main + foreach sub-function
275
+ const subFn = ir.functions.find(f => f.name.includes('foreach'));
276
+ expect(subFn).toBeDefined();
277
+ });
278
+ it('emits execute as ... run function', () => {
279
+ const ir = compile('fn kill_all() { foreach (e in @e[type=zombie]) { kill(e); } }');
280
+ const mainFn = getFunction(ir, 'kill_all');
281
+ const rawCmds = getRawCommands(mainFn);
282
+ expect(rawCmds.some(cmd => cmd.includes('execute as @e[type=minecraft:zombie]') && cmd.includes('run function'))).toBe(true);
283
+ });
284
+ it('binding maps to @s in sub-function', () => {
285
+ const ir = compile('fn kill_all() { foreach (e in @e[type=zombie]) { kill(e); } }');
286
+ const subFn = ir.functions.find(f => f.name.includes('foreach'));
287
+ const rawCmds = getRawCommands(subFn);
288
+ expect(rawCmds.some(cmd => cmd === 'kill @s')).toBe(true);
289
+ });
290
+ it('lowers foreach over array into a counting loop', () => {
291
+ const ir = compile('fn walk() { let arr: int[] = [1, 2, 3]; foreach (x in arr) { let y: int = x; } }');
292
+ const fn = getFunction(ir, 'walk');
293
+ expect(fn.blocks.some(b => b.label.includes('foreach_array_check'))).toBe(true);
294
+ expect(fn.blocks.some(b => b.label.includes('foreach_array_body'))).toBe(true);
295
+ const rawCmds = getRawCommands(fn);
296
+ expect(rawCmds.some(cmd => cmd.includes('data get storage rs:heap arr'))).toBe(true);
297
+ });
298
+ it('lowers entity is-checks inside foreach bodies', () => {
299
+ const ir = compile(`
300
+ fn test() {
301
+ foreach (e in @e) {
302
+ if (e is Player) {
303
+ give(@s, "diamond", 1);
304
+ }
305
+ if (e is Zombie) {
306
+ kill(@s);
307
+ }
308
+ }
309
+ }
310
+ `);
311
+ const mainFn = getFunction(ir, 'test');
312
+ const foreachFn = ir.functions.find(f => f.name === 'test/foreach_0');
313
+ const thenFns = ir.functions.filter(f => /^test\/then_/.test(f.name)).sort((a, b) => a.name.localeCompare(b.name));
314
+ const rawCmds = getRawCommands(foreachFn);
315
+ const [playerThenFn, zombieThenFn] = thenFns;
316
+ expect(getRawCommands(mainFn)).toContain('execute as @e run function test:test/foreach_0');
317
+ expect(thenFns).toHaveLength(2);
318
+ expect(rawCmds).toContain(`execute if entity @s[type=minecraft:player] run function test:${playerThenFn.name}`);
319
+ expect(rawCmds).toContain(`execute if entity @s[type=minecraft:zombie] run function test:${zombieThenFn.name}`);
320
+ expect(getRawCommands(playerThenFn).some(cmd => cmd.includes('give @s diamond 1'))).toBe(true);
321
+ expect(getRawCommands(zombieThenFn)).toContain('kill @s');
322
+ });
323
+ });
324
+ describe('match statements', () => {
325
+ it('lowers match into guarded execute function calls', () => {
326
+ const ir = compile('fn choose() { let choice: int = 2; match (choice) { 1 => { say("one"); } 2 => { say("two"); } _ => { say("other"); } } }');
327
+ const fn = getFunction(ir, 'choose');
328
+ const rawCmds = getRawCommands(fn);
329
+ expect(rawCmds.some(cmd => cmd.includes('execute if score') && cmd.includes('matches 1 run function'))).toBe(true);
330
+ expect(rawCmds.some(cmd => cmd.includes('execute if score') && cmd.includes('matches 2 run function'))).toBe(true);
331
+ expect(rawCmds.some(cmd => cmd.includes('matches ..0 run function'))).toBe(true);
332
+ expect(ir.functions.filter(f => f.name.includes('match_')).length).toBe(3);
333
+ });
334
+ it('lowers enum variants to integer constants in comparisons and match arms', () => {
335
+ const ir = compile(`
336
+ enum Direction { North, South, East, West }
337
+
338
+ fn choose(dir: Direction) {
339
+ if (dir == Direction.South) {
340
+ say("south");
341
+ }
342
+ match (dir) {
343
+ Direction.North => { say("north"); }
344
+ Direction.South => { say("south"); }
345
+ _ => { say("other"); }
346
+ }
347
+ }
348
+ `);
349
+ const fn = getFunction(ir, 'choose');
350
+ const rawCmds = getRawCommands(fn);
351
+ expect(getInstructions(fn).some(i => i.op === 'cmp' && i.rhs.value === 1)).toBe(true);
352
+ expect(rawCmds.some(cmd => cmd.includes('matches 0 run function'))).toBe(true);
353
+ expect(rawCmds.some(cmd => cmd.includes('matches 1 run function'))).toBe(true);
354
+ });
355
+ });
356
+ describe('arrays', () => {
357
+ it('lowers array literal initialization', () => {
358
+ const ir = compile('fn test() { let arr: int[] = [1, 2, 3]; }');
359
+ const fn = getFunction(ir, 'test');
360
+ const rawCmds = getRawCommands(fn);
361
+ expect(rawCmds).toContain('data modify storage rs:heap arr set value []');
362
+ expect(rawCmds).toContain('data modify storage rs:heap arr append value 1');
363
+ expect(rawCmds).toContain('data modify storage rs:heap arr append value 2');
364
+ expect(rawCmds).toContain('data modify storage rs:heap arr append value 3');
365
+ });
366
+ it('lowers array len property', () => {
367
+ const ir = compile('fn test() { let arr: int[] = [1]; let n: int = arr.len; }');
368
+ const fn = getFunction(ir, 'test');
369
+ const rawCmds = getRawCommands(fn);
370
+ expect(rawCmds.some(cmd => cmd.includes('execute store result score') && cmd.includes('run data get storage rs:heap arr'))).toBe(true);
371
+ });
372
+ it('lowers static array indexing', () => {
373
+ const ir = compile('fn test() { let arr: int[] = [7, 8]; let x: int = arr[0]; }');
374
+ const fn = getFunction(ir, 'test');
375
+ const rawCmds = getRawCommands(fn);
376
+ expect(rawCmds.some(cmd => cmd.includes('run data get storage rs:heap arr[0]'))).toBe(true);
377
+ });
378
+ it('lowers dynamic array indexing via macro helper', () => {
379
+ const ir = compile('fn test() { let arr: int[] = [7, 8]; let i: int = 1; let x: int = arr[i]; }');
380
+ const fn = getFunction(ir, 'test');
381
+ const rawCmds = getRawCommands(fn);
382
+ expect(rawCmds.some(cmd => cmd.includes('with storage rs:heap'))).toBe(true);
383
+ const helperFn = ir.functions.find(f => f.name.includes('array_get_'));
384
+ expect(helperFn).toBeDefined();
385
+ expect(getRawCommands(helperFn).some(cmd => cmd.includes('arr[$('))).toBe(true);
386
+ });
387
+ it('lowers array push', () => {
388
+ const ir = compile('fn test() { let arr: int[] = []; arr.push(4); }');
389
+ const fn = getFunction(ir, 'test');
390
+ const rawCmds = getRawCommands(fn);
391
+ expect(rawCmds).toContain('data modify storage rs:heap arr append value 4');
392
+ });
393
+ it('lowers array pop', () => {
394
+ const ir = compile('fn test() { let arr: int[] = [1, 2]; let x: int = arr.pop(); }');
395
+ const fn = getFunction(ir, 'test');
396
+ const rawCmds = getRawCommands(fn);
397
+ expect(rawCmds.some(cmd => cmd.includes('run data get storage rs:heap arr[-1]'))).toBe(true);
398
+ expect(rawCmds).toContain('data remove storage rs:heap arr[-1]');
399
+ });
400
+ });
401
+ describe('as/at blocks', () => {
402
+ it('extracts as block into sub-function', () => {
403
+ const ir = compile('fn test() { as @a { say("hello"); } }');
404
+ expect(ir.functions.length).toBe(2);
405
+ const mainFn = getFunction(ir, 'test');
406
+ const rawCmds = getRawCommands(mainFn);
407
+ expect(rawCmds.some(cmd => cmd.includes('execute as @a') && cmd.includes('run function'))).toBe(true);
408
+ });
409
+ it('extracts at block into sub-function', () => {
410
+ const ir = compile('fn test() { at @s { summon("zombie"); } }');
411
+ expect(ir.functions.length).toBe(2);
412
+ const mainFn = getFunction(ir, 'test');
413
+ const rawCmds = getRawCommands(mainFn);
414
+ expect(rawCmds.some(cmd => cmd.includes('execute at @s') && cmd.includes('run function'))).toBe(true);
415
+ });
416
+ });
417
+ describe('execute inline blocks', () => {
418
+ it('extracts execute as run block into sub-function', () => {
419
+ const ir = compile('fn test() { execute as @a run { say("hello from each"); } }');
420
+ expect(ir.functions.length).toBe(2);
421
+ const mainFn = getFunction(ir, 'test');
422
+ const rawCmds = getRawCommands(mainFn);
423
+ expect(rawCmds.some(cmd => cmd.includes('execute as @a run function'))).toBe(true);
424
+ });
425
+ it('extracts execute as at run block into sub-function', () => {
426
+ const ir = compile('fn test() { execute as @a at @s run { particle("flame"); } }');
427
+ expect(ir.functions.length).toBe(2);
428
+ const mainFn = getFunction(ir, 'test');
429
+ const rawCmds = getRawCommands(mainFn);
430
+ expect(rawCmds.some(cmd => cmd.includes('execute as @a at @s run function'))).toBe(true);
431
+ });
432
+ it('handles execute with if entity condition', () => {
433
+ const ir = compile('fn test() { execute as @a if entity @s[tag=admin] run { give(@s, "diamond", 1); } }');
434
+ expect(ir.functions.length).toBe(2);
435
+ const mainFn = getFunction(ir, 'test');
436
+ const rawCmds = getRawCommands(mainFn);
437
+ expect(rawCmds.some(cmd => cmd.includes('execute as @a if entity @s[tag=admin] run function'))).toBe(true);
438
+ });
439
+ it('handles execute with unless entity condition', () => {
440
+ const ir = compile('fn test() { execute as @a unless entity @s[tag=dead] run { effect(@s, "regeneration", 5); } }');
441
+ expect(ir.functions.length).toBe(2);
442
+ const mainFn = getFunction(ir, 'test');
443
+ const rawCmds = getRawCommands(mainFn);
444
+ expect(rawCmds.some(cmd => cmd.includes('execute as @a unless entity @s[tag=dead] run function'))).toBe(true);
445
+ });
446
+ it('handles execute with in dimension', () => {
447
+ const ir = compile('fn test() { execute in the_nether run { say("in nether"); } }');
448
+ expect(ir.functions.length).toBe(2);
449
+ const mainFn = getFunction(ir, 'test');
450
+ const rawCmds = getRawCommands(mainFn);
451
+ expect(rawCmds.some(cmd => cmd.includes('execute in the_nether run function'))).toBe(true);
452
+ });
453
+ it('lowered sub-function contains body commands', () => {
454
+ const ir = compile('fn test() { execute as @a run { say("inner"); give(@s, "bread", 1); } }');
455
+ const subFn = ir.functions.find(f => f.name.includes('exec_'));
456
+ expect(subFn).toBeDefined();
457
+ const rawCmds = getRawCommands(subFn);
458
+ expect(rawCmds).toContain('say inner');
459
+ expect(rawCmds.some(cmd => cmd.includes('give @s bread 1'))).toBe(true);
460
+ });
461
+ });
462
+ describe('builtins', () => {
463
+ it('lowers say()', () => {
464
+ const ir = compile('fn test() { say("hello"); }');
465
+ const fn = getFunction(ir, 'test');
466
+ const rawCmds = getRawCommands(fn);
467
+ expect(rawCmds).toContain('say hello');
468
+ });
469
+ it('lowers kill()', () => {
470
+ const ir = compile('fn test() { kill(@e[type=zombie]); }');
471
+ const fn = getFunction(ir, 'test');
472
+ const rawCmds = getRawCommands(fn);
473
+ expect(rawCmds).toContain('kill @e[type=minecraft:zombie]');
474
+ });
475
+ it('lowers give()', () => {
476
+ const ir = compile('fn test() { give(@p, "diamond", 64); }');
477
+ const fn = getFunction(ir, 'test');
478
+ const rawCmds = getRawCommands(fn);
479
+ expect(rawCmds).toContain('give @p diamond 64');
480
+ });
481
+ it('lowers actionbar(), subtitle(), and title_times()', () => {
482
+ const ir = compile('fn test() { actionbar(@a, "Fight!"); subtitle(@a, "Next wave"); title_times(@a, 10, 40, 10); }');
483
+ const fn = getFunction(ir, 'test');
484
+ const rawCmds = getRawCommands(fn);
485
+ expect(rawCmds).toContain('title @a actionbar {"text":"Fight!"}');
486
+ expect(rawCmds).toContain('title @a subtitle {"text":"Next wave"}');
487
+ expect(rawCmds).toContain('title @a times 10 40 10');
488
+ });
489
+ it('lowers announce()', () => {
490
+ const ir = compile('fn test() { announce("Server event starting"); }');
491
+ const fn = getFunction(ir, 'test');
492
+ const rawCmds = getRawCommands(fn);
493
+ expect(rawCmds).toContain('tellraw @a {"text":"Server event starting"}');
494
+ });
495
+ it('lowers interpolated say() to tellraw score components', () => {
496
+ const ir = compile('fn test() { let score: int = 7; say("You have ${score} points"); }');
497
+ const fn = getFunction(ir, 'test');
498
+ const rawCmds = getRawCommands(fn);
499
+ expect(rawCmds).toContain('tellraw @a ["",{"text":"You have "},{"score":{"name":"$test_score","objective":"rs"}},{"text":" points"}]');
500
+ });
501
+ it('lowers f-string output builtins to tellraw/title JSON components', () => {
502
+ const ir = compile('fn test() { let score: int = 7; say(f"Score: {score}"); tellraw(@a, f"Score: {score}"); actionbar(@s, f"Score: {score}"); title(@s, f"Score: {score}"); }');
503
+ const fn = getFunction(ir, 'test');
504
+ const rawCmds = getRawCommands(fn);
505
+ expect(rawCmds).toContain('tellraw @a ["",{"text":"Score: "},{"score":{"name":"$test_score","objective":"rs"}}]');
506
+ expect(rawCmds).toContain('title @s actionbar ["",{"text":"Score: "},{"score":{"name":"$test_score","objective":"rs"}}]');
507
+ expect(rawCmds).toContain('title @s title ["",{"text":"Score: "},{"score":{"name":"$test_score","objective":"rs"}}]');
508
+ });
509
+ it('lowers summon()', () => {
510
+ const ir = compile('fn test() { summon("zombie"); }');
511
+ const fn = getFunction(ir, 'test');
512
+ const rawCmds = getRawCommands(fn);
513
+ expect(rawCmds.some(cmd => cmd.includes('summon zombie'))).toBe(true);
514
+ });
515
+ it('lowers effect()', () => {
516
+ const ir = compile('fn test() { effect(@a, "speed", 30, 1); }');
517
+ const fn = getFunction(ir, 'test');
518
+ const rawCmds = getRawCommands(fn);
519
+ expect(rawCmds.some(cmd => cmd.includes('effect give @a speed 30 1'))).toBe(true);
520
+ });
521
+ it('lowers tp() for both positions and entity destinations', () => {
522
+ const ir = compile('fn test() { tp(@s, (~1, ~0, ~-1)); tp(@s, "^0", "^1", "^0"); tp(@s, @p); tp(@a, (1, 64, 1)); }');
523
+ const fn = getFunction(ir, 'test');
524
+ const rawCmds = getRawCommands(fn);
525
+ expect(rawCmds).toContain('tp @s ~1 ~ ~-1');
526
+ expect(rawCmds).toContain('tp @s ^0 ^1 ^0');
527
+ expect(rawCmds).toContain('tp @s @p');
528
+ expect(rawCmds).toContain('tp @a 1 64 1');
529
+ });
530
+ it('warns when using tp_to()', () => {
531
+ const { ir, warnings } = compileWithWarnings('fn test() { tp_to(@s, @p); }');
532
+ const fn = getFunction(ir, 'test');
533
+ const rawCmds = getRawCommands(fn);
534
+ expect(rawCmds).toContain('tp @s @p');
535
+ expect(warnings).toContainEqual(expect.objectContaining({
536
+ code: 'W_DEPRECATED',
537
+ message: 'tp_to is deprecated; use tp instead',
538
+ }));
539
+ });
540
+ it('lowers inventory and player admin commands', () => {
541
+ const ir = compile('fn test() { clear(@s); clear(@s, "minecraft:stick"); kick(@p); kick(@p, "AFK"); }');
542
+ const fn = getFunction(ir, 'test');
543
+ const rawCmds = getRawCommands(fn);
544
+ expect(rawCmds).toContain('clear @s');
545
+ expect(rawCmds).toContain('clear @s minecraft:stick');
546
+ expect(rawCmds).toContain('kick @p');
547
+ expect(rawCmds).toContain('kick @p AFK');
548
+ });
549
+ it('lowers world management commands', () => {
550
+ const ir = compile('fn test() { weather("rain"); time_set("day"); time_add(1000); gamerule("doDaylightCycle", "false"); difficulty("hard"); }');
551
+ const fn = getFunction(ir, 'test');
552
+ const rawCmds = getRawCommands(fn);
553
+ expect(rawCmds).toContain('weather rain');
554
+ expect(rawCmds).toContain('time set day');
555
+ expect(rawCmds).toContain('time add 1000');
556
+ expect(rawCmds).toContain('gamerule doDaylightCycle false');
557
+ expect(rawCmds).toContain('difficulty hard');
558
+ });
559
+ it('lowers tag_add() and tag_remove()', () => {
560
+ const ir = compile('fn test() { tag_add(@s, "boss"); tag_remove(@s, "boss"); }');
561
+ const fn = getFunction(ir, 'test');
562
+ const rawCmds = getRawCommands(fn);
563
+ expect(rawCmds).toContain('tag @s add boss');
564
+ expect(rawCmds).toContain('tag @s remove boss');
565
+ });
566
+ it('lowers setblock(), fill(), and clone()', () => {
567
+ const ir = compile('fn test() { setblock((4, 65, 4), "stone"); fill((0, 64, 0), (8, 64, 8), "minecraft:smooth_stone"); clone((0, 64, 0), (4, 68, 4), (10, 64, 10)); setblock("~", "~", "~", "legacy"); }');
568
+ const fn = getFunction(ir, 'test');
569
+ const rawCmds = getRawCommands(fn);
570
+ expect(rawCmds).toContain('setblock 4 65 4 stone');
571
+ expect(rawCmds).toContain('fill 0 64 0 8 64 8 minecraft:smooth_stone');
572
+ expect(rawCmds).toContain('clone 0 64 0 4 68 4 10 64 10');
573
+ expect(rawCmds).toContain('setblock ~ ~ ~ legacy');
574
+ });
575
+ it('lowers BlockPos locals in coordinate builtins', () => {
576
+ const ir = compile('fn test() { let spawn: BlockPos = (4, 65, 4); setblock(spawn, "minecraft:stone"); }');
577
+ const fn = getFunction(ir, 'test');
578
+ const rawCmds = getRawCommands(fn);
579
+ expect(rawCmds).toContain('setblock 4 65 4 minecraft:stone');
580
+ });
581
+ it('lowers xp_add() and xp_set()', () => {
582
+ const ir = compile('fn test() { xp_add(@s, 5); xp_add(@s, 2, "levels"); xp_set(@s, 0); xp_set(@s, 3, "levels"); }');
583
+ const fn = getFunction(ir, 'test');
584
+ const rawCmds = getRawCommands(fn);
585
+ expect(rawCmds).toContain('xp add @s 5 points');
586
+ expect(rawCmds).toContain('xp add @s 2 levels');
587
+ expect(rawCmds).toContain('xp set @s 0 points');
588
+ expect(rawCmds).toContain('xp set @s 3 levels');
589
+ });
590
+ it('lowers scoreboard display and objective management builtins', () => {
591
+ const ir = compile(`
592
+ fn test() {
593
+ scoreboard_display("sidebar", "kills");
594
+ scoreboard_display("list", "coins");
595
+ scoreboard_display("belowName", "hp");
596
+ scoreboard_hide("sidebar");
597
+ scoreboard_add_objective("kills", "playerKillCount", "Kill Count");
598
+ scoreboard_remove_objective("kills");
599
+ }
600
+ `);
601
+ const fn = getFunction(ir, 'test');
602
+ const rawCmds = getRawCommands(fn);
603
+ expect(rawCmds).toContain('scoreboard objectives setdisplay sidebar test.kills');
604
+ expect(rawCmds).toContain('scoreboard objectives setdisplay list test.coins');
605
+ expect(rawCmds).toContain('scoreboard objectives setdisplay belowName test.hp');
606
+ expect(rawCmds).toContain('scoreboard objectives setdisplay sidebar');
607
+ expect(rawCmds).toContain('scoreboard objectives add test.kills playerKillCount "Kill Count"');
608
+ expect(rawCmds).toContain('scoreboard objectives remove test.kills');
609
+ });
610
+ it('lowers bossbar management builtins', () => {
611
+ const ir = compile(`
612
+ fn test() {
613
+ bossbar_add("ns:health", "Boss Health");
614
+ bossbar_set_value("ns:health", 50);
615
+ bossbar_set_max("ns:health", 100);
616
+ bossbar_set_color("ns:health", "red");
617
+ bossbar_set_style("ns:health", "notched_10");
618
+ bossbar_set_visible("ns:health", true);
619
+ bossbar_set_players("ns:health", @a);
620
+ bossbar_remove("ns:health");
621
+ let current: int = bossbar_get_value("ns:health");
622
+ }
623
+ `);
624
+ const fn = getFunction(ir, 'test');
625
+ const rawCmds = getRawCommands(fn);
626
+ expect(rawCmds).toContain('bossbar add ns:health {"text":"Boss Health"}');
627
+ expect(rawCmds).toContain('bossbar set ns:health value 50');
628
+ expect(rawCmds).toContain('bossbar set ns:health max 100');
629
+ expect(rawCmds).toContain('bossbar set ns:health color red');
630
+ expect(rawCmds).toContain('bossbar set ns:health style notched_10');
631
+ expect(rawCmds).toContain('bossbar set ns:health visible true');
632
+ expect(rawCmds).toContain('bossbar set ns:health players @a');
633
+ expect(rawCmds).toContain('bossbar remove ns:health');
634
+ expect(rawCmds.some(cmd => /^execute store result score \$_\d+ rs run bossbar get ns:health value$/.test(cmd))).toBe(true);
635
+ });
636
+ it('lowers team management builtins', () => {
637
+ const ir = compile(`
638
+ fn test() {
639
+ team_add("red", "Red Team");
640
+ team_remove("red");
641
+ team_join("red", @a[tag=red_team]);
642
+ team_leave(@s);
643
+ team_option("red", "friendlyFire", "false");
644
+ team_option("red", "color", "red");
645
+ team_option("red", "prefix", "[Red] ");
646
+ }
647
+ `);
648
+ const fn = getFunction(ir, 'test');
649
+ const rawCmds = getRawCommands(fn);
650
+ expect(rawCmds).toContain('team add red {"text":"Red Team"}');
651
+ expect(rawCmds).toContain('team remove red');
652
+ expect(rawCmds).toContain('team join red @a[tag=red_team]');
653
+ expect(rawCmds).toContain('team leave @s');
654
+ expect(rawCmds).toContain('team modify red friendlyFire false');
655
+ expect(rawCmds).toContain('team modify red color red');
656
+ expect(rawCmds).toContain('team modify red prefix {"text":"[Red] "}');
657
+ });
658
+ it('lowers random()', () => {
659
+ const ir = compile('fn test() { let x: int = random(1, 100); }');
660
+ const fn = getFunction(ir, 'test');
661
+ const rawCmds = getRawCommands(fn);
662
+ expect(rawCmds).toContain('scoreboard players random $_0 rs 1 100');
663
+ });
664
+ it('lowers random_native()', () => {
665
+ const ir = compile('fn test() { let x: int = random_native(1, 6); }');
666
+ const fn = getFunction(ir, 'test');
667
+ const rawCmds = getRawCommands(fn);
668
+ expect(rawCmds).toContain('execute store result score $_0 rs run random value 1 6');
669
+ });
670
+ it('lowers random_sequence()', () => {
671
+ const ir = compile('fn test() { random_sequence("loot", 42); }');
672
+ const fn = getFunction(ir, 'test');
673
+ const rawCmds = getRawCommands(fn);
674
+ expect(rawCmds).toContain('random reset loot 42');
675
+ });
676
+ it('lowers setTimeout() to a scheduled helper function', () => {
677
+ const ir = compile('fn test() { setTimeout(100, () => { say("hi"); }); }');
678
+ const fn = getFunction(ir, 'test');
679
+ const timeoutFn = getFunction(ir, '__timeout_0');
680
+ const rawCmds = getRawCommands(fn);
681
+ const timeoutCmds = getRawCommands(timeoutFn);
682
+ expect(rawCmds).toContain('schedule function test:__timeout_0 100t');
683
+ expect(timeoutCmds).toContain('say hi');
684
+ });
685
+ it('lowers setInterval() to a self-rescheduling helper function', () => {
686
+ const ir = compile('fn test() { setInterval(20, () => { say("tick"); }); }');
687
+ const fn = getFunction(ir, 'test');
688
+ const intervalFn = getFunction(ir, '__interval_0');
689
+ const intervalBodyFn = getFunction(ir, '__interval_body_0');
690
+ const rawCmds = getRawCommands(fn);
691
+ const intervalCmds = getRawCommands(intervalFn);
692
+ const intervalBodyCmds = getRawCommands(intervalBodyFn);
693
+ expect(rawCmds).toContain('schedule function test:__interval_0 20t');
694
+ expect(intervalCmds).toContain('function test:__interval_body_0');
695
+ expect(intervalCmds).toContain('schedule function test:__interval_0 20t');
696
+ expect(intervalBodyCmds).toContain('say tick');
697
+ });
698
+ it('lowers clearInterval() to schedule clear for the generated interval function', () => {
699
+ const ir = compile(`
700
+ fn test() {
701
+ let intervalId: int = setInterval(20, () => { say("tick"); });
702
+ clearInterval(intervalId);
703
+ }
704
+ `);
705
+ const fn = getFunction(ir, 'test');
706
+ const rawCmds = getRawCommands(fn);
707
+ expect(rawCmds).toContain('schedule function test:__interval_0 20t');
708
+ expect(rawCmds).toContain('schedule clear test:__interval_0');
709
+ });
710
+ it('lowers data_get from entity', () => {
711
+ const ir = compile('fn test() { let item_count: int = data_get("entity", "@s", "SelectedItem.Count"); }');
712
+ const fn = getFunction(ir, 'test');
713
+ const rawCmds = getRawCommands(fn);
714
+ expect(rawCmds.some(cmd => cmd.includes('execute store result score') &&
715
+ cmd.includes('run data get entity @s SelectedItem.Count 1'))).toBe(true);
716
+ });
717
+ it('lowers data_get from block', () => {
718
+ const ir = compile('fn test() { let furnace_fuel: int = data_get("block", "~ ~ ~", "BurnTime"); }');
719
+ const fn = getFunction(ir, 'test');
720
+ const rawCmds = getRawCommands(fn);
721
+ expect(rawCmds.some(cmd => cmd.includes('run data get block ~ ~ ~ BurnTime 1'))).toBe(true);
722
+ });
723
+ it('lowers data_get from storage', () => {
724
+ const ir = compile('fn test() { let val: int = data_get("storage", "mypack:globals", "player_count"); }');
725
+ const fn = getFunction(ir, 'test');
726
+ const rawCmds = getRawCommands(fn);
727
+ expect(rawCmds.some(cmd => cmd.includes('run data get storage mypack:globals player_count 1'))).toBe(true);
728
+ });
729
+ it('lowers data_get with scale factor', () => {
730
+ const ir = compile('fn test() { let scaled: int = data_get("entity", "@s", "Pos[0]", "1000"); }');
731
+ const fn = getFunction(ir, 'test');
732
+ const rawCmds = getRawCommands(fn);
733
+ expect(rawCmds.some(cmd => cmd.includes('run data get entity @s Pos[0] 1000'))).toBe(true);
734
+ });
735
+ it('data_get result can be used in expressions', () => {
736
+ const ir = compile(`
737
+ fn test() {
738
+ let count: int = data_get("entity", "@s", "SelectedItem.Count");
739
+ let doubled: int = count * 2;
740
+ }
741
+ `);
742
+ const fn = getFunction(ir, 'test');
743
+ const instrs = getInstructions(fn);
744
+ expect(instrs.some(i => i.op === 'binop' && i.bop === '*')).toBe(true);
745
+ });
746
+ it('accepts bare selector targets in scoreboard_get', () => {
747
+ const ir = compile('fn test() { let score: int = scoreboard_get(@s, "score"); }');
748
+ const fn = getFunction(ir, 'test');
749
+ const rawCmds = getRawCommands(fn);
750
+ expect(rawCmds.some(cmd => cmd.includes('run scoreboard players get @s test.score'))).toBe(true);
751
+ });
752
+ it('accepts bare selector targets in scoreboard_set', () => {
753
+ const ir = compile('fn test() { scoreboard_set(@a, "kills", 0); }');
754
+ const fn = getFunction(ir, 'test');
755
+ const rawCmds = getRawCommands(fn);
756
+ expect(rawCmds).toContain('scoreboard players set @a test.kills 0');
757
+ });
758
+ it('skips prefixing raw mc_name objectives', () => {
759
+ const ir = compile('fn test() { scoreboard_set(@s, #health, 100); }');
760
+ const fn = getFunction(ir, 'test');
761
+ const rawCmds = getRawCommands(fn);
762
+ expect(rawCmds).toContain('scoreboard players set @s health 100');
763
+ });
764
+ it('warns on quoted selectors in scoreboard_get', () => {
765
+ const { ir, warnings } = compileWithWarnings('fn test() { let score: int = scoreboard_get("@s", "score"); }');
766
+ const fn = getFunction(ir, 'test');
767
+ const rawCmds = getRawCommands(fn);
768
+ expect(rawCmds.some(cmd => cmd.includes('run scoreboard players get @s test.score'))).toBe(true);
769
+ expect(warnings).toContainEqual(expect.objectContaining({
770
+ code: 'W_QUOTED_SELECTOR',
771
+ message: 'Quoted selector "@s" is deprecated; pass @s without quotes',
772
+ }));
773
+ });
774
+ it('does not warn on fake player names', () => {
775
+ const { ir, warnings } = compileWithWarnings('fn test() { let total: int = scoreboard_get("#global", "total"); }');
776
+ const fn = getFunction(ir, 'test');
777
+ const rawCmds = getRawCommands(fn);
778
+ expect(rawCmds.some(cmd => cmd.includes('run scoreboard players get #global test.total'))).toBe(true);
779
+ expect(warnings).toHaveLength(0);
780
+ });
781
+ it('warns on quoted selectors in data_get entity targets', () => {
782
+ const { ir, warnings } = compileWithWarnings('fn test() { let pos: int = data_get("entity", "@s", "Pos[0]"); }');
783
+ const fn = getFunction(ir, 'test');
784
+ const rawCmds = getRawCommands(fn);
785
+ expect(rawCmds.some(cmd => cmd.includes('run data get entity @s Pos[0] 1'))).toBe(true);
786
+ expect(warnings).toContainEqual(expect.objectContaining({
787
+ code: 'W_QUOTED_SELECTOR',
788
+ message: 'Quoted selector "@s" is deprecated; pass @s without quotes',
789
+ }));
790
+ });
791
+ it('keeps already-qualified scoreboard objectives unchanged', () => {
792
+ const ir = compile('fn test() { scoreboard_set(@s, "custom.timer", 5); }');
793
+ const fn = getFunction(ir, 'test');
794
+ const rawCmds = getRawCommands(fn);
795
+ expect(rawCmds).toContain('scoreboard players set @s custom.timer 5');
796
+ });
797
+ });
798
+ describe('timer builtins', () => {
799
+ it('lowers timer builtins into schedule commands and wrapper functions', () => {
800
+ const ir = compile(`
801
+ fn test() {
802
+ let intervalId: int = setInterval(20, () => {
803
+ say("tick");
804
+ });
805
+ setTimeout(100, () => {
806
+ say("later");
807
+ });
808
+ clearInterval(intervalId);
809
+ }
810
+ `);
811
+ const fn = getFunction(ir, 'test');
812
+ const rawCmds = getRawCommands(fn);
813
+ expect(rawCmds).toContain('schedule function test:__interval_0 20t');
814
+ expect(rawCmds).toContain('schedule function test:__timeout_0 100t');
815
+ expect(rawCmds).toContain('schedule clear test:__interval_0');
816
+ const intervalFn = getFunction(ir, '__interval_0');
817
+ expect(getRawCommands(intervalFn)).toEqual([
818
+ 'function test:__interval_body_0',
819
+ 'schedule function test:__interval_0 20t',
820
+ ]);
821
+ });
822
+ });
823
+ describe('decorators', () => {
824
+ it('marks @tick function', () => {
825
+ const ir = compile('@tick fn game_loop() {}');
826
+ const fn = getFunction(ir, 'game_loop');
827
+ expect(fn.isTickLoop).toBe(true);
828
+ });
829
+ it('marks @on_trigger function', () => {
830
+ const ir = compile('@on_trigger("my_trigger") fn handle_trigger() {}');
831
+ const fn = getFunction(ir, 'handle_trigger');
832
+ expect(fn.isTriggerHandler).toBe(true);
833
+ expect(fn.triggerName).toBe('my_trigger');
834
+ });
835
+ it('marks @on_advancement function for advancement json generation', () => {
836
+ const ir = compile('@on_advancement("story/mine_diamond") fn handle_advancement() {}');
837
+ const fn = getFunction(ir, 'handle_advancement');
838
+ expect(fn.eventTrigger).toEqual({ kind: 'advancement', value: 'story/mine_diamond' });
839
+ });
840
+ it('marks @on event functions and binds player to @s', () => {
841
+ const ir = compile('@on(PlayerDeath) fn handle_death(player: Player) { tp(player, @p); }');
842
+ const fn = getFunction(ir, 'handle_death');
843
+ expect(fn.eventHandler).toEqual({ eventType: 'PlayerDeath', tag: 'rs.just_died' });
844
+ expect(fn.params).toEqual([]);
845
+ expect(getRawCommands(fn)).toContain('tp @s @p');
846
+ });
847
+ });
848
+ describe('selectors', () => {
849
+ it('converts selector with filters to string', () => {
850
+ const ir = compile('fn test() { kill(@e[type=zombie, distance=..10, tag=boss]); }');
851
+ const fn = getFunction(ir, 'test');
852
+ const rawCmds = getRawCommands(fn);
853
+ const killCmd = rawCmds.find(cmd => cmd.startsWith('kill'));
854
+ expect(killCmd).toContain('type=minecraft:zombie');
855
+ expect(killCmd).toContain('distance=..10');
856
+ expect(killCmd).toContain('tag=boss');
857
+ });
858
+ it('warns and auto-qualifies unnamespaced entity types', () => {
859
+ const { ir, warnings } = compileWithWarnings('fn test() { kill(@e[type=zombie]); }');
860
+ const fn = getFunction(ir, 'test');
861
+ const rawCmds = getRawCommands(fn);
862
+ expect(rawCmds).toContain('kill @e[type=minecraft:zombie]');
863
+ expect(warnings).toContainEqual({
864
+ code: 'W_UNNAMESPACED_TYPE',
865
+ message: 'Unnamespaced entity type "zombie", auto-qualifying to "minecraft:zombie"',
866
+ });
867
+ });
868
+ it('passes through minecraft entity types without warnings', () => {
869
+ const { ir, warnings } = compileWithWarnings('fn test() { kill(@e[type=minecraft:zombie]); }');
870
+ const fn = getFunction(ir, 'test');
871
+ const rawCmds = getRawCommands(fn);
872
+ expect(rawCmds).toContain('kill @e[type=minecraft:zombie]');
873
+ expect(warnings).toHaveLength(0);
874
+ });
875
+ it('passes through custom namespaced entity types without warnings', () => {
876
+ const { ir, warnings } = compileWithWarnings('fn test() { kill(@e[type=my_mod:custom_mob]); }');
877
+ const fn = getFunction(ir, 'test');
878
+ const rawCmds = getRawCommands(fn);
879
+ expect(rawCmds).toContain('kill @e[type=my_mod:custom_mob]');
880
+ expect(warnings).toHaveLength(0);
881
+ });
882
+ it('throws on invalid entity type format', () => {
883
+ expect(() => compileWithWarnings('fn test() { kill(@e[type=invalid!!!]); }'))
884
+ .toThrow('Invalid entity type format: "invalid!!!"');
885
+ });
886
+ });
887
+ describe('raw commands', () => {
888
+ it('passes through raw commands', () => {
889
+ const ir = compile('fn test() { raw("tp @a ~ ~10 ~"); }');
890
+ const fn = getFunction(ir, 'test');
891
+ const rawCmds = getRawCommands(fn);
892
+ expect(rawCmds).toContain('tp @a ~ ~10 ~');
893
+ });
894
+ });
895
+ describe('assignment operators', () => {
896
+ it('lowers compound assignment', () => {
897
+ const ir = compile('fn test() { let x: int = 5; x += 3; }');
898
+ const fn = getFunction(ir, 'test');
899
+ const instrs = getInstructions(fn);
900
+ const binop = instrs.find(i => i.op === 'binop' && i.bop === '+');
901
+ expect(binop).toBeDefined();
902
+ });
903
+ });
904
+ describe('entity tag methods', () => {
905
+ it('lowers entity.tag()', () => {
906
+ const ir = compile('fn test() { @s.tag("boss"); }');
907
+ const fn = getFunction(ir, 'test');
908
+ const rawCmds = getRawCommands(fn);
909
+ expect(rawCmds).toContain('tag @s add boss');
910
+ });
911
+ it('lowers entity.untag()', () => {
912
+ const ir = compile('fn test() { @s.untag("boss"); }');
913
+ const fn = getFunction(ir, 'test');
914
+ const rawCmds = getRawCommands(fn);
915
+ expect(rawCmds).toContain('tag @s remove boss');
916
+ });
917
+ it('lowers entity.has_tag() and returns temp var', () => {
918
+ const ir = compile('fn test() { let x: bool = @s.has_tag("boss"); }');
919
+ const fn = getFunction(ir, 'test');
920
+ const rawCmds = getRawCommands(fn);
921
+ expect(rawCmds.some(cmd => cmd.includes('execute store result score') && cmd.includes('if entity @s[tag=boss]'))).toBe(true);
922
+ });
923
+ it('lowers entity.tag() on selector with filters', () => {
924
+ const ir = compile('fn test() { @e[type=zombie].tag("marked"); }');
925
+ const fn = getFunction(ir, 'test');
926
+ const rawCmds = getRawCommands(fn);
927
+ expect(rawCmds.some(cmd => cmd.includes('tag @e[type=minecraft:zombie] add marked'))).toBe(true);
928
+ });
929
+ });
930
+ describe('complex programs', () => {
931
+ it('compiles add function correctly', () => {
932
+ const source = `
933
+ fn add(a: int, b: int) -> int {
934
+ return a + b;
935
+ }
936
+ `;
937
+ const ir = compile(source);
938
+ const fn = getFunction(ir, 'add');
939
+ expect(fn.params).toEqual(['$a', '$b']);
940
+ const instrs = getInstructions(fn);
941
+ expect(instrs.some(i => i.op === 'binop' && i.bop === '+')).toBe(true);
942
+ const term = fn.blocks[fn.blocks.length - 1].term;
943
+ expect(term.op).toBe('return');
944
+ expect(term.value?.kind).toBe('var');
945
+ });
946
+ it('compiles abs function with if/else', () => {
947
+ const source = `
948
+ fn abs(x: int) -> int {
949
+ if (x < 0) {
950
+ return -x;
951
+ } else {
952
+ return x;
953
+ }
954
+ }
955
+ `;
956
+ const ir = compile(source);
957
+ const fn = getFunction(ir, 'abs');
958
+ expect(fn.blocks.length).toBeGreaterThanOrEqual(3);
959
+ // Should have comparison
960
+ const instrs = getInstructions(fn);
961
+ expect(instrs.some(i => i.op === 'cmp' && i.cop === '<')).toBe(true);
962
+ });
963
+ it('compiles countdown with while', () => {
964
+ const source = `
965
+ fn count_down() {
966
+ let i: int = 10;
967
+ while (i > 0) {
968
+ i = i - 1;
969
+ }
970
+ }
971
+ `;
972
+ const ir = compile(source);
973
+ const fn = getFunction(ir, 'count_down');
974
+ // Should have loop structure
975
+ const checkBlock = fn.blocks.find(b => b.label.includes('loop_check'));
976
+ const bodyBlock = fn.blocks.find(b => b.label.includes('loop_body'));
977
+ expect(checkBlock).toBeDefined();
978
+ expect(bodyBlock).toBeDefined();
979
+ });
980
+ });
981
+ describe('Global variables', () => {
982
+ it('registers global in IR globals with init value', () => {
983
+ const ir = compile('let x: int = 42;\nfn test() { say("hi"); }');
984
+ expect(ir.globals).toContainEqual({ name: '$x', init: 42 });
985
+ });
986
+ it('reads global variable in function body', () => {
987
+ const ir = compile('let count: int = 0;\nfn test() { let y: int = count; }');
988
+ const fn = getFunction(ir, 'test');
989
+ const instrs = getInstructions(fn);
990
+ expect(instrs.some(i => i.op === 'assign' && i.dst === '$test_y' && i.src.kind === 'var' && i.src.name === '$count')).toBe(true);
991
+ });
992
+ it('writes global variable in function body', () => {
993
+ const ir = compile('let count: int = 0;\nfn inc() { count = 5; }');
994
+ const fn = getFunction(ir, 'inc');
995
+ const instrs = getInstructions(fn);
996
+ expect(instrs.some(i => i.op === 'assign' && i.dst === '$count' && i.src.kind === 'const' && i.src.value === 5)).toBe(true);
997
+ });
998
+ it('compound assignment on global variable', () => {
999
+ const ir = compile('let count: int = 0;\nfn inc() { count += 1; }');
1000
+ const fn = getFunction(ir, 'inc');
1001
+ const instrs = getInstructions(fn);
1002
+ expect(instrs.some(i => i.op === 'binop' && i.lhs.name === '$count' && i.bop === '+' && i.rhs.value === 1)).toBe(true);
1003
+ });
1004
+ it('const cannot be reassigned', () => {
1005
+ const src = 'const X: int = 5;\nfn bad() { X = 10; }';
1006
+ expect(() => compile(src)).toThrow(/Cannot assign to constant/);
1007
+ });
1008
+ it('multiple globals with different init values', () => {
1009
+ const ir = compile('let a: int = 10;\nlet b: int = 20;\nfn test() { a = b; }');
1010
+ expect(ir.globals).toContainEqual({ name: '$a', init: 10 });
1011
+ expect(ir.globals).toContainEqual({ name: '$b', init: 20 });
1012
+ });
1013
+ });
1014
+ });
1015
+ //# sourceMappingURL=lowering.test.js.map