redscript-mc 3.0.1 → 3.0.2

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 (225) hide show
  1. package/.github/workflows/ci.yml +1 -0
  2. package/README.md +119 -313
  3. package/README.zh.md +118 -314
  4. package/ROADMAP.md +5 -5
  5. package/dist/data/impl_test/function/counter/get.mcfunction +5 -0
  6. package/dist/data/impl_test/function/counter/inc.mcfunction +7 -0
  7. package/dist/data/impl_test/function/counter/new.mcfunction +4 -0
  8. package/dist/data/impl_test/function/load.mcfunction +1 -0
  9. package/dist/data/impl_test/function/test_impl.mcfunction +10 -0
  10. package/dist/data/minecraft/tags/function/load.json +5 -0
  11. package/dist/data/playground/function/load.mcfunction +1 -0
  12. package/dist/data/playground/function/start.mcfunction +4 -0
  13. package/dist/data/playground/function/start__say_macro_t1.mcfunction +1 -0
  14. package/dist/data/playground/function/stop.mcfunction +5 -0
  15. package/dist/data/playground/function/stop__say_macro_t0.mcfunction +1 -0
  16. package/dist/data/stdlib_queue8_test/function/__queue_append_apply.mcfunction +4 -0
  17. package/dist/data/stdlib_queue8_test/function/__queue_peek_apply.mcfunction +4 -0
  18. package/dist/data/stdlib_queue8_test/function/__queue_size_raw_apply.mcfunction +4 -0
  19. package/dist/data/stdlib_queue8_test/function/load.mcfunction +1 -0
  20. package/dist/data/stdlib_queue8_test/function/queue_clear.mcfunction +6 -0
  21. package/dist/data/stdlib_queue8_test/function/queue_empty__merge_1.mcfunction +5 -0
  22. package/dist/data/stdlib_queue8_test/function/queue_empty__then_0.mcfunction +5 -0
  23. package/dist/data/stdlib_queue8_test/function/queue_peek__merge_1.mcfunction +13 -0
  24. package/dist/data/stdlib_queue8_test/function/queue_peek__then_0.mcfunction +5 -0
  25. package/dist/data/stdlib_queue8_test/function/queue_pop__merge_1.mcfunction +15 -0
  26. package/dist/data/stdlib_queue8_test/function/queue_pop__then_0.mcfunction +5 -0
  27. package/dist/data/stdlib_queue8_test/function/queue_push__const_11.mcfunction +6 -0
  28. package/dist/data/stdlib_queue8_test/function/queue_push__const_22.mcfunction +6 -0
  29. package/dist/data/stdlib_queue8_test/function/queue_size.mcfunction +13 -0
  30. package/dist/data/stdlib_queue8_test/function/test_queue_push_and_size.mcfunction +13 -0
  31. package/dist/data/test/function/load.mcfunction +1 -0
  32. package/dist/data/test/function/say_at.mcfunction +6 -0
  33. package/dist/data/test/function/test.mcfunction +4 -0
  34. package/dist/pack.mcmeta +6 -0
  35. package/dist/package.json +1 -1
  36. package/dist/src/__tests__/formatter-extra.test.d.ts +7 -0
  37. package/dist/src/__tests__/formatter-extra.test.js +123 -0
  38. package/dist/src/__tests__/global-vars.test.d.ts +13 -0
  39. package/dist/src/__tests__/global-vars.test.js +156 -0
  40. package/dist/src/__tests__/lint/new-rules.test.d.ts +9 -0
  41. package/dist/src/__tests__/lint/new-rules.test.js +402 -0
  42. package/dist/src/__tests__/lsp-rename.test.d.ts +8 -0
  43. package/dist/src/__tests__/lsp-rename.test.js +157 -0
  44. package/dist/src/__tests__/mc-integration/say-fstring.test.d.ts +11 -0
  45. package/dist/src/__tests__/mc-integration/say-fstring.test.js +220 -0
  46. package/dist/src/__tests__/mc-integration/stdlib-coverage-2.test.js +1 -1
  47. package/dist/src/__tests__/mc-integration/stdlib-coverage-3.test.js +1 -1
  48. package/dist/src/__tests__/mc-integration/stdlib-coverage-4.test.js +1 -1
  49. package/dist/src/__tests__/mc-integration/stdlib-coverage-5.test.js +1 -1
  50. package/dist/src/__tests__/mc-integration/stdlib-coverage-6.test.js +1 -1
  51. package/dist/src/__tests__/mc-integration/stdlib-coverage-7.test.js +1 -1
  52. package/dist/src/__tests__/mc-integration/stdlib-coverage-8.test.js +1 -1
  53. package/dist/src/__tests__/mc-syntax.test.js +4 -1
  54. package/dist/src/__tests__/monomorphize-coverage.test.d.ts +9 -0
  55. package/dist/src/__tests__/monomorphize-coverage.test.js +204 -0
  56. package/dist/src/__tests__/optimizer-cse.test.d.ts +7 -0
  57. package/dist/src/__tests__/optimizer-cse.test.js +226 -0
  58. package/dist/src/__tests__/parser.test.js +4 -13
  59. package/dist/src/__tests__/repl-server-extra.test.js +6 -7
  60. package/dist/src/__tests__/repl-server.test.js +5 -7
  61. package/dist/src/__tests__/stdlib/queue.test.js +6 -6
  62. package/dist/src/cli.js +0 -0
  63. package/dist/src/lexer/index.js +2 -1
  64. package/dist/src/lint/index.d.ts +12 -5
  65. package/dist/src/lint/index.js +730 -5
  66. package/dist/src/lsp/main.js +0 -0
  67. package/dist/src/mc-test/client.d.ts +21 -0
  68. package/dist/src/mc-test/client.js +34 -0
  69. package/dist/src/mir/lower.js +108 -6
  70. package/dist/src/optimizer/interprocedural.js +37 -2
  71. package/dist/src/parser/decl-parser.d.ts +19 -0
  72. package/dist/src/parser/decl-parser.js +323 -0
  73. package/dist/src/parser/expr-parser.d.ts +46 -0
  74. package/dist/src/parser/expr-parser.js +759 -0
  75. package/dist/src/parser/index.d.ts +8 -129
  76. package/dist/src/parser/index.js +13 -2262
  77. package/dist/src/parser/stmt-parser.d.ts +28 -0
  78. package/dist/src/parser/stmt-parser.js +577 -0
  79. package/dist/src/parser/type-parser.d.ts +20 -0
  80. package/dist/src/parser/type-parser.js +257 -0
  81. package/dist/src/parser/utils.d.ts +34 -0
  82. package/dist/src/parser/utils.js +141 -0
  83. package/docs/dev/README-mc-integration-tests.md +141 -0
  84. package/docs/lint-rules.md +162 -0
  85. package/docs/stdlib/bigint.md +2 -0
  86. package/editors/vscode/README.md +63 -41
  87. package/editors/vscode/out/extension.js +1881 -1776
  88. package/editors/vscode/out/lsp-server.js +4257 -3651
  89. package/editors/vscode/package-lock.json +3 -3
  90. package/editors/vscode/package.json +1 -1
  91. package/examples/loops-demo.mcrs +87 -0
  92. package/package.json +1 -1
  93. package/redscript-docs/docs/en/stdlib/advanced.md +629 -0
  94. package/redscript-docs/docs/en/stdlib/bigint.md +316 -0
  95. package/redscript-docs/docs/en/stdlib/bits.md +292 -0
  96. package/redscript-docs/docs/en/stdlib/bossbar.md +177 -0
  97. package/redscript-docs/docs/en/stdlib/calculus.md +289 -0
  98. package/redscript-docs/docs/en/stdlib/color.md +353 -0
  99. package/redscript-docs/docs/en/stdlib/combat.md +88 -0
  100. package/redscript-docs/docs/en/stdlib/cooldown.md +82 -0
  101. package/redscript-docs/docs/en/stdlib/dialog.md +155 -0
  102. package/redscript-docs/docs/en/stdlib/easing.md +558 -0
  103. package/redscript-docs/docs/en/stdlib/ecs.md +475 -0
  104. package/redscript-docs/docs/en/stdlib/effects.md +324 -0
  105. package/redscript-docs/docs/en/stdlib/events.md +3 -0
  106. package/redscript-docs/docs/en/stdlib/expr.md +45 -0
  107. package/redscript-docs/docs/en/stdlib/fft.md +141 -0
  108. package/redscript-docs/docs/en/stdlib/geometry.md +430 -0
  109. package/redscript-docs/docs/en/stdlib/graph.md +259 -0
  110. package/redscript-docs/docs/en/stdlib/heap.md +185 -0
  111. package/redscript-docs/docs/en/stdlib/interactions.md +179 -0
  112. package/redscript-docs/docs/en/stdlib/inventory.md +97 -0
  113. package/redscript-docs/docs/en/stdlib/linalg.md +557 -0
  114. package/redscript-docs/docs/en/stdlib/list.md +559 -0
  115. package/redscript-docs/docs/en/stdlib/map.md +140 -0
  116. package/redscript-docs/docs/en/stdlib/math.md +193 -0
  117. package/redscript-docs/docs/en/stdlib/math_hp.md +149 -0
  118. package/redscript-docs/docs/en/stdlib/matrix.md +403 -0
  119. package/redscript-docs/docs/en/stdlib/mobs.md +965 -0
  120. package/redscript-docs/docs/en/stdlib/noise.md +244 -0
  121. package/redscript-docs/docs/en/stdlib/ode.md +253 -0
  122. package/redscript-docs/docs/en/stdlib/parabola.md +342 -0
  123. package/redscript-docs/docs/en/stdlib/particles.md +311 -0
  124. package/redscript-docs/docs/en/stdlib/pathfind.md +255 -0
  125. package/redscript-docs/docs/en/stdlib/physics.md +493 -0
  126. package/redscript-docs/docs/en/stdlib/player.md +78 -0
  127. package/redscript-docs/docs/en/stdlib/quaternion.md +673 -0
  128. package/redscript-docs/docs/en/stdlib/queue.md +134 -0
  129. package/redscript-docs/docs/en/stdlib/random.md +223 -0
  130. package/redscript-docs/docs/en/stdlib/result.md +143 -0
  131. package/redscript-docs/docs/en/stdlib/scheduler.md +183 -0
  132. package/redscript-docs/docs/en/stdlib/set_int.md +190 -0
  133. package/redscript-docs/docs/en/stdlib/sets.md +101 -0
  134. package/redscript-docs/docs/en/stdlib/signal.md +400 -0
  135. package/redscript-docs/docs/en/stdlib/sort.md +104 -0
  136. package/redscript-docs/docs/en/stdlib/spawn.md +147 -0
  137. package/redscript-docs/docs/en/stdlib/state.md +142 -0
  138. package/redscript-docs/docs/en/stdlib/strings.md +154 -0
  139. package/redscript-docs/docs/en/stdlib/tags.md +3451 -0
  140. package/redscript-docs/docs/en/stdlib/teams.md +153 -0
  141. package/redscript-docs/docs/en/stdlib/timer.md +246 -0
  142. package/redscript-docs/docs/en/stdlib/vec.md +158 -0
  143. package/redscript-docs/docs/en/stdlib/world.md +298 -0
  144. package/redscript-docs/docs/zh/stdlib/advanced.md +615 -0
  145. package/redscript-docs/docs/zh/stdlib/bigint.md +316 -0
  146. package/redscript-docs/docs/zh/stdlib/bits.md +292 -0
  147. package/redscript-docs/docs/zh/stdlib/bossbar.md +170 -0
  148. package/redscript-docs/docs/zh/stdlib/calculus.md +287 -0
  149. package/redscript-docs/docs/zh/stdlib/color.md +353 -0
  150. package/redscript-docs/docs/zh/stdlib/combat.md +88 -0
  151. package/redscript-docs/docs/zh/stdlib/cooldown.md +84 -0
  152. package/redscript-docs/docs/zh/stdlib/dialog.md +152 -0
  153. package/redscript-docs/docs/zh/stdlib/easing.md +558 -0
  154. package/redscript-docs/docs/zh/stdlib/ecs.md +472 -0
  155. package/redscript-docs/docs/zh/stdlib/effects.md +324 -0
  156. package/redscript-docs/docs/zh/stdlib/events.md +3 -0
  157. package/redscript-docs/docs/zh/stdlib/expr.md +37 -0
  158. package/redscript-docs/docs/zh/stdlib/fft.md +128 -0
  159. package/redscript-docs/docs/zh/stdlib/geometry.md +430 -0
  160. package/redscript-docs/docs/zh/stdlib/graph.md +259 -0
  161. package/redscript-docs/docs/zh/stdlib/heap.md +185 -0
  162. package/redscript-docs/docs/zh/stdlib/interactions.md +160 -0
  163. package/redscript-docs/docs/zh/stdlib/inventory.md +94 -0
  164. package/redscript-docs/docs/zh/stdlib/linalg.md +543 -0
  165. package/redscript-docs/docs/zh/stdlib/list.md +561 -0
  166. package/redscript-docs/docs/zh/stdlib/map.md +132 -0
  167. package/redscript-docs/docs/zh/stdlib/math.md +193 -0
  168. package/redscript-docs/docs/zh/stdlib/math_hp.md +143 -0
  169. package/redscript-docs/docs/zh/stdlib/matrix.md +396 -0
  170. package/redscript-docs/docs/zh/stdlib/mobs.md +965 -0
  171. package/redscript-docs/docs/zh/stdlib/noise.md +244 -0
  172. package/redscript-docs/docs/zh/stdlib/ode.md +243 -0
  173. package/redscript-docs/docs/zh/stdlib/parabola.md +337 -0
  174. package/redscript-docs/docs/zh/stdlib/particles.md +307 -0
  175. package/redscript-docs/docs/zh/stdlib/pathfind.md +255 -0
  176. package/redscript-docs/docs/zh/stdlib/physics.md +493 -0
  177. package/redscript-docs/docs/zh/stdlib/player.md +78 -0
  178. package/redscript-docs/docs/zh/stdlib/quaternion.md +669 -0
  179. package/redscript-docs/docs/zh/stdlib/queue.md +124 -0
  180. package/redscript-docs/docs/zh/stdlib/random.md +222 -0
  181. package/redscript-docs/docs/zh/stdlib/result.md +147 -0
  182. package/redscript-docs/docs/zh/stdlib/scheduler.md +173 -0
  183. package/redscript-docs/docs/zh/stdlib/set_int.md +180 -0
  184. package/redscript-docs/docs/zh/stdlib/sets.md +107 -0
  185. package/redscript-docs/docs/zh/stdlib/signal.md +373 -0
  186. package/redscript-docs/docs/zh/stdlib/sort.md +104 -0
  187. package/redscript-docs/docs/zh/stdlib/spawn.md +142 -0
  188. package/redscript-docs/docs/zh/stdlib/state.md +134 -0
  189. package/redscript-docs/docs/zh/stdlib/strings.md +107 -0
  190. package/redscript-docs/docs/zh/stdlib/tags.md +3451 -0
  191. package/redscript-docs/docs/zh/stdlib/teams.md +150 -0
  192. package/redscript-docs/docs/zh/stdlib/timer.md +254 -0
  193. package/redscript-docs/docs/zh/stdlib/vec.md +158 -0
  194. package/redscript-docs/docs/zh/stdlib/world.md +289 -0
  195. package/src/__tests__/formatter-extra.test.ts +139 -0
  196. package/src/__tests__/global-vars.test.ts +171 -0
  197. package/src/__tests__/lint/new-rules.test.ts +437 -0
  198. package/src/__tests__/lsp-rename.test.ts +171 -0
  199. package/src/__tests__/mc-integration/say-fstring.test.ts +211 -0
  200. package/src/__tests__/mc-integration/stdlib-coverage-2.test.ts +1 -1
  201. package/src/__tests__/mc-integration/stdlib-coverage-3.test.ts +1 -1
  202. package/src/__tests__/mc-integration/stdlib-coverage-4.test.ts +1 -1
  203. package/src/__tests__/mc-integration/stdlib-coverage-5.test.ts +1 -1
  204. package/src/__tests__/mc-integration/stdlib-coverage-6.test.ts +1 -1
  205. package/src/__tests__/mc-integration/stdlib-coverage-7.test.ts +1 -1
  206. package/src/__tests__/mc-integration/stdlib-coverage-8.test.ts +1 -1
  207. package/src/__tests__/mc-syntax.test.ts +3 -0
  208. package/src/__tests__/monomorphize-coverage.test.ts +220 -0
  209. package/src/__tests__/optimizer-cse.test.ts +250 -0
  210. package/src/__tests__/parser.test.ts +4 -13
  211. package/src/__tests__/repl-server-extra.test.ts +6 -6
  212. package/src/__tests__/repl-server.test.ts +5 -6
  213. package/src/__tests__/stdlib/queue.test.ts +6 -6
  214. package/src/lexer/index.ts +2 -1
  215. package/src/lint/index.ts +713 -5
  216. package/src/mc-test/client.ts +40 -0
  217. package/src/mir/lower.ts +111 -2
  218. package/src/optimizer/interprocedural.ts +40 -2
  219. package/src/parser/decl-parser.ts +349 -0
  220. package/src/parser/expr-parser.ts +838 -0
  221. package/src/parser/index.ts +17 -2558
  222. package/src/parser/stmt-parser.ts +585 -0
  223. package/src/parser/type-parser.ts +276 -0
  224. package/src/parser/utils.ts +173 -0
  225. package/src/stdlib/queue.mcrs +19 -6
