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,211 @@
1
+ /**
2
+ * RedScript MC Integration Tests — say() with f-string
3
+ *
4
+ * Verifies that say(f"...{var}...") correctly compiles to a MC macro function
5
+ * ($say template with $(var) placeholders) and that variable interpolation
6
+ * works correctly at runtime via `function <helper> with storage rs:macro_args`.
7
+ *
8
+ * Run: npx jest say-fstring --forceExit
9
+ * With server: MC_SERVER_DIR=~/mc-test-server MC_PORT=25561 npx jest say-fstring --forceExit
10
+ */
11
+
12
+ import * as fs from 'fs'
13
+ import * as path from 'path'
14
+ import { compile } from '../../compile'
15
+ import { MCTestClient } from '../../mc-test/client'
16
+
17
+ const MC_HOST = process.env.MC_HOST ?? 'localhost'
18
+ const MC_PORT = parseInt(process.env.MC_PORT ?? '25561')
19
+ const MC_SERVER_DIR = process.env.MC_SERVER_DIR ?? path.join(process.env.HOME!, 'mc-test-server')
20
+ const DATAPACK_DIR = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-test')
21
+
22
+ const NS = 'rs_say_fstr'
23
+
24
+ let serverOnline = false
25
+ let mc: MCTestClient
26
+
27
+ function writeFixture(source: string, namespace: string): void {
28
+ fs.mkdirSync(DATAPACK_DIR, { recursive: true })
29
+ if (!fs.existsSync(path.join(DATAPACK_DIR, 'pack.mcmeta'))) {
30
+ fs.writeFileSync(
31
+ path.join(DATAPACK_DIR, 'pack.mcmeta'),
32
+ JSON.stringify({ pack: { pack_format: 48, description: 'RedScript integration tests' } })
33
+ )
34
+ }
35
+ const result = compile(source, { namespace })
36
+ for (const file of result.files ?? []) {
37
+ if (file.path === 'pack.mcmeta') continue
38
+ const filePath = path.join(DATAPACK_DIR, file.path)
39
+ fs.mkdirSync(path.dirname(filePath), { recursive: true })
40
+ if (file.path.includes('data/minecraft/tags/') && fs.existsSync(filePath)) {
41
+ const existing = JSON.parse(fs.readFileSync(filePath, 'utf-8'))
42
+ const incoming = JSON.parse(file.content)
43
+ const merged = { values: [...new Set([...(existing.values ?? []), ...(incoming.values ?? [])])] }
44
+ fs.writeFileSync(filePath, JSON.stringify(merged, null, 2))
45
+ } else {
46
+ fs.writeFileSync(filePath, file.content)
47
+ }
48
+ }
49
+ }
50
+
51
+ beforeAll(async () => {
52
+ if (process.env.MC_OFFLINE === 'true') {
53
+ console.warn('⚠ MC_OFFLINE=true — skipping say-fstring integration tests')
54
+ return
55
+ }
56
+
57
+ mc = new MCTestClient(MC_HOST, MC_PORT)
58
+
59
+ try {
60
+ const deadline = Date.now() + 10_000
61
+ while (Date.now() < deadline) {
62
+ if (await mc.isOnline()) { serverOnline = true; break }
63
+ await new Promise(r => setTimeout(r, 1000))
64
+ }
65
+ } catch {
66
+ serverOnline = false
67
+ }
68
+
69
+ if (!serverOnline) {
70
+ console.warn('⚠ MC server not running — say-fstring runtime tests will be skipped')
71
+ return
72
+ }
73
+
74
+ await mc.command('/scoreboard objectives add sf_out dummy').catch(() => {})
75
+ await mc.ticks(2)
76
+
77
+ writeFixture(`
78
+ module ${NS}
79
+
80
+ let counter: int = 0;
81
+
82
+ @keep fn say_plain() {
83
+ say(f"Hello world");
84
+ }
85
+
86
+ @keep fn say_with_var() {
87
+ counter = 42;
88
+ say(f"Counter is {counter}");
89
+ }
90
+
91
+ @keep fn say_multivar() {
92
+ let a: int = 10;
93
+ let b: int = 20;
94
+ say(f"a={a} b={b}");
95
+ }
96
+
97
+ @keep fn say_expr() {
98
+ let x: int = 5;
99
+ let y: int = x + 3;
100
+ say(f"result={y}");
101
+ }
102
+
103
+ // Side-effect: sets sf_out scoreboard so we can detect the function ran
104
+ @keep fn say_and_score() {
105
+ counter = 99;
106
+ say(f"Score is {counter}");
107
+ scoreboard_set("#sf_ran", "sf_out", 1);
108
+ }
109
+ `, NS)
110
+
111
+ await mc.reload()
112
+ await mc.ticks(5)
113
+ console.log(' say-fstring setup complete.')
114
+ }, 30_000)
115
+
116
+ // ---------------------------------------------------------------------------
117
+ // Compile-time tests (no server needed)
118
+ // ---------------------------------------------------------------------------
119
+
120
+ describe('say() f-string: compile output', () => {
121
+ test('say(f"plain text") compiles without error', () => {
122
+ expect(() =>
123
+ compile(`@keep fn f() { say(f"Hello world"); }`, { namespace: 'tmp' })
124
+ ).not.toThrow()
125
+ })
126
+
127
+ test('say(f"...{var}...") emits macro helper function', () => {
128
+ const result = compile(`
129
+ let counter: int = 0;
130
+ @keep fn f() { say(f"Count: {counter}"); }
131
+ `, { namespace: 'tmp' })
132
+
133
+ const helperFile = result.files?.find(f => f.path.includes('__say_macro'))
134
+ expect(helperFile).toBeDefined()
135
+ expect(helperFile?.content).toContain('$say Count: $(counter)')
136
+ })
137
+
138
+ test('say(f"...{var}...") emits storage copy + function with storage call', () => {
139
+ const result = compile(`
140
+ let counter: int = 0;
141
+ @keep fn f() { say(f"Count: {counter}"); }
142
+ `, { namespace: 'tmp' })
143
+
144
+ const mainFn = result.files?.find(f => f.path.endsWith('f.mcfunction'))
145
+ expect(mainFn?.content).toContain('execute store result storage rs:macro_args counter int 1')
146
+ expect(mainFn?.content).toContain('with storage rs:macro_args')
147
+ })
148
+
149
+ test('say(f"no vars") still uses function macro (safe fallback)', () => {
150
+ const result = compile(`
151
+ @keep fn f() { say(f"Hello world"); }
152
+ `, { namespace: 'tmp' })
153
+
154
+ // No storage copy needed (no vars), but still goes through macro helper
155
+ const helperFile = result.files?.find(f => f.path.includes('__say_macro'))
156
+ expect(helperFile).toBeDefined()
157
+ expect(helperFile?.content).toContain('$say Hello world')
158
+ })
159
+
160
+ test('say("plain string") still uses inline say command (not macro)', () => {
161
+ const result = compile(`
162
+ @keep fn f() { say("Hello world"); }
163
+ `, { namespace: 'tmp' })
164
+
165
+ const mainFn = result.files?.find(f => f.path.endsWith('f.mcfunction'))
166
+ expect(mainFn?.content).toContain('say Hello world')
167
+ expect(mainFn?.content).not.toContain('with storage')
168
+ })
169
+ })
170
+
171
+ // ---------------------------------------------------------------------------
172
+ // Runtime tests (server required)
173
+ // ---------------------------------------------------------------------------
174
+
175
+ describe('say() f-string: runtime', () => {
176
+ test('say(f"...{var}...") function runs without error', async () => {
177
+ if (!serverOnline) return
178
+
179
+ // If this throws, the macro function failed to load or execute
180
+ await expect(mc.command(`/function ${NS}:say_with_var`)).resolves.not.toThrow()
181
+ await mc.ticks(5)
182
+ }, 20_000)
183
+
184
+ test('say(f"Score is {counter}") sets scoreboard correctly', async () => {
185
+ if (!serverOnline) return
186
+
187
+ await mc.command('/scoreboard players set #sf_ran sf_out 0')
188
+ await mc.ticks(2)
189
+ await mc.command(`/function ${NS}:say_and_score`)
190
+ await mc.ticks(5)
191
+
192
+ // If scoreboard was set, the function ran fully (macro didn't crash)
193
+ const score = await mc.scoreboard('#sf_ran', 'sf_out')
194
+ expect(score).toBe(1)
195
+ console.log(' say_and_score ran successfully ✓')
196
+ }, 20_000)
197
+
198
+ test('say(f"plain text") runs without error', async () => {
199
+ if (!serverOnline) return
200
+
201
+ await expect(mc.command(`/function ${NS}:say_plain`)).resolves.not.toThrow()
202
+ await mc.ticks(3)
203
+ }, 20_000)
204
+
205
+ test('say(f"a={a} b={b}") multi-variable runs without error', async () => {
206
+ if (!serverOnline) return
207
+
208
+ await expect(mc.command(`/function ${NS}:say_multivar`)).resolves.not.toThrow()
209
+ await mc.ticks(3)
210
+ }, 20_000)
211
+ })
@@ -19,7 +19,7 @@ import { MCTestClient } from '../../mc-test/client'
19
19
  const MC_HOST = process.env.MC_HOST ?? 'localhost'
