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,226 @@
1
+ "use strict";
2
+ /**
3
+ * Tests for src/optimizer/cse.ts — Common Subexpression Elimination
4
+ *
5
+ * Covers: exprKey computation, commutative normalization, CSE replacement,
6
+ * invalidation on side effects, self-modifying instruction handling.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ const cse_1 = require("../optimizer/cse");
10
+ function temp(name) {
11
+ return name;
12
+ }
13
+ function tempOp(name) {
14
+ return { kind: 'temp', name };
15
+ }
16
+ function constOp(value) {
17
+ return { kind: 'const', value };
18
+ }
19
+ function makeBlock(instrs) {
20
+ return { id: 'b0', instrs, term: { kind: 'return' }, preds: [] };
21
+ }
22
+ function makeFn(blocks) {
23
+ return {
24
+ name: 'test',
25
+ params: [],
26
+ returnType: { kind: 'named', name: 'void' },
27
+ blocks,
28
+ temps: [],
29
+ };
30
+ }
31
+ describe('CSE — basic elimination', () => {
32
+ it('replaces duplicate add with copy', () => {
33
+ const block = makeBlock([
34
+ { kind: 'add', dst: temp('t1'), a: tempOp('t0'), b: constOp(1) },
35
+ { kind: 'add', dst: temp('t2'), a: tempOp('t0'), b: constOp(1) },
36
+ ]);
37
+ const result = (0, cse_1.cse)(makeFn([block]));
38
+ const instrs = result.blocks[0].instrs;
39
+ expect(instrs[0].kind).toBe('add');
40
+ expect(instrs[1].kind).toBe('copy');
41
+ if (instrs[1].kind === 'copy') {
42
+ expect(instrs[1].dst).toBe('t2');
43
+ expect(instrs[1].src).toEqual(tempOp('t1'));
44
+ }
45
+ });
46
+ it('replaces duplicate sub with copy', () => {
47
+ const block = makeBlock([
48
+ { kind: 'sub', dst: temp('t1'), a: tempOp('t0'), b: constOp(2) },
49
+ { kind: 'sub', dst: temp('t2'), a: tempOp('t0'), b: constOp(2) },
50
+ ]);
51
+ const result = (0, cse_1.cse)(makeFn([block]));
52
+ expect(result.blocks[0].instrs[1].kind).toBe('copy');
53
+ });
54
+ it('replaces duplicate mul with copy', () => {
55
+ const block = makeBlock([
56
+ { kind: 'mul', dst: temp('t1'), a: tempOp('x'), b: tempOp('y') },
57
+ { kind: 'mul', dst: temp('t2'), a: tempOp('x'), b: tempOp('y') },
58
+ ]);
59
+ const result = (0, cse_1.cse)(makeFn([block]));
60
+ expect(result.blocks[0].instrs[1].kind).toBe('copy');
61
+ });
62
+ it('replaces duplicate neg with copy', () => {
63
+ const block = makeBlock([
64
+ { kind: 'neg', dst: temp('t1'), src: tempOp('t0') },
65
+ { kind: 'neg', dst: temp('t2'), src: tempOp('t0') },
66
+ ]);
67
+ const result = (0, cse_1.cse)(makeFn([block]));
68
+ expect(result.blocks[0].instrs[1].kind).toBe('copy');
69
+ });
70
+ it('replaces duplicate not with copy', () => {
71
+ const block = makeBlock([
72
+ { kind: 'not', dst: temp('t1'), src: tempOp('t0') },
73
+ { kind: 'not', dst: temp('t2'), src: tempOp('t0') },
74
+ ]);
75
+ const result = (0, cse_1.cse)(makeFn([block]));
76
+ expect(result.blocks[0].instrs[1].kind).toBe('copy');
77
+ });
78
+ it('replaces duplicate cmp with copy', () => {
79
+ const block = makeBlock([
80
+ { kind: 'cmp', dst: temp('t1'), op: 'lt', a: tempOp('x'), b: tempOp('y') },
81
+ { kind: 'cmp', dst: temp('t2'), op: 'lt', a: tempOp('x'), b: tempOp('y') },
82
+ ]);
83
+ const result = (0, cse_1.cse)(makeFn([block]));
84
+ expect(result.blocks[0].instrs[1].kind).toBe('copy');
85
+ });
86
+ });
87
+ describe('CSE — commutative normalization', () => {
88
+ it('treats a+b and b+a as same expression', () => {
89
+ const block = makeBlock([
90
+ { kind: 'add', dst: temp('t1'), a: tempOp('x'), b: tempOp('y') },
91
+ { kind: 'add', dst: temp('t2'), a: tempOp('y'), b: tempOp('x') },
92
+ ]);
93
+ const result = (0, cse_1.cse)(makeFn([block]));
94
+ expect(result.blocks[0].instrs[1].kind).toBe('copy');
95
+ });
96
+ it('treats a*b and b*a as same expression', () => {
97
+ const block = makeBlock([
98
+ { kind: 'mul', dst: temp('t1'), a: tempOp('x'), b: tempOp('y') },
99
+ { kind: 'mul', dst: temp('t2'), a: tempOp('y'), b: tempOp('x') },
100
+ ]);
101
+ const result = (0, cse_1.cse)(makeFn([block]));
102
+ expect(result.blocks[0].instrs[1].kind).toBe('copy');
103
+ });
104
+ it('does NOT treat a-b and b-a as same (sub is non-commutative)', () => {
105
+ const block = makeBlock([
106
+ { kind: 'sub', dst: temp('t1'), a: tempOp('x'), b: tempOp('y') },
107
+ { kind: 'sub', dst: temp('t2'), a: tempOp('y'), b: tempOp('x') },
108
+ ]);
109
+ const result = (0, cse_1.cse)(makeFn([block]));
110
+ expect(result.blocks[0].instrs[1].kind).toBe('sub');
111
+ });
112
+ it('does NOT treat a/b and b/a as same (div is non-commutative)', () => {
113
+ const block = makeBlock([
114
+ { kind: 'div', dst: temp('t1'), a: tempOp('x'), b: tempOp('y') },
115
+ { kind: 'div', dst: temp('t2'), a: tempOp('y'), b: tempOp('x') },
116
+ ]);
117
+ const result = (0, cse_1.cse)(makeFn([block]));
118
+ expect(result.blocks[0].instrs[1].kind).toBe('div');
119
+ });
120
+ });
121
+ describe('CSE — invalidation', () => {
122
+ it('invalidates expression when operand is redefined', () => {
123
+ const block = makeBlock([
124
+ { kind: 'add', dst: temp('t1'), a: tempOp('x'), b: constOp(1) },
125
+ { kind: 'copy', dst: temp('x'), src: constOp(99) },
126
+ { kind: 'add', dst: temp('t2'), a: tempOp('x'), b: constOp(1) },
127
+ ]);
128
+ const result = (0, cse_1.cse)(makeFn([block]));
129
+ // t2 should NOT be replaced because x was modified
130
+ expect(result.blocks[0].instrs[2].kind).toBe('add');
131
+ });
132
+ it('invalidates all expressions after a call', () => {
133
+ const block = makeBlock([
134
+ { kind: 'add', dst: temp('t1'), a: tempOp('x'), b: constOp(1) },
135
+ { kind: 'call', dst: temp('t_ret'), fn: 'sideEffect', args: [] },
136
+ { kind: 'add', dst: temp('t2'), a: tempOp('x'), b: constOp(1) },
137
+ ]);
138
+ const result = (0, cse_1.cse)(makeFn([block]));
139
+ expect(result.blocks[0].instrs[2].kind).toBe('add');
140
+ });
141
+ it('invalidates all expressions after score_write', () => {
142
+ const block = makeBlock([
143
+ { kind: 'add', dst: temp('t1'), a: tempOp('x'), b: constOp(1) },
144
+ { kind: 'score_write', target: '@s', objective: 'test', src: tempOp('v') },
145
+ { kind: 'add', dst: temp('t2'), a: tempOp('x'), b: constOp(1) },
146
+ ]);
147
+ const result = (0, cse_1.cse)(makeFn([block]));
148
+ expect(result.blocks[0].instrs[2].kind).toBe('add');
149
+ });
150
+ });
151
+ describe('CSE — self-modifying skip', () => {
152
+ it('does not CSE self-modifying instruction (t5 = t5 + 1)', () => {
153
+ const block = makeBlock([
154
+ { kind: 'add', dst: temp('t5'), a: tempOp('t5'), b: constOp(1) },
155
+ { kind: 'add', dst: temp('t5'), a: tempOp('t5'), b: constOp(1) },
156
+ ]);
157
+ const result = (0, cse_1.cse)(makeFn([block]));
158
+ // Both should remain as 'add' because they're self-modifying
159
+ expect(result.blocks[0].instrs[0].kind).toBe('add');
160
+ expect(result.blocks[0].instrs[1].kind).toBe('add');
161
+ });
162
+ });
163
+ describe('CSE — different expressions not merged', () => {
164
+ it('does not merge different operations on same operands', () => {
165
+ const block = makeBlock([
166
+ { kind: 'add', dst: temp('t1'), a: tempOp('x'), b: tempOp('y') },
167
+ { kind: 'sub', dst: temp('t2'), a: tempOp('x'), b: tempOp('y') },
168
+ ]);
169
+ const result = (0, cse_1.cse)(makeFn([block]));
170
+ expect(result.blocks[0].instrs[0].kind).toBe('add');
171
+ expect(result.blocks[0].instrs[1].kind).toBe('sub');
172
+ });
173
+ it('does not merge same operation with different constants', () => {
174
+ const block = makeBlock([
175
+ { kind: 'add', dst: temp('t1'), a: tempOp('x'), b: constOp(1) },
176
+ { kind: 'add', dst: temp('t2'), a: tempOp('x'), b: constOp(2) },
177
+ ]);
178
+ const result = (0, cse_1.cse)(makeFn([block]));
179
+ expect(result.blocks[0].instrs[0].kind).toBe('add');
180
+ expect(result.blocks[0].instrs[1].kind).toBe('add');
181
+ });
182
+ it('different cmp operators are not merged', () => {
183
+ const block = makeBlock([
184
+ { kind: 'cmp', dst: temp('t1'), op: 'lt', a: tempOp('x'), b: tempOp('y') },
185
+ { kind: 'cmp', dst: temp('t2'), op: 'gt', a: tempOp('x'), b: tempOp('y') },
186
+ ]);
187
+ const result = (0, cse_1.cse)(makeFn([block]));
188
+ expect(result.blocks[0].instrs[0].kind).toBe('cmp');
189
+ expect(result.blocks[0].instrs[1].kind).toBe('cmp');
190
+ });
191
+ });
192
+ describe('CSE — multiple blocks', () => {
193
+ it('processes each block independently', () => {
194
+ const block1 = makeBlock([
195
+ { kind: 'add', dst: temp('t1'), a: tempOp('x'), b: constOp(1) },
196
+ ]);
197
+ const block2 = makeBlock([
198
+ // Same expression in different block — should NOT be CSE'd
199
+ { kind: 'add', dst: temp('t2'), a: tempOp('x'), b: constOp(1) },
200
+ ]);
201
+ block2.id = 'b1';
202
+ const result = (0, cse_1.cse)(makeFn([block1, block2]));
203
+ expect(result.blocks[0].instrs[0].kind).toBe('add');
204
+ expect(result.blocks[1].instrs[0].kind).toBe('add');
205
+ });
206
+ });
207
+ describe('CSE — passthrough of non-pure instructions', () => {
208
+ it('passes through const instruction unchanged', () => {
209
+ const block = makeBlock([
210
+ { kind: 'const', dst: temp('t1'), value: '42' },
211
+ { kind: 'const', dst: temp('t2'), value: '42' },
212
+ ]);
213
+ const result = (0, cse_1.cse)(makeFn([block]));
214
+ // const instructions don't have exprKey so both should pass through
215
+ expect(result.blocks[0].instrs.length).toBe(2);
216
+ });
217
+ it('preserves jump and branch instructions', () => {
218
+ const block = makeBlock([
219
+ { kind: 'add', dst: temp('t1'), a: tempOp('x'), b: constOp(1) },
220
+ { kind: 'branch', cond: tempOp('t1'), thenBlock: 'b1', elseBlock: 'b2' },
221
+ ]);
222
+ const result = (0, cse_1.cse)(makeFn([block]));
223
+ expect(result.blocks[0].instrs[1].kind).toBe('branch');
224
+ });
225
+ });
226
+ //# sourceMappingURL=optimizer-cse.test.js.map
@@ -424,21 +424,12 @@ impl Point {
424
424
  const expr = parseExpr('"hello"');
425
425
  expect(expr).toEqual({ kind: 'str_lit', value: 'hello' });
426
426
  });
427
- it('parses interpolated string literal', () => {
427
+ it('parses dollar-brace as plain string literal (no interpolation)', () => {
428
+ // ${...} in plain strings is NOT interpolated — only f"..." strings support {expr}
428
429
  const expr = parseExpr('"Hello ${name}, score is ${score + 1}"');
429
430
  expect(expr).toEqual({
430
- kind: 'str_interp',
431
- parts: [
432
- 'Hello ',
433
- { kind: 'ident', name: 'name' },
434
- ', score is ',
435
- {
436
- kind: 'binary',
437
- op: '+',
438
- left: { kind: 'ident', name: 'score' },
439
- right: { kind: 'int_lit', value: 1 },
440
- },
441
- ],
431
+ kind: 'str_lit',
432
+ value: 'Hello ${name}, score is ${score + 1}',
442
433
  });
443
434
  });
444
435
  it('parses f-string literal', () => {
@@ -44,9 +44,9 @@ var __importStar = (this && this.__importStar) || (function () {
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
45
  const http = __importStar(require("http"));
46
46
  const repl_server_1 = require("../repl-server");
47
- // Use a different port from repl-server.test.ts (3000) to avoid port conflicts
48
- const PORT = 3001;
47
+ // Use port 0 so the OS assigns a free port — avoids port conflicts
49
48
  const server = http.createServer(repl_server_1.requestHandler);
49
+ let PORT = 0;
50
50
  function rawRequest(method, urlPath, body, headers = {}) {
51
51
  return new Promise((resolve, reject) => {
52
52
  const options = {
@@ -89,12 +89,11 @@ function jsonRequest(method, urlPath, body) {
89
89
  });
90
90
  }
91
91
  beforeAll(done => {
92
- if (server.listening) {
92
+ server.listen(0, () => {
93
+ const addr = server.address();
94
+ PORT = addr.port;
93
95
  done();
94
- }
95
- else {
96
- server.listen(PORT, done);
97
- }
96
+ });
98
97
  });
99
98
  afterAll(done => {
100
99
  server.close(done);
@@ -40,7 +40,7 @@ var __importStar = (this && this.__importStar) || (function () {
40
40
  Object.defineProperty(exports, "__esModule", { value: true });
41
41
  const http = __importStar(require("http"));
42
42
  const repl_server_1 = require("../repl-server");
43
- const PORT = 3000;
43
+ let PORT = 0;
44
44
  function request(method, path, body) {
45
45
  return new Promise((resolve, reject) => {
46
46
  const payload = body !== undefined ? JSON.stringify(body) : undefined;
@@ -74,13 +74,11 @@ function request(method, path, body) {
74
74
  });
75
75
  }
76
76
  beforeAll(done => {
77
- // If the server is already listening (e.g. imported elsewhere), just proceed.
78
- if (repl_server_1.server.listening) {
77
+ repl_server_1.server.listen(0, () => {
78
+ const addr = repl_server_1.server.address();
79
+ PORT = addr.port;
79
80
  done();
80
- }
81
- else {
82
- repl_server_1.server.listen(PORT, done);
83
- }
81
+ });
84
82
  });
85
83
  afterAll(done => {
86
84
  repl_server_1.server.close(done);
@@ -59,18 +59,18 @@ describe('stdlib/queue.mcrs', () => {
59
59
  test('queue_pop reads head index from rs.q_head scoreboard', () => {
60
60
  const r = compileWith(`@keep fn t(): int { return queue_pop(); }`);
61
61
  const all = r.files.map(f => f.content).join('\n');
62
- expect(all).toContain('scoreboard players get rs.q_head __RS__');
62
+ expect(all).toContain('scoreboard players get rs.q_head rs');
63
63
  });
64
64
  test('queue_pop advances head pointer after reading', () => {
65
65
  const r = compileWith(`@keep fn t(): int { return queue_pop(); }`);
66
66
  const all = r.files.map(f => f.content).join('\n');
67
- expect(all).toContain('scoreboard players add rs.q_head __RS__ 1');
67
+ expect(all).toContain('scoreboard players add rs.q_head rs 1');
68
68
  });
69
69
  test('queue_pop uses __queue_peek_apply macro to read element', () => {
70
70
  const r = compileWith(`@keep fn t(): int { return queue_pop(); }`);
71
71
  const all = r.files.map(f => f.content).join('\n');
72
72
  expect(all).toContain('function test:__queue_peek_apply with storage rs:macro_args');
73
- expect(all).toContain('$execute store result score $ret __test run data get storage rs:arrays Queue[$(idx)]');
73
+ expect(all).toContain('$execute store result score rs.peek_tmp rs run data get storage rs:arrays Queue[$(idx)]');
74
74
  });
75
75
  test('queue_peek uses __queue_peek_apply macro', () => {
76
76
  const r = compileWith(`@keep fn t(): int { return queue_peek(); }`);
@@ -80,18 +80,18 @@ describe('stdlib/queue.mcrs', () => {
80
80
  test('queue_size subtracts head from raw list length', () => {
81
81
  const r = compileWith(`@keep fn t(): int { return queue_size(); }`);
82
82
  const all = r.files.map(f => f.content).join('\n');
83
- expect(all).toContain('scoreboard players operation $ret __test -= rs.q_head __RS__');
83
+ expect(all).toContain('scoreboard players operation $ret __test -= rs.q_head rs');
84
84
  });
85
85
  test('queue_size uses __queue_size_raw_apply to read raw list length', () => {
86
86
  const r = compileWith(`@keep fn t(): int { return queue_size(); }`);
87
87
  const all = r.files.map(f => f.content).join('\n');
88
- expect(all).toContain('function test:__queue_size_raw_apply with storage rs:macro_args');
88
+ expect(all).toContain('function test:__queue_size_raw_apply');
89
89
  });
90
90
  test('queue_clear resets Queue list and head pointer', () => {
91
91
  const r = compileWith(`@keep fn t() { queue_clear(); }`);
92
92
  const all = r.files.map(f => f.content).join('\n');
93
93
  expect(all).toContain('data modify storage rs:arrays Queue set value []');
94
- expect(all).toContain('scoreboard players set rs.q_head __RS__ 0');
94
+ expect(all).toContain('scoreboard players set rs.q_head rs 0');
95
95
  });
96
96
  });
97
97
  //# sourceMappingURL=queue.test.js.map
package/dist/src/cli.js CHANGED
File without changes
@@ -464,8 +464,9 @@ class Lexer {
464
464
  value += this.advance();
465
465
  continue;
466
466
  }
467
+ // Check for {...} interpolation
467
468
  if (interpolationDepth === 0 && this.peek() === '{') {
468
- value += this.advance();
469
+ value += this.advance(); // {
469
470
  interpolationDepth = 1;
470
471
  interpolationString = false;
471
472
  continue;
@@ -5,11 +5,16 @@
5
5
  * Run after HIR lowering, before MIR.
6
6
  *
7
7
  * Rules:
8
- * unused-variable — let x = 5 but x never read
9
- * unused-import — import math::sin but sin never called
10
- * magic-number — literal number > 1 used directly (0 and 1 ignored)
11
- * dead-branch — if (const == const) always-true/false condition
12
- * function-too-long — function body exceeds 50 lines
8
+ * unused-variable — let x = 5 but x never read
9
+ * unused-import — import math::sin but sin never called
10
+ * magic-number — literal number > 1 used directly (0 and 1 ignored)
11
+ * dead-branch — if (const == const) always-true/false condition
12
+ * function-too-long — function body exceeds 50 lines
13
+ * no-dead-assignment — variable assigned but never read after the assignment
14
+ * prefer-match-exhaustive — Option match missing Some or None arm
15
+ * no-empty-catch — empty else branch in if_let_some (silent failure)
16
+ * naming-convention — variables must be camelCase; types must be PascalCase
17
+ * no-magic-numbers — any literal number other than 0 or 1 used in an expression
13
18
  */
14
19
  import type { HIRModule } from '../hir/types';
15
20
  import type { ImportDecl } from '../ast/types';
@@ -24,6 +29,8 @@ export interface LintOptions {
24
29
  filePath?: string;
25
30
  /** Max function body lines before function-too-long fires (default: 50) */
26
31
  maxFunctionLines?: number;
32
+ /** Allowed literal number values for no-magic-numbers (default: [0, 1]) */
33
+ allowedNumbers?: number[];
27
34
  }
28
35
  /**
29
36
  * Lint a RedScript source file. Parses → lowers → analyzes HIR.