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,3008 @@
1
+ /**
2
+ * Migration tests — representative cases from the existing 920-test suite
3
+ * run through the NEW v2 compiler pipeline.
4
+ *
5
+ * Pattern:
6
+ * 1. Compile source with src2/emit/compile.ts
7
+ * 2. Load .mcfunction files into MCRuntime
8
+ * 3. Execute functions and assert scoreboard state
9
+ *
10
+ * NOTE: v2 uses objective `__<ns>` (not `rs`), load function is `ns:load`
11
+ * (not `ns:__load`), and return values go to `$ret` on the objective.
12
+ */
13
+
14
+ import { compile } from '../../emit/compile'
15
+ import { MCRuntime } from '../../../src/runtime'
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // Helpers
19
+ // ---------------------------------------------------------------------------
20
+
21
+ const NS = 'test'
22
+ const OBJ = `__${NS}`
23
+
24
+ function getFile(
25
+ files: { path: string; content: string }[],
26
+ pathSubstr: string,
27
+ ): string | undefined {
28
+ return files.find(f => f.path.includes(pathSubstr))?.content
29
+ }
30
+
31
+ /** Compile source, load all .mcfunction files into MCRuntime, init objective */
32
+ function makeRuntime(source: string, namespace = NS): MCRuntime {
33
+ const result = compile(source, { namespace })
34
+ const rt = new MCRuntime(namespace)
35
+ for (const file of result.files) {
36
+ if (!file.path.endsWith('.mcfunction')) continue
37
+ const m = file.path.match(/data\/([^/]+)\/function\/(.+)\.mcfunction$/)
38
+ if (!m) continue
39
+ rt.loadFunction(`${m[1]}:${m[2]}`, file.content.split('\n'))
40
+ }
41
+ // Init the objective (v2 load function creates it)
42
+ rt.execFunction(`${namespace}:load`)
43
+ return rt
44
+ }
45
+
46
+ /** Execute a function and return the $ret value on the v2 objective */
47
+ function callAndGetRet(rt: MCRuntime, fnName: string, namespace = NS): number {
48
+ rt.execFunction(`${namespace}:${fnName}`)
49
+ return rt.getScore('$ret', `__${namespace}`)
50
+ }
51
+
52
+ // ===========================================================================
53
+ // 1. Compilation smoke tests — does the compiler not crash?
54
+ // ===========================================================================
55
+
56
+ describe('v2 migration: compilation smoke', () => {
57
+ const cases: [string, string][] = [
58
+ ['empty function', 'fn noop(): void {}'],
59
+ ['return constant', 'fn f(): int { return 42; }'],
60
+ ['arithmetic', 'fn f(): int { return 1 + 2; }'],
61
+ ['variable', 'fn f(): int { let x: int = 10; return x; }'],
62
+ ['negation', 'fn f(): int { return -5; }'],
63
+ ['comparison', 'fn f(): bool { return 3 > 2; }'],
64
+ ['if/else', 'fn f(x: int): int { if (x > 0) { return 1; } else { return 0; } }'],
65
+ ['while loop', 'fn f(): void { let i: int = 0; while (i < 10) { i = i + 1; } }'],
66
+ ['multiple functions', 'fn a(): int { return 1; }\nfn b(): int { return 2; }'],
67
+ ['function call', 'fn add(a: int, b: int): int { return a + b; }\nfn main(): int { return add(3, 4); }'],
68
+ ['boolean AND', 'fn f(): bool { return true && false; }'],
69
+ ['boolean OR', 'fn f(): bool { return true || false; }'],
70
+ ['boolean NOT', 'fn f(): bool { return !true; }'],
71
+ ['nested arithmetic', 'fn f(): int { return (1 + 2) * (3 - 4); }'],
72
+ ['modulo', 'fn f(): int { return 10 % 3; }'],
73
+ ['@tick decorator', '@tick fn game_tick(): void { let x: int = 1; }'],
74
+ ['@load decorator', '@load fn setup(): void { let x: int = 0; }'],
75
+ ['chained comparison', 'fn f(x: int): bool { return x >= 0 && x <= 100; }'],
76
+ ]
77
+
78
+ test.each(cases)('%s compiles without error', (_name, source) => {
79
+ expect(() => compile(source, { namespace: NS })).not.toThrow()
80
+ })
81
+ })
82
+
83
+ // ===========================================================================
84
+ // 2. Structural tests — output shape
85
+ // ===========================================================================
86
+
87
+ describe('v2 migration: output structure', () => {
88
+ test('pack.mcmeta present with pack_format 26', () => {
89
+ const result = compile('fn noop(): void {}', { namespace: NS })
90
+ const meta = getFile(result.files, 'pack.mcmeta')
91
+ expect(meta).toBeDefined()
92
+ expect(JSON.parse(meta!).pack.pack_format).toBe(26)
93
+ })
94
+
95
+ test('load.mcfunction creates scoreboard objective', () => {
96
+ const result = compile('fn noop(): void {}', { namespace: NS })
97
+ const load = getFile(result.files, 'load.mcfunction')
98
+ expect(load).toBeDefined()
99
+ expect(load).toContain(`scoreboard objectives add ${OBJ} dummy`)
100
+ })
101
+
102
+ test('load.json always includes namespace:load', () => {
103
+ const result = compile('fn noop(): void {}', { namespace: NS })
104
+ const loadJson = getFile(result.files, 'load.json')
105
+ expect(loadJson).toBeDefined()
106
+ expect(JSON.parse(loadJson!).values).toContain(`${NS}:load`)
107
+ })
108
+
109
+ test('@tick function appears in tick.json', () => {
110
+ const result = compile('@tick fn game_tick(): void { let x: int = 1; }', { namespace: NS })
111
+ const tickJson = getFile(result.files, 'tick.json')
112
+ expect(tickJson).toBeDefined()
113
+ expect(JSON.parse(tickJson!).values).toContain(`${NS}:game_tick`)
114
+ })
115
+
116
+ test('@load function appears in load.json', () => {
117
+ const result = compile('@load fn setup(): void { let x: int = 0; }', { namespace: NS })
118
+ const loadJson = getFile(result.files, 'load.json')
119
+ expect(loadJson).toBeDefined()
120
+ expect(JSON.parse(loadJson!).values).toContain(`${NS}:setup`)
121
+ })
122
+
123
+ test('no tick.json when no @tick functions', () => {
124
+ const result = compile('fn noop(): void {}', { namespace: NS })
125
+ const tickJson = getFile(result.files, 'tick.json')
126
+ expect(tickJson).toBeUndefined()
127
+ })
128
+
129
+ test('function names are lowercased in output paths', () => {
130
+ const result = compile('fn MyFunc(): void {}', { namespace: NS })
131
+ const fn = result.files.find(f => f.path.includes('myfunc.mcfunction'))
132
+ expect(fn).toBeDefined()
133
+ })
134
+
135
+ test('simple function produces scoreboard commands', () => {
136
+ const result = compile('fn add(a: int, b: int): int { return a + b; }', { namespace: NS })
137
+ const fn = getFile(result.files, 'add.mcfunction')
138
+ expect(fn).toBeDefined()
139
+ expect(fn).toContain('scoreboard players operation')
140
+ expect(fn).toContain(OBJ)
141
+ })
142
+
143
+ test('constant assignment produces score_set', () => {
144
+ const result = compile('fn init(): int { let x: int = 42; return x; }', { namespace: NS })
145
+ const fn = getFile(result.files, 'init.mcfunction')
146
+ expect(fn).toBeDefined()
147
+ expect(fn).toContain('scoreboard players set')
148
+ expect(fn).toContain('42')
149
+ })
150
+
151
+ test('if/else produces conditional call pattern', () => {
152
+ const source = `
153
+ fn check(x: int): int {
154
+ if (x > 0) { return 1; } else { return 0; }
155
+ }
156
+ `
157
+ const result = compile(source, { namespace: NS })
158
+ const fn = getFile(result.files, 'check.mcfunction')
159
+ expect(fn).toBeDefined()
160
+ expect(fn).toContain('execute if score')
161
+ expect(fn).toContain('matches')
162
+ expect(fn).toContain('run function')
163
+ })
164
+
165
+ test('while loop produces loop structure with function calls', () => {
166
+ const source = `
167
+ fn count(): void {
168
+ let i: int = 0;
169
+ while (i < 10) { i = i + 1; }
170
+ }
171
+ `
172
+ const result = compile(source, { namespace: NS })
173
+ const fnFiles = result.files.filter(f => f.path.endsWith('.mcfunction'))
174
+ expect(fnFiles.length).toBeGreaterThan(1) // main + loop blocks
175
+ const allContent = fnFiles.map(f => f.content).join('\n')
176
+ expect(allContent).toContain('execute if score')
177
+ expect(allContent).toContain('run function')
178
+ })
179
+ })
180
+
181
+ // ===========================================================================
182
+ // 3. Runtime behavioural tests — execute and check scoreboard values
183
+ // ===========================================================================
184
+
185
+ describe('v2 migration: return values', () => {
186
+ test('return constant 42', () => {
187
+ const rt = makeRuntime('fn f(): int { return 42; }')
188
+ expect(callAndGetRet(rt, 'f')).toBe(42)
189
+ })
190
+
191
+ test('return zero', () => {
192
+ const rt = makeRuntime('fn f(): int { return 0; }')
193
+ expect(callAndGetRet(rt, 'f')).toBe(0)
194
+ })
195
+
196
+ test('return negative', () => {
197
+ const rt = makeRuntime('fn f(): int { return -10; }')
198
+ expect(callAndGetRet(rt, 'f')).toBe(-10)
199
+ })
200
+ })
201
+
202
+ describe('v2 migration: arithmetic', () => {
203
+ test('1 + 2 = 3', () => {
204
+ const rt = makeRuntime('fn f(): int { return 1 + 2; }')
205
+ expect(callAndGetRet(rt, 'f')).toBe(3)
206
+ })
207
+
208
+ test('10 - 3 = 7', () => {
209
+ const rt = makeRuntime('fn f(): int { return 10 - 3; }')
210
+ expect(callAndGetRet(rt, 'f')).toBe(7)
211
+ })
212
+
213
+ test('4 * 5 = 20', () => {
214
+ const rt = makeRuntime('fn f(): int { return 4 * 5; }')
215
+ expect(callAndGetRet(rt, 'f')).toBe(20)
216
+ })
217
+
218
+ test('20 / 4 = 5', () => {
219
+ const rt = makeRuntime('fn f(): int { return 20 / 4; }')
220
+ expect(callAndGetRet(rt, 'f')).toBe(5)
221
+ })
222
+
223
+ test('10 % 3 = 1', () => {
224
+ const rt = makeRuntime('fn f(): int { return 10 % 3; }')
225
+ expect(callAndGetRet(rt, 'f')).toBe(1)
226
+ })
227
+
228
+ test('chained: (2 + 3) * 4 = 20', () => {
229
+ const rt = makeRuntime('fn f(): int { return (2 + 3) * 4; }')
230
+ expect(callAndGetRet(rt, 'f')).toBe(20)
231
+ })
232
+
233
+ test('negation: -(5) = -5', () => {
234
+ const rt = makeRuntime('fn f(): int { return -(5); }')
235
+ expect(callAndGetRet(rt, 'f')).toBe(-5)
236
+ })
237
+ })
238
+
239
+ describe('v2 migration: variables', () => {
240
+ test('let and return', () => {
241
+ const rt = makeRuntime('fn f(): int { let x: int = 42; return x; }')
242
+ expect(callAndGetRet(rt, 'f')).toBe(42)
243
+ })
244
+
245
+ test('variable reassignment', () => {
246
+ const rt = makeRuntime(`
247
+ fn f(): int {
248
+ let x: int = 1;
249
+ x = 10;
250
+ return x;
251
+ }
252
+ `)
253
+ expect(callAndGetRet(rt, 'f')).toBe(10)
254
+ })
255
+
256
+ test('multiple variables', () => {
257
+ const rt = makeRuntime(`
258
+ fn f(): int {
259
+ let a: int = 3;
260
+ let b: int = 7;
261
+ return a + b;
262
+ }
263
+ `)
264
+ expect(callAndGetRet(rt, 'f')).toBe(10)
265
+ })
266
+
267
+ test('variable used in expression', () => {
268
+ const rt = makeRuntime(`
269
+ fn f(): int {
270
+ let x: int = 5;
271
+ let y: int = x * 2 + 1;
272
+ return y;
273
+ }
274
+ `)
275
+ expect(callAndGetRet(rt, 'f')).toBe(11)
276
+ })
277
+ })
278
+
279
+ describe('v2 migration: comparisons', () => {
280
+ test('3 > 2 is true (1)', () => {
281
+ const rt = makeRuntime('fn f(): int { if (3 > 2) { return 1; } else { return 0; } }')
282
+ expect(callAndGetRet(rt, 'f')).toBe(1)
283
+ })
284
+
285
+ test('2 > 3 is false (0)', () => {
286
+ const rt = makeRuntime('fn f(): int { if (2 > 3) { return 1; } else { return 0; } }')
287
+ expect(callAndGetRet(rt, 'f')).toBe(0)
288
+ })
289
+
290
+ test('5 == 5 is true', () => {
291
+ const rt = makeRuntime('fn f(): int { if (5 == 5) { return 1; } else { return 0; } }')
292
+ expect(callAndGetRet(rt, 'f')).toBe(1)
293
+ })
294
+
295
+ test('5 != 3 is true', () => {
296
+ const rt = makeRuntime('fn f(): int { if (5 != 3) { return 1; } else { return 0; } }')
297
+ expect(callAndGetRet(rt, 'f')).toBe(1)
298
+ })
299
+
300
+ test('3 <= 3 is true', () => {
301
+ const rt = makeRuntime('fn f(): int { if (3 <= 3) { return 1; } else { return 0; } }')
302
+ expect(callAndGetRet(rt, 'f')).toBe(1)
303
+ })
304
+
305
+ test('4 >= 5 is false', () => {
306
+ const rt = makeRuntime('fn f(): int { if (4 >= 5) { return 1; } else { return 0; } }')
307
+ expect(callAndGetRet(rt, 'f')).toBe(0)
308
+ })
309
+ })
310
+
311
+ describe('v2 migration: if/else control flow', () => {
312
+ test('if-true branch taken', () => {
313
+ const rt = makeRuntime(`
314
+ fn f(): int {
315
+ let x: int = 10;
316
+ if (x > 5) { return 1; } else { return 0; }
317
+ }
318
+ `)
319
+ expect(callAndGetRet(rt, 'f')).toBe(1)
320
+ })
321
+
322
+ test('if-false branch taken', () => {
323
+ const rt = makeRuntime(`
324
+ fn f(): int {
325
+ let x: int = 2;
326
+ if (x > 5) { return 1; } else { return 0; }
327
+ }
328
+ `)
329
+ expect(callAndGetRet(rt, 'f')).toBe(0)
330
+ })
331
+
332
+ test('if without else — falls through', () => {
333
+ const rt = makeRuntime(`
334
+ fn f(): int {
335
+ let x: int = 0;
336
+ if (true) { x = 42; }
337
+ return x;
338
+ }
339
+ `)
340
+ expect(callAndGetRet(rt, 'f')).toBe(42)
341
+ })
342
+
343
+ test('nested if/else', () => {
344
+ const rt = makeRuntime(`
345
+ fn f(): int {
346
+ let x: int = 15;
347
+ if (x > 20) {
348
+ return 3;
349
+ } else {
350
+ if (x > 10) {
351
+ return 2;
352
+ } else {
353
+ return 1;
354
+ }
355
+ }
356
+ }
357
+ `)
358
+ expect(callAndGetRet(rt, 'f')).toBe(2)
359
+ })
360
+ })
361
+
362
+ describe('v2 migration: while loops', () => {
363
+ test('simple countdown', () => {
364
+ const rt = makeRuntime(`
365
+ fn f(): int {
366
+ let i: int = 10;
367
+ while (i > 0) { i = i - 1; }
368
+ return i;
369
+ }
370
+ `)
371
+ expect(callAndGetRet(rt, 'f')).toBe(0)
372
+ })
373
+
374
+ test('sum 1 to 5', () => {
375
+ const rt = makeRuntime(`
376
+ fn f(): int {
377
+ let sum: int = 0;
378
+ let i: int = 1;
379
+ while (i <= 5) {
380
+ sum = sum + i;
381
+ i = i + 1;
382
+ }
383
+ return sum;
384
+ }
385
+ `)
386
+ expect(callAndGetRet(rt, 'f')).toBe(15)
387
+ })
388
+
389
+ test('while false — never executes', () => {
390
+ const rt = makeRuntime(`
391
+ fn f(): int {
392
+ let x: int = 99;
393
+ while (false) { x = 0; }
394
+ return x;
395
+ }
396
+ `)
397
+ expect(callAndGetRet(rt, 'f')).toBe(99)
398
+ })
399
+ })
400
+
401
+ describe('v2 migration: function calls', () => {
402
+ test('call simple function', () => {
403
+ const rt = makeRuntime(`
404
+ fn double(x: int): int { return x * 2; }
405
+ fn f(): int { return double(21); }
406
+ `)
407
+ expect(callAndGetRet(rt, 'f')).toBe(42)
408
+ })
409
+
410
+ test('call with two params', () => {
411
+ const rt = makeRuntime(`
412
+ fn add(a: int, b: int): int { return a + b; }
413
+ fn f(): int { return add(17, 25); }
414
+ `)
415
+ expect(callAndGetRet(rt, 'f')).toBe(42)
416
+ })
417
+
418
+ test('chain of calls', () => {
419
+ const rt = makeRuntime(`
420
+ fn inc(x: int): int { return x + 1; }
421
+ fn f(): int { return inc(inc(inc(0))); }
422
+ `)
423
+ expect(callAndGetRet(rt, 'f')).toBe(3)
424
+ })
425
+
426
+ test('call function with no return used in expression', () => {
427
+ const rt = makeRuntime(`
428
+ fn five(): int { return 5; }
429
+ fn f(): int {
430
+ let x: int = five() + five();
431
+ return x;
432
+ }
433
+ `)
434
+ expect(callAndGetRet(rt, 'f')).toBe(10)
435
+ })
436
+ })
437
+
438
+ describe('v2 migration: boolean logic', () => {
439
+ test('true AND true = 1', () => {
440
+ const rt = makeRuntime(`
441
+ fn f(): int {
442
+ if (true && true) { return 1; } else { return 0; }
443
+ }
444
+ `)
445
+ expect(callAndGetRet(rt, 'f')).toBe(1)
446
+ })
447
+
448
+ test('true AND false = 0', () => {
449
+ const rt = makeRuntime(`
450
+ fn f(): int {
451
+ if (true && false) { return 1; } else { return 0; }
452
+ }
453
+ `)
454
+ expect(callAndGetRet(rt, 'f')).toBe(0)
455
+ })
456
+
457
+ test('false OR true = 1', () => {
458
+ const rt = makeRuntime(`
459
+ fn f(): int {
460
+ if (false || true) { return 1; } else { return 0; }
461
+ }
462
+ `)
463
+ expect(callAndGetRet(rt, 'f')).toBe(1)
464
+ })
465
+
466
+ test('NOT true = 0', () => {
467
+ const rt = makeRuntime(`
468
+ fn f(): int {
469
+ if (!true) { return 1; } else { return 0; }
470
+ }
471
+ `)
472
+ expect(callAndGetRet(rt, 'f')).toBe(0)
473
+ })
474
+ })
475
+
476
+ // ===========================================================================
477
+ // 4. More complex patterns from the original test suite
478
+ // ===========================================================================
479
+
480
+ describe('v2 migration: compound expressions', () => {
481
+ test('abs via if/else', () => {
482
+ const rt = makeRuntime(`
483
+ fn abs(x: int): int {
484
+ if (x < 0) { return -x; } else { return x; }
485
+ }
486
+ fn f(): int { return abs(-7); }
487
+ `)
488
+ expect(callAndGetRet(rt, 'f')).toBe(7)
489
+ })
490
+
491
+ test('max of two', () => {
492
+ const rt = makeRuntime(`
493
+ fn max(a: int, b: int): int {
494
+ if (a > b) { return a; } else { return b; }
495
+ }
496
+ fn f(): int { return max(3, 7); }
497
+ `)
498
+ expect(callAndGetRet(rt, 'f')).toBe(7)
499
+ })
500
+
501
+ test('factorial iterative', () => {
502
+ const rt = makeRuntime(`
503
+ fn fact(n: int): int {
504
+ let result: int = 1;
505
+ let i: int = 1;
506
+ while (i <= n) {
507
+ result = result * i;
508
+ i = i + 1;
509
+ }
510
+ return result;
511
+ }
512
+ fn f(): int { return fact(5); }
513
+ `)
514
+ expect(callAndGetRet(rt, 'f')).toBe(120)
515
+ })
516
+
517
+ test('fibonacci iterative', () => {
518
+ const rt = makeRuntime(`
519
+ fn fib(n: int): int {
520
+ let a: int = 0;
521
+ let b: int = 1;
522
+ let i: int = 0;
523
+ while (i < n) {
524
+ let tmp: int = b;
525
+ b = a + b;
526
+ a = tmp;
527
+ i = i + 1;
528
+ }
529
+ return a;
530
+ }
531
+ fn f(): int { return fib(10); }
532
+ `)
533
+ expect(callAndGetRet(rt, 'f')).toBe(55)
534
+ })
535
+ })
536
+
537
+ // ===========================================================================
538
+ // 5. Break / continue
539
+ // ===========================================================================
540
+
541
+ describe('v2 migration: break and continue', () => {
542
+ test('break exits loop early', () => {
543
+ const rt = makeRuntime(`
544
+ fn f(): int {
545
+ let i: int = 0;
546
+ while (true) {
547
+ if (i == 5) { break; }
548
+ i = i + 1;
549
+ }
550
+ return i;
551
+ }
552
+ `)
553
+ expect(callAndGetRet(rt, 'f')).toBe(5)
554
+ })
555
+
556
+ test('continue skips iteration', () => {
557
+ const rt = makeRuntime(`
558
+ fn f(): int {
559
+ let sum: int = 0;
560
+ let i: int = 0;
561
+ while (i < 10) {
562
+ i = i + 1;
563
+ if (i % 2 == 0) { continue; }
564
+ sum = sum + i;
565
+ }
566
+ return sum;
567
+ }
568
+ `)
569
+ // sum of odd numbers 1..9 = 1+3+5+7+9 = 25
570
+ expect(callAndGetRet(rt, 'f')).toBe(25)
571
+ })
572
+ })
573
+
574
+ // ===========================================================================
575
+ // 6. Compound assignment operators (desugared in HIR)
576
+ // ===========================================================================
577
+
578
+ describe('v2 migration: compound assignment', () => {
579
+ test('+= operator', () => {
580
+ const rt = makeRuntime(`
581
+ fn f(): int { let x: int = 10; x += 5; return x; }
582
+ `)
583
+ expect(callAndGetRet(rt, 'f')).toBe(15)
584
+ })
585
+
586
+ test('-= operator', () => {
587
+ const rt = makeRuntime(`
588
+ fn f(): int { let x: int = 10; x -= 3; return x; }
589
+ `)
590
+ expect(callAndGetRet(rt, 'f')).toBe(7)
591
+ })
592
+
593
+ test('*= operator', () => {
594
+ const rt = makeRuntime(`
595
+ fn f(): int { let x: int = 4; x *= 5; return x; }
596
+ `)
597
+ expect(callAndGetRet(rt, 'f')).toBe(20)
598
+ })
599
+ })
600
+
601
+ // ===========================================================================
602
+ // 7. Multiple return paths
603
+ // ===========================================================================
604
+
605
+ describe('v2 migration: multiple return paths', () => {
606
+ test('early return from if', () => {
607
+ const rt = makeRuntime(`
608
+ fn f(): int {
609
+ let x: int = 42;
610
+ if (x > 10) { return x; }
611
+ return 0;
612
+ }
613
+ `)
614
+ expect(callAndGetRet(rt, 'f')).toBe(42)
615
+ })
616
+
617
+ test('return from else path', () => {
618
+ const rt = makeRuntime(`
619
+ fn f(): int {
620
+ let x: int = 5;
621
+ if (x > 10) { return 1; }
622
+ return 2;
623
+ }
624
+ `)
625
+ expect(callAndGetRet(rt, 'f')).toBe(2)
626
+ })
627
+
628
+ test('return from nested if chains', () => {
629
+ const rt = makeRuntime(`
630
+ fn classify(x: int): int {
631
+ if (x < 0) { return -1; }
632
+ if (x == 0) { return 0; }
633
+ return 1;
634
+ }
635
+ fn f(): int {
636
+ return classify(-5) + classify(0) + classify(7);
637
+ }
638
+ `)
639
+ // -1 + 0 + 1 = 0
640
+ expect(callAndGetRet(rt, 'f')).toBe(0)
641
+ })
642
+ })
643
+
644
+ // ===========================================================================
645
+ // 8. Mutual function calls
646
+ // ===========================================================================
647
+
648
+ describe('v2 migration: mutual calls and recursion-like patterns', () => {
649
+ test('function calling function calling function', () => {
650
+ const rt = makeRuntime(`
651
+ fn a(): int { return 1; }
652
+ fn b(): int { return a() + 2; }
653
+ fn c(): int { return b() + 3; }
654
+ fn f(): int { return c(); }
655
+ `)
656
+ expect(callAndGetRet(rt, 'f')).toBe(6)
657
+ })
658
+
659
+ test('iterative power function', () => {
660
+ const rt = makeRuntime(`
661
+ fn pow(base: int, exp: int): int {
662
+ let result: int = 1;
663
+ let i: int = 0;
664
+ while (i < exp) {
665
+ result = result * base;
666
+ i = i + 1;
667
+ }
668
+ return result;
669
+ }
670
+ fn f(): int { return pow(2, 10); }
671
+ `)
672
+ expect(callAndGetRet(rt, 'f')).toBe(1024)
673
+ })
674
+ })
675
+
676
+ // ===========================================================================
677
+ // 9. Edge cases
678
+ // ===========================================================================
679
+
680
+ describe('v2 migration: edge cases', () => {
681
+ test('zero division (MC truncates to 0)', () => {
682
+ // MC scoreboard division by zero returns 0
683
+ const rt = makeRuntime('fn f(): int { return 10 / 0; }')
684
+ // This may throw or return 0 depending on MCRuntime behavior
685
+ try {
686
+ const val = callAndGetRet(rt, 'f')
687
+ expect(val).toBe(0)
688
+ } catch {
689
+ // Division by zero is undefined in MC — just ensure no crash
690
+ }
691
+ })
692
+
693
+ test('deeply nested arithmetic', () => {
694
+ const rt = makeRuntime(`
695
+ fn f(): int {
696
+ return ((1 + 2) * (3 + 4)) - ((5 - 6) * (7 + 8));
697
+ }
698
+ `)
699
+ // (3 * 7) - ((-1) * 15) = 21 - (-15) = 36
700
+ expect(callAndGetRet(rt, 'f')).toBe(36)
701
+ })
702
+
703
+ test('many variables', () => {
704
+ const rt = makeRuntime(`
705
+ fn f(): int {
706
+ let a: int = 1;
707
+ let b: int = 2;
708
+ let c: int = 3;
709
+ let d: int = 4;
710
+ let e: int = 5;
711
+ return a + b + c + d + e;
712
+ }
713
+ `)
714
+ expect(callAndGetRet(rt, 'f')).toBe(15)
715
+ })
716
+
717
+ test('nested while loops', () => {
718
+ const rt = makeRuntime(`
719
+ fn f(): int {
720
+ let sum: int = 0;
721
+ let i: int = 0;
722
+ while (i < 3) {
723
+ let j: int = 0;
724
+ while (j < 3) {
725
+ sum = sum + 1;
726
+ j = j + 1;
727
+ }
728
+ i = i + 1;
729
+ }
730
+ return sum;
731
+ }
732
+ `)
733
+ expect(callAndGetRet(rt, 'f')).toBe(9)
734
+ })
735
+
736
+ test('if inside while', () => {
737
+ const rt = makeRuntime(`
738
+ fn f(): int {
739
+ let count: int = 0;
740
+ let i: int = 0;
741
+ while (i < 10) {
742
+ if (i % 3 == 0) {
743
+ count = count + 1;
744
+ }
745
+ i = i + 1;
746
+ }
747
+ return count;
748
+ }
749
+ `)
750
+ // 0, 3, 6, 9 are divisible by 3 → count = 4
751
+ expect(callAndGetRet(rt, 'f')).toBe(4)
752
+ })
753
+
754
+ test('while inside if', () => {
755
+ const rt = makeRuntime(`
756
+ fn f(): int {
757
+ let x: int = 1;
758
+ if (x > 0) {
759
+ let sum: int = 0;
760
+ let i: int = 0;
761
+ while (i < 5) {
762
+ sum = sum + i;
763
+ i = i + 1;
764
+ }
765
+ return sum;
766
+ }
767
+ return -1;
768
+ }
769
+ `)
770
+ expect(callAndGetRet(rt, 'f')).toBe(10)
771
+ })
772
+ })
773
+
774
+ // ===========================================================================
775
+ // 10. Function calls with 3+ parameters
776
+ // ===========================================================================
777
+
778
+ describe('v2 migration: multi-param functions', () => {
779
+ test('function with 3 parameters', () => {
780
+ const rt = makeRuntime(`
781
+ fn add3(a: int, b: int, c: int): int { return a + b + c; }
782
+ fn f(): int { return add3(10, 20, 30); }
783
+ `)
784
+ expect(callAndGetRet(rt, 'f')).toBe(60)
785
+ })
786
+
787
+ test('function with 4 parameters', () => {
788
+ const rt = makeRuntime(`
789
+ fn sum4(a: int, b: int, c: int, d: int): int { return a + b + c + d; }
790
+ fn f(): int { return sum4(1, 2, 3, 4); }
791
+ `)
792
+ expect(callAndGetRet(rt, 'f')).toBe(10)
793
+ })
794
+
795
+ test('function with 5 parameters', () => {
796
+ const rt = makeRuntime(`
797
+ fn sum5(a: int, b: int, c: int, d: int, e: int): int {
798
+ return a + b + c + d + e;
799
+ }
800
+ fn f(): int { return sum5(2, 4, 6, 8, 10); }
801
+ `)
802
+ expect(callAndGetRet(rt, 'f')).toBe(30)
803
+ })
804
+
805
+ test('3-param function with mixed operations', () => {
806
+ const rt = makeRuntime(`
807
+ fn weighted(a: int, b: int, w: int): int {
808
+ return a * w + b * (100 - w);
809
+ }
810
+ fn f(): int { return weighted(10, 5, 60); }
811
+ `)
812
+ // 10 * 60 + 5 * 40 = 600 + 200 = 800
813
+ expect(callAndGetRet(rt, 'f')).toBe(800)
814
+ })
815
+ })
816
+
817
+ // ===========================================================================
818
+ // 11. Call chains and nested calls
819
+ // ===========================================================================
820
+
821
+ describe('v2 migration: call chains', () => {
822
+ test('4-deep call chain', () => {
823
+ const rt = makeRuntime(`
824
+ fn a(): int { return 1; }
825
+ fn b(): int { return a() + 10; }
826
+ fn c(): int { return b() + 100; }
827
+ fn d(): int { return c() + 1000; }
828
+ fn f(): int { return d(); }
829
+ `)
830
+ expect(callAndGetRet(rt, 'f')).toBe(1111)
831
+ })
832
+
833
+ test('function calling same function multiple times', () => {
834
+ const rt = makeRuntime(`
835
+ fn square(x: int): int { return x * x; }
836
+ fn f(): int { return square(3) + square(4); }
837
+ `)
838
+ // 9 + 16 = 25
839
+ expect(callAndGetRet(rt, 'f')).toBe(25)
840
+ })
841
+
842
+ test('function result used as argument', () => {
843
+ const rt = makeRuntime(`
844
+ fn add(a: int, b: int): int { return a + b; }
845
+ fn f(): int { return add(add(1, 2), add(3, 4)); }
846
+ `)
847
+ // add(3, 7) = 10
848
+ expect(callAndGetRet(rt, 'f')).toBe(10)
849
+ })
850
+
851
+ test('mutual helper functions', () => {
852
+ const rt = makeRuntime(`
853
+ fn double(x: int): int { return x * 2; }
854
+ fn triple(x: int): int { return x * 3; }
855
+ fn f(): int { return double(5) + triple(5); }
856
+ `)
857
+ expect(callAndGetRet(rt, 'f')).toBe(25)
858
+ })
859
+ })
860
+
861
+ // ===========================================================================
862
+ // 12. Math-style patterns (inline, no stdlib import)
863
+ // ===========================================================================
864
+
865
+ describe('v2 migration: math patterns', () => {
866
+ test('min of two numbers', () => {
867
+ const rt = makeRuntime(`
868
+ fn min(a: int, b: int): int {
869
+ if (a < b) { return a; } else { return b; }
870
+ }
871
+ fn f(): int { return min(7, 3); }
872
+ `)
873
+ expect(callAndGetRet(rt, 'f')).toBe(3)
874
+ })
875
+
876
+ test('min returns first when equal', () => {
877
+ const rt = makeRuntime(`
878
+ fn min(a: int, b: int): int {
879
+ if (a < b) { return a; } else { return b; }
880
+ }
881
+ fn f(): int { return min(5, 5); }
882
+ `)
883
+ expect(callAndGetRet(rt, 'f')).toBe(5)
884
+ })
885
+
886
+ test('abs of positive stays positive', () => {
887
+ const rt = makeRuntime(`
888
+ fn abs(x: int): int {
889
+ if (x < 0) { return -x; } else { return x; }
890
+ }
891
+ fn f(): int { return abs(42); }
892
+ `)
893
+ expect(callAndGetRet(rt, 'f')).toBe(42)
894
+ })
895
+
896
+ test('abs of zero is zero', () => {
897
+ const rt = makeRuntime(`
898
+ fn abs(x: int): int {
899
+ if (x < 0) { return -x; } else { return x; }
900
+ }
901
+ fn f(): int { return abs(0); }
902
+ `)
903
+ expect(callAndGetRet(rt, 'f')).toBe(0)
904
+ })
905
+
906
+ test('clamp value in range', () => {
907
+ const rt = makeRuntime(`
908
+ fn clamp(val: int, lo: int, hi: int): int {
909
+ if (val < lo) { return lo; }
910
+ if (val > hi) { return hi; }
911
+ return val;
912
+ }
913
+ fn f(): int { return clamp(50, 0, 100); }
914
+ `)
915
+ expect(callAndGetRet(rt, 'f')).toBe(50)
916
+ })
917
+
918
+ test('clamp below minimum', () => {
919
+ const rt = makeRuntime(`
920
+ fn clamp(val: int, lo: int, hi: int): int {
921
+ if (val < lo) { return lo; }
922
+ if (val > hi) { return hi; }
923
+ return val;
924
+ }
925
+ fn f(): int { return clamp(-5, 0, 100); }
926
+ `)
927
+ expect(callAndGetRet(rt, 'f')).toBe(0)
928
+ })
929
+
930
+ test('clamp above maximum', () => {
931
+ const rt = makeRuntime(`
932
+ fn clamp(val: int, lo: int, hi: int): int {
933
+ if (val < lo) { return lo; }
934
+ if (val > hi) { return hi; }
935
+ return val;
936
+ }
937
+ fn f(): int { return clamp(200, 0, 100); }
938
+ `)
939
+ expect(callAndGetRet(rt, 'f')).toBe(100)
940
+ })
941
+
942
+ test('sign function', () => {
943
+ const rt = makeRuntime(`
944
+ fn sign(x: int): int {
945
+ if (x > 0) { return 1; }
946
+ if (x < 0) { return -1; }
947
+ return 0;
948
+ }
949
+ fn f(): int { return sign(-42) + sign(0) + sign(99); }
950
+ `)
951
+ // -1 + 0 + 1 = 0
952
+ expect(callAndGetRet(rt, 'f')).toBe(0)
953
+ })
954
+ })
955
+
956
+ // ===========================================================================
957
+ // 13. More break/continue patterns
958
+ // ===========================================================================
959
+
960
+ describe('v2 migration: advanced break/continue', () => {
961
+ test('break in nested if inside loop', () => {
962
+ const rt = makeRuntime(`
963
+ fn f(): int {
964
+ let sum: int = 0;
965
+ let i: int = 0;
966
+ while (i < 100) {
967
+ sum = sum + i;
968
+ i = i + 1;
969
+ if (sum > 10) { break; }
970
+ }
971
+ return sum;
972
+ }
973
+ `)
974
+ // 0+1+2+3+4+5 = 15 > 10, breaks at i=6
975
+ expect(callAndGetRet(rt, 'f')).toBe(15)
976
+ })
977
+
978
+ test('continue with counter (using == pattern)', () => {
979
+ const rt = makeRuntime(`
980
+ fn f(): int {
981
+ let count: int = 0;
982
+ let i: int = 0;
983
+ while (i < 10) {
984
+ i = i + 1;
985
+ if (i % 3 == 0) { continue; }
986
+ count = count + 1;
987
+ }
988
+ return count;
989
+ }
990
+ `)
991
+ // i=1..10, skip 3,6,9 → count = 7
992
+ expect(callAndGetRet(rt, 'f')).toBe(7)
993
+ })
994
+
995
+ test('break from while(true) with accumulator', () => {
996
+ const rt = makeRuntime(`
997
+ fn f(): int {
998
+ let n: int = 1;
999
+ while (true) {
1000
+ n = n * 2;
1001
+ if (n >= 64) { break; }
1002
+ }
1003
+ return n;
1004
+ }
1005
+ `)
1006
+ expect(callAndGetRet(rt, 'f')).toBe(64)
1007
+ })
1008
+
1009
+ test('continue and break in same loop', () => {
1010
+ const rt = makeRuntime(`
1011
+ fn f(): int {
1012
+ let sum: int = 0;
1013
+ let i: int = 0;
1014
+ while (true) {
1015
+ i = i + 1;
1016
+ if (i > 10) { break; }
1017
+ if (i % 2 == 0) { continue; }
1018
+ sum = sum + i;
1019
+ }
1020
+ return sum;
1021
+ }
1022
+ `)
1023
+ // odd numbers 1..10: 1+3+5+7+9 = 25
1024
+ expect(callAndGetRet(rt, 'f')).toBe(25)
1025
+ })
1026
+ })
1027
+
1028
+ // ===========================================================================
1029
+ // 14. for loops (desugared to while in HIR)
1030
+ // ===========================================================================
1031
+
1032
+ describe('v2 migration: for loops', () => {
1033
+ test('simple for loop sum', () => {
1034
+ const rt = makeRuntime(`
1035
+ fn f(): int {
1036
+ let sum: int = 0;
1037
+ for (let i: int = 1; i <= 10; i = i + 1) {
1038
+ sum = sum + i;
1039
+ }
1040
+ return sum;
1041
+ }
1042
+ `)
1043
+ expect(callAndGetRet(rt, 'f')).toBe(55)
1044
+ })
1045
+
1046
+ test('for loop with multiplication', () => {
1047
+ const rt = makeRuntime(`
1048
+ fn f(): int {
1049
+ let product: int = 1;
1050
+ for (let i: int = 1; i <= 6; i = i + 1) {
1051
+ product = product * i;
1052
+ }
1053
+ return product;
1054
+ }
1055
+ `)
1056
+ // 6! = 720
1057
+ expect(callAndGetRet(rt, 'f')).toBe(720)
1058
+ })
1059
+
1060
+ test('for loop counting down', () => {
1061
+ const rt = makeRuntime(`
1062
+ fn f(): int {
1063
+ let last: int = 0;
1064
+ for (let i: int = 10; i > 0; i = i - 1) {
1065
+ last = i;
1066
+ }
1067
+ return last;
1068
+ }
1069
+ `)
1070
+ expect(callAndGetRet(rt, 'f')).toBe(1)
1071
+ })
1072
+
1073
+ test('nested for loops', () => {
1074
+ const rt = makeRuntime(`
1075
+ fn f(): int {
1076
+ let count: int = 0;
1077
+ for (let i: int = 0; i < 4; i = i + 1) {
1078
+ for (let j: int = 0; j < 3; j = j + 1) {
1079
+ count = count + 1;
1080
+ }
1081
+ }
1082
+ return count;
1083
+ }
1084
+ `)
1085
+ expect(callAndGetRet(rt, 'f')).toBe(12)
1086
+ })
1087
+
1088
+ test('for loop with break', () => {
1089
+ const rt = makeRuntime(`
1090
+ fn f(): int {
1091
+ let result: int = 0;
1092
+ for (let i: int = 0; i < 100; i = i + 1) {
1093
+ if (i == 7) {
1094
+ result = i;
1095
+ break;
1096
+ }
1097
+ }
1098
+ return result;
1099
+ }
1100
+ `)
1101
+ expect(callAndGetRet(rt, 'f')).toBe(7)
1102
+ })
1103
+ })
1104
+
1105
+ // ===========================================================================
1106
+ // 15. @tick and @load runtime behavior
1107
+ // ===========================================================================
1108
+
1109
+ describe('v2 migration: @tick/@load runtime', () => {
1110
+ test('@tick function executes and modifies state', () => {
1111
+ const source = `
1112
+ let counter: int = 0;
1113
+ @tick fn game_tick(): void {
1114
+ counter = counter + 1;
1115
+ }
1116
+ fn get_counter(): int { return counter; }
1117
+ `
1118
+ // @tick functions should compile and be callable
1119
+ expect(() => compile(source, { namespace: NS })).not.toThrow()
1120
+ })
1121
+
1122
+ test('@load function runs on load', () => {
1123
+ const source = `
1124
+ @load fn setup(): void {
1125
+ let x: int = 42;
1126
+ }
1127
+ `
1128
+ const result = compile(source, { namespace: NS })
1129
+ const loadJson = getFile(result.files, 'load.json')
1130
+ expect(loadJson).toBeDefined()
1131
+ const values = JSON.parse(loadJson!).values
1132
+ expect(values).toContain(`${NS}:setup`)
1133
+ expect(values).toContain(`${NS}:load`)
1134
+ })
1135
+
1136
+ test('@tick and @load on different functions', () => {
1137
+ const source = `
1138
+ @tick fn on_tick(): void { let x: int = 1; }
1139
+ @load fn on_load(): void { let y: int = 2; }
1140
+ `
1141
+ const result = compile(source, { namespace: NS })
1142
+ const tickJson = getFile(result.files, 'tick.json')
1143
+ const loadJson = getFile(result.files, 'load.json')
1144
+ expect(tickJson).toBeDefined()
1145
+ expect(JSON.parse(tickJson!).values).toContain(`${NS}:on_tick`)
1146
+ expect(loadJson).toBeDefined()
1147
+ expect(JSON.parse(loadJson!).values).toContain(`${NS}:on_load`)
1148
+ })
1149
+
1150
+ test('multiple @tick functions all appear in tick.json', () => {
1151
+ const source = `
1152
+ @tick fn tick1(): void { let x: int = 1; }
1153
+ @tick fn tick2(): void { let y: int = 2; }
1154
+ `
1155
+ const result = compile(source, { namespace: NS })
1156
+ const tickJson = getFile(result.files, 'tick.json')
1157
+ expect(tickJson).toBeDefined()
1158
+ const values = JSON.parse(tickJson!).values
1159
+ expect(values).toContain(`${NS}:tick1`)
1160
+ expect(values).toContain(`${NS}:tick2`)
1161
+ })
1162
+
1163
+ test('@tick function with logic compiles and runs', () => {
1164
+ const rt = makeRuntime(`
1165
+ @tick fn game_tick(): void {
1166
+ let x: int = 5;
1167
+ let y: int = x + 10;
1168
+ }
1169
+ fn f(): int { return 1; }
1170
+ `)
1171
+ // Tick function should be callable without crashing
1172
+ rt.execFunction(`${NS}:game_tick`)
1173
+ expect(callAndGetRet(rt, 'f')).toBe(1)
1174
+ })
1175
+
1176
+ test('@load function body executes correctly in runtime', () => {
1177
+ const rt = makeRuntime(`
1178
+ @load fn init(): void {
1179
+ let x: int = 100;
1180
+ }
1181
+ fn f(): int { return 42; }
1182
+ `)
1183
+ expect(callAndGetRet(rt, 'f')).toBe(42)
1184
+ })
1185
+ })
1186
+
1187
+ // ===========================================================================
1188
+ // 16. Struct declaration and field access (smoke + structural)
1189
+ // ===========================================================================
1190
+
1191
+ describe('v2 migration: struct smoke tests', () => {
1192
+ test('struct declaration compiles', () => {
1193
+ expect(() => compile(`
1194
+ struct Point { x: int, y: int }
1195
+ fn f(): int { return 1; }
1196
+ `, { namespace: NS })).not.toThrow()
1197
+ })
1198
+
1199
+ test('struct literal compiles', () => {
1200
+ expect(() => compile(`
1201
+ struct Vec2 { x: int, y: int }
1202
+ fn f(): int {
1203
+ let p: Vec2 = { x: 10, y: 20 };
1204
+ return 1;
1205
+ }
1206
+ `, { namespace: NS })).not.toThrow()
1207
+ })
1208
+
1209
+ test('struct with impl compiles', () => {
1210
+ expect(() => compile(`
1211
+ struct Counter { value: int }
1212
+ impl Counter {
1213
+ fn new(): Counter {
1214
+ return { value: 0 };
1215
+ }
1216
+ }
1217
+ fn f(): int { return 1; }
1218
+ `, { namespace: NS })).not.toThrow()
1219
+ })
1220
+
1221
+ test('struct field access compiles', () => {
1222
+ expect(() => compile(`
1223
+ struct Point { x: int, y: int }
1224
+ fn f(): int {
1225
+ let p: Point = { x: 5, y: 10 };
1226
+ let val: int = p.x;
1227
+ return val;
1228
+ }
1229
+ `, { namespace: NS })).not.toThrow()
1230
+ })
1231
+
1232
+ test('struct field assignment compiles', () => {
1233
+ expect(() => compile(`
1234
+ struct Point { x: int, y: int }
1235
+ fn f(): void {
1236
+ let p: Point = { x: 5, y: 10 };
1237
+ p.x = 20;
1238
+ }
1239
+ `, { namespace: NS })).not.toThrow()
1240
+ })
1241
+
1242
+ test('struct method call via static_call compiles', () => {
1243
+ expect(() => compile(`
1244
+ struct Vec2 { x: int, y: int }
1245
+ impl Vec2 {
1246
+ fn new(x: int, y: int): Vec2 {
1247
+ return { x: x, y: y };
1248
+ }
1249
+ }
1250
+ fn f(): void {
1251
+ let v: Vec2 = Vec2::new(1, 2);
1252
+ }
1253
+ `, { namespace: NS })).not.toThrow()
1254
+ })
1255
+
1256
+ test('struct static method generates function file', () => {
1257
+ const result = compile(`
1258
+ struct Vec2 { x: int, y: int }
1259
+ impl Vec2 {
1260
+ fn new(x: int, y: int): Vec2 {
1261
+ return { x: x, y: y };
1262
+ }
1263
+ }
1264
+ fn f(): void {
1265
+ let v: Vec2 = Vec2::new(1, 2);
1266
+ }
1267
+ `, { namespace: NS })
1268
+ // impl methods are named Type::method in LIR → type/method in path
1269
+ const fnFiles = result.files.filter(f => f.path.endsWith('.mcfunction'))
1270
+ expect(fnFiles.length).toBeGreaterThanOrEqual(2) // at least f + Vec2::new
1271
+ })
1272
+
1273
+ test('multiple struct declarations compile', () => {
1274
+ expect(() => compile(`
1275
+ struct Point { x: int, y: int }
1276
+ struct Color { r: int, g: int, b: int }
1277
+ fn f(): int { return 1; }
1278
+ `, { namespace: NS })).not.toThrow()
1279
+ })
1280
+ })
1281
+
1282
+ // ===========================================================================
1283
+ // 17. Execute context blocks (structural)
1284
+ // ===========================================================================
1285
+
1286
+ describe('v2 migration: execute blocks', () => {
1287
+ test('as @a block compiles', () => {
1288
+ expect(() => compile(`
1289
+ fn f(): void {
1290
+ as @a {
1291
+ let x: int = 1;
1292
+ }
1293
+ }
1294
+ `, { namespace: NS })).not.toThrow()
1295
+ })
1296
+
1297
+ test('as @a generates execute as command', () => {
1298
+ const result = compile(`
1299
+ fn f(): void {
1300
+ as @a {
1301
+ let x: int = 1;
1302
+ }
1303
+ }
1304
+ `, { namespace: NS })
1305
+ const allContent = result.files
1306
+ .filter(f => f.path.endsWith('.mcfunction'))
1307
+ .map(f => f.content).join('\n')
1308
+ expect(allContent).toContain('execute as @a run function')
1309
+ })
1310
+
1311
+ test('at @s block compiles', () => {
1312
+ expect(() => compile(`
1313
+ fn f(): void {
1314
+ at @s {
1315
+ let x: int = 1;
1316
+ }
1317
+ }
1318
+ `, { namespace: NS })).not.toThrow()
1319
+ })
1320
+
1321
+ test('as @e at @s compiles', () => {
1322
+ expect(() => compile(`
1323
+ fn f(): void {
1324
+ as @e at @s {
1325
+ let x: int = 1;
1326
+ }
1327
+ }
1328
+ `, { namespace: NS })).not.toThrow()
1329
+ })
1330
+
1331
+ test('as @e at @s generates execute command', () => {
1332
+ const result = compile(`
1333
+ fn f(): void {
1334
+ as @e at @s {
1335
+ let x: int = 1;
1336
+ }
1337
+ }
1338
+ `, { namespace: NS })
1339
+ const allContent = result.files
1340
+ .filter(f => f.path.endsWith('.mcfunction'))
1341
+ .map(f => f.content).join('\n')
1342
+ expect(allContent).toContain('execute as @e at @s run function')
1343
+ })
1344
+
1345
+ test('nested execute blocks compile', () => {
1346
+ expect(() => compile(`
1347
+ fn f(): void {
1348
+ as @a {
1349
+ as @e {
1350
+ let x: int = 1;
1351
+ }
1352
+ }
1353
+ }
1354
+ `, { namespace: NS })).not.toThrow()
1355
+ })
1356
+
1357
+ test('execute body creates helper function', () => {
1358
+ const result = compile(`
1359
+ fn f(): void {
1360
+ as @a {
1361
+ let x: int = 42;
1362
+ }
1363
+ }
1364
+ `, { namespace: NS })
1365
+ const fnFiles = result.files.filter(f => f.path.endsWith('.mcfunction'))
1366
+ // Should have at least: load, f, f__exec helper
1367
+ expect(fnFiles.length).toBeGreaterThanOrEqual(3)
1368
+ })
1369
+ })
1370
+
1371
+ // ===========================================================================
1372
+ // 18. foreach (structural)
1373
+ // ===========================================================================
1374
+
1375
+ describe('v2 migration: foreach', () => {
1376
+ test('foreach with selector compiles', () => {
1377
+ expect(() => compile(`
1378
+ fn f(): void {
1379
+ foreach (e in @e) {
1380
+ let x: int = 1;
1381
+ }
1382
+ }
1383
+ `, { namespace: NS })).not.toThrow()
1384
+ })
1385
+
1386
+ test('foreach generates execute as ... run function', () => {
1387
+ const result = compile(`
1388
+ fn f(): void {
1389
+ foreach (e in @e) {
1390
+ let x: int = 1;
1391
+ }
1392
+ }
1393
+ `, { namespace: NS })
1394
+ const allContent = result.files
1395
+ .filter(f => f.path.endsWith('.mcfunction'))
1396
+ .map(f => f.content).join('\n')
1397
+ expect(allContent).toContain('execute as @e run function')
1398
+ })
1399
+
1400
+ test('foreach with @a selector', () => {
1401
+ const result = compile(`
1402
+ fn f(): void {
1403
+ foreach (p in @a) {
1404
+ let x: int = 1;
1405
+ }
1406
+ }
1407
+ `, { namespace: NS })
1408
+ const allContent = result.files
1409
+ .filter(f => f.path.endsWith('.mcfunction'))
1410
+ .map(f => f.content).join('\n')
1411
+ expect(allContent).toContain('execute as @a run function')
1412
+ })
1413
+
1414
+ test('foreach creates helper function', () => {
1415
+ const result = compile(`
1416
+ fn f(): void {
1417
+ foreach (e in @e) {
1418
+ let x: int = 1;
1419
+ }
1420
+ }
1421
+ `, { namespace: NS })
1422
+ const fnFiles = result.files.filter(f => f.path.endsWith('.mcfunction'))
1423
+ // Should have at least: load, f, foreach helper
1424
+ expect(fnFiles.length).toBeGreaterThanOrEqual(3)
1425
+ })
1426
+ })
1427
+
1428
+ // ===========================================================================
1429
+ // 19. Selectors (smoke)
1430
+ // ===========================================================================
1431
+
1432
+ describe('v2 migration: selectors', () => {
1433
+ test('@a selector in foreach compiles', () => {
1434
+ expect(() => compile(`
1435
+ fn f(): void {
1436
+ foreach (p in @a) { let x: int = 1; }
1437
+ }
1438
+ `, { namespace: NS })).not.toThrow()
1439
+ })
1440
+
1441
+ test('@e selector in foreach compiles', () => {
1442
+ expect(() => compile(`
1443
+ fn f(): void {
1444
+ foreach (e in @e) { let x: int = 1; }
1445
+ }
1446
+ `, { namespace: NS })).not.toThrow()
1447
+ })
1448
+
1449
+ test('@s selector in as block compiles', () => {
1450
+ expect(() => compile(`
1451
+ fn f(): void {
1452
+ at @s { let x: int = 1; }
1453
+ }
1454
+ `, { namespace: NS })).not.toThrow()
1455
+ })
1456
+
1457
+ test('@p selector compiles', () => {
1458
+ expect(() => compile(`
1459
+ fn f(): void {
1460
+ foreach (p in @p) { let x: int = 1; }
1461
+ }
1462
+ `, { namespace: NS })).not.toThrow()
1463
+ })
1464
+ })
1465
+
1466
+ // ===========================================================================
1467
+ // 20. Raw commands
1468
+ // ===========================================================================
1469
+
1470
+ describe('v2 migration: raw commands', () => {
1471
+ test('raw command compiles', () => {
1472
+ expect(() => compile(`
1473
+ fn f(): void {
1474
+ raw("say hello world");
1475
+ }
1476
+ `, { namespace: NS })).not.toThrow()
1477
+ })
1478
+
1479
+ test('raw command appears in output', () => {
1480
+ const result = compile(`
1481
+ fn f(): void {
1482
+ raw("say hello world");
1483
+ }
1484
+ `, { namespace: NS })
1485
+ const fn = getFile(result.files, '/f.mcfunction')
1486
+ expect(fn).toBeDefined()
1487
+ expect(fn).toContain('say hello world')
1488
+ })
1489
+
1490
+ test('multiple raw commands', () => {
1491
+ const result = compile(`
1492
+ fn f(): void {
1493
+ raw("say line one");
1494
+ raw("say line two");
1495
+ }
1496
+ `, { namespace: NS })
1497
+ const fn = getFile(result.files, '/f.mcfunction')
1498
+ expect(fn).toContain('say line one')
1499
+ expect(fn).toContain('say line two')
1500
+ })
1501
+ })
1502
+
1503
+ // ===========================================================================
1504
+ // 21. Match statement
1505
+ // ===========================================================================
1506
+
1507
+ describe('v2 migration: match statement', () => {
1508
+ test('match compiles without error', () => {
1509
+ expect(() => compile(`
1510
+ fn f(x: int): int {
1511
+ match (x) {
1512
+ 1 => { return 10; }
1513
+ 2 => { return 20; }
1514
+ _ => { return 0; }
1515
+ }
1516
+ }
1517
+ `, { namespace: NS })).not.toThrow()
1518
+ })
1519
+
1520
+ test('match selects correct arm', () => {
1521
+ const rt = makeRuntime(`
1522
+ fn classify(x: int): int {
1523
+ match (x) {
1524
+ 1 => { return 10; }
1525
+ 2 => { return 20; }
1526
+ 3 => { return 30; }
1527
+ _ => { return 0; }
1528
+ }
1529
+ }
1530
+ fn f(): int { return classify(2); }
1531
+ `)
1532
+ expect(callAndGetRet(rt, 'f')).toBe(20)
1533
+ })
1534
+
1535
+ test('match default arm', () => {
1536
+ const rt = makeRuntime(`
1537
+ fn classify(x: int): int {
1538
+ match (x) {
1539
+ 1 => { return 10; }
1540
+ _ => { return 99; }
1541
+ }
1542
+ }
1543
+ fn f(): int { return classify(42); }
1544
+ `)
1545
+ expect(callAndGetRet(rt, 'f')).toBe(99)
1546
+ })
1547
+
1548
+ test('match first arm', () => {
1549
+ const rt = makeRuntime(`
1550
+ fn classify(x: int): int {
1551
+ match (x) {
1552
+ 1 => { return 10; }
1553
+ 2 => { return 20; }
1554
+ _ => { return 0; }
1555
+ }
1556
+ }
1557
+ fn f(): int { return classify(1); }
1558
+ `)
1559
+ expect(callAndGetRet(rt, 'f')).toBe(10)
1560
+ })
1561
+ })
1562
+
1563
+ // ===========================================================================
1564
+ // 22. Complex integration patterns
1565
+ // ===========================================================================
1566
+
1567
+ describe('v2 migration: complex patterns', () => {
1568
+ test('GCD iterative (Euclid)', () => {
1569
+ const rt = makeRuntime(`
1570
+ fn gcd(a: int, b: int): int {
1571
+ while (b != 0) {
1572
+ let temp: int = b;
1573
+ b = a % b;
1574
+ a = temp;
1575
+ }
1576
+ return a;
1577
+ }
1578
+ fn f(): int { return gcd(48, 18); }
1579
+ `)
1580
+ expect(callAndGetRet(rt, 'f')).toBe(6)
1581
+ })
1582
+
1583
+ test('sum of squares', () => {
1584
+ const rt = makeRuntime(`
1585
+ fn sum_sq(n: int): int {
1586
+ let sum: int = 0;
1587
+ let i: int = 1;
1588
+ while (i <= n) {
1589
+ sum = sum + i * i;
1590
+ i = i + 1;
1591
+ }
1592
+ return sum;
1593
+ }
1594
+ fn f(): int { return sum_sq(5); }
1595
+ `)
1596
+ // 1 + 4 + 9 + 16 + 25 = 55
1597
+ expect(callAndGetRet(rt, 'f')).toBe(55)
1598
+ })
1599
+
1600
+ test('collatz steps', () => {
1601
+ const rt = makeRuntime(`
1602
+ fn collatz(n: int): int {
1603
+ let steps: int = 0;
1604
+ while (n != 1) {
1605
+ if (n % 2 == 0) {
1606
+ n = n / 2;
1607
+ } else {
1608
+ n = n * 3 + 1;
1609
+ }
1610
+ steps = steps + 1;
1611
+ }
1612
+ return steps;
1613
+ }
1614
+ fn f(): int { return collatz(6); }
1615
+ `)
1616
+ // 6 → 3 → 10 → 5 → 16 → 8 → 4 → 2 → 1 = 8 steps
1617
+ expect(callAndGetRet(rt, 'f')).toBe(8)
1618
+ })
1619
+
1620
+ test('is_prime check', () => {
1621
+ const rt = makeRuntime(`
1622
+ fn is_prime(n: int): int {
1623
+ if (n <= 1) { return 0; }
1624
+ let i: int = 2;
1625
+ while (i * i <= n) {
1626
+ if (n % i == 0) { return 0; }
1627
+ i = i + 1;
1628
+ }
1629
+ return 1;
1630
+ }
1631
+ fn f(): int {
1632
+ return is_prime(2) + is_prime(7) + is_prime(11) + is_prime(4) + is_prime(9);
1633
+ }
1634
+ `)
1635
+ // 2=prime(1), 7=prime(1), 11=prime(1), 4=not(0), 9=not(0) → 3
1636
+ expect(callAndGetRet(rt, 'f')).toBe(3)
1637
+ })
1638
+
1639
+ test('bubble sort pass count', () => {
1640
+ const rt = makeRuntime(`
1641
+ fn f(): int {
1642
+ let a: int = 5;
1643
+ let b: int = 3;
1644
+ let c: int = 8;
1645
+ let d: int = 1;
1646
+ let swaps: int = 0;
1647
+
1648
+ // Pass 1
1649
+ if (a > b) { let t: int = a; a = b; b = t; swaps = swaps + 1; }
1650
+ if (b > c) { let t: int = b; b = c; c = t; swaps = swaps + 1; }
1651
+ if (c > d) { let t: int = c; c = d; d = t; swaps = swaps + 1; }
1652
+
1653
+ // Pass 2
1654
+ if (a > b) { let t: int = a; a = b; b = t; swaps = swaps + 1; }
1655
+ if (b > c) { let t: int = b; b = c; c = t; swaps = swaps + 1; }
1656
+
1657
+ // Pass 3
1658
+ if (a > b) { let t: int = a; a = b; b = t; swaps = swaps + 1; }
1659
+
1660
+ return swaps;
1661
+ }
1662
+ `)
1663
+ // 5,3,8,1 → pass1: swap(5,3)→3,5,8,1 swap(8,1)→3,5,1,8 → pass2: swap(5,1)→3,1,5,8 → pass3: swap(3,1)→1,3,5,8
1664
+ // swaps: 1+0+1 + 0+1 + 1 = 4
1665
+ expect(callAndGetRet(rt, 'f')).toBe(4)
1666
+ })
1667
+
1668
+ test('linear search', () => {
1669
+ const rt = makeRuntime(`
1670
+ fn f(): int {
1671
+ let a: int = 10;
1672
+ let b: int = 20;
1673
+ let c: int = 30;
1674
+ let d: int = 40;
1675
+ let e: int = 50;
1676
+ let target: int = 30;
1677
+
1678
+ if (a == target) { return 0; }
1679
+ if (b == target) { return 1; }
1680
+ if (c == target) { return 2; }
1681
+ if (d == target) { return 3; }
1682
+ if (e == target) { return 4; }
1683
+ return -1;
1684
+ }
1685
+ `)
1686
+ expect(callAndGetRet(rt, 'f')).toBe(2)
1687
+ })
1688
+ })
1689
+
1690
+ // ===========================================================================
1691
+ // 23. Const declarations
1692
+ // ===========================================================================
1693
+
1694
+ describe('v2 migration: const declarations', () => {
1695
+ test('const inlined in expression', () => {
1696
+ expect(() => compile(`
1697
+ const MAX: int = 100;
1698
+ fn f(): int { return MAX; }
1699
+ `, { namespace: NS })).not.toThrow()
1700
+ })
1701
+ })
1702
+
1703
+ // ===========================================================================
1704
+ // 24. Boolean logic edge cases
1705
+ // ===========================================================================
1706
+
1707
+ describe('v2 migration: boolean edge cases', () => {
1708
+ test('double negation', () => {
1709
+ const rt = makeRuntime(`
1710
+ fn f(): int {
1711
+ if (!!true) { return 1; } else { return 0; }
1712
+ }
1713
+ `)
1714
+ expect(callAndGetRet(rt, 'f')).toBe(1)
1715
+ })
1716
+
1717
+ test('complex boolean expression', () => {
1718
+ const rt = makeRuntime(`
1719
+ fn f(): int {
1720
+ let a: int = 5;
1721
+ let b: int = 10;
1722
+ if (a > 0 && b > 0 && a < b) { return 1; } else { return 0; }
1723
+ }
1724
+ `)
1725
+ expect(callAndGetRet(rt, 'f')).toBe(1)
1726
+ })
1727
+
1728
+ test('OR short-circuit: first true', () => {
1729
+ const rt = makeRuntime(`
1730
+ fn f(): int {
1731
+ if (true || false) { return 1; } else { return 0; }
1732
+ }
1733
+ `)
1734
+ expect(callAndGetRet(rt, 'f')).toBe(1)
1735
+ })
1736
+
1737
+ test('AND with comparison', () => {
1738
+ const rt = makeRuntime(`
1739
+ fn f(): int {
1740
+ let x: int = 5;
1741
+ if (x >= 1 && x <= 10) { return 1; } else { return 0; }
1742
+ }
1743
+ `)
1744
+ expect(callAndGetRet(rt, 'f')).toBe(1)
1745
+ })
1746
+
1747
+ test('AND false short-circuit', () => {
1748
+ const rt = makeRuntime(`
1749
+ fn f(): int {
1750
+ if (false && true) { return 1; } else { return 0; }
1751
+ }
1752
+ `)
1753
+ expect(callAndGetRet(rt, 'f')).toBe(0)
1754
+ })
1755
+ })
1756
+
1757
+ // ===========================================================================
1758
+ // 25. Struct declaration and field access (behavioral)
1759
+ // ===========================================================================
1760
+
1761
+ // TODO: Struct field access/assignment is stubbed at MIR level (returns const 0).
1762
+ // These behavioral tests will pass once MIR struct lowering is implemented.
1763
+ // See src2/mir/lower.ts — struct_lit, member, member_assign are opaque at MIR.
1764
+ describe('v2 migration: struct behavioral', () => {
1765
+ test('struct literal creates instance (compiles)', () => {
1766
+ expect(() => compile(`
1767
+ struct Point { x: int, y: int }
1768
+ fn f(): int {
1769
+ let p: Point = { x: 10, y: 20 };
1770
+ return 1;
1771
+ }
1772
+ `, { namespace: NS })).not.toThrow()
1773
+ })
1774
+
1775
+ test('struct field read returns correct value', () => {
1776
+ const rt = makeRuntime(`
1777
+ struct Point { x: int, y: int }
1778
+ fn f(): int {
1779
+ let p: Point = { x: 42, y: 10 };
1780
+ return p.x;
1781
+ }
1782
+ `)
1783
+ expect(callAndGetRet(rt, 'f')).toBe(42)
1784
+ })
1785
+
1786
+ test('struct field read second field', () => {
1787
+ const rt = makeRuntime(`
1788
+ struct Point { x: int, y: int }
1789
+ fn f(): int {
1790
+ let p: Point = { x: 5, y: 99 };
1791
+ return p.y;
1792
+ }
1793
+ `)
1794
+ expect(callAndGetRet(rt, 'f')).toBe(99)
1795
+ })
1796
+
1797
+ test('struct field write and read back', () => {
1798
+ const rt = makeRuntime(`
1799
+ struct Point { x: int, y: int }
1800
+ fn f(): int {
1801
+ let p: Point = { x: 1, y: 2 };
1802
+ p.x = 50;
1803
+ return p.x;
1804
+ }
1805
+ `)
1806
+ expect(callAndGetRet(rt, 'f')).toBe(50)
1807
+ })
1808
+
1809
+ test('struct field arithmetic', () => {
1810
+ const rt = makeRuntime(`
1811
+ struct Vec2 { x: int, y: int }
1812
+ fn f(): int {
1813
+ let v: Vec2 = { x: 3, y: 4 };
1814
+ return v.x + v.y;
1815
+ }
1816
+ `)
1817
+ expect(callAndGetRet(rt, 'f')).toBe(7)
1818
+ })
1819
+
1820
+ test('struct field assignment from expression', () => {
1821
+ const rt = makeRuntime(`
1822
+ struct Counter { value: int }
1823
+ fn f(): int {
1824
+ let c: Counter = { value: 10 };
1825
+ c.value = c.value + 5;
1826
+ return c.value;
1827
+ }
1828
+ `)
1829
+ expect(callAndGetRet(rt, 'f')).toBe(15)
1830
+ })
1831
+
1832
+ test('two struct instances', () => {
1833
+ const rt = makeRuntime(`
1834
+ struct Point { x: int, y: int }
1835
+ fn f(): int {
1836
+ let a: Point = { x: 1, y: 2 };
1837
+ let b: Point = { x: 10, y: 20 };
1838
+ return a.x + b.x;
1839
+ }
1840
+ `)
1841
+ expect(callAndGetRet(rt, 'f')).toBe(11)
1842
+ })
1843
+
1844
+ test('struct with three fields', () => {
1845
+ const rt = makeRuntime(`
1846
+ struct Vec3 { x: int, y: int, z: int }
1847
+ fn f(): int {
1848
+ let v: Vec3 = { x: 1, y: 2, z: 3 };
1849
+ return v.x + v.y + v.z;
1850
+ }
1851
+ `)
1852
+ expect(callAndGetRet(rt, 'f')).toBe(6)
1853
+ })
1854
+ })
1855
+
1856
+ // ===========================================================================
1857
+ // 26. Struct impl methods (behavioral)
1858
+ // ===========================================================================
1859
+
1860
+ // TODO: Struct impl methods depend on struct field access/assignment (stubbed).
1861
+ // These tests will pass once MIR struct lowering is implemented.
1862
+ describe('v2 migration: struct impl methods', () => {
1863
+ test('static constructor method', () => {
1864
+ const rt = makeRuntime(`
1865
+ struct Point { x: int, y: int }
1866
+ impl Point {
1867
+ fn new(x: int, y: int): Point {
1868
+ return { x: x, y: y };
1869
+ }
1870
+ }
1871
+ fn f(): int {
1872
+ let p: Point = Point::new(10, 20);
1873
+ return p.x;
1874
+ }
1875
+ `)
1876
+ expect(callAndGetRet(rt, 'f')).toBe(10)
1877
+ })
1878
+
1879
+ test('instance method on self', () => {
1880
+ const rt = makeRuntime(`
1881
+ struct Vec2 { x: int, y: int }
1882
+ impl Vec2 {
1883
+ fn sum(self): int {
1884
+ return self.x + self.y;
1885
+ }
1886
+ }
1887
+ fn f(): int {
1888
+ let v: Vec2 = { x: 3, y: 7 };
1889
+ return v.sum();
1890
+ }
1891
+ `)
1892
+ expect(callAndGetRet(rt, 'f')).toBe(10)
1893
+ })
1894
+
1895
+ test('static method and instance method together', () => {
1896
+ const rt = makeRuntime(`
1897
+ struct Point { x: int, y: int }
1898
+ impl Point {
1899
+ fn new(x: int, y: int): Point {
1900
+ return { x: x, y: y };
1901
+ }
1902
+ fn distance(self): int {
1903
+ return self.x + self.y;
1904
+ }
1905
+ }
1906
+ fn f(): int {
1907
+ let p: Point = Point::new(5, 15);
1908
+ return p.distance();
1909
+ }
1910
+ `)
1911
+ expect(callAndGetRet(rt, 'f')).toBe(20)
1912
+ })
1913
+
1914
+ test('impl method generates separate function file', () => {
1915
+ const result = compile(`
1916
+ struct Vec2 { x: int, y: int }
1917
+ impl Vec2 {
1918
+ fn new(x: int, y: int): Vec2 {
1919
+ return { x: x, y: y };
1920
+ }
1921
+ fn sum(self): int {
1922
+ return self.x + self.y;
1923
+ }
1924
+ }
1925
+ fn f(): int { return 1; }
1926
+ `, { namespace: NS })
1927
+ const fnNames = result.files
1928
+ .filter(f => f.path.endsWith('.mcfunction'))
1929
+ .map(f => f.path)
1930
+ // Should have Vec2_new or vec2/new or similar
1931
+ expect(fnNames.length).toBeGreaterThanOrEqual(3)
1932
+ })
1933
+
1934
+ test('multiple impl methods compile', () => {
1935
+ expect(() => compile(`
1936
+ struct Counter { value: int }
1937
+ impl Counter {
1938
+ fn new(): Counter { return { value: 0 }; }
1939
+ fn increment(self): void { self.value = self.value + 1; }
1940
+ fn get(self): int { return self.value; }
1941
+ fn reset(self): void { self.value = 0; }
1942
+ }
1943
+ fn f(): int { return 1; }
1944
+ `, { namespace: NS })).not.toThrow()
1945
+ })
1946
+ })
1947
+
1948
+ // ===========================================================================
1949
+ // 27. @tick and @load runtime behavior
1950
+ // ===========================================================================
1951
+
1952
+ describe('v2 migration: @tick/@load runtime', () => {
1953
+ test('@tick function is registered in tick.json', () => {
1954
+ const result = compile(`
1955
+ @tick fn game_loop(): void { let x: int = 1; }
1956
+ fn f(): int { return 1; }
1957
+ `, { namespace: NS })
1958
+ const tickJson = getFile(result.files, 'tick.json')
1959
+ expect(tickJson).toBeDefined()
1960
+ expect(tickJson).toContain(`${NS}:game_loop`)
1961
+ })
1962
+
1963
+ test('@load function is registered in load.json', () => {
1964
+ const result = compile(`
1965
+ @load fn setup(): void { let x: int = 1; }
1966
+ fn f(): int { return 1; }
1967
+ `, { namespace: NS })
1968
+ const loadJson = getFile(result.files, 'load.json')
1969
+ expect(loadJson).toBeDefined()
1970
+ expect(loadJson).toContain(`${NS}:setup`)
1971
+ })
1972
+
1973
+ test('multiple @tick functions all registered', () => {
1974
+ const result = compile(`
1975
+ @tick fn tick_a(): void { let x: int = 1; }
1976
+ @tick fn tick_b(): void { let y: int = 2; }
1977
+ fn f(): int { return 1; }
1978
+ `, { namespace: NS })
1979
+ const tickJson = getFile(result.files, 'tick.json')
1980
+ expect(tickJson).toContain(`${NS}:tick_a`)
1981
+ expect(tickJson).toContain(`${NS}:tick_b`)
1982
+ })
1983
+
1984
+ test('multiple @load functions all registered', () => {
1985
+ const result = compile(`
1986
+ @load fn init_a(): void { let x: int = 1; }
1987
+ @load fn init_b(): void { let y: int = 2; }
1988
+ fn f(): int { return 1; }
1989
+ `, { namespace: NS })
1990
+ const loadJson = getFile(result.files, 'load.json')
1991
+ expect(loadJson).toContain(`${NS}:init_a`)
1992
+ expect(loadJson).toContain(`${NS}:init_b`)
1993
+ })
1994
+
1995
+ test('@tick function executes without crashing', () => {
1996
+ const rt = makeRuntime(`
1997
+ @tick fn heartbeat(): void {
1998
+ let counter: int = 0;
1999
+ counter = counter + 1;
2000
+ }
2001
+ fn f(): int { return 99; }
2002
+ `)
2003
+ rt.execFunction(`${NS}:heartbeat`)
2004
+ rt.execFunction(`${NS}:heartbeat`)
2005
+ expect(callAndGetRet(rt, 'f')).toBe(99)
2006
+ })
2007
+
2008
+ test('@load function runs during makeRuntime init', () => {
2009
+ const rt = makeRuntime(`
2010
+ @load fn init(): void {
2011
+ let x: int = 42;
2012
+ }
2013
+ fn f(): int { return 1; }
2014
+ `)
2015
+ // If load function crashes, makeRuntime would throw
2016
+ expect(callAndGetRet(rt, 'f')).toBe(1)
2017
+ })
2018
+
2019
+ test('@tick with logic body', () => {
2020
+ const rt = makeRuntime(`
2021
+ @tick fn tick(): void {
2022
+ let x: int = 5;
2023
+ let y: int = 10;
2024
+ let sum: int = x + y;
2025
+ }
2026
+ fn f(): int { return 1; }
2027
+ `)
2028
+ rt.execFunction(`${NS}:tick`)
2029
+ expect(callAndGetRet(rt, 'f')).toBe(1)
2030
+ })
2031
+
2032
+ test('@tick and @load coexist', () => {
2033
+ const result = compile(`
2034
+ @tick fn game_tick(): void { let x: int = 1; }
2035
+ @load fn game_init(): void { let y: int = 2; }
2036
+ fn f(): int { return 1; }
2037
+ `, { namespace: NS })
2038
+ const tickJson = getFile(result.files, 'tick.json')
2039
+ const loadJson = getFile(result.files, 'load.json')
2040
+ expect(tickJson).toContain(`${NS}:game_tick`)
2041
+ expect(loadJson).toContain(`${NS}:game_init`)
2042
+ })
2043
+ })
2044
+
2045
+ // ===========================================================================
2046
+ // 28. Execute context blocks (behavioral)
2047
+ // ===========================================================================
2048
+
2049
+ describe('v2 migration: execute blocks behavioral', () => {
2050
+ test('as @a generates correct execute as command', () => {
2051
+ const result = compile(`
2052
+ fn f(): void {
2053
+ as @a {
2054
+ raw("say hello");
2055
+ }
2056
+ }
2057
+ `, { namespace: NS })
2058
+ const allContent = result.files
2059
+ .filter(f => f.path.endsWith('.mcfunction'))
2060
+ .map(f => f.content).join('\n')
2061
+ expect(allContent).toContain('execute as @a run function')
2062
+ })
2063
+
2064
+ test('at @s generates correct execute at command', () => {
2065
+ const result = compile(`
2066
+ fn f(): void {
2067
+ at @s {
2068
+ raw("particle flame ~ ~ ~ 0 0 0 0 1");
2069
+ }
2070
+ }
2071
+ `, { namespace: NS })
2072
+ const allContent = result.files
2073
+ .filter(f => f.path.endsWith('.mcfunction'))
2074
+ .map(f => f.content).join('\n')
2075
+ expect(allContent).toContain('execute at @s run function')
2076
+ })
2077
+
2078
+ test('as @e at @s generates combined execute', () => {
2079
+ const result = compile(`
2080
+ fn f(): void {
2081
+ as @e at @s {
2082
+ raw("particle flame ~ ~ ~ 0 0 0 0 1");
2083
+ }
2084
+ }
2085
+ `, { namespace: NS })
2086
+ const allContent = result.files
2087
+ .filter(f => f.path.endsWith('.mcfunction'))
2088
+ .map(f => f.content).join('\n')
2089
+ expect(allContent).toContain('execute as @e at @s run function')
2090
+ })
2091
+
2092
+ test('execute body raw command appears in helper', () => {
2093
+ const result = compile(`
2094
+ fn f(): void {
2095
+ as @a {
2096
+ raw("say inside execute");
2097
+ }
2098
+ }
2099
+ `, { namespace: NS })
2100
+ const allContent = result.files
2101
+ .filter(f => f.path.endsWith('.mcfunction'))
2102
+ .map(f => f.content).join('\n')
2103
+ expect(allContent).toContain('say inside execute')
2104
+ })
2105
+
2106
+ test('as @e[type=zombie] with selector args', () => {
2107
+ const result = compile(`
2108
+ fn f(): void {
2109
+ as @e[type=zombie] {
2110
+ raw("say I am zombie");
2111
+ }
2112
+ }
2113
+ `, { namespace: NS })
2114
+ const allContent = result.files
2115
+ .filter(f => f.path.endsWith('.mcfunction'))
2116
+ .map(f => f.content).join('\n')
2117
+ expect(allContent).toContain('execute as @e[type=zombie] run function')
2118
+ })
2119
+
2120
+ test('nested execute blocks both generate functions', () => {
2121
+ const result = compile(`
2122
+ fn f(): void {
2123
+ as @a {
2124
+ at @s {
2125
+ raw("particle flame ~ ~ ~ 0 0 0 0 1");
2126
+ }
2127
+ }
2128
+ }
2129
+ `, { namespace: NS })
2130
+ const fnFiles = result.files.filter(f => f.path.endsWith('.mcfunction'))
2131
+ // At least: load, f, as-helper, at-helper
2132
+ expect(fnFiles.length).toBeGreaterThanOrEqual(3)
2133
+ })
2134
+
2135
+ test('execute with variable assignment in body', () => {
2136
+ expect(() => compile(`
2137
+ fn f(): void {
2138
+ as @a {
2139
+ let x: int = 42;
2140
+ let y: int = x + 1;
2141
+ }
2142
+ }
2143
+ `, { namespace: NS })).not.toThrow()
2144
+ })
2145
+ })
2146
+
2147
+ // ===========================================================================
2148
+ // 29. Function calls with multiple args and return values
2149
+ // ===========================================================================
2150
+
2151
+ describe('v2 migration: multi-arg functions', () => {
2152
+ test('function with 3 parameters', () => {
2153
+ const rt = makeRuntime(`
2154
+ fn add3(a: int, b: int, c: int): int {
2155
+ return a + b + c;
2156
+ }
2157
+ fn f(): int { return add3(10, 20, 30); }
2158
+ `)
2159
+ expect(callAndGetRet(rt, 'f')).toBe(60)
2160
+ })
2161
+
2162
+ test('function with 4 parameters', () => {
2163
+ const rt = makeRuntime(`
2164
+ fn sum4(a: int, b: int, c: int, d: int): int {
2165
+ return a + b + c + d;
2166
+ }
2167
+ fn f(): int { return sum4(1, 2, 3, 4); }
2168
+ `)
2169
+ expect(callAndGetRet(rt, 'f')).toBe(10)
2170
+ })
2171
+
2172
+ test('function with 5 parameters', () => {
2173
+ const rt = makeRuntime(`
2174
+ fn sum5(a: int, b: int, c: int, d: int, e: int): int {
2175
+ return a + b + c + d + e;
2176
+ }
2177
+ fn f(): int { return sum5(2, 4, 6, 8, 10); }
2178
+ `)
2179
+ expect(callAndGetRet(rt, 'f')).toBe(30)
2180
+ })
2181
+
2182
+ test('function calling function (2-deep chain)', () => {
2183
+ const rt = makeRuntime(`
2184
+ fn double(x: int): int { return x * 2; }
2185
+ fn quadruple(x: int): int { return double(double(x)); }
2186
+ fn f(): int { return quadruple(3); }
2187
+ `)
2188
+ expect(callAndGetRet(rt, 'f')).toBe(12)
2189
+ })
2190
+
2191
+ test('function calling function (3-deep chain)', () => {
2192
+ const rt = makeRuntime(`
2193
+ fn inc(x: int): int { return x + 1; }
2194
+ fn add2(x: int): int { return inc(inc(x)); }
2195
+ fn add4(x: int): int { return add2(add2(x)); }
2196
+ fn f(): int { return add4(10); }
2197
+ `)
2198
+ expect(callAndGetRet(rt, 'f')).toBe(14)
2199
+ })
2200
+
2201
+ test('function result used in arithmetic', () => {
2202
+ const rt = makeRuntime(`
2203
+ fn square(x: int): int { return x * x; }
2204
+ fn f(): int { return square(3) + square(4); }
2205
+ `)
2206
+ // 9 + 16 = 25
2207
+ expect(callAndGetRet(rt, 'f')).toBe(25)
2208
+ })
2209
+
2210
+ test('function with param used in condition', () => {
2211
+ const rt = makeRuntime(`
2212
+ fn max_val(a: int, b: int, c: int): int {
2213
+ let m: int = a;
2214
+ if (b > m) { m = b; }
2215
+ if (c > m) { m = c; }
2216
+ return m;
2217
+ }
2218
+ fn f(): int { return max_val(5, 12, 8); }
2219
+ `)
2220
+ expect(callAndGetRet(rt, 'f')).toBe(12)
2221
+ })
2222
+
2223
+ test('recursive-like pattern via loop', () => {
2224
+ const rt = makeRuntime(`
2225
+ fn power(base: int, exp: int): int {
2226
+ let result: int = 1;
2227
+ let i: int = 0;
2228
+ while (i < exp) {
2229
+ result = result * base;
2230
+ i = i + 1;
2231
+ }
2232
+ return result;
2233
+ }
2234
+ fn f(): int { return power(2, 8); }
2235
+ `)
2236
+ expect(callAndGetRet(rt, 'f')).toBe(256)
2237
+ })
2238
+ })
2239
+
2240
+ // ===========================================================================
2241
+ // 30. Math-style stdlib patterns (inline, not via import)
2242
+ // ===========================================================================
2243
+
2244
+ describe('v2 migration: math patterns', () => {
2245
+ test('min of two values', () => {
2246
+ const rt = makeRuntime(`
2247
+ fn min(a: int, b: int): int {
2248
+ if (a < b) { return a; } else { return b; }
2249
+ }
2250
+ fn f(): int { return min(7, 3); }
2251
+ `)
2252
+ expect(callAndGetRet(rt, 'f')).toBe(3)
2253
+ })
2254
+
2255
+ test('max of two values', () => {
2256
+ const rt = makeRuntime(`
2257
+ fn max(a: int, b: int): int {
2258
+ if (a > b) { return a; } else { return b; }
2259
+ }
2260
+ fn f(): int { return max(7, 3); }
2261
+ `)
2262
+ expect(callAndGetRet(rt, 'f')).toBe(7)
2263
+ })
2264
+
2265
+ test('abs of positive', () => {
2266
+ const rt = makeRuntime(`
2267
+ fn abs(x: int): int {
2268
+ if (x < 0) { return 0 - x; } else { return x; }
2269
+ }
2270
+ fn f(): int { return abs(42); }
2271
+ `)
2272
+ expect(callAndGetRet(rt, 'f')).toBe(42)
2273
+ })
2274
+
2275
+ test('abs of negative', () => {
2276
+ const rt = makeRuntime(`
2277
+ fn abs(x: int): int {
2278
+ if (x < 0) { return 0 - x; } else { return x; }
2279
+ }
2280
+ fn f(): int { return abs(-15); }
2281
+ `)
2282
+ expect(callAndGetRet(rt, 'f')).toBe(15)
2283
+ })
2284
+
2285
+ test('clamp value in range (below min)', () => {
2286
+ const rt = makeRuntime(`
2287
+ fn clamp(x: int, lo: int, hi: int): int {
2288
+ if (x < lo) { return lo; }
2289
+ if (x > hi) { return hi; }
2290
+ return x;
2291
+ }
2292
+ fn f(): int { return clamp(-5, 0, 100); }
2293
+ `)
2294
+ expect(callAndGetRet(rt, 'f')).toBe(0)
2295
+ })
2296
+
2297
+ test('clamp value in range (above max)', () => {
2298
+ const rt = makeRuntime(`
2299
+ fn clamp(x: int, lo: int, hi: int): int {
2300
+ if (x < lo) { return lo; }
2301
+ if (x > hi) { return hi; }
2302
+ return x;
2303
+ }
2304
+ fn f(): int { return clamp(200, 0, 100); }
2305
+ `)
2306
+ expect(callAndGetRet(rt, 'f')).toBe(100)
2307
+ })
2308
+
2309
+ test('clamp value in range (within)', () => {
2310
+ const rt = makeRuntime(`
2311
+ fn clamp(x: int, lo: int, hi: int): int {
2312
+ if (x < lo) { return lo; }
2313
+ if (x > hi) { return hi; }
2314
+ return x;
2315
+ }
2316
+ fn f(): int { return clamp(50, 0, 100); }
2317
+ `)
2318
+ expect(callAndGetRet(rt, 'f')).toBe(50)
2319
+ })
2320
+
2321
+ test('sign function', () => {
2322
+ const rt = makeRuntime(`
2323
+ fn sign(x: int): int {
2324
+ if (x > 0) { return 1; }
2325
+ if (x < 0) { return -1; }
2326
+ return 0;
2327
+ }
2328
+ fn f(): int { return sign(-42) + sign(0) + sign(100); }
2329
+ `)
2330
+ // -1 + 0 + 1 = 0
2331
+ expect(callAndGetRet(rt, 'f')).toBe(0)
2332
+ })
2333
+
2334
+ test('integer division rounding', () => {
2335
+ const rt = makeRuntime(`
2336
+ fn div_round(a: int, b: int): int {
2337
+ return (a + b / 2) / b;
2338
+ }
2339
+ fn f(): int { return div_round(7, 3); }
2340
+ `)
2341
+ // (7 + 1) / 3 = 2 (integer)
2342
+ expect(callAndGetRet(rt, 'f')).toBe(2)
2343
+ })
2344
+
2345
+ test('is_even / is_odd', () => {
2346
+ const rt = makeRuntime(`
2347
+ fn is_even(x: int): int {
2348
+ if (x % 2 == 0) { return 1; } else { return 0; }
2349
+ }
2350
+ fn f(): int {
2351
+ return is_even(4) + is_even(7) + is_even(0) + is_even(13);
2352
+ }
2353
+ `)
2354
+ // 1 + 0 + 1 + 0 = 2
2355
+ expect(callAndGetRet(rt, 'f')).toBe(2)
2356
+ })
2357
+ })
2358
+
2359
+ // ===========================================================================
2360
+ // 31. Break/continue in loops (behavioral)
2361
+ // ===========================================================================
2362
+
2363
+ describe('v2 migration: break/continue behavioral', () => {
2364
+ test('break exits while loop early', () => {
2365
+ const rt = makeRuntime(`
2366
+ fn f(): int {
2367
+ let i: int = 0;
2368
+ let sum: int = 0;
2369
+ while (i < 100) {
2370
+ if (i == 5) { break; }
2371
+ sum = sum + i;
2372
+ i = i + 1;
2373
+ }
2374
+ return sum;
2375
+ }
2376
+ `)
2377
+ // 0+1+2+3+4 = 10
2378
+ expect(callAndGetRet(rt, 'f')).toBe(10)
2379
+ })
2380
+
2381
+ test('continue skips to next iteration', () => {
2382
+ const rt = makeRuntime(`
2383
+ fn f(): int {
2384
+ let i: int = 0;
2385
+ let sum: int = 0;
2386
+ while (i < 10) {
2387
+ i = i + 1;
2388
+ if (i % 2 == 1) { continue; }
2389
+ sum = sum + i;
2390
+ }
2391
+ return sum;
2392
+ }
2393
+ `)
2394
+ // 2+4+6+8+10 = 30
2395
+ expect(callAndGetRet(rt, 'f')).toBe(30)
2396
+ })
2397
+
2398
+ test('break in for loop', () => {
2399
+ const rt = makeRuntime(`
2400
+ fn f(): int {
2401
+ let result: int = 0;
2402
+ for i in 0..100 {
2403
+ if (i == 7) { break; }
2404
+ result = result + 1;
2405
+ }
2406
+ return result;
2407
+ }
2408
+ `)
2409
+ expect(callAndGetRet(rt, 'f')).toBe(7)
2410
+ })
2411
+
2412
+ test('continue in for loop', () => {
2413
+ const rt = makeRuntime(`
2414
+ fn f(): int {
2415
+ let sum: int = 0;
2416
+ for i in 0..10 {
2417
+ if (i % 3 == 0) { continue; }
2418
+ sum = sum + 1;
2419
+ }
2420
+ return sum;
2421
+ }
2422
+ `)
2423
+ // 10 iterations, skip i=0,3,6,9 → 6 counted
2424
+ expect(callAndGetRet(rt, 'f')).toBe(6)
2425
+ })
2426
+
2427
+ test('break with accumulator', () => {
2428
+ const rt = makeRuntime(`
2429
+ fn f(): int {
2430
+ let product: int = 1;
2431
+ let i: int = 1;
2432
+ while (i <= 10) {
2433
+ product = product * i;
2434
+ if (product > 100) { break; }
2435
+ i = i + 1;
2436
+ }
2437
+ return product;
2438
+ }
2439
+ `)
2440
+ // 1*1=1, 1*2=2, 2*3=6, 6*4=24, 24*5=120 > 100 → break → 120
2441
+ expect(callAndGetRet(rt, 'f')).toBe(120)
2442
+ })
2443
+
2444
+ test('multiple breaks in loop (first wins)', () => {
2445
+ const rt = makeRuntime(`
2446
+ fn f(): int {
2447
+ let i: int = 0;
2448
+ while (i < 20) {
2449
+ if (i == 3) { break; }
2450
+ if (i == 7) { break; }
2451
+ i = i + 1;
2452
+ }
2453
+ return i;
2454
+ }
2455
+ `)
2456
+ expect(callAndGetRet(rt, 'f')).toBe(3)
2457
+ })
2458
+
2459
+ test('break and continue in same loop', () => {
2460
+ const rt = makeRuntime(`
2461
+ fn f(): int {
2462
+ let i: int = 0;
2463
+ let sum: int = 0;
2464
+ while (i < 20) {
2465
+ i = i + 1;
2466
+ if (i % 2 == 0) { continue; }
2467
+ if (i > 10) { break; }
2468
+ sum = sum + i;
2469
+ }
2470
+ return sum;
2471
+ }
2472
+ `)
2473
+ // odd numbers 1,3,5,7,9 → sum = 25; i=11 is odd > 10 → break
2474
+ expect(callAndGetRet(rt, 'f')).toBe(25)
2475
+ })
2476
+ })
2477
+
2478
+ // ===========================================================================
2479
+ // 32. Foreach (behavioral)
2480
+ // ===========================================================================
2481
+
2482
+ describe('v2 migration: foreach behavioral', () => {
2483
+ test('foreach with @e generates execute as run function', () => {
2484
+ const result = compile(`
2485
+ fn f(): void {
2486
+ foreach (e in @e) {
2487
+ raw("say hi");
2488
+ }
2489
+ }
2490
+ `, { namespace: NS })
2491
+ const allContent = result.files
2492
+ .filter(f => f.path.endsWith('.mcfunction'))
2493
+ .map(f => f.content).join('\n')
2494
+ expect(allContent).toContain('execute as @e run function')
2495
+ expect(allContent).toContain('say hi')
2496
+ })
2497
+
2498
+ test('foreach with @a selector', () => {
2499
+ const result = compile(`
2500
+ fn f(): void {
2501
+ foreach (p in @a) {
2502
+ raw("effect give @s speed 1 1");
2503
+ }
2504
+ }
2505
+ `, { namespace: NS })
2506
+ const allContent = result.files
2507
+ .filter(f => f.path.endsWith('.mcfunction'))
2508
+ .map(f => f.content).join('\n')
2509
+ expect(allContent).toContain('execute as @a run function')
2510
+ expect(allContent).toContain('effect give @s speed 1 1')
2511
+ })
2512
+
2513
+ test('foreach with filtered selector', () => {
2514
+ const result = compile(`
2515
+ fn f(): void {
2516
+ foreach (z in @e[type=zombie]) {
2517
+ raw("kill @s");
2518
+ }
2519
+ }
2520
+ `, { namespace: NS })
2521
+ const allContent = result.files
2522
+ .filter(f => f.path.endsWith('.mcfunction'))
2523
+ .map(f => f.content).join('\n')
2524
+ expect(allContent).toContain('execute as @e[type=zombie] run function')
2525
+ })
2526
+
2527
+ test('foreach with complex body compiles', () => {
2528
+ expect(() => compile(`
2529
+ fn f(): void {
2530
+ foreach (e in @e[type=zombie,distance=..10]) {
2531
+ raw("effect give @s slowness 1 1");
2532
+ raw("damage @s 2");
2533
+ }
2534
+ }
2535
+ `, { namespace: NS })).not.toThrow()
2536
+ })
2537
+ })
2538
+
2539
+ // ===========================================================================
2540
+ // 33. For loop advanced patterns
2541
+ // ===========================================================================
2542
+
2543
+ describe('v2 migration: for loop advanced', () => {
2544
+ test('for loop with range 0..0 produces 0 iterations', () => {
2545
+ const rt = makeRuntime(`
2546
+ fn f(): int {
2547
+ let count: int = 0;
2548
+ for i in 0..0 {
2549
+ count = count + 1;
2550
+ }
2551
+ return count;
2552
+ }
2553
+ `)
2554
+ expect(callAndGetRet(rt, 'f')).toBe(0)
2555
+ })
2556
+
2557
+ test('for loop with range 0..1 produces 1 iteration', () => {
2558
+ const rt = makeRuntime(`
2559
+ fn f(): int {
2560
+ let count: int = 0;
2561
+ for i in 0..1 {
2562
+ count = count + 1;
2563
+ }
2564
+ return count;
2565
+ }
2566
+ `)
2567
+ expect(callAndGetRet(rt, 'f')).toBe(1)
2568
+ })
2569
+
2570
+ test('for loop accumulates sum', () => {
2571
+ const rt = makeRuntime(`
2572
+ fn f(): int {
2573
+ let sum: int = 0;
2574
+ for i in 0..5 {
2575
+ sum = sum + i;
2576
+ }
2577
+ return sum;
2578
+ }
2579
+ `)
2580
+ // 0+1+2+3+4 = 10
2581
+ expect(callAndGetRet(rt, 'f')).toBe(10)
2582
+ })
2583
+
2584
+ test('for loop with large range', () => {
2585
+ const rt = makeRuntime(`
2586
+ fn f(): int {
2587
+ let count: int = 0;
2588
+ for i in 0..20 {
2589
+ count = count + 1;
2590
+ }
2591
+ return count;
2592
+ }
2593
+ `)
2594
+ expect(callAndGetRet(rt, 'f')).toBe(20)
2595
+ })
2596
+
2597
+ test('nested for loops (product)', () => {
2598
+ const rt = makeRuntime(`
2599
+ fn f(): int {
2600
+ let count: int = 0;
2601
+ for i in 0..3 {
2602
+ for j in 0..4 {
2603
+ count = count + 1;
2604
+ }
2605
+ }
2606
+ return count;
2607
+ }
2608
+ `)
2609
+ expect(callAndGetRet(rt, 'f')).toBe(12)
2610
+ })
2611
+ })
2612
+
2613
+ // ===========================================================================
2614
+ // 34. Complex control flow patterns
2615
+ // ===========================================================================
2616
+
2617
+ describe('v2 migration: complex control flow', () => {
2618
+ test('nested loops with outer counter', () => {
2619
+ const rt = makeRuntime(`
2620
+ fn f(): int {
2621
+ let total: int = 0;
2622
+ let i: int = 0;
2623
+ while (i < 3) {
2624
+ let j: int = 0;
2625
+ while (j < 3) {
2626
+ total = total + 1;
2627
+ j = j + 1;
2628
+ }
2629
+ i = i + 1;
2630
+ }
2631
+ return total;
2632
+ }
2633
+ `)
2634
+ expect(callAndGetRet(rt, 'f')).toBe(9)
2635
+ })
2636
+
2637
+ test('loop with early return', () => {
2638
+ const rt = makeRuntime(`
2639
+ fn find_first_gt(threshold: int): int {
2640
+ let i: int = 0;
2641
+ while (i < 100) {
2642
+ if (i * i > threshold) { return i; }
2643
+ i = i + 1;
2644
+ }
2645
+ return -1;
2646
+ }
2647
+ fn f(): int { return find_first_gt(50); }
2648
+ `)
2649
+ // 8*8=64 > 50 → return 8
2650
+ expect(callAndGetRet(rt, 'f')).toBe(8)
2651
+ })
2652
+
2653
+ test('multiple early returns in if/else chain', () => {
2654
+ const rt = makeRuntime(`
2655
+ fn classify(x: int): int {
2656
+ if (x < 0) { return -1; }
2657
+ if (x == 0) { return 0; }
2658
+ if (x < 10) { return 1; }
2659
+ if (x < 100) { return 2; }
2660
+ return 3;
2661
+ }
2662
+ fn f(): int {
2663
+ return classify(-5) + classify(0) + classify(7) + classify(50) + classify(200);
2664
+ }
2665
+ `)
2666
+ // -1 + 0 + 1 + 2 + 3 = 5
2667
+ expect(callAndGetRet(rt, 'f')).toBe(5)
2668
+ })
2669
+
2670
+ test('while with complex condition', () => {
2671
+ const rt = makeRuntime(`
2672
+ fn f(): int {
2673
+ let a: int = 10;
2674
+ let b: int = 20;
2675
+ while (a < b) {
2676
+ a = a + 3;
2677
+ b = b - 1;
2678
+ }
2679
+ return a;
2680
+ }
2681
+ `)
2682
+ // a=10,b=20 → a=13,b=19 → a=16,b=18 → a=19,b=17 → exit (19 >= 17)
2683
+ expect(callAndGetRet(rt, 'f')).toBe(19)
2684
+ })
2685
+
2686
+ test('deeply nested if/else', () => {
2687
+ const rt = makeRuntime(`
2688
+ fn f(): int {
2689
+ let x: int = 7;
2690
+ if (x > 0) {
2691
+ if (x > 5) {
2692
+ if (x > 10) {
2693
+ return 3;
2694
+ } else {
2695
+ return 2;
2696
+ }
2697
+ } else {
2698
+ return 1;
2699
+ }
2700
+ } else {
2701
+ return 0;
2702
+ }
2703
+ }
2704
+ `)
2705
+ expect(callAndGetRet(rt, 'f')).toBe(2)
2706
+ })
2707
+ })
2708
+
2709
+ // ===========================================================================
2710
+ // 35. Raw commands (extended)
2711
+ // ===========================================================================
2712
+
2713
+ describe('v2 migration: raw commands extended', () => {
2714
+ test('raw command with execute', () => {
2715
+ const result = compile(`
2716
+ fn f(): void {
2717
+ raw("execute as @a at @s run particle flame ~ ~ ~ 0.5 0.5 0.5 0 10");
2718
+ }
2719
+ `, { namespace: NS })
2720
+ const fn = getFile(result.files, '/f.mcfunction')
2721
+ expect(fn).toContain('execute as @a at @s run particle flame')
2722
+ })
2723
+
2724
+ test('raw command with scoreboard', () => {
2725
+ const result = compile(`
2726
+ fn f(): void {
2727
+ raw("scoreboard players set @s health 20");
2728
+ }
2729
+ `, { namespace: NS })
2730
+ const fn = getFile(result.files, '/f.mcfunction')
2731
+ expect(fn).toContain('scoreboard players set @s health 20')
2732
+ })
2733
+
2734
+ test('raw command with tellraw', () => {
2735
+ const result = compile(`
2736
+ fn f(): void {
2737
+ raw("tellraw @a {\\"text\\":\\"Hello World\\"}");
2738
+ }
2739
+ `, { namespace: NS })
2740
+ const fn = getFile(result.files, '/f.mcfunction')
2741
+ expect(fn).toContain('tellraw @a')
2742
+ })
2743
+
2744
+ test('raw command mixed with regular code', () => {
2745
+ const rt = makeRuntime(`
2746
+ fn f(): int {
2747
+ let x: int = 10;
2748
+ raw("say test");
2749
+ let y: int = x + 5;
2750
+ return y;
2751
+ }
2752
+ `)
2753
+ expect(callAndGetRet(rt, 'f')).toBe(15)
2754
+ })
2755
+
2756
+ test('multiple raw commands interleaved', () => {
2757
+ const rt = makeRuntime(`
2758
+ fn f(): int {
2759
+ raw("say start");
2760
+ let x: int = 1;
2761
+ raw("say middle");
2762
+ x = x + 9;
2763
+ raw("say end");
2764
+ return x;
2765
+ }
2766
+ `)
2767
+ expect(callAndGetRet(rt, 'f')).toBe(10)
2768
+ })
2769
+ })
2770
+
2771
+ // ===========================================================================
2772
+ // 36. Variable scoping and edge cases
2773
+ // ===========================================================================
2774
+
2775
+ describe('v2 migration: variable scoping', () => {
2776
+ test('same variable name in different functions', () => {
2777
+ const rt = makeRuntime(`
2778
+ fn foo(): int {
2779
+ let x: int = 10;
2780
+ return x;
2781
+ }
2782
+ fn bar(): int {
2783
+ let x: int = 20;
2784
+ return x;
2785
+ }
2786
+ fn f(): int { return foo() + bar(); }
2787
+ `)
2788
+ expect(callAndGetRet(rt, 'f')).toBe(30)
2789
+ })
2790
+
2791
+ test('variable reassignment chain', () => {
2792
+ const rt = makeRuntime(`
2793
+ fn f(): int {
2794
+ let x: int = 1;
2795
+ x = x + 1;
2796
+ x = x * 3;
2797
+ x = x - 2;
2798
+ x = x + 10;
2799
+ return x;
2800
+ }
2801
+ `)
2802
+ // 1 → 2 → 6 → 4 → 14
2803
+ expect(callAndGetRet(rt, 'f')).toBe(14)
2804
+ })
2805
+
2806
+ test('many local variables', () => {
2807
+ const rt = makeRuntime(`
2808
+ fn f(): int {
2809
+ let a: int = 1;
2810
+ let b: int = 2;
2811
+ let c: int = 3;
2812
+ let d: int = 4;
2813
+ let e: int = 5;
2814
+ let g: int = 6;
2815
+ let h: int = 7;
2816
+ return a + b + c + d + e + g + h;
2817
+ }
2818
+ `)
2819
+ expect(callAndGetRet(rt, 'f')).toBe(28)
2820
+ })
2821
+
2822
+ test('variable used across if/else branches', () => {
2823
+ const rt = makeRuntime(`
2824
+ fn f(): int {
2825
+ let result: int = 0;
2826
+ let x: int = 5;
2827
+ if (x > 3) {
2828
+ result = 100;
2829
+ } else {
2830
+ result = 200;
2831
+ }
2832
+ return result;
2833
+ }
2834
+ `)
2835
+ expect(callAndGetRet(rt, 'f')).toBe(100)
2836
+ })
2837
+
2838
+ test('variable modified in loop', () => {
2839
+ const rt = makeRuntime(`
2840
+ fn f(): int {
2841
+ let x: int = 1;
2842
+ let i: int = 0;
2843
+ while (i < 5) {
2844
+ x = x * 2;
2845
+ i = i + 1;
2846
+ }
2847
+ return x;
2848
+ }
2849
+ `)
2850
+ // 1 → 2 → 4 → 8 → 16 → 32
2851
+ expect(callAndGetRet(rt, 'f')).toBe(32)
2852
+ })
2853
+ })
2854
+
2855
+ // ===========================================================================
2856
+ // 37. Compound assignment operators
2857
+ // ===========================================================================
2858
+
2859
+ describe('v2 migration: compound assignment extended', () => {
2860
+ test('+= in loop', () => {
2861
+ const rt = makeRuntime(`
2862
+ fn f(): int {
2863
+ let sum: int = 0;
2864
+ let i: int = 1;
2865
+ while (i <= 5) {
2866
+ sum += i;
2867
+ i += 1;
2868
+ }
2869
+ return sum;
2870
+ }
2871
+ `)
2872
+ expect(callAndGetRet(rt, 'f')).toBe(15)
2873
+ })
2874
+
2875
+ test('-= countdown', () => {
2876
+ const rt = makeRuntime(`
2877
+ fn f(): int {
2878
+ let x: int = 100;
2879
+ x -= 30;
2880
+ x -= 25;
2881
+ x -= 10;
2882
+ return x;
2883
+ }
2884
+ `)
2885
+ expect(callAndGetRet(rt, 'f')).toBe(35)
2886
+ })
2887
+
2888
+ test('*= doubling', () => {
2889
+ const rt = makeRuntime(`
2890
+ fn f(): int {
2891
+ let x: int = 1;
2892
+ x *= 2;
2893
+ x *= 3;
2894
+ x *= 4;
2895
+ return x;
2896
+ }
2897
+ `)
2898
+ expect(callAndGetRet(rt, 'f')).toBe(24)
2899
+ })
2900
+
2901
+ test('/= division', () => {
2902
+ const rt = makeRuntime(`
2903
+ fn f(): int {
2904
+ let x: int = 1000;
2905
+ x /= 2;
2906
+ x /= 5;
2907
+ return x;
2908
+ }
2909
+ `)
2910
+ expect(callAndGetRet(rt, 'f')).toBe(100)
2911
+ })
2912
+
2913
+ test('%= modulo', () => {
2914
+ const rt = makeRuntime(`
2915
+ fn f(): int {
2916
+ let x: int = 17;
2917
+ x %= 5;
2918
+ return x;
2919
+ }
2920
+ `)
2921
+ expect(callAndGetRet(rt, 'f')).toBe(2)
2922
+ })
2923
+ })
2924
+
2925
+ // ===========================================================================
2926
+ // 38. Complex algorithmic patterns
2927
+ // ===========================================================================
2928
+
2929
+ describe('v2 migration: algorithmic patterns', () => {
2930
+ test('digit sum', () => {
2931
+ const rt = makeRuntime(`
2932
+ fn digit_sum(n: int): int {
2933
+ let sum: int = 0;
2934
+ while (n > 0) {
2935
+ sum = sum + n % 10;
2936
+ n = n / 10;
2937
+ }
2938
+ return sum;
2939
+ }
2940
+ fn f(): int { return digit_sum(12345); }
2941
+ `)
2942
+ // 1+2+3+4+5 = 15
2943
+ expect(callAndGetRet(rt, 'f')).toBe(15)
2944
+ })
2945
+
2946
+ test('count digits', () => {
2947
+ const rt = makeRuntime(`
2948
+ fn count_digits(n: int): int {
2949
+ if (n == 0) { return 1; }
2950
+ let count: int = 0;
2951
+ while (n > 0) {
2952
+ n = n / 10;
2953
+ count = count + 1;
2954
+ }
2955
+ return count;
2956
+ }
2957
+ fn f(): int { return count_digits(9876); }
2958
+ `)
2959
+ expect(callAndGetRet(rt, 'f')).toBe(4)
2960
+ })
2961
+
2962
+ test('sum of divisors', () => {
2963
+ const rt = makeRuntime(`
2964
+ fn sum_divisors(n: int): int {
2965
+ let sum: int = 0;
2966
+ let i: int = 1;
2967
+ while (i <= n) {
2968
+ if (n % i == 0) {
2969
+ sum = sum + i;
2970
+ }
2971
+ i = i + 1;
2972
+ }
2973
+ return sum;
2974
+ }
2975
+ fn f(): int { return sum_divisors(12); }
2976
+ `)
2977
+ // 1+2+3+4+6+12 = 28
2978
+ expect(callAndGetRet(rt, 'f')).toBe(28)
2979
+ })
2980
+
2981
+ test('triangle number', () => {
2982
+ const rt = makeRuntime(`
2983
+ fn triangle(n: int): int {
2984
+ return n * (n + 1) / 2;
2985
+ }
2986
+ fn f(): int { return triangle(10); }
2987
+ `)
2988
+ expect(callAndGetRet(rt, 'f')).toBe(55)
2989
+ })
2990
+
2991
+ test('is_perfect_square', () => {
2992
+ const rt = makeRuntime(`
2993
+ fn is_perfect_sq(n: int): int {
2994
+ let i: int = 0;
2995
+ while (i * i <= n) {
2996
+ if (i * i == n) { return 1; }
2997
+ i = i + 1;
2998
+ }
2999
+ return 0;
3000
+ }
3001
+ fn f(): int {
3002
+ return is_perfect_sq(16) + is_perfect_sq(25) + is_perfect_sq(10);
3003
+ }
3004
+ `)
3005
+ // 1 + 1 + 0 = 2
3006
+ expect(callAndGetRet(rt, 'f')).toBe(2)
3007
+ })
3008
+ })