20
20
  const MC_PORT = parseInt(process.env.MC_PORT ?? '25561')
21
21
  const MC_SERVER_DIR = process.env.MC_SERVER_DIR ?? path.join(process.env.HOME!, 'mc-test-server')
22
- const DATAPACK_DIR = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-test2')
22
+ const DATAPACK_DIR = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-test')
23
23
 
24
24
  const STDLIB_DIR = path.join(__dirname, '../../stdlib')
25
25
 
@@ -19,7 +19,7 @@ import { MCTestClient } from '../../mc-test/client'
19
19
  const MC_HOST = process.env.MC_HOST ?? 'localhost'
20
20
  const MC_PORT = parseInt(process.env.MC_PORT ?? '25561')
21
21
  const MC_SERVER_DIR = process.env.MC_SERVER_DIR ?? path.join(process.env.HOME!, 'mc-test-server')
22
- const DATAPACK_DIR = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-test3')
22
+ const DATAPACK_DIR = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-test')
23
23
 
24
24
  const STDLIB_DIR = path.join(__dirname, '../../stdlib')
25
25
 
@@ -19,7 +19,7 @@ import { MCTestClient } from '../../mc-test/client'
19
19
  const MC_HOST = process.env.MC_HOST ?? 'localhost'
20
20
  const MC_PORT = parseInt(process.env.MC_PORT ?? '25561')