@@ -0,0 +1,250 @@
1
+ /**
2
+ * Tests for src/optimizer/cse.ts — Common Subexpression Elimination
3
+ *
4
+ * Covers: exprKey computation, commutative normalization, CSE replacement,
5
+ * invalidation on side effects, self-modifying instruction handling.
6
+ */
7
+
8
+ import { cse } from '../optimizer/cse'
9
+ import type { MIRFunction, MIRBlock, MIRInstr, Operand, Temp, CmpOp } from '../mir/types'
10
+
11
+ function temp(name: string): Temp {
12
+ return name
13
+ }
14
+
15
+ function tempOp(name: string): Operand {
16
+ return { kind: 'temp', name }
17
+ }
18
+
19
+ function constOp(value: number): Operand {
20
+ return { kind: 'const', value }
21
+ }
22
+
23
+ function makeBlock(instrs: MIRInstr[]): MIRBlock {
24
+ return { id: 'b0', instrs, term: { kind: 'return' } as MIRInstr, preds: [] }
25
+ }
26
+
27
+ function makeFn(blocks: MIRBlock[]): MIRFunction {
28
+ return {
29
+ name: 'test',
30
+ params: [],
31
+ returnType: { kind: 'named', name: 'void' },
32
+ blocks,
33
+ temps: [],
34
+ } as unknown as MIRFunction
35
+ }
36
+
37
+ describe('CSE — basic elimination', () => {
38
+ it('replaces duplicate add with copy', () => {
39
+ const block = makeBlock([
40
+ { kind: 'add', dst: temp('t1'), a: tempOp('t0'), b: constOp(1) },
41
+ { kind: 'add', dst: temp('t2'), a: tempOp('t0'), b: constOp(1) },
42
+ ])
43
+ const result = cse(makeFn([block]))
44
+ const instrs = result.blocks[0].instrs
45
+ expect(instrs[0].kind).toBe('add')
46
+ expect(instrs[1].kind).toBe('copy')
47
+ if (instrs[1].kind === 'copy') {
48
+ expect(instrs[1].dst).toBe('t2')
49
+ expect(instrs[1].src).toEqual(tempOp('t1'))
50
+ }
51
+ })
52
+
53
+ it('replaces duplicate sub with copy', () => {
54
+ const block = makeBlock([
55
+ { kind: 'sub', dst: temp('t1'), a: tempOp('t0'), b: constOp(2) },
56
+ { kind: 'sub', dst: temp('t2'), a: tempOp('t0'), b: constOp(2) },
57
+ ])
58
+ const result = cse(makeFn([block]))
59
+ expect(result.blocks[0].instrs[1].kind).toBe('copy')
60
+ })
61
+
62
+ it('replaces duplicate mul with copy', () => {
63
+ const block = makeBlock([
64
+ { kind: 'mul', dst: temp('t1'), a: tempOp('x'), b: tempOp('y') },
65
+ { kind: 'mul', dst: temp('t2'), a: tempOp('x'), b: tempOp('y') },
66
+ ])
67
+ const result = cse(makeFn([block]))
68
+ expect(result.blocks[0].instrs[1].kind).toBe('copy')
69
+ })
70
+
71
+ it('replaces duplicate neg with copy', () => {
72
+ const block = makeBlock([
73
+ { kind: 'neg', dst: temp('t1'), src: tempOp('t0') },
74
+ { kind: 'neg', dst: temp('t2'), src: tempOp('t0') },
75
+ ])
76
+ const result = cse(makeFn([block]))
77
+ expect(result.blocks[0].instrs[1].kind).toBe('copy')
78
+ })
79
+
80
+ it('replaces duplicate not with copy', () => {
81
+ const block = makeBlock([
82
+ { kind: 'not', dst: temp('t1'), src: tempOp('t0') },
83
+ { kind: 'not', dst: temp('t2'), src: tempOp('t0') },
84
+ ])
85
+ const result = cse(makeFn([block]))
86
+ expect(result.blocks[0].instrs[1].kind).toBe('copy')
87
+ })
88
+
89
+ it('replaces duplicate cmp with copy', () => {
90
+ const block = makeBlock([
91
+ { kind: 'cmp', dst: temp('t1'), op: 'lt' as CmpOp, a: tempOp('x'), b: tempOp('y') },
92
+ { kind: 'cmp', dst: temp('t2'), op: 'lt' as CmpOp, a: tempOp('x'), b: tempOp('y') },
93
+ ])
94
+ const result = cse(makeFn([block]))
95
+ expect(result.blocks[0].instrs[1].kind).toBe('copy')
96
+ })
97
+ })
98
+
99
+ describe('CSE — commutative normalization', () => {
100
+ it('treats a+b and b+a as same expression', () => {
101
+ const block = makeBlock([
102
+ { kind: 'add', dst: temp('t1'), a: tempOp('x'), b: tempOp('y') },
103
+ { kind: 'add', dst: temp('t2'), a: tempOp('y'), b: tempOp('x') },
104
+ ])
105
+ const result = cse(makeFn([block]))
106
+ expect(result.blocks[0].instrs[1].kind).toBe('copy')
107
+ })
108
+
109
+ it('treats a*b and b*a as same expression', () => {
110
+ const block = makeBlock([
111
+ { kind: 'mul', dst: temp('t1'), a: tempOp('x'), b: tempOp('y') },
112
+ { kind: 'mul', dst: temp('t2'), a: tempOp('y'), b: tempOp('x') },
113
+ ])
114
+ const result = cse(makeFn([block]))
115
+ expect(result.blocks[0].instrs[1].kind).toBe('copy')
116
+ })
117
+
118
+ it('does NOT treat a-b and b-a as same (sub is non-commutative)', () => {
119
+ const block = makeBlock([
120
+ { kind: 'sub', dst: temp('t1'), a: tempOp('x'), b: tempOp('y') },
121
+ { kind: 'sub', dst: temp('t2'), a: tempOp('y'), b: tempOp('x') },
122
+ ])
123
+ const result = cse(makeFn([block]))
124
+ expect(result.blocks[0].instrs[1].kind).toBe('sub')
125
+ })
126
+
127
+ it('does NOT treat a/b and b/a as same (div is non-commutative)', () => {
128
+ const block = makeBlock([
129
+ { kind: 'div', dst: temp('t1'), a: tempOp('x'), b: tempOp('y') },
130
+ { kind: 'div', dst: temp('t2'), a: tempOp('y'), b: tempOp('x') },
131
+ ])
132
+ const result = cse(makeFn([block]))
133
+ expect(result.blocks[0].instrs[1].kind).toBe('div')
134
+ })
135
+ })
136
+
137
+ describe('CSE — invalidation', () => {
138
+ it('invalidates expression when operand is redefined', () => {
139
+ const block = makeBlock([
140
+ { kind: 'add', dst: temp('t1'), a: tempOp('x'), b: constOp(1) },
141
+ { kind: 'copy', dst: temp('x'), src: constOp(99) },
142
+ { kind: 'add', dst: temp('t2'), a: tempOp('x'), b: constOp(1) },
143
+ ])
144
+ const result = cse(makeFn([block]))
145
+ // t2 should NOT be replaced because x was modified
146
+ expect(result.blocks[0].instrs[2].kind).toBe('add')
147
+ })
148
+
149
+ it('invalidates all expressions after a call', () => {
150
+ const block = makeBlock([
151
+ { kind: 'add', dst: temp('t1'), a: tempOp('x'), b: constOp(1) },
152
+ { kind: 'call', dst: temp('t_ret'), fn: 'sideEffect', args: [] } as unknown as MIRInstr,
153
+ { kind: 'add', dst: temp('t2'), a: tempOp('x'), b: constOp(1) },
154
+ ])
155
+ const result = cse(makeFn([block]))
156
+ expect(result.blocks[0].instrs[2].kind).toBe('add')
157
+ })
158
+
159
+ it('invalidates all expressions after score_write', () => {
160
+ const block = makeBlock([
161
+ { kind: 'add', dst: temp('t1'), a: tempOp('x'), b: constOp(1) },
162
+ { kind: 'score_write', target: '@s', objective: 'test', src: tempOp('v') } as unknown as MIRInstr,
163
+ { kind: 'add', dst: temp('t2'), a: tempOp('x'), b: constOp(1) },
164
+ ])
165
+ const result = cse(makeFn([block]))
166
+ expect(result.blocks[0].instrs[2].kind).toBe('add')
167
+ })
168
+ })
169
+
170
+ describe('CSE — self-modifying skip', () => {
171
+ it('does not CSE self-modifying instruction (t5 = t5 + 1)', () => {
172
+ const block = makeBlock([
173
+ { kind: 'add', dst: temp('t5'), a: tempOp('t5'), b: constOp(1) },
174
+ { kind: 'add', dst: temp('t5'), a: tempOp('t5'), b: constOp(1) },
175
+ ])
176
+ const result = cse(makeFn([block]))
177
+ // Both should remain as 'add' because they're self-modifying
178
+ expect(result.blocks[0].instrs[0].kind).toBe('add')
179
+ expect(result.blocks[0].instrs[1].kind).toBe('add')
180
+ })
181
+ })
182
+
183
+ describe('CSE — different expressions not merged', () => {
184
+ it('does not merge different operations on same operands', () => {
185
+ const block = makeBlock([
186
+ { kind: 'add', dst: temp('t1'), a: tempOp('x'), b: tempOp('y') },
187
+ { kind: 'sub', dst: temp('t2'), a: tempOp('x'), b: tempOp('y') },
188
+ ])
189
+ const result = cse(makeFn([block]))
190
+ expect(result.blocks[0].instrs[0].kind).toBe('add')
191
+ expect(result.blocks[0].instrs[1].kind).toBe('sub')
192
+ })
193
+
194
+ it('does not merge same operation with different constants', () => {
195
+ const block = makeBlock([
196
+ { kind: 'add', dst: temp('t1'), a: tempOp('x'), b: constOp(1) },
197
+ { kind: 'add', dst: temp('t2'), a: tempOp('x'), b: constOp(2) },
198
+ ])
199
+ const result = cse(makeFn([block]))
200
+ expect(result.blocks[0].instrs[0].kind).toBe('add')
201
+ expect(result.blocks[0].instrs[1].kind).toBe('add')
202
+ })
203
+
204
+ it('different cmp operators are not merged', () => {
205
+ const block = makeBlock([
206
+ { kind: 'cmp', dst: temp('t1'), op: 'lt' as CmpOp, a: tempOp('x'), b: tempOp('y') },
207
+ { kind: 'cmp', dst: temp('t2'), op: 'gt' as CmpOp, a: tempOp('x'), b: tempOp('y') },
208
+ ])
209
+ const result = cse(makeFn([block]))
210
+ expect(result.blocks[0].instrs[0].kind).toBe('cmp')
211
+ expect(result.blocks[0].instrs[1].kind).toBe('cmp')
212
+ })
213
+ })
214
+
215
+ describe('CSE — multiple blocks', () => {
216
+ it('processes each block independently', () => {
217
+ const block1 = makeBlock([
218
+ { kind: 'add', dst: temp('t1'), a: tempOp('x'), b: constOp(1) },
219
+ ])
220
+ const block2 = makeBlock([
221
+ // Same expression in different block — should NOT be CSE'd
222
+ { kind: 'add', dst: temp('t2'), a: tempOp('x'), b: constOp(1) },
223
+ ])
224
+ block2.id = 'b1'
225
+ const result = cse(makeFn([block1, block2]))
226
+ expect(result.blocks[0].instrs[0].kind).toBe('add')
227
+ expect(result.blocks[1].instrs[0].kind).toBe('add')
228
+ })
229
+ })
230
+
231
+ describe('CSE — passthrough of non-pure instructions', () => {
232
+ it('passes through const instruction unchanged', () => {
233
+ const block = makeBlock([
234
+ { kind: 'const', dst: temp('t1'), value: '42' } as unknown as MIRInstr,
235
+ { kind: 'const', dst: temp('t2'), value: '42' } as unknown as MIRInstr,
236
+ ])
237
+ const result = cse(makeFn([block]))
238
+ // const instructions don't have exprKey so both should pass through
239
+ expect(result.blocks[0].instrs.length).toBe(2)
240
+ })
241
+
242
+ it('preserves jump and branch instructions', () => {
243
+ const block = makeBlock([
244
+ { kind: 'add', dst: temp('t1'), a: tempOp('x'), b: constOp(1) },
245
+ { kind: 'branch', cond: tempOp('t1'), thenBlock: 'b1', elseBlock: 'b2' } as unknown as MIRInstr,
246
+ ])
247
+ const result = cse(makeFn([block]))
248
+ expect(result.blocks[0].instrs[1].kind).toBe('branch')
249
+ })
250
+ })
@@ -477,21 +477,12 @@ impl Point {
477
477
  expect(expr).toEqual({ kind: 'str_lit', value: 'hello' })
478
478
  })
