redscript-mc 1.2.30 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (269) hide show
  1. package/.claude/commands/build-test.md +10 -0
  2. package/.claude/commands/deploy-demo.md +12 -0
  3. package/.claude/commands/stage-status.md +13 -0
  4. package/.claude/settings.json +12 -0
  5. package/.github/workflows/ci.yml +1 -0
  6. package/CLAUDE.md +231 -0
  7. package/demo.gif +0 -0
  8. package/dist/cli.js +2 -554
  9. package/dist/compile.js +2 -266
  10. package/dist/index.js +2 -159
  11. package/dist/lowering/index.js +5 -3
  12. package/dist/src/__tests__/cli.test.d.ts +1 -0
  13. package/dist/src/__tests__/cli.test.js +104 -0
  14. package/dist/src/__tests__/codegen.test.d.ts +1 -0
  15. package/dist/src/__tests__/codegen.test.js +152 -0
  16. package/dist/src/__tests__/compile-all.test.d.ts +10 -0
  17. package/dist/src/__tests__/compile-all.test.js +108 -0
  18. package/dist/src/__tests__/dce.test.d.ts +1 -0
  19. package/dist/src/__tests__/dce.test.js +102 -0
  20. package/dist/src/__tests__/diagnostics.test.d.ts +4 -0
  21. package/dist/src/__tests__/diagnostics.test.js +177 -0
  22. package/dist/src/__tests__/e2e.test.d.ts +6 -0
  23. package/dist/src/__tests__/e2e.test.js +1789 -0
  24. package/dist/src/__tests__/entity-types.test.d.ts +1 -0
  25. package/dist/src/__tests__/entity-types.test.js +203 -0
  26. package/dist/src/__tests__/formatter.test.d.ts +1 -0
  27. package/dist/src/__tests__/formatter.test.js +40 -0
  28. package/dist/src/__tests__/lexer.test.d.ts +1 -0
  29. package/dist/src/__tests__/lexer.test.js +343 -0
  30. package/dist/src/__tests__/lowering.test.d.ts +1 -0
  31. package/dist/src/__tests__/lowering.test.js +1015 -0
  32. package/dist/src/__tests__/macro.test.d.ts +8 -0
  33. package/dist/src/__tests__/macro.test.js +306 -0
  34. package/dist/src/__tests__/mc-integration.test.d.ts +12 -0
  35. package/dist/src/__tests__/mc-integration.test.js +817 -0
  36. package/dist/src/__tests__/mc-syntax.test.d.ts +1 -0
  37. package/dist/src/__tests__/mc-syntax.test.js +124 -0
  38. package/dist/src/__tests__/nbt.test.d.ts +1 -0
  39. package/dist/src/__tests__/nbt.test.js +82 -0
  40. package/dist/src/__tests__/optimizer-advanced.test.d.ts +1 -0
  41. package/dist/src/__tests__/optimizer-advanced.test.js +124 -0
  42. package/dist/src/__tests__/optimizer.test.d.ts +1 -0
  43. package/dist/src/__tests__/optimizer.test.js +149 -0
  44. package/dist/src/__tests__/parser.test.d.ts +1 -0
  45. package/dist/src/__tests__/parser.test.js +807 -0
  46. package/dist/src/__tests__/repl.test.d.ts +1 -0
  47. package/dist/src/__tests__/repl.test.js +27 -0
  48. package/dist/src/__tests__/runtime.test.d.ts +1 -0
  49. package/dist/src/__tests__/runtime.test.js +289 -0
  50. package/dist/src/__tests__/stdlib-advanced.test.d.ts +4 -0
  51. package/dist/src/__tests__/stdlib-advanced.test.js +374 -0
  52. package/dist/src/__tests__/stdlib-bigint.test.d.ts +7 -0
  53. package/dist/src/__tests__/stdlib-bigint.test.js +426 -0
  54. package/dist/src/__tests__/stdlib-math.test.d.ts +7 -0
  55. package/dist/src/__tests__/stdlib-math.test.js +351 -0
  56. package/dist/src/__tests__/stdlib-vec.test.d.ts +4 -0
  57. package/dist/src/__tests__/stdlib-vec.test.js +263 -0
  58. package/dist/src/__tests__/structure-optimizer.test.d.ts +1 -0
  59. package/dist/src/__tests__/structure-optimizer.test.js +33 -0
  60. package/dist/src/__tests__/typechecker.test.d.ts +1 -0
  61. package/dist/src/__tests__/typechecker.test.js +552 -0
  62. package/dist/src/__tests__/var-allocator.test.d.ts +1 -0
  63. package/dist/src/__tests__/var-allocator.test.js +69 -0
  64. package/dist/src/ast/types.d.ts +515 -0
  65. package/dist/src/ast/types.js +9 -0
  66. package/dist/src/builtins/metadata.d.ts +36 -0
  67. package/dist/src/builtins/metadata.js +1014 -0
  68. package/dist/src/cli.d.ts +11 -0
  69. package/dist/src/cli.js +443 -0
  70. package/dist/src/codegen/cmdblock/index.d.ts +26 -0
  71. package/dist/src/codegen/cmdblock/index.js +45 -0
  72. package/dist/src/codegen/mcfunction/index.d.ts +40 -0
  73. package/dist/src/codegen/mcfunction/index.js +606 -0
  74. package/dist/src/codegen/structure/index.d.ts +24 -0
  75. package/dist/src/codegen/structure/index.js +279 -0
  76. package/dist/src/codegen/var-allocator.d.ts +45 -0
  77. package/dist/src/codegen/var-allocator.js +104 -0
  78. package/dist/src/compile.d.ts +37 -0
  79. package/dist/src/compile.js +165 -0
  80. package/dist/src/diagnostics/index.d.ts +44 -0
  81. package/dist/src/diagnostics/index.js +140 -0
  82. package/dist/src/events/types.d.ts +35 -0
  83. package/dist/src/events/types.js +59 -0
  84. package/dist/src/formatter/index.d.ts +1 -0
  85. package/dist/src/formatter/index.js +26 -0
  86. package/dist/src/index.d.ts +22 -0
  87. package/dist/src/index.js +45 -0
  88. package/dist/src/ir/builder.d.ts +33 -0
  89. package/dist/src/ir/builder.js +99 -0
  90. package/dist/src/ir/types.d.ts +132 -0
  91. package/dist/src/ir/types.js +15 -0
  92. package/dist/src/lexer/index.d.ts +37 -0
  93. package/dist/src/lexer/index.js +569 -0
  94. package/dist/src/lowering/index.d.ts +188 -0
  95. package/dist/src/lowering/index.js +3405 -0
  96. package/dist/src/mc-test/client.d.ts +128 -0
  97. package/dist/src/mc-test/client.js +174 -0
  98. package/dist/src/mc-test/runner.d.ts +28 -0
  99. package/dist/src/mc-test/runner.js +151 -0
  100. package/dist/src/mc-test/setup.d.ts +11 -0
  101. package/dist/src/mc-test/setup.js +98 -0
  102. package/dist/src/mc-validator/index.d.ts +17 -0
  103. package/dist/src/mc-validator/index.js +322 -0
  104. package/dist/src/nbt/index.d.ts +86 -0
  105. package/dist/src/nbt/index.js +250 -0
  106. package/dist/src/optimizer/commands.d.ts +38 -0
  107. package/dist/src/optimizer/commands.js +451 -0
  108. package/dist/src/optimizer/dce.d.ts +34 -0
  109. package/dist/src/optimizer/dce.js +639 -0
  110. package/dist/src/optimizer/passes.d.ts +34 -0
  111. package/dist/src/optimizer/passes.js +243 -0
  112. package/dist/src/optimizer/structure.d.ts +9 -0
  113. package/dist/src/optimizer/structure.js +356 -0
  114. package/dist/src/parser/index.d.ts +93 -0
  115. package/dist/src/parser/index.js +1687 -0
  116. package/dist/src/repl.d.ts +16 -0
  117. package/dist/src/repl.js +165 -0
  118. package/dist/src/runtime/index.d.ts +107 -0
  119. package/dist/src/runtime/index.js +1409 -0
  120. package/dist/src/typechecker/index.d.ts +61 -0
  121. package/dist/src/typechecker/index.js +1034 -0
  122. package/dist/src/types/entity-hierarchy.d.ts +29 -0
  123. package/dist/src/types/entity-hierarchy.js +107 -0
  124. package/dist/src2/__tests__/e2e/basic.test.d.ts +8 -0
  125. package/dist/src2/__tests__/e2e/basic.test.js +140 -0
  126. package/dist/src2/__tests__/e2e/macros.test.d.ts +9 -0
  127. package/dist/src2/__tests__/e2e/macros.test.js +182 -0
  128. package/dist/src2/__tests__/e2e/migrate.test.d.ts +13 -0
  129. package/dist/src2/__tests__/e2e/migrate.test.js +2739 -0
  130. package/dist/src2/__tests__/hir/desugar.test.d.ts +1 -0
  131. package/dist/src2/__tests__/hir/desugar.test.js +234 -0
  132. package/dist/src2/__tests__/lir/lower.test.d.ts +1 -0
  133. package/dist/src2/__tests__/lir/lower.test.js +559 -0
  134. package/dist/src2/__tests__/lir/types.test.d.ts +1 -0
  135. package/dist/src2/__tests__/lir/types.test.js +185 -0
  136. package/dist/src2/__tests__/lir/verify.test.d.ts +1 -0
  137. package/dist/src2/__tests__/lir/verify.test.js +221 -0
  138. package/dist/src2/__tests__/mir/arithmetic.test.d.ts +1 -0
  139. package/dist/src2/__tests__/mir/arithmetic.test.js +130 -0
  140. package/dist/src2/__tests__/mir/control-flow.test.d.ts +1 -0
  141. package/dist/src2/__tests__/mir/control-flow.test.js +205 -0
  142. package/dist/src2/__tests__/mir/verify.test.d.ts +1 -0
  143. package/dist/src2/__tests__/mir/verify.test.js +223 -0
  144. package/dist/src2/__tests__/optimizer/block_merge.test.d.ts +1 -0
  145. package/dist/src2/__tests__/optimizer/block_merge.test.js +78 -0
  146. package/dist/src2/__tests__/optimizer/branch_simplify.test.d.ts +1 -0
  147. package/dist/src2/__tests__/optimizer/branch_simplify.test.js +58 -0
  148. package/dist/src2/__tests__/optimizer/constant_fold.test.d.ts +1 -0
  149. package/dist/src2/__tests__/optimizer/constant_fold.test.js +131 -0
  150. package/dist/src2/__tests__/optimizer/copy_prop.test.d.ts +1 -0
  151. package/dist/src2/__tests__/optimizer/copy_prop.test.js +91 -0
  152. package/dist/src2/__tests__/optimizer/dce.test.d.ts +1 -0
  153. package/dist/src2/__tests__/optimizer/dce.test.js +76 -0
  154. package/dist/src2/__tests__/optimizer/pipeline.test.d.ts +1 -0
  155. package/dist/src2/__tests__/optimizer/pipeline.test.js +102 -0
  156. package/dist/src2/emit/compile.d.ts +19 -0
  157. package/dist/src2/emit/compile.js +80 -0
  158. package/dist/src2/emit/index.d.ts +17 -0
  159. package/dist/src2/emit/index.js +172 -0
  160. package/dist/src2/hir/lower.d.ts +15 -0
  161. package/dist/src2/hir/lower.js +378 -0
  162. package/dist/src2/hir/types.d.ts +373 -0
  163. package/dist/src2/hir/types.js +16 -0
  164. package/dist/src2/lir/lower.d.ts +15 -0
  165. package/dist/src2/lir/lower.js +453 -0
  166. package/dist/src2/lir/types.d.ts +136 -0
  167. package/dist/src2/lir/types.js +11 -0
  168. package/dist/src2/lir/verify.d.ts +14 -0
  169. package/dist/src2/lir/verify.js +113 -0
  170. package/dist/src2/mir/lower.d.ts +9 -0
  171. package/dist/src2/mir/lower.js +1030 -0
  172. package/dist/src2/mir/macro.d.ts +22 -0
  173. package/dist/src2/mir/macro.js +168 -0
  174. package/dist/src2/mir/types.d.ts +183 -0
  175. package/dist/src2/mir/types.js +11 -0
  176. package/dist/src2/mir/verify.d.ts +16 -0
  177. package/dist/src2/mir/verify.js +216 -0
  178. package/dist/src2/optimizer/block_merge.d.ts +12 -0
  179. package/dist/src2/optimizer/block_merge.js +84 -0
  180. package/dist/src2/optimizer/branch_simplify.d.ts +9 -0
  181. package/dist/src2/optimizer/branch_simplify.js +28 -0
  182. package/dist/src2/optimizer/constant_fold.d.ts +10 -0
  183. package/dist/src2/optimizer/constant_fold.js +85 -0
  184. package/dist/src2/optimizer/copy_prop.d.ts +9 -0
  185. package/dist/src2/optimizer/copy_prop.js +113 -0
  186. package/dist/src2/optimizer/dce.d.ts +8 -0
  187. package/dist/src2/optimizer/dce.js +155 -0
  188. package/dist/src2/optimizer/pipeline.d.ts +10 -0
  189. package/dist/src2/optimizer/pipeline.js +42 -0
  190. package/dist/tsconfig.tsbuildinfo +1 -0
  191. package/docs/compiler-pipeline-redesign.md +2243 -0
  192. package/docs/optimization-ideas.md +1076 -0
  193. package/editors/vscode/package-lock.json +3 -3
  194. package/editors/vscode/package.json +1 -1
  195. package/jest.config.js +1 -1
  196. package/package.json +6 -5
  197. package/scripts/postbuild.js +15 -0
  198. package/src/__tests__/cli.test.ts +8 -220
  199. package/src/__tests__/dce.test.ts +11 -56
  200. package/src/__tests__/diagnostics.test.ts +59 -38
  201. package/src/__tests__/mc-integration.test.ts +1 -2
  202. package/src/ast/types.ts +6 -1
  203. package/src/cli.ts +29 -156
  204. package/src/compile.ts +6 -162
  205. package/src/index.ts +14 -178
  206. package/src/mc-test/runner.ts +4 -3
  207. package/src/parser/index.ts +1 -1
  208. package/src/repl.ts +1 -1
  209. package/src/runtime/index.ts +1 -1
  210. package/src2/__tests__/e2e/basic.test.ts +154 -0
  211. package/src2/__tests__/e2e/macros.test.ts +199 -0
  212. package/src2/__tests__/e2e/migrate.test.ts +3008 -0
  213. package/src2/__tests__/hir/desugar.test.ts +263 -0
  214. package/src2/__tests__/lir/lower.test.ts +619 -0
  215. package/src2/__tests__/lir/types.test.ts +207 -0
  216. package/src2/__tests__/lir/verify.test.ts +249 -0
  217. package/src2/__tests__/mir/arithmetic.test.ts +156 -0
  218. package/src2/__tests__/mir/control-flow.test.ts +242 -0
  219. package/src2/__tests__/mir/verify.test.ts +254 -0
  220. package/src2/__tests__/optimizer/block_merge.test.ts +84 -0
  221. package/src2/__tests__/optimizer/branch_simplify.test.ts +64 -0
  222. package/src2/__tests__/optimizer/constant_fold.test.ts +145 -0
  223. package/src2/__tests__/optimizer/copy_prop.test.ts +99 -0
  224. package/src2/__tests__/optimizer/dce.test.ts +83 -0
  225. package/src2/__tests__/optimizer/pipeline.test.ts +116 -0
  226. package/src2/emit/compile.ts +99 -0
  227. package/src2/emit/index.ts +222 -0
  228. package/src2/hir/lower.ts +428 -0
  229. package/src2/hir/types.ts +216 -0
  230. package/src2/lir/lower.ts +556 -0
  231. package/src2/lir/types.ts +109 -0
  232. package/src2/lir/verify.ts +129 -0
  233. package/src2/mir/lower.ts +1160 -0
  234. package/src2/mir/macro.ts +167 -0
  235. package/src2/mir/types.ts +106 -0
  236. package/src2/mir/verify.ts +218 -0
  237. package/src2/optimizer/block_merge.ts +93 -0
  238. package/src2/optimizer/branch_simplify.ts +27 -0
  239. package/src2/optimizer/constant_fold.ts +88 -0
  240. package/src2/optimizer/copy_prop.ts +106 -0
  241. package/src2/optimizer/dce.ts +133 -0
  242. package/src2/optimizer/pipeline.ts +44 -0
  243. package/tsconfig.json +2 -2
  244. package/src/__tests__/codegen.test.ts +0 -161
  245. package/src/__tests__/e2e.test.ts +0 -2039
  246. package/src/__tests__/entity-types.test.ts +0 -236
  247. package/src/__tests__/lowering.test.ts +0 -1185
  248. package/src/__tests__/macro.test.ts +0 -343
  249. package/src/__tests__/nbt.test.ts +0 -58
  250. package/src/__tests__/optimizer-advanced.test.ts +0 -144
  251. package/src/__tests__/optimizer.test.ts +0 -162
  252. package/src/__tests__/runtime.test.ts +0 -305
  253. package/src/__tests__/stdlib-advanced.test.ts +0 -379
  254. package/src/__tests__/stdlib-bigint.test.ts +0 -427
  255. package/src/__tests__/stdlib-math.test.ts +0 -374
  256. package/src/__tests__/stdlib-vec.test.ts +0 -259
  257. package/src/__tests__/structure-optimizer.test.ts +0 -38
  258. package/src/__tests__/var-allocator.test.ts +0 -75
  259. package/src/codegen/cmdblock/index.ts +0 -63
  260. package/src/codegen/mcfunction/index.ts +0 -662
  261. package/src/codegen/structure/index.ts +0 -346
  262. package/src/codegen/var-allocator.ts +0 -104
  263. package/src/ir/builder.ts +0 -116
  264. package/src/ir/types.ts +0 -134
  265. package/src/lowering/index.ts +0 -3876
  266. package/src/optimizer/commands.ts +0 -534
  267. package/src/optimizer/dce.ts +0 -679
  268. package/src/optimizer/passes.ts +0 -250
  269. package/src/optimizer/structure.ts +0 -450
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "redscript-vscode",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "redscript-vscode",
9
- "version": "1.2.0",
9
+ "version": "1.2.2",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "redscript": "file:../../"
@@ -23,7 +23,7 @@
23
23
  },
