redscript-mc 1.2.29 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. package/.claude/commands/build-test.md +10 -0
  2. package/.claude/commands/deploy-demo.md +12 -0
  3. package/.claude/commands/stage-status.md +13 -0
  4. package/.claude/settings.json +12 -0
  5. package/.github/workflows/ci.yml +1 -0
  6. package/CLAUDE.md +231 -0
  7. package/README.md +29 -28
  8. package/README.zh.md +28 -28
  9. package/demo.gif +0 -0
  10. package/dist/cli.js +2 -554
  11. package/dist/compile.js +2 -266
  12. package/dist/index.js +2 -159
  13. package/dist/lexer/index.js +9 -1
  14. package/dist/lowering/index.js +22 -5
  15. package/dist/src/__tests__/cli.test.d.ts +1 -0
  16. package/dist/src/__tests__/cli.test.js +104 -0
  17. package/dist/src/__tests__/codegen.test.d.ts +1 -0
  18. package/dist/src/__tests__/codegen.test.js +152 -0
  19. package/dist/src/__tests__/compile-all.test.d.ts +10 -0
  20. package/dist/src/__tests__/compile-all.test.js +108 -0
  21. package/dist/src/__tests__/dce.test.d.ts +1 -0
  22. package/dist/src/__tests__/dce.test.js +102 -0
  23. package/dist/src/__tests__/diagnostics.test.d.ts +4 -0
  24. package/dist/src/__tests__/diagnostics.test.js +177 -0
  25. package/dist/src/__tests__/e2e.test.d.ts +6 -0
  26. package/dist/src/__tests__/e2e.test.js +1789 -0
  27. package/dist/src/__tests__/entity-types.test.d.ts +1 -0
  28. package/dist/src/__tests__/entity-types.test.js +203 -0
  29. package/dist/src/__tests__/formatter.test.d.ts +1 -0
  30. package/dist/src/__tests__/formatter.test.js +40 -0
  31. package/dist/src/__tests__/lexer.test.d.ts +1 -0
  32. package/dist/src/__tests__/lexer.test.js +343 -0
  33. package/dist/src/__tests__/lowering.test.d.ts +1 -0
  34. package/dist/src/__tests__/lowering.test.js +1015 -0
  35. package/dist/src/__tests__/macro.test.d.ts +8 -0
  36. package/dist/src/__tests__/macro.test.js +306 -0
  37. package/dist/src/__tests__/mc-integration.test.d.ts +12 -0
  38. package/dist/src/__tests__/mc-integration.test.js +817 -0
  39. package/dist/src/__tests__/mc-syntax.test.d.ts +1 -0
  40. package/dist/src/__tests__/mc-syntax.test.js +124 -0
  41. package/dist/src/__tests__/nbt.test.d.ts +1 -0
  42. package/dist/src/__tests__/nbt.test.js +82 -0
  43. package/dist/src/__tests__/optimizer-advanced.test.d.ts +1 -0
  44. package/dist/src/__tests__/optimizer-advanced.test.js +124 -0
  45. package/dist/src/__tests__/optimizer.test.d.ts +1 -0
  46. package/dist/src/__tests__/optimizer.test.js +149 -0
  47. package/dist/src/__tests__/parser.test.d.ts +1 -0
  48. package/dist/src/__tests__/parser.test.js +807 -0
  49. package/dist/src/__tests__/repl.test.d.ts +1 -0
  50. package/dist/src/__tests__/repl.test.js +27 -0
  51. package/dist/src/__tests__/runtime.test.d.ts +1 -0
  52. package/dist/src/__tests__/runtime.test.js +289 -0
  53. package/dist/src/__tests__/stdlib-advanced.test.d.ts +4 -0
  54. package/dist/src/__tests__/stdlib-advanced.test.js +374 -0
  55. package/dist/src/__tests__/stdlib-bigint.test.d.ts +7 -0
  56. package/dist/src/__tests__/stdlib-bigint.test.js +426 -0
  57. package/dist/src/__tests__/stdlib-math.test.d.ts +7 -0
  58. package/dist/src/__tests__/stdlib-math.test.js +351 -0
  59. package/dist/src/__tests__/stdlib-vec.test.d.ts +4 -0
  60. package/dist/src/__tests__/stdlib-vec.test.js +263 -0
  61. package/dist/src/__tests__/structure-optimizer.test.d.ts +1 -0
  62. package/dist/src/__tests__/structure-optimizer.test.js +33 -0
  63. package/dist/src/__tests__/typechecker.test.d.ts +1 -0
  64. package/dist/src/__tests__/typechecker.test.js +552 -0
  65. package/dist/src/__tests__/var-allocator.test.d.ts +1 -0
  66. package/dist/src/__tests__/var-allocator.test.js +69 -0
  67. package/dist/src/ast/types.d.ts +515 -0
  68. package/dist/src/ast/types.js +9 -0
  69. package/dist/src/builtins/metadata.d.ts +36 -0
  70. package/dist/src/builtins/metadata.js +1014 -0
  71. package/dist/src/cli.d.ts +11 -0
  72. package/dist/src/cli.js +443 -0
  73. package/dist/src/codegen/cmdblock/index.d.ts +26 -0
  74. package/dist/src/codegen/cmdblock/index.js +45 -0
  75. package/dist/src/codegen/mcfunction/index.d.ts +40 -0
  76. package/dist/src/codegen/mcfunction/index.js +606 -0
  77. package/dist/src/codegen/structure/index.d.ts +24 -0
  78. package/dist/src/codegen/structure/index.js +279 -0
  79. package/dist/src/codegen/var-allocator.d.ts +45 -0
  80. package/dist/src/codegen/var-allocator.js +104 -0
  81. package/dist/src/compile.d.ts +37 -0
  82. package/dist/src/compile.js +165 -0
  83. package/dist/src/diagnostics/index.d.ts +44 -0
  84. package/dist/src/diagnostics/index.js +140 -0
  85. package/dist/src/events/types.d.ts +35 -0
  86. package/dist/src/events/types.js +59 -0
  87. package/dist/src/formatter/index.d.ts +1 -0
  88. package/dist/src/formatter/index.js +26 -0
  89. package/dist/src/index.d.ts +22 -0
  90. package/dist/src/index.js +45 -0
  91. package/dist/src/ir/builder.d.ts +33 -0
  92. package/dist/src/ir/builder.js +99 -0
  93. package/dist/src/ir/types.d.ts +132 -0
  94. package/dist/src/ir/types.js +15 -0
  95. package/dist/src/lexer/index.d.ts +37 -0
  96. package/dist/src/lexer/index.js +569 -0
  97. package/dist/src/lowering/index.d.ts +188 -0
  98. package/dist/src/lowering/index.js +3405 -0
  99. package/dist/src/mc-test/client.d.ts +128 -0
  100. package/dist/src/mc-test/client.js +174 -0
  101. package/dist/src/mc-test/runner.d.ts +28 -0
  102. package/dist/src/mc-test/runner.js +151 -0
  103. package/dist/src/mc-test/setup.d.ts +11 -0
  104. package/dist/src/mc-test/setup.js +98 -0
  105. package/dist/src/mc-validator/index.d.ts +17 -0
  106. package/dist/src/mc-validator/index.js +322 -0
  107. package/dist/src/nbt/index.d.ts +86 -0
  108. package/dist/src/nbt/index.js +250 -0
  109. package/dist/src/optimizer/commands.d.ts +38 -0
  110. package/dist/src/optimizer/commands.js +451 -0
  111. package/dist/src/optimizer/dce.d.ts +34 -0
  112. package/dist/src/optimizer/dce.js +639 -0
  113. package/dist/src/optimizer/passes.d.ts +34 -0
  114. package/dist/src/optimizer/passes.js +243 -0
  115. package/dist/src/optimizer/structure.d.ts +9 -0
  116. package/dist/src/optimizer/structure.js +356 -0
  117. package/dist/src/parser/index.d.ts +93 -0
  118. package/dist/src/parser/index.js +1687 -0
  119. package/dist/src/repl.d.ts +16 -0
  120. package/dist/src/repl.js +165 -0
  121. package/dist/src/runtime/index.d.ts +107 -0
  122. package/dist/src/runtime/index.js +1409 -0
  123. package/dist/src/typechecker/index.d.ts +61 -0
  124. package/dist/src/typechecker/index.js +1034 -0
  125. package/dist/src/types/entity-hierarchy.d.ts +29 -0
  126. package/dist/src/types/entity-hierarchy.js +107 -0
  127. package/dist/src2/__tests__/e2e/basic.test.d.ts +8 -0
  128. package/dist/src2/__tests__/e2e/basic.test.js +140 -0
  129. package/dist/src2/__tests__/e2e/macros.test.d.ts +9 -0
  130. package/dist/src2/__tests__/e2e/macros.test.js +182 -0
  131. package/dist/src2/__tests__/e2e/migrate.test.d.ts +13 -0
  132. package/dist/src2/__tests__/e2e/migrate.test.js +2739 -0
  133. package/dist/src2/__tests__/hir/desugar.test.d.ts +1 -0
  134. package/dist/src2/__tests__/hir/desugar.test.js +234 -0
  135. package/dist/src2/__tests__/lir/lower.test.d.ts +1 -0
  136. package/dist/src2/__tests__/lir/lower.test.js +559 -0
  137. package/dist/src2/__tests__/lir/types.test.d.ts +1 -0
  138. package/dist/src2/__tests__/lir/types.test.js +185 -0
  139. package/dist/src2/__tests__/lir/verify.test.d.ts +1 -0
  140. package/dist/src2/__tests__/lir/verify.test.js +221 -0
  141. package/dist/src2/__tests__/mir/arithmetic.test.d.ts +1 -0
  142. package/dist/src2/__tests__/mir/arithmetic.test.js +130 -0
  143. package/dist/src2/__tests__/mir/control-flow.test.d.ts +1 -0
  144. package/dist/src2/__tests__/mir/control-flow.test.js +205 -0
  145. package/dist/src2/__tests__/mir/verify.test.d.ts +1 -0
  146. package/dist/src2/__tests__/mir/verify.test.js +223 -0
  147. package/dist/src2/__tests__/optimizer/block_merge.test.d.ts +1 -0
  148. package/dist/src2/__tests__/optimizer/block_merge.test.js +78 -0
  149. package/dist/src2/__tests__/optimizer/branch_simplify.test.d.ts +1 -0
  150. package/dist/src2/__tests__/optimizer/branch_simplify.test.js +58 -0
  151. package/dist/src2/__tests__/optimizer/constant_fold.test.d.ts +1 -0
  152. package/dist/src2/__tests__/optimizer/constant_fold.test.js +131 -0
  153. package/dist/src2/__tests__/optimizer/copy_prop.test.d.ts +1 -0
  154. package/dist/src2/__tests__/optimizer/copy_prop.test.js +91 -0
  155. package/dist/src2/__tests__/optimizer/dce.test.d.ts +1 -0
  156. package/dist/src2/__tests__/optimizer/dce.test.js +76 -0
  157. package/dist/src2/__tests__/optimizer/pipeline.test.d.ts +1 -0
  158. package/dist/src2/__tests__/optimizer/pipeline.test.js +102 -0
  159. package/dist/src2/emit/compile.d.ts +19 -0
  160. package/dist/src2/emit/compile.js +80 -0
  161. package/dist/src2/emit/index.d.ts +17 -0
  162. package/dist/src2/emit/index.js +172 -0
  163. package/dist/src2/hir/lower.d.ts +15 -0
  164. package/dist/src2/hir/lower.js +378 -0
  165. package/dist/src2/hir/types.d.ts +373 -0
  166. package/dist/src2/hir/types.js +16 -0
  167. package/dist/src2/lir/lower.d.ts +15 -0
  168. package/dist/src2/lir/lower.js +453 -0
  169. package/dist/src2/lir/types.d.ts +136 -0
  170. package/dist/src2/lir/types.js +11 -0
  171. package/dist/src2/lir/verify.d.ts +14 -0
  172. package/dist/src2/lir/verify.js +113 -0
  173. package/dist/src2/mir/lower.d.ts +9 -0
  174. package/dist/src2/mir/lower.js +1030 -0
  175. package/dist/src2/mir/macro.d.ts +22 -0
  176. package/dist/src2/mir/macro.js +168 -0
  177. package/dist/src2/mir/types.d.ts +183 -0
  178. package/dist/src2/mir/types.js +11 -0
  179. package/dist/src2/mir/verify.d.ts +16 -0
  180. package/dist/src2/mir/verify.js +216 -0
  181. package/dist/src2/optimizer/block_merge.d.ts +12 -0
  182. package/dist/src2/optimizer/block_merge.js +84 -0
  183. package/dist/src2/optimizer/branch_simplify.d.ts +9 -0
  184. package/dist/src2/optimizer/branch_simplify.js +28 -0
  185. package/dist/src2/optimizer/constant_fold.d.ts +10 -0
  186. package/dist/src2/optimizer/constant_fold.js +85 -0
  187. package/dist/src2/optimizer/copy_prop.d.ts +9 -0
  188. package/dist/src2/optimizer/copy_prop.js +113 -0
  189. package/dist/src2/optimizer/dce.d.ts +8 -0
  190. package/dist/src2/optimizer/dce.js +155 -0
  191. package/dist/src2/optimizer/pipeline.d.ts +10 -0
  192. package/dist/src2/optimizer/pipeline.js +42 -0
  193. package/dist/tsconfig.tsbuildinfo +1 -0
  194. package/docs/compiler-pipeline-redesign.md +2243 -0
  195. package/docs/optimization-ideas.md +1076 -0
  196. package/editors/vscode/package-lock.json +3 -3
  197. package/editors/vscode/package.json +1 -1
  198. package/examples/readme-demo.mcrs +44 -66
  199. package/jest.config.js +1 -1
  200. package/package.json +6 -5
  201. package/scripts/postbuild.js +15 -0
  202. package/src/__tests__/cli.test.ts +8 -220
  203. package/src/__tests__/dce.test.ts +11 -56
  204. package/src/__tests__/diagnostics.test.ts +59 -38
  205. package/src/__tests__/mc-integration.test.ts +1 -2
  206. package/src/ast/types.ts +6 -1
  207. package/src/cli.ts +29 -156
  208. package/src/compile.ts +6 -162
  209. package/src/index.ts +14 -178
  210. package/src/lexer/index.ts +9 -1
  211. package/src/mc-test/runner.ts +4 -3
  212. package/src/parser/index.ts +1 -1
  213. package/src/repl.ts +1 -1
  214. package/src/runtime/index.ts +1 -1
  215. package/src2/__tests__/e2e/basic.test.ts +154 -0
  216. package/src2/__tests__/e2e/macros.test.ts +199 -0
  217. package/src2/__tests__/e2e/migrate.test.ts +3008 -0
  218. package/src2/__tests__/hir/desugar.test.ts +263 -0
  219. package/src2/__tests__/lir/lower.test.ts +619 -0
  220. package/src2/__tests__/lir/types.test.ts +207 -0
  221. package/src2/__tests__/lir/verify.test.ts +249 -0
  222. package/src2/__tests__/mir/arithmetic.test.ts +156 -0
  223. package/src2/__tests__/mir/control-flow.test.ts +242 -0
  224. package/src2/__tests__/mir/verify.test.ts +254 -0
  225. package/src2/__tests__/optimizer/block_merge.test.ts +84 -0
  226. package/src2/__tests__/optimizer/branch_simplify.test.ts +64 -0
  227. package/src2/__tests__/optimizer/constant_fold.test.ts +145 -0
  228. package/src2/__tests__/optimizer/copy_prop.test.ts +99 -0
  229. package/src2/__tests__/optimizer/dce.test.ts +83 -0
  230. package/src2/__tests__/optimizer/pipeline.test.ts +116 -0
  231. package/src2/emit/compile.ts +99 -0
  232. package/src2/emit/index.ts +222 -0
  233. package/src2/hir/lower.ts +428 -0
  234. package/src2/hir/types.ts +216 -0
  235. package/src2/lir/lower.ts +556 -0
  236. package/src2/lir/types.ts +109 -0
  237. package/src2/lir/verify.ts +129 -0
  238. package/src2/mir/lower.ts +1160 -0
  239. package/src2/mir/macro.ts +167 -0
  240. package/src2/mir/types.ts +106 -0
  241. package/src2/mir/verify.ts +218 -0
  242. package/src2/optimizer/block_merge.ts +93 -0
  243. package/src2/optimizer/branch_simplify.ts +27 -0
  244. package/src2/optimizer/constant_fold.ts +88 -0
  245. package/src2/optimizer/copy_prop.ts +106 -0
  246. package/src2/optimizer/dce.ts +133 -0
  247. package/src2/optimizer/pipeline.ts +44 -0
  248. package/tsconfig.json +2 -2
  249. package/src/__tests__/codegen.test.ts +0 -161
  250. package/src/__tests__/e2e.test.ts +0 -2039
  251. package/src/__tests__/entity-types.test.ts +0 -236
  252. package/src/__tests__/lowering.test.ts +0 -1185
  253. package/src/__tests__/macro.test.ts +0 -343
  254. package/src/__tests__/nbt.test.ts +0 -58
  255. package/src/__tests__/optimizer-advanced.test.ts +0 -144
  256. package/src/__tests__/optimizer.test.ts +0 -162
  257. package/src/__tests__/runtime.test.ts +0 -305
  258. package/src/__tests__/stdlib-advanced.test.ts +0 -379
  259. package/src/__tests__/stdlib-bigint.test.ts +0 -427
  260. package/src/__tests__/stdlib-math.test.ts +0 -374
  261. package/src/__tests__/stdlib-vec.test.ts +0 -259
  262. package/src/__tests__/structure-optimizer.test.ts +0 -38
  263. package/src/__tests__/var-allocator.test.ts +0 -75
  264. package/src/codegen/cmdblock/index.ts +0 -63
  265. package/src/codegen/mcfunction/index.ts +0 -662
  266. package/src/codegen/structure/index.ts +0 -346
  267. package/src/codegen/var-allocator.ts +0 -104
  268. package/src/ir/builder.ts +0 -116
  269. package/src/ir/types.ts +0 -134
  270. package/src/lowering/index.ts +0 -3860
  271. package/src/optimizer/commands.ts +0 -534
  272. package/src/optimizer/dce.ts +0 -679
  273. package/src/optimizer/passes.ts +0 -250
  274. package/src/optimizer/structure.ts +0 -450