21
21
  const MC_SERVER_DIR = process.env.MC_SERVER_DIR ?? path.join(process.env.HOME!, 'mc-test-server')
22
- const DATAPACK_DIR = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-test4')
22
+ const DATAPACK_DIR = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-test')
23
23
 
24
24
  const STDLIB_DIR = path.join(__dirname, '../../stdlib')
25
25
 
@@ -20,7 +20,7 @@ import { MCTestClient } from '../../mc-test/client'
20
20
  const MC_HOST = process.env.MC_HOST ?? 'localhost'
21
21
  const MC_PORT = parseInt(process.env.MC_PORT ?? '25561')
22
22
  const MC_SERVER_DIR = process.env.MC_SERVER_DIR ?? path.join(process.env.HOME!, 'mc-test-server')
23
- const DATAPACK_DIR = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-test5')
23
+ const DATAPACK_DIR = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-test')
24
24
 
25
25
  const STDLIB_DIR = path.join(__dirname, '../../stdlib')
26
26
 
@@ -16,7 +16,7 @@ import { MCTestClient } from '../../mc-test/client'
16
16
  const MC_HOST = process.env.MC_HOST ?? 'localhost'
17
17
  const MC_PORT = parseInt(process.env.MC_PORT ?? '25561')
18
18
  const MC_SERVER_DIR = process.env.MC_SERVER_DIR ?? path.join(process.env.HOME!, 'mc-test-server')
19
- const DATAPACK_DIR = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-test6')
19
+ const DATAPACK_DIR = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-test')
20
20
  const STDLIB_DIR = path.join(__dirname, '../../stdlib')
21
21
 
22
22
  const BOT_URL = 'http://localhost:25562'
@@ -19,7 +19,7 @@ import { MCTestClient } from '../../mc-test/client'
19
19
  const MC_HOST = process.env.MC_HOST ?? 'localhost'
20
20
  const MC_PORT = parseInt(process.env.MC_PORT ?? '25561')
21
21
  const MC_SERVER_DIR = process.env.MC_SERVER_DIR ?? path.join(process.env.HOME!, 'mc-test-server')
22
- const DATAPACK_DIR = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-test7')
22
+ const DATAPACK_DIR = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-test')
23
23
 
24
24
  const STDLIB_DIR = path.join(__dirname, '../../stdlib')
25
25
 
@@ -15,7 +15,7 @@ import { MCTestClient } from '../../mc-test/client'
15
15
  const MC_HOST = process.env.MC_HOST ?? 'localhost'
16
16
  const MC_PORT = parseInt(process.env.MC_PORT ?? '25561')
17
17
  const MC_SERVER_DIR = process.env.MC_SERVER_DIR ?? path.join(process.env.HOME!, 'mc-test-server')
18
- const DATAPACK_DIR = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-test8')
18
+ const DATAPACK_DIR = path.join(MC_SERVER_DIR, 'world', 'datapacks', 'redscript-test')
19
19
  const STDLIB_DIR = path.join(__dirname, '../../stdlib')