479
479
 
480
- it('parses interpolated string literal', () => {
480
+ it('parses dollar-brace as plain string literal (no interpolation)', () => {
481
+ // ${...} in plain strings is NOT interpolated — only f"..." strings support {expr}
481
482
  const expr = parseExpr('"Hello ${name}, score is ${score + 1}"')
482
483
  expect(expr).toEqual({
483
- kind: 'str_interp',
484
- parts: [
485
- 'Hello ',
486
- { kind: 'ident', name: 'name' },
487
- ', score is ',
488
- {
489
- kind: 'binary',
490
- op: '+',
491
- left: { kind: 'ident', name: 'score' },
492
- right: { kind: 'int_lit', value: 1 },
493
- },
494
- ],
484
+ kind: 'str_lit',
485
+ value: 'Hello ${name}, score is ${score + 1}',
495
486
  })
496
487
  })
497
488
 
@@ -11,9 +11,9 @@
11
11
  import * as http from 'http'
12
12
  import { requestHandler } from '../repl-server'
13
13
 
14
- // Use a different port from repl-server.test.ts (3000) to avoid port conflicts
15
- const PORT = 3001
14
+ // Use port 0 so the OS assigns a free port — avoids port conflicts
16
15
  const server = http.createServer(requestHandler)
16
+ let PORT = 0
17
17
 
18
18
  function rawRequest(
19
19
  method: string,
@@ -65,11 +65,11 @@ function jsonRequest(
65
65
  }
66
66
 
67
67
  beforeAll(done => {
68
- if (server.listening) {
68
+ server.listen(0, () => {
69
+ const addr = server.address() as import('net').AddressInfo
70
+ PORT = addr.port
69
71
  done()
70
- } else {
71
- server.listen(PORT, done)
72
- }
72
+ })
73
73
  })
74
74
 
75
75
  afterAll(done => {
@@ -7,7 +7,7 @@
7
7
  import * as http from 'http'
8
8
  import { server } from '../repl-server'
9
9
 
10
- const PORT = 3000
10
+ let PORT = 0
11
11
 
12
12
  function request(
13
13
  method: string,
@@ -46,12 +46,11 @@ function request(
46
46
  }
47
47
 
48
48
  beforeAll(done => {
49
- // If the server is already listening (e.g. imported elsewhere), just proceed.
50
- if (server.listening) {
49
+ server.listen(0, () => {
50
+ const addr = server.address() as import('net').AddressInfo
51
+ PORT = addr.port
51
52
  done()
52
- } else {
53
- server.listen(PORT, done)
54
- }
53
+ })
55
54
  })
56
55
 
57
56
  afterAll(done => {
@@ -30,20 +30,20 @@ describe('stdlib/queue.mcrs', () => {
30
30
  test('queue_pop reads head index from rs.q_head scoreboard', () => {
31
31
  const r = compileWith(`@keep fn t(): int { return queue_pop(); }`)
32
32
  const all = r.files.map(f => f.content).join('\n')
33
- expect(all).toContain('scoreboard players get rs.q_head __RS__')
33
+ expect(all).toContain('scoreboard players get rs.q_head rs')
34
34
  })
35
35
 
36
36
  test('queue_pop advances head pointer after reading', () => {
37
37
  const r = compileWith(`@keep fn t(): int { return queue_pop(); }`)
38
38
  const all = r.files.map(f => f.content).join('\n')
39
- expect(all).toContain('scoreboard players add rs.q_head __RS__ 1')
39
+ expect(all).toContain('scoreboard players add rs.q_head rs 1')
40
40
  })
41
41
 
42
42
  test('queue_pop uses __queue_peek_apply macro to read element', () => {
43
43
  const r = compileWith(`@keep fn t(): int { return queue_pop(); }`)
44
44
  const all = r.files.map(f => f.content).join('\n')
45
45
  expect(all).toContain('function test:__queue_peek_apply with storage rs:macro_args')
46
- expect(all).toContain('$execute store result score $ret __test run data get storage rs:arrays Queue[$(idx)]')
46
+ expect(all).toContain('$execute store result score rs.peek_tmp rs run data get storage rs:arrays Queue[$(idx)]')
47
47
  })
48
48
 
49
49
  test('queue_peek uses __queue_peek_apply macro', () => {
@@ -55,19 +55,19 @@ describe('stdlib/queue.mcrs', () => {
55
55
  test('queue_size subtracts head from raw list length', () => {
56
56
  const r = compileWith(`@keep fn t(): int { return queue_size(); }`)
57
57
  const all = r.files.map(f => f.content).join('\n')
58
- expect(all).toContain('scoreboard players operation $ret __test -= rs.q_head __RS__')
58
+ expect(all).toContain('scoreboard players operation $ret __test -= rs.q_head rs')
59
59
  })
60
60
 
61
61
  test('queue_size uses __queue_size_raw_apply to read raw list length', () => {
62
62
  const r = compileWith(`@keep fn t(): int { return queue_size(); }`)
63
63
  const all = r.files.map(f => f.content).join('\n')
64
- expect(all).toContain('function test:__queue_size_raw_apply with storage rs:macro_args')
64
+ expect(all).toContain('function test:__queue_size_raw_apply')
65
65
  })
66
66
 
67
67
  test('queue_clear resets Queue list and head pointer', () => {
68
68
  const r = compileWith(`@keep fn t() { queue_clear(); }`)
69
69
  const all = r.files.map(f => f.content).join('\n')
70
70
  expect(all).toContain('data modify storage rs:arrays Queue set value []')
71
- expect(all).toContain('scoreboard players set rs.q_head __RS__ 0')
71
+ expect(all).toContain('scoreboard players set rs.q_head rs 0')
72
72
  })
73
73
  })
@@ -568,8 +568,9 @@ export class Lexer {
568
568
  continue
569
569
  }
570
570
 
571
+ // Check for {...} interpolation
571
572
  if (interpolationDepth === 0 && this.peek() === '{') {
572
- value += this.advance()
573
+ value += this.advance() // {
573
574
  interpolationDepth = 1
574
575
  interpolationString = false
575
576
  continue