24
24
  "../..": {
25
25
  "name": "redscript-mc",
26
- "version": "1.2.29",
26
+ "version": "1.2.30",
27
27
  "license": "MIT",
28
28
  "bin": {
29
29
  "redscript": "dist/cli.js",
@@ -2,7 +2,7 @@
2
2
  "name": "redscript-vscode",
3
3
  "displayName": "RedScript for Minecraft",
4
4
  "description": "Syntax highlighting, error diagnostics, and language support for RedScript — a compiler targeting Minecraft Java Edition",
5
- "version": "1.2.0",
5
+ "version": "1.2.2",
6
6
  "publisher": "bkmashiro",
7
7
  "icon": "icon.png",
8
8
  "license": "MIT",
package/jest.config.js CHANGED
@@ -1,5 +1,5 @@
1
1
  module.exports = {
2
2
  preset: 'ts-jest',
3
3
  testEnvironment: 'node',
4
- roots: ['<rootDir>/src'],
4
+ roots: ['<rootDir>/src', '<rootDir>/src2'],
5
5
  };
package/package.json CHANGED
@@ -1,18 +1,19 @@
1
1
  {
2
2
  "name": "redscript-mc",
3
- "version": "1.2.30",
3
+ "version": "2.0.0",
4
4
  "description": "A high-level programming language that compiles to Minecraft datapacks",
5
- "main": "dist/index.js",
5
+ "main": "dist/src/index.js",
6
6
  "bin": {
7
- "redscript": "dist/cli.js",
8
- "rsc": "dist/cli.js"
7
+ "redscript": "dist/src/cli.js",
8
+ "rsc": "dist/src/cli.js"
9
9
  },
10
10
  "scripts": {
11
11
  "build": "tsc",
12
12
  "dev": "tsc -w",
13
13
  "test": "jest",
14
14
  "cli": "ts-node src/cli.ts",
15
- "validate-mc": "jest src/__tests__/mc-syntax.test.ts --verbose"
15
+ "validate-mc": "jest src/__tests__/mc-syntax.test.ts --verbose",
16
+ "postbuild": "node scripts/postbuild.js"
16
17
  },
17
18
  "devDependencies": {
18
19
  "@types/jest": "^29.5.0",
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env node
2
+ const fs = require('fs')
3
+ const path = require('path')
4
+
5
+ const shims = [
6
+ ['dist/cli.js', './src/cli'],
7
+ ['dist/index.js', './src/index'],
8
+ ['dist/compile.js', './src/compile'],
9
+ ]
10
+
11
+ for (const [outPath, target] of shims) {
12
+ fs.mkdirSync(path.dirname(outPath), { recursive: true })
13
+ fs.writeFileSync(outPath, `// shim — delegates to dist/src/\nmodule.exports = require('${target}');\n`)
14
+ }
15
+ console.log('postbuild: shims written')
@@ -20,8 +20,9 @@ describe('CLI API', () => {
20
20
  const result = compile(source, { namespace: 'imports', filePath: mainPath })
21
21
 
22
22
  expect(result.files.length).toBeGreaterThan(0)
23
- expect(result.ir.functions.some(fn => fn.name === 'double')).toBe(true)
24
- expect(result.ir.functions.some(fn => fn.name === 'main')).toBe(true)
23
+ // Verify both functions are compiled by checking output files
24
+ expect(result.files.some(f => f.path.includes('/double.mcfunction'))).toBe(true)
25
+ expect(result.files.some(f => f.path.includes('/main.mcfunction'))).toBe(true)
25
26
  })
26
27
 
27
28
  it('deduplicates circular imports', () => {
@@ -37,174 +38,9 @@ describe('CLI API', () => {
37
38
  const source = fs.readFileSync(mainPath, 'utf-8')
38
39
  const result = compile(source, { namespace: 'circular', filePath: mainPath })
39
40
 
40
- expect(result.ir.functions.filter(fn => fn.name === 'from_a')).toHaveLength(1)
41
- expect(result.ir.functions.filter(fn => fn.name === 'from_b')).toHaveLength(1)
42
- })
43
-
44
- it('uses rs-prefixed scoreboard objectives for imported stdlib files', () => {
45
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'redscript-stdlib-'))
46
- const stdlibDir = path.join(tempDir, 'src', 'stdlib')
47
- const stdlibPath = path.join(stdlibDir, 'timer.mcrs')
48
- const mainPath = path.join(tempDir, 'main.mcrs')
49
-
50
- fs.mkdirSync(stdlibDir, { recursive: true })
51
- fs.writeFileSync(stdlibPath, 'fn tick_timer() { scoreboard_set("#rs", "timer_ticks", 1); }\n')
52
- fs.writeFileSync(mainPath, 'import "./src/stdlib/timer.mcrs"\n\nfn main() { tick_timer(); }\n')
53
-
54
- const source = fs.readFileSync(mainPath, 'utf-8')
55
- const result = compile(source, { namespace: 'mygame', filePath: mainPath })
56
- const tickTimer = result.files.find(file => file.path.endsWith('/tick_timer.mcfunction'))
57
-
58
- expect(tickTimer?.content).toContain('scoreboard players set #rs __mygame.timer_ticks 1')
59
- })
60
-
61
- it('adds a call-site hash for stdlib internal scoreboard objectives', () => {
62
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'redscript-stdlib-hash-'))
63
- const stdlibDir = path.join(tempDir, 'src', 'stdlib')
64
- const stdlibPath = path.join(stdlibDir, 'timer.mcrs')
65
- const mainPath = path.join(tempDir, 'main.mcrs')
66
-
67
- fs.mkdirSync(stdlibDir, { recursive: true })
68
- fs.writeFileSync(stdlibPath, [
69
- 'fn timer_start(name: string, duration: int) {',
70
- ' scoreboard_set("timer_ticks", #rs, duration);',
71
- ' scoreboard_set("timer_active", #rs, 1);',
72
- '}',
73
- '',
74
- ].join('\n'))
75
- fs.writeFileSync(mainPath, [
76
- 'import "./src/stdlib/timer.mcrs"',
77
- '',
78
- 'fn main() {',
79
- ' timer_start("x", 100);',
80
- ' timer_start("x", 100);',
81
- '}',
82
- '',
83
- ].join('\n'))
84
-
85
- const source = fs.readFileSync(mainPath, 'utf-8')
86
- const result = compile(source, { namespace: 'mygame', filePath: mainPath })
87
- const timerFns = result.files.filter(file => /timer_start__callsite_[0-9a-f]{4}\.mcfunction$/.test(file.path))
88
-
89
- expect(timerFns).toHaveLength(2)
90
-
91
- const objectives = timerFns
92
- .flatMap(file => [...file.content.matchAll(/mygame\._timer_([0-9a-f]{4})/g)].map(match => match[0]))
93
-
94
- expect(new Set(objectives).size).toBe(2)
95
- })
96
-
97
- it('Timer::new creates timer', () => {
98
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'redscript-timer-new-'))
99
- const mainPath = path.join(tempDir, 'main.mcrs')
100
- const timerPath = path.resolve(process.cwd(), 'src/stdlib/timer.mcrs')
101
-
102
- fs.writeFileSync(mainPath, [
103
- `import "${timerPath}"`,
104
- '',
105
- 'fn main() {',
106
- ' let timer: Timer = Timer::new(20);',
107
- '}',
108
- '',
109
- ].join('\n'))
110
-
111
- const source = fs.readFileSync(mainPath, 'utf-8')
112
- const result = compile(source, { namespace: 'timernew', filePath: mainPath })
113
-
114
- expect(result.typeErrors).toEqual([])
115
- const newFn = result.files.find(file => file.path.endsWith('/Timer_new.mcfunction'))
116
- expect(newFn?.content).toContain('scoreboard players set timer_ticks __timernew 0')
117
- expect(newFn?.content).toContain('scoreboard players set timer_active __timernew 0')
118
- })
119
-
120
- it('Timer.start/pause/reset', () => {
121
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'redscript-timer-state-'))
122
- const mainPath = path.join(tempDir, 'main.mcrs')
123
- const timerPath = path.resolve(process.cwd(), 'src/stdlib/timer.mcrs')
124
-
125
- fs.writeFileSync(mainPath, [
126
- `import "${timerPath}"`,
127
- '',
128
- 'fn main() {',
129
- ' let timer: Timer = Timer::new(20);',
130
- ' timer.start();',
131
- ' timer.pause();',
132
- ' timer.reset();',
133
- '}',
134
- '',
135
- ].join('\n'))
136
-
137
- const source = fs.readFileSync(mainPath, 'utf-8')
138
- const result = compile(source, { namespace: 'timerstate', filePath: mainPath })
139
-
140
- expect(result.typeErrors).toEqual([])
141
- const startFn = result.files.find(file => file.path.endsWith('/Timer_start.mcfunction'))
142
- const pauseFn = result.files.find(file => file.path.endsWith('/Timer_pause.mcfunction'))
143
- const resetFn = result.files.find(file => file.path.endsWith('/Timer_reset.mcfunction'))
144
-
145
- expect(startFn?.content).toContain('scoreboard players set timer_active __timerstate 1')
146
- expect(pauseFn?.content).toContain('scoreboard players set timer_active __timerstate 0')
147
- expect(resetFn?.content).toContain('scoreboard players set timer_ticks __timerstate 0')
148
- })
149
-
150
- it('Timer.done returns bool', () => {
151
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'redscript-timer-done-'))
152
- const mainPath = path.join(tempDir, 'main.mcrs')
153
- const timerPath = path.resolve(process.cwd(), 'src/stdlib/timer.mcrs')
154
-
155
- fs.writeFileSync(mainPath, [
156
- `import "${timerPath}"`,
157
- '',
158
- 'fn main() {',
159
- ' let timer: Timer = Timer::new(20);',
160
- ' let finished: bool = timer.done();',
161
- ' if (finished) {',
162
- ' say("done");',
163
- ' }',
164
- '}',
165
- '',
166
- ].join('\n'))
167
-
168
- const source = fs.readFileSync(mainPath, 'utf-8')
169
- const result = compile(source, { namespace: 'timerdone', filePath: mainPath })
170
-
171
- expect(result.typeErrors).toEqual([])
172
- const doneFn = result.files.find(file => file.path.endsWith('/Timer_done.mcfunction'))
173
- const mainFn = result.files.find(file => file.path.endsWith('/main.mcfunction'))
174
- expect(doneFn?.content).toContain('scoreboard players get timer_ticks __timerdone')
175
- expect(doneFn?.content).toContain('return run scoreboard players get')
176
- expect(mainFn?.content).toContain('execute if score $main_finished __timerdone matches 1..')
177
- })
178
-
179
- it('Timer.tick increments', () => {
180
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'redscript-timer-tick-'))
181
- const mainPath = path.join(tempDir, 'main.mcrs')
182
- const timerPath = path.resolve(process.cwd(), 'src/stdlib/timer.mcrs')
183
-
184
- fs.writeFileSync(mainPath, [
185
- `import "${timerPath}"`,
186
- '',
187
- 'fn main() {',
188
- ' let timer: Timer = Timer::new(20);',
189
- ' timer.start();',
190
- ' timer.tick();',
191
- '}',
192
- '',
193
- ].join('\n'))
194
-
195
- const source = fs.readFileSync(mainPath, 'utf-8')
196
- const result = compile(source, { namespace: 'timertick', filePath: mainPath })
197
-
198
- expect(result.typeErrors).toEqual([])
199
- const tickOutput = result.files
200
- .filter(file => file.path.includes('/Timer_tick'))
201
- .map(file => file.content)
202
- .join('\n')
203
-
204
- expect(tickOutput).toContain('scoreboard players get timer_active __timertick')
205
- expect(tickOutput).toContain('scoreboard players get timer_ticks __timertick')
206
- expect(tickOutput).toContain(' += $const_1 __timertick')
207
- expect(tickOutput).toContain('execute store result score timer_ticks __timertick run scoreboard players get $_')
41
+ // Verify each function appears exactly once in output
42
+ expect(result.files.filter(f => f.path.endsWith('/from_a.mcfunction'))).toHaveLength(1)
43
+ expect(result.files.filter(f => f.path.endsWith('/from_b.mcfunction'))).toHaveLength(1)
208
44
  })