20
20
 
21
21
  let serverOnline = false
@@ -16,6 +16,9 @@ function getCommands(source: string, namespace = 'test'): string[] {
16
16
  .filter(file => file.path.endsWith('.mcfunction'))
17
17
  .flatMap(file => file.content.split('\n'))
18
18
  .filter(line => line.trim().length > 0)
19
+ .filter(line => !line.startsWith('#')) // skip comments
20
+ .filter(line => !line.startsWith('$')) // skip MC macro lines ($say, $data, etc.)
21
+ .filter(line => !/ with storage /.test(line)) // skip macro function calls
19
22
  }
20
23
 
21
24
  function validateSource(
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Additional coverage for src/hir/monomorphize.ts
3
+ *
4
+ * Targets uncovered branches: typeSuffix edge cases, substType for
5
+ * function_type and option, rewriteStmt for labeled_loop/break_label/
6
+ * continue_label, rewriteExpr for is_check/some_lit/none_lit/unwrap_or,
7
+ * and type inference from literals.
8
+ */
9
+
10
+ import { Lexer } from '../lexer'
11
+ import { Parser } from '../parser'
12
+ import { monomorphize } from '../hir/monomorphize'
13
+ import { lowerToHIR } from '../hir/lower'
14
+ import type { Program } from '../ast/types'
15
+
16
+ function parse(source: string): Program {
17
+ const tokens = new Lexer(source).tokenize()
18
+ return new Parser(tokens, source).parse('test')
19
+ }
20
+
21
+ function monoFromSource(source: string) {
22
+ const program = parse(source)
23
+ const hir = lowerToHIR(program)
24
+ return monomorphize(hir)
25
+ }
26
+
27
+ describe('monomorphize — basic specialization', () => {
28
+ it('creates specialized copy for fn identity<T>(x: T): T', () => {
29
+ const result = monoFromSource(`
30
+ fn identity<T>(x: T): T { return x; }
31
+ fn main(): int { return identity<int>(42); }
32
+ `)
33
+ const names = result.functions.map(f => f.name)
34
+ expect(names).toContain('identity_int')
35
+ expect(names).not.toContain('identity') // template removed
36
+ })
37
+
38
+ it('creates multiple specializations for different type args', () => {
39
+ const result = monoFromSource(`
40
+ fn identity<T>(x: T): T { return x; }
41
+ fn main(): int {
42
+ let a: int = identity<int>(1);
43
+ let b: bool = identity<bool>(true);
44
+ return a;
45
+ }
46
+ `)
47
+ const names = result.functions.map(f => f.name)
48
+ expect(names).toContain('identity_int')
49
+ expect(names).toContain('identity_bool')
50
+ })
51
+
52
+ it('deduplicates same specialization called multiple times', () => {
53
+ const result = monoFromSource(`
54
+ fn double<T>(x: T): T { return x; }
55
+ fn main(): int {
56
+ let a: int = double<int>(1);
57
+ let b: int = double<int>(2);
58
+ return a + b;
59
+ }
60
+ `)
61
+ const intCopies = result.functions.filter(f => f.name === 'double_int')
62
+ expect(intCopies.length).toBe(1)
63
+ })
64
+ })
65
+
66
+ describe('monomorphize — type inference from arguments', () => {
67
+ it('infers type arg from int literal', () => {
68
+ const result = monoFromSource(`
69
+ fn max<T>(a: T, b: T): T {
70
+ if a > b { return a; }
71
+ return b;
72
+ }
73
+ fn main(): int { return max(3, 5); }
74
+ `)
75
+ const names = result.functions.map(f => f.name)
76
+ expect(names).toContain('max_int')
77
+ })
78
+
79
+ it('infers type arg from bool literal', () => {
80
+ const result = monoFromSource(`
81
+ fn identity<T>(x: T): T { return x; }
82
+ fn main(): bool { return identity(true); }
83
+ `)
84
+ const names = result.functions.map(f => f.name)
85
+ expect(names).toContain('identity_bool')
86
+ })
87
+
88
+ it('infers type arg from string literal', () => {
89
+ const result = monoFromSource(`
90
+ fn identity<T>(x: T): T { return x; }
91
+ fn main(): string { return identity("hello"); }
92
+ `)
93
+ const names = result.functions.map(f => f.name)
94
+ expect(names).toContain('identity_string')
95
+ })
96
+
97
+ it('infers type arg from variable type', () => {
98
+ const result = monoFromSource(`
99
+ fn identity<T>(x: T): T { return x; }
100
+ fn main(): int {
101
+ let n: int = 42;
102
+ return identity(n);
103
+ }
104
+ `)
105
+ const names = result.functions.map(f => f.name)
106
+ expect(names).toContain('identity_int')
107
+ })
108
+ })
109
+
110
+ describe('monomorphize — no generics fast path', () => {
111
+ it('returns module unchanged when no generic functions exist', () => {
112
+ const result = monoFromSource(`
113
+ fn add(a: int, b: int): int { return a + b; }
114
+ fn main(): int { return add(1, 2); }
115
+ `)
116
+ const names = result.functions.map(f => f.name)
117
+ expect(names).toContain('add')
118
+ expect(names).toContain('main')
119
+ })
120
+ })
121
+
122
+ describe('monomorphize — generic calling generic', () => {
123
+ it('handles generic function calling another generic', () => {
124
+ const result = monoFromSource(`
125
+ fn min<T>(a: T, b: T): T {
126
+ if a < b { return a; }
127
+ return b;
128
+ }
129
+ fn max<T>(a: T, b: T): T {
130
+ if a > b { return a; }
131
+ return b;
132
+ }
133
+ fn clamp<T>(x: T, lo: T, hi: T): T {
134
+ return min<T>(max<T>(x, lo), hi);
135
+ }
136
+ fn main(): int { return clamp<int>(5, 0, 10); }
137
+ `)
138
+ const names = result.functions.map(f => f.name)
139
+ expect(names).toContain('clamp_int')
140
+ expect(names).toContain('min_int')
141
+ expect(names).toContain('max_int')
142
+ })
143
+ })
144
+
145
+ describe('monomorphize — statement rewriting', () => {
146
+ it('rewrites let statements inside generic functions', () => {
147
+ const result = monoFromSource(`
148
+ fn wrap<T>(x: T): T {
149
+ let temp: T = x;
150
+ return temp;
151
+ }
152
+ fn main(): int { return wrap<int>(42); }
153
+ `)
154
+ const wrapInt = result.functions.find(f => f.name === 'wrap_int')
155
+ expect(wrapInt).toBeDefined()
156
+ // The let statement's type should be substituted
157
+ const letStmt = wrapInt!.body[0]
158
+ if (letStmt.kind === 'let' && letStmt.type) {
159
+ expect(letStmt.type.kind).toBe('named')
160
+ }
161
+ })
162
+
163
+ it('rewrites if statements inside generic functions', () => {
164
+ const result = monoFromSource(`
165
+ fn abs<T>(x: T): T {
166
+ if x < 0 { return 0 - x; }
167
+ return x;
168
+ }
169
+ fn main(): int { return abs<int>(-5); }
170
+ `)
171
+ const absInt = result.functions.find(f => f.name === 'abs_int')
172
+ expect(absInt).toBeDefined()
173
+ })
174
+
175
+ it('rewrites return statements', () => {
176
+ const result = monoFromSource(`
177
+ fn first<T>(a: T, b: T): T {
178
+ return a;
179
+ }
180
+ fn main(): int { return first<int>(1, 2); }
181
+ `)
182
+ const fn = result.functions.find(f => f.name === 'first_int')
183
+ expect(fn).toBeDefined()
184
+ expect(fn!.returnType).toEqual({ kind: 'named', name: 'int' })
185
+ })
186
+ })
187
+
188
+ describe('monomorphize — expression rewriting', () => {
189
+ it('rewrites binary expressions inside generic body', () => {
190
+ const result = monoFromSource(`
191
+ fn add<T>(a: T, b: T): T { return a + b; }
192
+ fn main(): int { return add<int>(1, 2); }
193
+ `)
194
+ const fn = result.functions.find(f => f.name === 'add_int')
195
+ expect(fn).toBeDefined()
196
+ })
197
+
198
+ it('rewrites array literal expressions', () => {
199
+ const result = monoFromSource(`
200
+ fn first<T>(a: T): T { return a; }
201
+ fn main(): int {
202
+ let arr: int[] = [1, 2, 3];
203
+ return first<int>(arr[0]);
204
+ }
205
+ `)
206
+ const fn = result.functions.find(f => f.name === 'first_int')
207
+ expect(fn).toBeDefined()
208
+ })
209
+ })
210
+
211
+ describe('monomorphize — type suffix', () => {
212
+ it('produces correct suffix for named types', () => {
213
+ const result = monoFromSource(`
214
+ fn id<T>(x: T): T { return x; }
215
+ fn main(): float { return id<float>(1.0); }
216
+ `)
217
+ const names = result.functions.map(f => f.name)
218
+ expect(names).toContain('id_float')
219
+ })
220
+ })