@@ -1,343 +0,0 @@
1
- /**
2
- * Tests for MC 1.20.2+ macro function support
3
- *
4
- * When a function uses runtime parameters in positions that require literal
5
- * values in MC commands (coordinates, entity types, etc.), RedScript should
6
- * automatically compile it as a macro function using $-prefixed syntax.
7
- */
8
-
9
- import { Lexer } from '../lexer'
10
- import { Parser } from '../parser'
11
- import { Lowering } from '../lowering'
12
- import { generateDatapack } from '../codegen/mcfunction'
13
- import type { IRModule, IRFunction, IRInstr } from '../ir/types'
14
-
15
- function compile(source: string, namespace = 'test'): IRModule {
16
- const tokens = new Lexer(source).tokenize()
17
- const ast = new Parser(tokens).parse(namespace)
18
- const lowering = new Lowering(namespace)
19
- return lowering.lower(ast)
20
- }
21
-
22
- function getFunction(module: IRModule, name: string): IRFunction | undefined {
23
- return module.functions.find(f => f.name === name)
24
- }
25
-
26
- function getRawCommands(fn: IRFunction): string[] {
27
- return fn.blocks
28
- .flatMap(b => b.instrs)
29
- .filter((i): i is IRInstr & { op: 'raw' } => i.op === 'raw')
30
- .map(i => i.cmd)
31
- }
32
-
33
- function getGeneratedContent(module: IRModule, fnName: string): string | undefined {
34
- const files = generateDatapack(module)
35
- const file = files.find(f => f.path.includes(`/${fnName}.mcfunction`))
36
- return file?.content
37
- }
38
-
39
- // ---------------------------------------------------------------------------
40
- // Macro function detection
41
- // ---------------------------------------------------------------------------
42
-
43
- describe('MC macro function detection', () => {
44
- it('marks function as macro when int param used in summon coordinates', () => {
45
- const ir = compile(`
46
- fn spawn_zombie(x: int, y: int, z: int) {
47
- summon("minecraft:zombie", x, y, z);
48
- }
49
- `)
50
- const fn = getFunction(ir, 'spawn_zombie')
51
- expect(fn).toBeDefined()
52
- expect(fn!.isMacroFunction).toBe(true)
53
- expect(fn!.macroParamNames).toEqual(expect.arrayContaining(['x', 'y', 'z']))
54
- })
55
-
56
- it('does NOT mark function as macro when all summon args are constants', () => {
57
- const ir = compile(`
58
- fn spawn_fixed() {
59
- summon("minecraft:zombie", 100, 64, 200);
60
- }
61
- `)
62
- const fn = getFunction(ir, 'spawn_fixed')
63
- expect(fn).toBeDefined()
64
- expect(fn!.isMacroFunction).toBeFalsy()
65
- })
66
-
67
- it('marks function as macro when int param used in particle coordinates', () => {
68
- const ir = compile(`
69
- fn show_particle(x: int, y: int, z: int) {
70
- particle("minecraft:flame", x, y, z);
71
- }
72
- `)
73
- const fn = getFunction(ir, 'show_particle')
74
- expect(fn).toBeDefined()
75
- expect(fn!.isMacroFunction).toBe(true)
76
- })
77
-
78
- it('marks function as macro when int param used in setblock coordinates', () => {
79
- const ir = compile(`
80
- fn place_block(x: int, y: int, z: int) {
81
- setblock(x, y, z, "minecraft:stone");
82
- }
83
- `)
84
- const fn = getFunction(ir, 'place_block')
85
- expect(fn).toBeDefined()
86
- expect(fn!.isMacroFunction).toBe(true)
87
- expect(fn!.macroParamNames).toEqual(expect.arrayContaining(['x', 'y', 'z']))
88
- })
89
-
90
- it('identifies only the params used in macro positions', () => {
91
- const ir = compile(`
92
- fn do_stuff(count: int, x: int, y: int, z: int) {
93
- summon("minecraft:zombie", x, y, z);
94
- // count is not used in a macro position
95
- }
96
- `)
97
- const fn = getFunction(ir, 'do_stuff')
98
- expect(fn).toBeDefined()
99
- expect(fn!.isMacroFunction).toBe(true)
100
- // x, y, z should be macro params; count should NOT be
101
- expect(fn!.macroParamNames).toEqual(expect.arrayContaining(['x', 'y', 'z']))
102
- expect(fn!.macroParamNames).not.toContain('count')
103
- })
104
- })
105
-
106
- // ---------------------------------------------------------------------------
107
- // Macro command generation in function body
108
- // ---------------------------------------------------------------------------
109
-
110
- describe('MC macro command generation', () => {
111
- it('generates $-prefixed summon command with $(param) for macro params', () => {
112
- const ir = compile(`
113
- fn spawn_zombie(x: int, y: int, z: int) {
114
- summon("minecraft:zombie", x, y, z);
115
- }
116
- `)
117
- const fn = getFunction(ir, 'spawn_zombie')!
118
- const cmds = getRawCommands(fn)
119
-
120
- // Should have a macro command for summon
121
- const macroCmd = cmds.find(c => c.startsWith('$summon'))
122
- expect(macroCmd).toBeDefined()
123
- expect(macroCmd).toContain('$(x)')
124
- expect(macroCmd).toContain('$(y)')
125
- expect(macroCmd).toContain('$(z)')
126
- expect(macroCmd).toBe('$summon minecraft:zombie $(x) $(y) $(z)')
127
- })
128
-
129
- it('generates non-prefixed command when args are literals', () => {
130
- const ir = compile(`
131
- fn spawn_fixed() {
132
- summon("minecraft:zombie", 100, 64, 200);
133
- }
134
- `)
135
- const fn = getFunction(ir, 'spawn_fixed')!
136
- const cmds = getRawCommands(fn)
137
- const summonCmd = cmds.find(c => c.includes('summon'))
138
- expect(summonCmd).toBeDefined()
139
- expect(summonCmd!.startsWith('$')).toBe(false)
140
- expect(summonCmd).toContain('100')
141
- expect(summonCmd).toContain('64')
142
- expect(summonCmd).toContain('200')
143
- })
144
-
145
- it('generates $-prefixed particle command with $(param)', () => {
146
- const ir = compile(`
147
- fn show_particle(x: int, y: int, z: int) {
148
- particle("minecraft:flame", x, y, z);
149
- }
150
- `)
151
- const fn = getFunction(ir, 'show_particle')!
152
- const cmds = getRawCommands(fn)
153
- const macroCmd = cmds.find(c => c.startsWith('$particle'))
154
- expect(macroCmd).toBeDefined()
155
- expect(macroCmd).toContain('$(x)')
156
- })
157
-
158
- it('generates $-prefixed setblock command with $(param)', () => {
159
- const ir = compile(`
160
- fn place_block(x: int, y: int, z: int) {
161
- setblock(x, y, z, "minecraft:stone");
162
- }
163
- `)
164
- const fn = getFunction(ir, 'place_block')!
165
- const cmds = getRawCommands(fn)
166
- const macroCmd = cmds.find(c => c.startsWith('$setblock'))
167
- expect(macroCmd).toBeDefined()
168
- expect(macroCmd).toContain('$(x)')
169
- expect(macroCmd).toContain('$(y)')
170
- expect(macroCmd).toContain('$(z)')
171
- expect(macroCmd).toContain('minecraft:stone')
172
- })
173
- })
174
-
175
- // ---------------------------------------------------------------------------
176
- // Call site code generation
177
- // ---------------------------------------------------------------------------
178
-
179
- describe('MC macro call site generation', () => {
180
- it('emits NBT setup + with-storage call for variable args', () => {
181
- const ir = compile(`
182
- fn spawn_zombie(x: int, y: int, z: int) {
183
- summon("minecraft:zombie", x, y, z);
184
- }
185
-
186
- fn caller(px: int, pz: int) {
187
- spawn_zombie(px, 64, pz);
188
- }
189
- `)
190
- const callerFn = getFunction(ir, 'caller')!
191
- const cmds = getRawCommands(callerFn)
192
-
193
- // Should have NBT setup for variable params (px → x, pz → z)
194
- const xSetup = cmds.find(c => c.includes('macro_args') && c.includes(' x '))
195
- const zSetup = cmds.find(c => c.includes('macro_args') && c.includes(' z '))
196
- expect(xSetup).toBeDefined()
197
- expect(zSetup).toBeDefined()
198
-
199
- // Should have 'function test:spawn_zombie with storage rs:macro_args'
200
- const callCmd = cmds.find(c => c.includes('spawn_zombie') && c.includes('with storage'))
201
- expect(callCmd).toBeDefined()
202
- expect(callCmd).toContain('rs:macro_args')
203
- })
204
-
205
- it('emits NBT setup for constant args too', () => {
206
- const ir = compile(`
207
- fn spawn_zombie(x: int, y: int, z: int) {
208
- summon("minecraft:zombie", x, y, z);
209
- }
210
-
211
- fn caller_const() {
212
- spawn_zombie(100, 64, 200);
213
- }
214
- `)
215
- const callerFn = getFunction(ir, 'caller_const')!
216
- const cmds = getRawCommands(callerFn)
217
-
218
- // Should have NBT setup for all macro params
219
- const nbtCmds = cmds.filter(c => c.includes('macro_args'))
220
- expect(nbtCmds.length).toBeGreaterThan(0)
221
-
222
- // Should call with storage
223
- const callCmd = cmds.find(c => c.includes('spawn_zombie') && c.includes('with storage'))
224
- expect(callCmd).toBeDefined()
225
- })
226
-
227
- it('correctly sets up int variable args into NBT storage', () => {
228
- const ir = compile(`
229
- fn spawn_zombie(x: int, y: int, z: int) {
230
- summon("minecraft:zombie", x, y, z);
231
- }
232
-
233
- fn caller(my_x: int) {
234
- spawn_zombie(my_x, 64, 0);
235
- }
236
- `)
237
- const callerFn = getFunction(ir, 'caller')!
238
- const cmds = getRawCommands(callerFn)
239
-
240
- // For variable arg my_x → x: should use execute store result
241
- const varSetup = cmds.find(c =>
242
- c.includes('execute store result storage rs:macro_args x') &&
243
- c.includes('scoreboard players get')
244
- )
245
- expect(varSetup).toBeDefined()
246
-
247
- // For constant 64 → y: should use data modify ... set value
248
- const constSetup = cmds.find(c =>
249
- c.includes('data modify storage rs:macro_args y set value 64')
250
- )
251
- expect(constSetup).toBeDefined()
252
- })
253
- })
254
-
255
- // ---------------------------------------------------------------------------
256
- // Codegen output (mcfunction file content)
257
- // ---------------------------------------------------------------------------
258
-
259
- describe('MC macro function codegen output', () => {
260
- it('generates $-prefixed lines in the macro function mcfunction file', () => {
261
- const ir = compile(`
262
- fn spawn_zombie(x: int, y: int, z: int) {
263
- summon("minecraft:zombie", x, y, z);
264
- }
265
- `)
266
- const content = getGeneratedContent(ir, 'spawn_zombie')
267
- expect(content).toBeDefined()
268
- expect(content).toContain('$summon minecraft:zombie $(x) $(y) $(z)')
269
- })
270
-
271
- it('generates correct call site in caller mcfunction file', () => {
272
- const ir = compile(`
273
- fn spawn_zombie(x: int, y: int, z: int) {
274
- summon("minecraft:zombie", x, y, z);
275
- }
276
-
277
- fn caller(px: int, pz: int) {
278
- spawn_zombie(px, 64, pz);
279
- }
280
- `)
281
- const content = getGeneratedContent(ir, 'caller')
282
- expect(content).toBeDefined()
283
- expect(content).toContain('with storage rs:macro_args')
284
- expect(content).toContain('spawn_zombie')
285
- })
286
- })
287
-
288
- // ---------------------------------------------------------------------------
289
- // Edge cases
290
- // ---------------------------------------------------------------------------
291
-
292
- describe('MC macro edge cases', () => {
293
- it('handles mixed literal and variable args correctly', () => {
294
- const ir = compile(`
295
- fn teleport_y(y: int) {
296
- summon("minecraft:zombie", 100, y, 200);
297
- }
298
- `)
299
- const fn = getFunction(ir, 'teleport_y')!
300
- expect(fn.isMacroFunction).toBe(true)
301
- expect(fn.macroParamNames).toContain('y')
302
- expect(fn.macroParamNames).not.toContain('x')
303
-
304
- const cmds = getRawCommands(fn)
305
- const macroCmd = cmds.find(c => c.startsWith('$summon'))
306
- expect(macroCmd).toBeDefined()
307
- // x and z are literals, y is macro
308
- expect(macroCmd).toContain('100')
309
- expect(macroCmd).toContain('$(y)')
310
- expect(macroCmd).toContain('200')
311
- })
312
-
313
- it('non-macro functions still work normally', () => {
314
- const ir = compile(`
315
- fn greet() {
316
- say("hello world");
317
- }
318
- `)
319
- const fn = getFunction(ir, 'greet')!
320
- expect(fn.isMacroFunction).toBeFalsy()
321
- const cmds = getRawCommands(fn)
322
- const sayCmd = cmds.find(c => c.includes('say') || c.includes('tellraw'))
323
- expect(sayCmd).toBeDefined()
324
- expect(sayCmd!.startsWith('$')).toBe(false)
325
- })
326
-
327
- it('macro function with params used in arithmetic still works', () => {
328
- const ir = compile(`
329
- fn spawn_offset(x: int, y: int, z: int) {
330
- summon("minecraft:zombie", x, y, z);
331
- // params are also used in the macro commands
332
- }
333
- `)
334
- const fn = getFunction(ir, 'spawn_offset')!
335
- expect(fn.isMacroFunction).toBe(true)
336
-
337
- // The macro commands should use $(param) syntax
338
- const cmds = getRawCommands(fn)
339
- const macroCmd = cmds.find(c => c.startsWith('$summon'))
340
- expect(macroCmd).toBeDefined()
341
- expect(macroCmd).toContain('$(x)')
342
- })
343
- })
@@ -1,58 +0,0 @@
1
- import * as fs from 'fs'
2
-
3
- import { compileToStructure } from '../codegen/structure'
4
- import { nbt, readNbt, TagType, writeNbt, type CompoundTag } from '../nbt'
5
-
6
- describe('NBT codec', () => {
7
- test('round-trips a compound tag', () => {
8
- const tag = nbt.compound({ x: nbt.int(42), name: nbt.string('test') })
9
- const buf = writeNbt(tag, 'root')
10
- const parsed = readNbt(buf)
11
-
12
- expect(parsed.name).toBe('root')
13
- expect(parsed.tag).toEqual(tag)
14
- })
15
-
16
- test('round-trips nested lists and arrays', () => {
17
- const tag = nbt.compound({
18
- nested: nbt.list(TagType.Compound, [
19
- nbt.compound({ values: nbt.intArray([1, 2, 3]) }),
20
- nbt.compound({ bytes: nbt.byteArray([-1, 0, 1]) }),
21
- ]),
22
- longs: { type: TagType.LongArray, value: BigInt64Array.from([1n, 2n, 3n]) },
23
- })
24
-
25
- const buf = writeNbt(tag, 'root')
26
- const parsed = readNbt(buf)
27
-
28
- expect(parsed.tag).toEqual(tag)
29
- })
30
-
31
- test('handles longs correctly', () => {
32
- const tag = nbt.compound({ ts: nbt.long(9007199254740993n) })
33
- const buf = writeNbt(tag, '')
34
- const parsed = readNbt(buf)
35
- const root = parsed.tag as CompoundTag
36
-
37
- expect(root.entries.get('ts')).toEqual(nbt.long(9007199254740993n))
38
- expect((root.entries.get('ts') as { value: bigint }).value).toBe(9007199254740993n)
39
- })
40
- })
41
-
42
- describe('Structure generator', () => {
43
- test('compiles counter.mcrs to a non-empty structure', () => {
44
- const filePath = 'src/examples/counter.mcrs'
45
- const src = fs.readFileSync(filePath, 'utf-8')
46
- const { buffer, blockCount } = compileToStructure(src, 'counter', filePath)
47
-
48
- expect(buffer.length).toBeGreaterThan(100)
49
- expect(blockCount).toBeGreaterThan(0)
50
-
51
- const parsed = readNbt(buffer)
52
- const root = parsed.tag as CompoundTag
53
- const blocks = root.entries.get('blocks')
54
-
55
- expect(parsed.name).toBe('')
56
- expect(blocks?.type).toBe(TagType.List)
57
- })
58
- })
@@ -1,144 +0,0 @@
1
- import { compile } from '../index'
2
- import { compileToStructure } from '../codegen/structure'
3
-
4
- function getFileContent(files: ReturnType<typeof compile>['files'], suffix: string): string {
5
- const file = files.find(candidate => candidate.path.endsWith(suffix))
6
- if (!file) {
7
- throw new Error(`Missing file: ${suffix}`)
8
- }
9
- return file.content
10
- }
11
-
12
- describe('LICM', () => {
13
- test('hoists loop-invariant scoreboard read out of foreach', () => {
14
- const source = `
15
- fn turret_tick() {
16
- foreach (turret in @e[tag=turret]) {
17
- let range: int = scoreboard_get("config", "turret_range");
18
- if (range > 0) {
19
- if (range > -1) {
20
- say("ready");
21
- }
22
- }
23
- }
24
- }
25
- `
26
-
27
- const result = compile(source, { namespace: 'test' })
28
- const parent = getFileContent(result.files, 'data/test/function/turret_tick.mcfunction')
29
- const loopBody = getFileContent(result.files, 'data/test/function/turret_tick/foreach_0.mcfunction')
30
-
31
- const hoistedRead = 'execute store result score $_0 __test run scoreboard players get config test.turret_range'
32
- const executeCall = 'execute as @e[tag=turret] run function test:turret_tick/foreach_0'
33
-
34
- expect(parent).toContain(hoistedRead)
35
- expect(parent.indexOf(hoistedRead)).toBeLessThan(parent.indexOf(executeCall))
36
- expect(loopBody).not.toContain('scoreboard players get config test.turret_range')
37
- })
38
- })
39
-
40
- describe('CSE', () => {
41
- test('eliminates duplicate scoreboard reads', () => {
42
- const source = `
43
- fn read_twice() {
44
- let a: int = scoreboard_get(@s, "coins");
45
- let b: int = scoreboard_get(@s, "coins");
46
- if (a == b) {
47
- say("same");
48
- }
49
- }
50
- `
51
-
52
- const result = compile(source, { namespace: 'test' })
53
- const fn = getFileContent(result.files, 'data/test/function/read_twice.mcfunction')
54
- const readMatches = fn.match(/scoreboard players get @s test\.coins/g) ?? []
55
-
56
- expect(readMatches).toHaveLength(1)
57
- expect(fn).toContain('scoreboard players operation $_1 __test = $_0 __test')
58
- })
59
-
60
- test('reuses duplicate arithmetic sequences', () => {
61
- const source = `
62
- fn math() {
63
- let base: int = 4;
64
- let a: int = base + 2;
65
- let b: int = base + 2;
66
- if (a == b) {
67
- say("same");
68
- }
69
- }
70
- `
71
-
72
- const result = compile(source, { namespace: 'test' })
73
- const fn = getFileContent(result.files, 'data/test/function/math.mcfunction')
74
- const addMatches = fn.match(/\+= \$const_2 __test/g) ?? []
75
-
76
- expect(addMatches).toHaveLength(1)
77
- expect(fn).toContain('scoreboard players operation $_1 __test = $_0 __test')
78
- })
79
- })
80
-
81
- describe('setblock batching', () => {
82
- test('merges 4 consecutive setblocks into fill', () => {
83
- const source = `
84
- fn build() {
85
- setblock((0, 64, 0), "minecraft:stone");
86
- setblock((1, 64, 0), "minecraft:stone");
87
- setblock((2, 64, 0), "minecraft:stone");
88
- setblock((3, 64, 0), "minecraft:stone");
89
- }
90
- `
91
-
92
- const result = compile(source, { namespace: 'test' })
93
- const fn = getFileContent(result.files, 'data/test/function/build.mcfunction')
94
-
95
- expect(fn).toContain('fill 0 64 0 3 64 0 minecraft:stone')
96
- expect(fn).not.toContain('setblock 1 64 0 minecraft:stone')
97
- })
98
-
99
- test('does not merge setblocks with different blocks', () => {
100
- const source = `
101
- fn build() {
102
- setblock((0, 64, 0), "minecraft:stone");
103
- setblock((1, 64, 0), "minecraft:dirt");
104
- }
105
- `
106
-
107
- const result = compile(source, { namespace: 'test' })
108
- const fn = getFileContent(result.files, 'data/test/function/build.mcfunction')
109
-
110
- expect(fn).toContain('setblock 0 64 0 minecraft:stone')
111
- expect(fn).toContain('setblock 1 64 0 minecraft:dirt')
112
- expect(fn).not.toContain('fill 0 64 0 1 64 0')
113
- })
114
-
115
- test('does not merge non-adjacent setblocks', () => {
116
- const source = `
117
- fn build() {
118
- setblock((0, 64, 0), "minecraft:stone");
119
- setblock((2, 64, 0), "minecraft:stone");
120
- }
121
- `
122
-
123
- const result = compile(source, { namespace: 'test' })
124
- const fn = getFileContent(result.files, 'data/test/function/build.mcfunction')
125
-
126
- expect(fn).toContain('setblock 0 64 0 minecraft:stone')
127
- expect(fn).toContain('setblock 2 64 0 minecraft:stone')
128
- expect(fn).not.toContain('fill 0 64 0 2 64 0')
129
- })
130
-
131
- test('applies batching to structure target output too', () => {
132
- const source = `
133
- fn build() {
134
- setblock((0, 64, 0), "minecraft:stone");
135
- setblock((1, 64, 0), "minecraft:stone");
136
- setblock((2, 64, 0), "minecraft:stone");
137
- }
138
- `
139
-
140
- const result = compileToStructure(source, 'test')
141
-
142
- expect(result.blocks.some(block => block.command === 'fill 0 64 0 2 64 0 minecraft:stone')).toBe(true)
143
- })
144
- })