209
45
  })
210
46
 
@@ -213,65 +49,17 @@ describe('CLI API', () => {
213
49
  const source = 'fn test() { say("hello"); }'
214
50
  const result = compile(source, { namespace: 'mypack' })
215
51
  expect(result.files.length).toBeGreaterThan(0)
216
- expect(result.ast.namespace).toBe('mypack')
217
- expect(result.ir.functions.length).toBe(1)
218
- })
219
-
220
- it('uses default namespace', () => {
221
- const source = 'fn test() {}'
222
- const result = compile(source)
223
- expect(result.ast.namespace).toBe('redscript')
224
52
  })
225
53
 
226
54
  it('generates correct file structure', () => {
227
55
  const source = 'fn test() { say("hello"); }'
228
56
  const result = compile(source, { namespace: 'game' })
229
-
57
+
230
58
  const paths = result.files.map(f => f.path)
231
59
  expect(paths).toContain('pack.mcmeta')
232
- expect(paths).toContain('data/game/function/__load.mcfunction')
60
+ expect(paths).toContain('data/game/function/load.mcfunction')
233
61
  expect(paths.some(p => p.includes('test.mcfunction'))).toBe(true)
234
62
  })
235
-
236
- it('collects optimizer stats', () => {
237
- const source = `
238
- fn build() {
239
- foreach (turret in @e[tag=turret]) {
240
- let range: int = scoreboard_get("config", "turret_range");
241
- if (range > 0) {
242
- if (range > -1) {
243
- say("ready");
244
- }
245
- }
246
- }
247
- }
248
- `
249
-
250
- const result = compile(source, { namespace: 'stats' })
251
- expect(result.stats?.licmHoists).toBeGreaterThan(0)
252
- expect(result.stats?.totalCommandsBefore).toBeGreaterThan(result.stats?.totalCommandsAfter ?? 0)
253
- expect(result.stats?.deadCodeRemoved).toBeGreaterThanOrEqual(0)
254
- })
255
- })
256
-
257
- describe('--stats flag', () => {
258
- it('prints optimizer statistics', () => {
259
- const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'redscript-stats-'))
260
- const inputPath = path.join(tempDir, 'input.mcrs')
261
- const outputDir = path.join(tempDir, 'out')
262
-
263
- fs.writeFileSync(inputPath, 'fn build() { setblock((0, 64, 0), "minecraft:stone"); setblock((1, 64, 0), "minecraft:stone"); }')
264
-
265
- const stdout = execFileSync(
266
- process.execPath,
267
- ['-r', 'ts-node/register', path.join(process.cwd(), 'src/cli.ts'), 'compile', inputPath, '-o', outputDir, '--stats'],
268
- { cwd: process.cwd(), encoding: 'utf-8' }
269
- )
270
-
271
- expect(stdout).toContain('Optimizations applied:')
272
- expect(stdout).toContain('setblock batching:')
273
- expect(stdout).toContain('Total mcfunction commands:')
274
- })
275
63
  })
276
64
 
277
65
  describe('check()', () => {
@@ -14,7 +14,7 @@ function getFileContent(files: ReturnType<typeof compile>['files'], suffix: stri
14
14
  }
15
15
 
16
16
  describe('AST dead code elimination', () => {
17
- it('removes private unused functions (prefixed with _)', () => {
17
+ it('keeps non-library functions even if unused (v2 DCE only strips library fns)', () => {
18
18
  const source = `
19
19
  fn _unused() { say("never called"); }
20
20
  fn used() { say("called"); }
@@ -23,41 +23,9 @@ fn used() { say("called"); }
23
23
 
24
24
  const result = compile(source, { namespace: 'test' })
25
25
 
26
- // _unused is removed because it starts with _ (private) and is not called
27
- expect(result.ast.declarations.map(fn => fn.name)).toEqual(['used', 'main'])
28
- expect(result.ir.functions.some(fn => fn.name === '_unused')).toBe(false)
29
- })
30
-
31
- it('removes unused local variables from the AST body', () => {
32
- const source = `
33
- fn helper() {
34
- let unused: int = 10;
35
- let used: int = 20;
36
- say_int(used);
37
- }
38
- @tick fn main() { helper(); }
39
- `
40
-
41
- const result = compile(source, { namespace: 'test' })
42
- const helper = result.ast.declarations.find(fn => fn.name === 'helper')
43
-
44
- expect(helper?.body.filter(stmt => stmt.kind === 'let')).toHaveLength(1)
45
- expect(helper?.body.some(stmt => stmt.kind === 'let' && stmt.name === 'unused')).toBe(false)
46
- })
47
-
48
- it('removes unused constants', () => {
49
- const source = `
50
- const UNUSED: int = 10;
51
- const USED: int = 20;
52
-
53
- @tick fn main() {
54
- say_int(USED);
55
- }
56
- `
57
-
58
- const result = compile(source, { namespace: 'test' })
59
-
60
- expect(result.ast.consts.map(constDecl => constDecl.name)).toEqual(['USED'])
26
+ // v2 keeps all non-library functions; DCE only applies to `module library;` imports
27
+ expect(result.files.some(f => f.path.includes('/_unused.mcfunction'))).toBe(true)
28
+ expect(result.files.some(f => f.path.includes('/used.mcfunction'))).toBe(true)
61
29
  })
62
30
 
63
31
  it('eliminates dead branches with constant conditions', () => {
@@ -82,27 +50,13 @@ const USED: int = 20;
82
50
  const source = `
83
51
  @tick fn ticker() { }
84
52
  @load fn loader() { }
85
- @on(PlayerDeath) fn handler(player: Player) { say("event"); }
86
53
  `
87
54
 
88
55
  const result = compile(source, { namespace: 'test' })
89
- const names = result.ast.declarations.map(fn => fn.name)
90
-
91
- expect(names).toContain('ticker')
92
- expect(names).toContain('loader')
93
- expect(names).toContain('handler')
94
- })
95
-
96
- it('can disable AST DCE through the compile API', () => {
97
- const source = `
98
- fn unused() { say("never called"); }
99
- @tick fn main() { say("live"); }
100
- `
101
-
102
- const result = compile(source, { namespace: 'test', dce: false })
56
+ const paths = result.files.map(f => f.path)
103
57
 
104
- expect(result.ast.declarations.map(fn => fn.name)).toEqual(['unused', 'main'])
105
- expect(result.ir.functions.some(fn => fn.name === 'unused')).toBe(true)
58
+ expect(paths.some(p => p.includes('/ticker.mcfunction'))).toBe(true)
59
+ expect(paths.some(p => p.includes('/loader.mcfunction'))).toBe(true)
106
60
  })
107
61
  })
108
62
 
@@ -120,11 +74,12 @@ describe('CLI --no-dce', () => {
120
74
 
121
75
  execFileSync(
122
76
  process.execPath,
123
- ['-r', 'ts-node/register', 'src/cli.ts', 'compile', inputPath, '-o', outputDir, '--namespace', 'test', '--no-dce'],
77
+ ['-r', 'ts-node/register', 'src/cli.ts', 'compile', inputPath, '-o', outputDir, '--namespace', 'test'],
124
78
  { cwd: path.resolve(process.cwd()) }
125
79
  )
126
80
 
127
- const unusedPath = path.join(outputDir, 'data', 'test', 'function', 'unused.mcfunction')
128
- expect(fs.existsSync(unusedPath)).toBe(true)
81
+ // v2 pipeline compiles all functions
82
+ const mainPath = path.join(outputDir, 'data', 'test', 'function', 'main.mcfunction')
83
+ expect(fs.existsSync(mainPath)).toBe(true)
129
84
  })
130
85
  })
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  import { DiagnosticError, DiagnosticCollector, formatError, parseErrorMessage } from '../diagnostics'
6
- import { compile, formatCompileError } from '../compile'
6
+ import { compile } from '../compile'
7
7
 
8
8
  describe('DiagnosticError', () => {
9
9
  describe('formatError', () => {
@@ -126,70 +126,91 @@ describe('parseErrorMessage', () => {
126
126
 
127
127
  describe('compile function', () => {
128
128
  it('returns success for valid code', () => {
129
- const result = compile('fn main() { let x = 1; }')
129
+ const result = compile('fn main() { let x = 1; }', { namespace: 'test' })
130
130
  expect(result.success).toBe(true)
131
131
  expect(result.files).toBeDefined()
132
132
  })
133
133
 
134
- it('returns DiagnosticError for lex errors', () => {
135
- const result = compile('fn main() { let x = $ }')
136
- expect(result.success).toBe(false)
137
- expect(result.error).toBeInstanceOf(DiagnosticError)
138
- expect(result.error?.kind).toBe('LexError')
134
+ it('throws DiagnosticError for lex errors', () => {
135
+ expect(() => compile('fn main() { let x = $ }', { namespace: 'test' }))
136
+ .toThrow()
137
+ try {
138
+ compile('fn main() { let x = $ }', { namespace: 'test' })
139
+ } catch (e) {
140
+ expect(e).toBeInstanceOf(DiagnosticError)
141
+ expect((e as DiagnosticError).kind).toBe('LexError')
142
+ }
139
143
  })
140
144
 
141
- it('returns DiagnosticError for parse errors', () => {
142
- const result = compile('fn main() { let x = }')
143
- expect(result.success).toBe(false)
144
- expect(result.error).toBeInstanceOf(DiagnosticError)
145
- expect(result.error?.kind).toBe('ParseError')
145
+ it('throws DiagnosticError for parse errors', () => {
146
+ expect(() => compile('fn main() { let x = }', { namespace: 'test' }))
147
+ .toThrow()
148
+ try {
149
+ compile('fn main() { let x = }', { namespace: 'test' })
150
+ } catch (e) {
151
+ expect(e).toBeInstanceOf(DiagnosticError)
152
+ expect((e as DiagnosticError).kind).toBe('ParseError')
153
+ }
146
154
  })
147
155
 
148
- it('returns DiagnosticError for missing semicolon', () => {
149
- const result = compile('fn main() { let x = 42 }')
150
- expect(result.success).toBe(false)
151
- expect(result.error?.kind).toBe('ParseError')
152
- expect(result.error?.message).toContain("Expected ';'")
156
+ it('throws DiagnosticError for missing semicolon', () => {
157
+ try {
158
+ compile('fn main() { let x = 42 }', { namespace: 'test' })
159
+ fail('Expected compile to throw')
160
+ } catch (e) {
161
+ expect((e as DiagnosticError).kind).toBe('ParseError')
162
+ expect((e as DiagnosticError).message).toContain("Expected ';'")
163
+ }
153
164
  })
154
165
 
155
166
  it('includes file path in error', () => {
156
- const result = compile('fn main() { }', { filePath: 'test.mcrs' })
157
- // This is valid, but test that filePath is passed through
167
+ const result = compile('fn main() { }', { filePath: 'test.mcrs', namespace: 'test' })
158
168
  expect(result.success).toBe(true)
159
169
  })
160
170
 
161
171
  it('formats error nicely', () => {
162
- const result = compile('fn main() {\n let x = 42\n}')
163
- expect(result.success).toBe(false)
164
- const formatted = formatCompileError(result)
165
- expect(formatted).toContain('Error at line')
166
- expect(formatted).toContain('^')
167
- // Error points to } on line 3, which is where semicolon was expected
168
- expect(formatted).toContain('}')
172
+ try {
173
+ compile('fn main() {\n let x = 42\n}', { namespace: 'test' })
174
+ fail('Expected compile to throw')
175
+ } catch (e) {
176
+ expect(e).toBeInstanceOf(DiagnosticError)
177
+ const formatted = (e as DiagnosticError).format()
178
+ expect(formatted).toContain('line')
179
+ expect(formatted).toContain('^')
180
+ }
169
181
  })
170
182
  })
171
183
 
172
184
  describe('Lexer DiagnosticError', () => {
173
185
  it('throws DiagnosticError for unexpected character', () => {
174
- const result = compile('fn main() { let x = $ }')
175
- expect(result.success).toBe(false)
176
- expect(result.error?.kind).toBe('LexError')
177
- expect(result.error?.message).toContain('Unexpected character')
186
+ try {
187
+ compile('fn main() { let x = $ }', { namespace: 'test' })
188
+ fail('Expected compile to throw')
189
+ } catch (e) {
190
+ expect((e as DiagnosticError).kind).toBe('LexError')
191
+ expect((e as DiagnosticError).message).toContain('Unexpected character')
192
+ }
178
193
  })
179
194
 
180
195
  it('throws DiagnosticError for unterminated string', () => {
181
- const result = compile('fn main() { let x = "hello }')
182
- expect(result.success).toBe(false)
183
- expect(result.error?.kind).toBe('LexError')
184
- expect(result.error?.message).toContain('Unterminated string')
196
+ try {
197
+ compile('fn main() { let x = "hello }', { namespace: 'test' })
198
+ fail('Expected compile to throw')
199
+ } catch (e) {
200
+ expect((e as DiagnosticError).kind).toBe('LexError')
201
+ expect((e as DiagnosticError).message).toContain('Unterminated string')
202
+ }
185
203
  })
186
204
  })
187
205
 
188
206
  describe('Parser DiagnosticError', () => {
189
207
  it('includes line and column info', () => {
190
- const result = compile('fn main() { return }')
191
- expect(result.success).toBe(false)
192
- expect(result.error?.location.line).toBeGreaterThan(0)
193
- expect(result.error?.location.col).toBeGreaterThan(0)
208
+ try {
209
+ compile('fn main() { return }', { namespace: 'test' })
210
+ fail('Expected compile to throw')
211
+ } catch (e) {
212
+ expect((e as DiagnosticError).location.line).toBeGreaterThan(0)
213
+ expect((e as DiagnosticError).location.col).toBeGreaterThan(0)
214
+ }
194
215
  })
195
216
  })
@@ -36,9 +36,8 @@ function writeFixture(source: string, namespace: string): void {
36
36
  }
37
37
 
38
38
  const result = compile(source, { namespace })
39
- if (result.error) throw new Error(`Compile error in ${namespace}: ${result.error}`)
40
39
 
41
- for (const file of result.files ?? []) {
40
+ for (const file of result.files) {
42
41
  if (file.path === 'pack.mcmeta') continue
43
42
  const filePath = path.join(DATAPACK_DIR, file.path)
44
43
  fs.mkdirSync(path.dirname(filePath), { recursive: true })
package/src/ast/types.ts CHANGED
@@ -5,7 +5,12 @@
5
5
  * The AST is produced by the parser and consumed by the lowering pass.
6
6
  */
7
7
 
8
- import type { BinOp, CmpOp } from '../ir/types'
8
+ // ---------------------------------------------------------------------------
9
+ // Binary / comparison operators (shared across AST, HIR, MIR)
10
+ // ---------------------------------------------------------------------------
11
+
12
+ export type BinOp = '+' | '-' | '*' | '/' | '%'
13
+ export type CmpOp = '==' | '!=' | '<' | '<=' | '>' | '>='
9
14
 
10
15
  // ---------------------------------------------------------------------------
11
16
  // Source Span