redscript-mc 1.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 (272) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +31 -0
  3. package/.github/ISSUE_TEMPLATE/wrong_output.md +33 -0
  4. package/.github/PULL_REQUEST_TEMPLATE.md +34 -0
  5. package/.github/workflows/ci.yml +29 -0
  6. package/.github/workflows/publish-extension.yml +35 -0
  7. package/LICENSE +21 -0
  8. package/README.md +261 -0
  9. package/README.zh.md +261 -0
  10. package/dist/__tests__/cli.test.d.ts +1 -0
  11. package/dist/__tests__/cli.test.js +140 -0
  12. package/dist/__tests__/codegen.test.d.ts +1 -0
  13. package/dist/__tests__/codegen.test.js +121 -0
  14. package/dist/__tests__/diagnostics.test.d.ts +4 -0
  15. package/dist/__tests__/diagnostics.test.js +149 -0
  16. package/dist/__tests__/e2e.test.d.ts +6 -0
  17. package/dist/__tests__/e2e.test.js +1528 -0
  18. package/dist/__tests__/lexer.test.d.ts +1 -0
  19. package/dist/__tests__/lexer.test.js +316 -0
  20. package/dist/__tests__/lowering.test.d.ts +1 -0
  21. package/dist/__tests__/lowering.test.js +819 -0
  22. package/dist/__tests__/mc-integration.test.d.ts +12 -0
  23. package/dist/__tests__/mc-integration.test.js +395 -0
  24. package/dist/__tests__/mc-syntax.test.d.ts +1 -0
  25. package/dist/__tests__/mc-syntax.test.js +112 -0
  26. package/dist/__tests__/nbt.test.d.ts +1 -0
  27. package/dist/__tests__/nbt.test.js +82 -0
  28. package/dist/__tests__/optimizer-advanced.test.d.ts +1 -0
  29. package/dist/__tests__/optimizer-advanced.test.js +124 -0
  30. package/dist/__tests__/optimizer.test.d.ts +1 -0
  31. package/dist/__tests__/optimizer.test.js +118 -0
  32. package/dist/__tests__/parser.test.d.ts +1 -0
  33. package/dist/__tests__/parser.test.js +717 -0
  34. package/dist/__tests__/repl.test.d.ts +1 -0
  35. package/dist/__tests__/repl.test.js +27 -0
  36. package/dist/__tests__/runtime.test.d.ts +1 -0
  37. package/dist/__tests__/runtime.test.js +276 -0
  38. package/dist/__tests__/structure-optimizer.test.d.ts +1 -0
  39. package/dist/__tests__/structure-optimizer.test.js +33 -0
  40. package/dist/__tests__/typechecker.test.d.ts +1 -0
  41. package/dist/__tests__/typechecker.test.js +364 -0
  42. package/dist/ast/types.d.ts +357 -0
  43. package/dist/ast/types.js +9 -0
  44. package/dist/cli.d.ts +11 -0
  45. package/dist/cli.js +407 -0
  46. package/dist/codegen/cmdblock/index.d.ts +26 -0
  47. package/dist/codegen/cmdblock/index.js +45 -0
  48. package/dist/codegen/mcfunction/index.d.ts +34 -0
  49. package/dist/codegen/mcfunction/index.js +413 -0
  50. package/dist/codegen/structure/index.d.ts +18 -0
  51. package/dist/codegen/structure/index.js +249 -0
  52. package/dist/compile.d.ts +30 -0
  53. package/dist/compile.js +152 -0
  54. package/dist/data/arena/function/__load.mcfunction +6 -0
  55. package/dist/data/arena/function/__tick.mcfunction +2 -0
  56. package/dist/data/arena/function/announce_leaders/else_1.mcfunction +3 -0
  57. package/dist/data/arena/function/announce_leaders/foreach_0/merge_2.mcfunction +1 -0
  58. package/dist/data/arena/function/announce_leaders/foreach_0/then_0.mcfunction +3 -0
  59. package/dist/data/arena/function/announce_leaders/foreach_0.mcfunction +7 -0
  60. package/dist/data/arena/function/announce_leaders/foreach_1/merge_2.mcfunction +1 -0
  61. package/dist/data/arena/function/announce_leaders/foreach_1/then_0.mcfunction +4 -0
  62. package/dist/data/arena/function/announce_leaders/foreach_1.mcfunction +6 -0
  63. package/dist/data/arena/function/announce_leaders/merge_2.mcfunction +1 -0
  64. package/dist/data/arena/function/announce_leaders/then_0.mcfunction +4 -0
  65. package/dist/data/arena/function/announce_leaders.mcfunction +6 -0
  66. package/dist/data/arena/function/arena_tick/merge_2.mcfunction +1 -0
  67. package/dist/data/arena/function/arena_tick/then_0.mcfunction +4 -0
  68. package/dist/data/arena/function/arena_tick.mcfunction +11 -0
  69. package/dist/data/counter/function/__load.mcfunction +5 -0
  70. package/dist/data/counter/function/__tick.mcfunction +2 -0
  71. package/dist/data/counter/function/counter_tick/merge_2.mcfunction +1 -0
  72. package/dist/data/counter/function/counter_tick/then_0.mcfunction +3 -0
  73. package/dist/data/counter/function/counter_tick.mcfunction +11 -0
  74. package/dist/data/minecraft/tags/function/load.json +5 -0
  75. package/dist/data/minecraft/tags/function/tick.json +5 -0
  76. package/dist/data/quiz/function/__load.mcfunction +16 -0
  77. package/dist/data/quiz/function/__tick.mcfunction +6 -0
  78. package/dist/data/quiz/function/__trigger_quiz_a_dispatch.mcfunction +4 -0
  79. package/dist/data/quiz/function/__trigger_quiz_b_dispatch.mcfunction +4 -0
  80. package/dist/data/quiz/function/__trigger_quiz_c_dispatch.mcfunction +4 -0
  81. package/dist/data/quiz/function/__trigger_quiz_start_dispatch.mcfunction +4 -0
  82. package/dist/data/quiz/function/answer_a.mcfunction +4 -0
  83. package/dist/data/quiz/function/answer_b.mcfunction +4 -0
  84. package/dist/data/quiz/function/answer_c.mcfunction +4 -0
  85. package/dist/data/quiz/function/ask_question/else_1.mcfunction +5 -0
  86. package/dist/data/quiz/function/ask_question/else_4.mcfunction +5 -0
  87. package/dist/data/quiz/function/ask_question/else_7.mcfunction +4 -0
  88. package/dist/data/quiz/function/ask_question/merge_2.mcfunction +1 -0
  89. package/dist/data/quiz/function/ask_question/merge_5.mcfunction +2 -0
  90. package/dist/data/quiz/function/ask_question/merge_8.mcfunction +2 -0
  91. package/dist/data/quiz/function/ask_question/then_0.mcfunction +4 -0
  92. package/dist/data/quiz/function/ask_question/then_3.mcfunction +4 -0
  93. package/dist/data/quiz/function/ask_question/then_6.mcfunction +4 -0
  94. package/dist/data/quiz/function/ask_question.mcfunction +7 -0
  95. package/dist/data/quiz/function/finish_quiz.mcfunction +6 -0
  96. package/dist/data/quiz/function/handle_answer/else_1.mcfunction +5 -0
  97. package/dist/data/quiz/function/handle_answer/else_10.mcfunction +3 -0
  98. package/dist/data/quiz/function/handle_answer/else_16.mcfunction +3 -0
  99. package/dist/data/quiz/function/handle_answer/else_4.mcfunction +3 -0
  100. package/dist/data/quiz/function/handle_answer/else_7.mcfunction +5 -0
  101. package/dist/data/quiz/function/handle_answer/merge_11.mcfunction +2 -0
  102. package/dist/data/quiz/function/handle_answer/merge_14.mcfunction +2 -0
  103. package/dist/data/quiz/function/handle_answer/merge_17.mcfunction +2 -0
  104. package/dist/data/quiz/function/handle_answer/merge_2.mcfunction +8 -0
  105. package/dist/data/quiz/function/handle_answer/merge_5.mcfunction +2 -0
  106. package/dist/data/quiz/function/handle_answer/merge_8.mcfunction +2 -0
  107. package/dist/data/quiz/function/handle_answer/then_0.mcfunction +5 -0
  108. package/dist/data/quiz/function/handle_answer/then_12.mcfunction +5 -0
  109. package/dist/data/quiz/function/handle_answer/then_15.mcfunction +6 -0
  110. package/dist/data/quiz/function/handle_answer/then_3.mcfunction +6 -0
  111. package/dist/data/quiz/function/handle_answer/then_6.mcfunction +5 -0
  112. package/dist/data/quiz/function/handle_answer/then_9.mcfunction +6 -0
  113. package/dist/data/quiz/function/handle_answer.mcfunction +11 -0
  114. package/dist/data/quiz/function/start_quiz.mcfunction +5 -0
  115. package/dist/data/shop/function/__load.mcfunction +7 -0
  116. package/dist/data/shop/function/__tick.mcfunction +3 -0
  117. package/dist/data/shop/function/__trigger_shop_buy_dispatch.mcfunction +4 -0
  118. package/dist/data/shop/function/complete_purchase/else_1.mcfunction +5 -0
  119. package/dist/data/shop/function/complete_purchase/else_4.mcfunction +5 -0
  120. package/dist/data/shop/function/complete_purchase/else_7.mcfunction +3 -0
  121. package/dist/data/shop/function/complete_purchase/merge_2.mcfunction +2 -0
  122. package/dist/data/shop/function/complete_purchase/merge_5.mcfunction +2 -0
  123. package/dist/data/shop/function/complete_purchase/merge_8.mcfunction +2 -0
  124. package/dist/data/shop/function/complete_purchase/then_0.mcfunction +4 -0
  125. package/dist/data/shop/function/complete_purchase/then_3.mcfunction +4 -0
  126. package/dist/data/shop/function/complete_purchase/then_6.mcfunction +4 -0
  127. package/dist/data/shop/function/complete_purchase.mcfunction +7 -0
  128. package/dist/data/shop/function/handle_shop_trigger.mcfunction +3 -0
  129. package/dist/data/turret/function/__load.mcfunction +5 -0
  130. package/dist/data/turret/function/__tick.mcfunction +4 -0
  131. package/dist/data/turret/function/__trigger_deploy_turret_dispatch.mcfunction +4 -0
  132. package/dist/data/turret/function/deploy_turret.mcfunction +8 -0
  133. package/dist/data/turret/function/turret_tick/at_1.mcfunction +2 -0
  134. package/dist/data/turret/function/turret_tick/foreach_0.mcfunction +2 -0
  135. package/dist/data/turret/function/turret_tick/foreach_2.mcfunction +2 -0
  136. package/dist/data/turret/function/turret_tick/tick_body.mcfunction +3 -0
  137. package/dist/data/turret/function/turret_tick/tick_skip.mcfunction +1 -0
  138. package/dist/data/turret/function/turret_tick.mcfunction +5 -0
  139. package/dist/diagnostics/index.d.ts +44 -0
  140. package/dist/diagnostics/index.js +140 -0
  141. package/dist/index.d.ts +53 -0
  142. package/dist/index.js +126 -0
  143. package/dist/ir/builder.d.ts +32 -0
  144. package/dist/ir/builder.js +99 -0
  145. package/dist/ir/types.d.ts +117 -0
  146. package/dist/ir/types.js +15 -0
  147. package/dist/lexer/index.d.ts +36 -0
  148. package/dist/lexer/index.js +458 -0
  149. package/dist/lowering/index.d.ts +106 -0
  150. package/dist/lowering/index.js +2041 -0
  151. package/dist/mc-test/client.d.ts +128 -0
  152. package/dist/mc-test/client.js +174 -0
  153. package/dist/mc-test/runner.d.ts +28 -0
  154. package/dist/mc-test/runner.js +150 -0
  155. package/dist/mc-test/setup.d.ts +11 -0
  156. package/dist/mc-test/setup.js +98 -0
  157. package/dist/mc-validator/index.d.ts +17 -0
  158. package/dist/mc-validator/index.js +322 -0
  159. package/dist/nbt/index.d.ts +86 -0
  160. package/dist/nbt/index.js +250 -0
  161. package/dist/optimizer/commands.d.ts +36 -0
  162. package/dist/optimizer/commands.js +349 -0
  163. package/dist/optimizer/passes.d.ts +34 -0
  164. package/dist/optimizer/passes.js +227 -0
  165. package/dist/optimizer/structure.d.ts +8 -0
  166. package/dist/optimizer/structure.js +344 -0
  167. package/dist/pack.mcmeta +6 -0
  168. package/dist/parser/index.d.ts +76 -0
  169. package/dist/parser/index.js +1193 -0
  170. package/dist/repl.d.ts +16 -0
  171. package/dist/repl.js +165 -0
  172. package/dist/runtime/index.d.ts +101 -0
  173. package/dist/runtime/index.js +1288 -0
  174. package/dist/typechecker/index.d.ts +42 -0
  175. package/dist/typechecker/index.js +629 -0
  176. package/docs/COMPILATION_STATS.md +142 -0
  177. package/docs/IMPLEMENTATION_GUIDE.md +512 -0
  178. package/docs/LANGUAGE_REFERENCE.md +415 -0
  179. package/docs/MC_MAPPING.md +280 -0
  180. package/docs/STRUCTURE_TARGET.md +80 -0
  181. package/docs/mc-reference/commands.md +259 -0
  182. package/editors/vscode/.vscodeignore +10 -0
  183. package/editors/vscode/LICENSE +21 -0
  184. package/editors/vscode/README.md +78 -0
  185. package/editors/vscode/build.mjs +28 -0
  186. package/editors/vscode/icon.png +0 -0
  187. package/editors/vscode/mcfunction-language-configuration.json +28 -0
  188. package/editors/vscode/out/extension.js +7236 -0
  189. package/editors/vscode/package-lock.json +566 -0
  190. package/editors/vscode/package.json +137 -0
  191. package/editors/vscode/redscript-language-configuration.json +28 -0
  192. package/editors/vscode/snippets/redscript.json +114 -0
  193. package/editors/vscode/src/codeactions.ts +89 -0
  194. package/editors/vscode/src/completion.ts +130 -0
  195. package/editors/vscode/src/extension.ts +239 -0
  196. package/editors/vscode/src/hover.ts +1120 -0
  197. package/editors/vscode/src/symbols.ts +207 -0
  198. package/editors/vscode/syntaxes/mcfunction.tmLanguage.json +740 -0
  199. package/editors/vscode/syntaxes/redscript.tmLanguage.json +357 -0
  200. package/editors/vscode/tsconfig.json +13 -0
  201. package/jest.config.js +5 -0
  202. package/package.json +38 -0
  203. package/src/__tests__/cli.test.ts +130 -0
  204. package/src/__tests__/codegen.test.ts +128 -0
  205. package/src/__tests__/diagnostics.test.ts +195 -0
  206. package/src/__tests__/e2e.test.ts +1721 -0
  207. package/src/__tests__/fixtures/mc-commands-1.21.4.json +18734 -0
  208. package/src/__tests__/formatter.test.ts +46 -0
  209. package/src/__tests__/lexer.test.ts +356 -0
  210. package/src/__tests__/lowering.test.ts +962 -0
  211. package/src/__tests__/mc-integration.test.ts +409 -0
  212. package/src/__tests__/mc-syntax.test.ts +96 -0
  213. package/src/__tests__/nbt.test.ts +58 -0
  214. package/src/__tests__/optimizer-advanced.test.ts +144 -0
  215. package/src/__tests__/optimizer.test.ts +129 -0
  216. package/src/__tests__/parser.test.ts +800 -0
  217. package/src/__tests__/repl.test.ts +33 -0
  218. package/src/__tests__/runtime.test.ts +289 -0
  219. package/src/__tests__/structure-optimizer.test.ts +38 -0
  220. package/src/__tests__/typechecker.test.ts +395 -0
  221. package/src/ast/types.ts +248 -0
  222. package/src/cli.ts +445 -0
  223. package/src/codegen/cmdblock/index.ts +63 -0
  224. package/src/codegen/mcfunction/index.ts +471 -0
  225. package/src/codegen/structure/index.ts +305 -0
  226. package/src/compile.ts +188 -0
  227. package/src/diagnostics/index.ts +186 -0
  228. package/src/examples/README.md +77 -0
  229. package/src/examples/SHOWCASE_GAME.md +43 -0
  230. package/src/examples/arena.rs +44 -0
  231. package/src/examples/counter.rs +12 -0
  232. package/src/examples/pvp_arena.rs +131 -0
  233. package/src/examples/quiz.rs +90 -0
  234. package/src/examples/rpg.rs +13 -0
  235. package/src/examples/shop.rs +30 -0
  236. package/src/examples/showcase_game.rs +552 -0
  237. package/src/examples/stdlib_demo.rs +181 -0
  238. package/src/examples/turret.rs +27 -0
  239. package/src/examples/world_manager.rs +23 -0
  240. package/src/formatter/index.ts +22 -0
  241. package/src/index.ts +161 -0
  242. package/src/ir/builder.ts +114 -0
  243. package/src/ir/types.ts +119 -0
  244. package/src/lexer/index.ts +555 -0
  245. package/src/lowering/index.ts +2406 -0
  246. package/src/mc-test/client.ts +259 -0
  247. package/src/mc-test/runner.ts +140 -0
  248. package/src/mc-test/setup.ts +70 -0
  249. package/src/mc-validator/index.ts +367 -0
  250. package/src/nbt/index.ts +321 -0
  251. package/src/optimizer/commands.ts +416 -0
  252. package/src/optimizer/passes.ts +233 -0
  253. package/src/optimizer/structure.ts +441 -0
  254. package/src/parser/index.ts +1437 -0
  255. package/src/repl.ts +165 -0
  256. package/src/runtime/index.ts +1403 -0
  257. package/src/stdlib/README.md +156 -0
  258. package/src/stdlib/combat.rs +20 -0
  259. package/src/stdlib/cooldown.rs +45 -0
  260. package/src/stdlib/math.rs +49 -0
  261. package/src/stdlib/mobs.rs +99 -0
  262. package/src/stdlib/player.rs +29 -0
  263. package/src/stdlib/strings.rs +7 -0
  264. package/src/stdlib/timer.rs +51 -0
  265. package/src/templates/README.md +126 -0
  266. package/src/templates/combat.rs +96 -0
  267. package/src/templates/economy.rs +40 -0
  268. package/src/templates/mini-game-framework.rs +117 -0
  269. package/src/templates/quest.rs +78 -0
  270. package/src/test_programs/zombie_game.rs +25 -0
  271. package/src/typechecker/index.ts +737 -0
  272. package/tsconfig.json +16 -0
package/README.zh.md ADDED
@@ -0,0 +1,261 @@
1
+ <div align="center">
2
+
3
+ <img src="https://img.shields.io/badge/RedScript-1.0-red?style=for-the-badge&logo=minecraft&logoColor=white" alt="RedScript" />
4
+
5
+ **一个编译到 Minecraft Datapack 的类型化脚本语言。**
6
+
7
+ 写干净的游戏逻辑,把记分板的面条代码交给 RedScript 处理。
8
+
9
+ [![CI](https://github.com/bkmashiro/redscript/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/bkmashiro/redscript/actions/workflows/ci.yml)
10
+ [![npm](https://img.shields.io/npm/v/redscript-mc?color=cb3837)](https://www.npmjs.com/package/redscript-mc)
11
+ [![license](https://img.shields.io/badge/license-MIT-blue)](./LICENSE)
12
+ [![tests](https://img.shields.io/badge/tests-423%20passing-brightgreen)](./src/__tests__)
13
+
14
+ [English](./README.md) · [Wiki](https://github.com/bkmashiro/redscript/wiki) · [快速开始](#快速开始)
15
+
16
+ </div>
17
+
18
+ ---
19
+
20
+ ### RedScript 是什么?
21
+
22
+ 你想做一个 Minecraft 小游戏——倒计时、击杀计数、复活逻辑、记分板显示。用原版 MC 的话,这意味着 40+ 个 `.mcfunction` 文件、几百条 `execute if score` 命令,还要花一个周末调试。
23
+
24
+ 用 RedScript,就是这样:
25
+
26
+ ```rs
27
+ // pvp_game.rs
28
+ import "stdlib/player.rs"
29
+
30
+ const GAME_TIME: int = 300;
31
+
32
+ @tick(rate=20)
33
+ fn every_second() {
34
+ let time: int = scoreboard_get(#game, #timer);
35
+
36
+ if (time <= 0) {
37
+ end_game();
38
+ return;
39
+ }
40
+
41
+ scoreboard_set(#game, #timer, time - 1);
42
+ actionbar(@a, "⏱ 剩余 ${time} 秒");
43
+ }
44
+
45
+ fn start_game() {
46
+ scoreboard_set(#game, #timer, GAME_TIME);
47
+ scoreboard_set(#game, #running, 1);
48
+ title(@a, "开始战斗!", "游戏已开始");
49
+ tp(@a, (0, 64, 0));
50
+ }
51
+
52
+ fn end_game() {
53
+ scoreboard_set(#game, #running, 0);
54
+ title(@a, "游戏结束!");
55
+ announce("感谢游玩!");
56
+ }
57
+
58
+ @on_death
59
+ fn on_kill() {
60
+ scoreboard_add(@s, #kills, 1);
61
+ }
62
+ ```
63
+
64
+ 一个文件,几秒钟编译出可以直接用的 datapack。
65
+
66
+ ---
67
+
68
+ ### 快速开始
69
+
70
+ #### 安装
71
+
72
+ ```bash
73
+ npm install -g redscript
74
+ ```
75
+
76
+ #### 编译
77
+
78
+ ```bash
79
+ redscript compile pvp_game.rs -o ./my-datapack
80
+ ```
81
+
82
+ ```
83
+ ✓ 已编译 pvp_game.rs
84
+ 命名空间 : pvp_game
85
+ 函数数量 : 7
86
+ 命令数量 : 34 → 28 (优化器节省了 18%)
87
+ 输出目录 : ./my-datapack/
88
+ ```
89
+
90
+ #### 部署
91
+
92
+ 把输出文件夹丢进存档的 `datapacks/` 目录,游戏内跑 `/reload`,完成。
93
+
94
+ ---
95
+
96
+ ### 语言特性
97
+
98
+ #### 变量与类型
99
+
100
+ ```rs
101
+ let x: int = 42;
102
+ let name: string = "Steve";
103
+ let spawn: BlockPos = (0, 64, 0);
104
+ let nearby: BlockPos = (~5, ~0, ~5); // 相对坐标
105
+ const MAX: int = 100; // 编译期常量
106
+ ```
107
+
108
+ #### MC 名称(Objective / Tag / Team)
109
+
110
+ 用 `#name` 表示 Minecraft 标识符,不需要引号:
111
+
112
+ ```rs
113
+ // Objective、fake player、tag、team 名都不用引号
114
+ let hp: int = scoreboard_get(@s, #health);
115
+ scoreboard_set(#game, #timer, 300); // fake player #game,objective timer
116
+ tag_add(@s, #hasKey);
117
+ team_join(#red, @s);
118
+ gamerule(#keepInventory, true);
119
+
120
+ // 字符串仍然兼容(向后兼容)
121
+ scoreboard_get(@s, "health") // 和 #health 编译结果相同
122
+ ```
123
+
124
+ #### 函数与默认参数
125
+
126
+ ```rs
127
+ fn greet(player: selector, msg: string = "欢迎!") {
128
+ tell(player, msg);
129
+ }
130
+
131
+ greet(@s); // 使用默认消息
132
+ greet(@a, "你好!"); // 覆盖默认值
133
+ ```
134
+
135
+ #### 装饰器
136
+
137
+ ```rs
138
+ @tick // 每 tick 执行
139
+ fn heartbeat() { ... }
140
+
141
+ @tick(rate=20) // 每秒执行一次
142
+ fn every_second() { ... }
143
+
144
+ @on_advancement("story/mine_diamond")
145
+ fn on_diamond() {
146
+ give(@s, "minecraft:diamond", 5);
147
+ }
148
+
149
+ @on_death
150
+ fn on_death() {
151
+ scoreboard_add(@s, #deaths, 1);
152
+ }
153
+ ```
154
+
155
+ #### 控制流
156
+
157
+ ```rs
158
+ if (hp <= 0) {
159
+ respawn();
160
+ } else if (hp < 5) {
161
+ warn_player();
162
+ }
163
+
164
+ for (let i: int = 0; i < 10; i = i + 1) {
165
+ summon("minecraft:zombie", (i, 64, 0));
166
+ }
167
+
168
+ foreach (player in @a) {
169
+ heal(player, 2);
170
+ }
171
+ ```
172
+
173
+ #### 结构体与枚举
174
+
175
+ ```rs
176
+ enum Phase { Lobby, Playing, Ended }
177
+
178
+ struct Player {
179
+ score: int,
180
+ alive: bool,
181
+ }
182
+
183
+ match (phase) {
184
+ Phase::Lobby => { announce("等待玩家..."); }
185
+ Phase::Playing => { every_second(); }
186
+ Phase::Ended => { show_scoreboard(); }
187
+ }
188
+ ```
189
+
190
+ #### Lambda
191
+
192
+ ```rs
193
+ fn apply(f: (int) -> int, x: int) -> int {
194
+ return f(x);
195
+ }
196
+
197
+ let double = (x: int) -> int { return x * 2; };
198
+ apply(double, 5); // 10
199
+ ```
200
+
201
+ #### 数组
202
+
203
+ ```rs
204
+ let scores: int[] = [];
205
+ push(scores, 42);
206
+
207
+ foreach (s in scores) {
208
+ announce("得分:${s}");
209
+ }
210
+ ```
211
+
212
+ ---
213
+
214
+ ### CLI 参考
215
+
216
+ ```
217
+ redscript compile <file> 编译为 datapack(默认)或 structure
218
+ -o, --output <dir> 输出目录 [默认: ./out]
219
+ --target datapack|structure 输出格式 [默认: datapack]
220
+ --namespace <ns> Datapack 命名空间 [默认: 文件名]
221
+ --no-optimize 禁用优化器
222
+ --stats 输出优化器统计信息
223
+
224
+ redscript repl 启动交互式 REPL
225
+ redscript validate <file> 验证 MC 命令语法
226
+ ```
227
+
228
+ ---
229
+
230
+ ### 标准库
231
+
232
+ ```rs
233
+ import "stdlib/math.rs" // abs, min, max, clamp
234
+ import "stdlib/player.rs" // is_alive, in_range, get_health
235
+ import "stdlib/timer.rs" // start_timer, tick_timer, has_elapsed
236
+ import "stdlib/cooldown.rs" // set_cooldown, check_cooldown
237
+ import "stdlib/mobs.rs" // ZOMBIE, SKELETON, CREEPER ... (60+ 实体常量)
238
+ ```
239
+
240
+ ---
241
+
242
+ ### 更多文档
243
+
244
+ | | |
245
+ |---|---|
246
+ | 📖 [语言参考](docs/LANGUAGE_REFERENCE.md) | 完整语法与类型系统 |
247
+ | 🔧 [内置函数](https://github.com/bkmashiro/redscript/wiki/Builtins) | 所有 34+ MC 内置函数 |
248
+ | ⚡ [优化器](https://github.com/bkmashiro/redscript/wiki/Optimizer) | 各优化 Pass 说明 |
249
+ | 🧱 [结构体目标](docs/STRUCTURE_TARGET.md) | 编译到 NBT 命令方块结构体 |
250
+ | 🧪 [集成测试](https://github.com/bkmashiro/redscript/wiki/Integration-Testing) | 在真实 Paper 服务器上测试 |
251
+ | 🏗 [实现指南](docs/IMPLEMENTATION_GUIDE.md) | 编译器内部原理 |
252
+
253
+ ---
254
+
255
+ <div align="center">
256
+
257
+ MIT License · Copyright © 2026 [bkmashiro](https://github.com/bkmashiro)
258
+
259
+ *少写一点,多做一些,更快交付。*
260
+
261
+ </div>
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const index_1 = require("../index");
37
+ const fs = __importStar(require("fs"));
38
+ const os = __importStar(require("os"));
39
+ const path = __importStar(require("path"));
40
+ const child_process_1 = require("child_process");
41
+ // Note: watch command is tested manually as it's an interactive long-running process
42
+ describe('CLI API', () => {
43
+ describe('imports', () => {
44
+ it('compiles a file with imported helpers', () => {
45
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'redscript-imports-'));
46
+ const libPath = path.join(tempDir, 'lib.rs');
47
+ const mainPath = path.join(tempDir, 'main.rs');
48
+ fs.writeFileSync(libPath, 'fn double(x: int) -> int { return x + x; }\n');
49
+ fs.writeFileSync(mainPath, 'import "./lib.rs"\n\nfn main() { let value: int = double(2); }\n');
50
+ const source = fs.readFileSync(mainPath, 'utf-8');
51
+ const result = (0, index_1.compile)(source, { namespace: 'imports', filePath: mainPath });
52
+ expect(result.files.length).toBeGreaterThan(0);
53
+ expect(result.ir.functions.some(fn => fn.name === 'double')).toBe(true);
54
+ expect(result.ir.functions.some(fn => fn.name === 'main')).toBe(true);
55
+ });
56
+ it('deduplicates circular imports', () => {
57
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'redscript-circular-'));
58
+ const aPath = path.join(tempDir, 'a.rs');
59
+ const bPath = path.join(tempDir, 'b.rs');
60
+ const mainPath = path.join(tempDir, 'main.rs');
61
+ fs.writeFileSync(aPath, 'import "./b.rs"\n\nfn from_a() -> int { return 1; }\n');
62
+ fs.writeFileSync(bPath, 'import "./a.rs"\n\nfn from_b() -> int { return from_a(); }\n');
63
+ fs.writeFileSync(mainPath, 'import "./a.rs"\n\nfn main() { let value: int = from_b(); }\n');
64
+ const source = fs.readFileSync(mainPath, 'utf-8');
65
+ const result = (0, index_1.compile)(source, { namespace: 'circular', filePath: mainPath });
66
+ expect(result.ir.functions.filter(fn => fn.name === 'from_a')).toHaveLength(1);
67
+ expect(result.ir.functions.filter(fn => fn.name === 'from_b')).toHaveLength(1);
68
+ });
69
+ });
70
+ describe('compile()', () => {
71
+ it('compiles simple source', () => {
72
+ const source = 'fn test() { say("hello"); }';
73
+ const result = (0, index_1.compile)(source, { namespace: 'mypack' });
74
+ expect(result.files.length).toBeGreaterThan(0);
75
+ expect(result.ast.namespace).toBe('mypack');
76
+ expect(result.ir.functions.length).toBe(1);
77
+ });
78
+ it('uses default namespace', () => {
79
+ const source = 'fn test() {}';
80
+ const result = (0, index_1.compile)(source);
81
+ expect(result.ast.namespace).toBe('redscript');
82
+ });
83
+ it('generates correct file structure', () => {
84
+ const source = 'fn test() { say("hello"); }';
85
+ const result = (0, index_1.compile)(source, { namespace: 'game' });
86
+ const paths = result.files.map(f => f.path);
87
+ expect(paths).toContain('pack.mcmeta');
88
+ expect(paths).toContain('data/game/function/__load.mcfunction');
89
+ expect(paths.some(p => p.includes('test.mcfunction'))).toBe(true);
90
+ });
91
+ it('collects optimizer stats', () => {
92
+ const source = `
93
+ fn build() {
94
+ foreach (turret in @e[tag=turret]) {
95
+ let range: int = scoreboard_get("config", "turret_range");
96
+ if (range > 0) {
97
+ if (range > -1) {
98
+ say("ready");
99
+ }
100
+ }
101
+ }
102
+ }
103
+ `;
104
+ const result = (0, index_1.compile)(source, { namespace: 'stats' });
105
+ expect(result.stats?.licmHoists).toBeGreaterThan(0);
106
+ expect(result.stats?.totalCommandsBefore).toBeGreaterThan(result.stats?.totalCommandsAfter ?? 0);
107
+ expect(result.stats?.deadCodeRemoved).toBeGreaterThanOrEqual(0);
108
+ });
109
+ });
110
+ describe('--stats flag', () => {
111
+ it('prints optimizer statistics', () => {
112
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'redscript-stats-'));
113
+ const inputPath = path.join(tempDir, 'input.rs');
114
+ const outputDir = path.join(tempDir, 'out');
115
+ fs.writeFileSync(inputPath, 'fn build() { setblock((0, 64, 0), "minecraft:stone"); setblock((1, 64, 0), "minecraft:stone"); }');
116
+ const stdout = (0, child_process_1.execFileSync)(process.execPath, ['-r', 'ts-node/register', path.join(process.cwd(), 'src/cli.ts'), 'compile', inputPath, '-o', outputDir, '--stats'], { cwd: process.cwd(), encoding: 'utf-8' });
117
+ expect(stdout).toContain('Optimizations applied:');
118
+ expect(stdout).toContain('setblock batching:');
119
+ expect(stdout).toContain('Total mcfunction commands:');
120
+ });
121
+ });
122
+ describe('check()', () => {
123
+ it('returns null for valid source', () => {
124
+ const source = 'fn test() { say("hello"); }';
125
+ const error = (0, index_1.check)(source);
126
+ expect(error).toBeNull();
127
+ });
128
+ it('returns error for invalid source', () => {
129
+ const source = 'fn test( { say("hello"); }'; // Missing )
130
+ const error = (0, index_1.check)(source);
131
+ expect(error).toBeInstanceOf(Error);
132
+ });
133
+ it('returns error for syntax errors', () => {
134
+ const source = 'fn test() { let x = ; }'; // Missing value
135
+ const error = (0, index_1.check)(source);
136
+ expect(error).toBeInstanceOf(Error);
137
+ });
138
+ });
139
+ });
140
+ //# sourceMappingURL=cli.test.js.map
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const mcfunction_1 = require("../codegen/mcfunction");
4
+ describe('generateDatapack', () => {
5
+ it('generates pack.mcmeta', () => {
6
+ const mod = { namespace: 'test', functions: [], globals: [] };
7
+ const files = (0, mcfunction_1.generateDatapack)(mod);
8
+ const meta = files.find(f => f.path === 'pack.mcmeta');
9
+ expect(meta).toBeDefined();
10
+ expect(JSON.parse(meta.content).pack.pack_format).toBe(26);
11
+ });
12
+ it('generates __load.mcfunction with objective setup', () => {
13
+ const mod = { namespace: 'mypack', functions: [], globals: ['counter'] };
14
+ const files = (0, mcfunction_1.generateDatapack)(mod);
15
+ const load = files.find(f => f.path.includes('__load.mcfunction'));
16
+ expect(load?.content).toContain('scoreboard objectives add rs dummy');
17
+ expect(load?.content).toContain('scoreboard players set $counter rs 0');
18
+ });
19
+ it('generates function file for simple add(a, b)', () => {
20
+ const mod = {
21
+ namespace: 'mypack',
22
+ globals: [],
23
+ functions: [{
24
+ name: 'add',
25
+ params: ['a', 'b'],
26
+ locals: ['a', 'b', 'result'],
27
+ blocks: [{
28
+ label: 'entry',
29
+ instrs: [
30
+ { op: 'binop', dst: 'result', lhs: { kind: 'var', name: 'a' }, bop: '+', rhs: { kind: 'var', name: 'b' } },
31
+ ],
32
+ term: { op: 'return', value: { kind: 'var', name: 'result' } },
33
+ }],
34
+ }],
35
+ };
36
+ const files = (0, mcfunction_1.generateDatapack)(mod);
37
+ const fn = files.find(f => f.path.includes('add.mcfunction'));
38
+ expect(fn).toBeDefined();
39
+ // Should have param setup
40
+ expect(fn.content).toContain('scoreboard players operation $a rs = $p0 rs');
41
+ expect(fn.content).toContain('scoreboard players operation $b rs = $p1 rs');
42
+ // Should have add operation
43
+ expect(fn.content).toContain('+=');
44
+ });
45
+ it('generates tick tag for tick loop function', () => {
46
+ const mod = {
47
+ namespace: 'mypack',
48
+ globals: [],
49
+ functions: [{
50
+ name: 'game_loop',
51
+ params: [],
52
+ locals: [],
53
+ blocks: [{ label: 'entry', instrs: [], term: { op: 'return' } }],
54
+ isTickLoop: true,
55
+ }],
56
+ };
57
+ const files = (0, mcfunction_1.generateDatapack)(mod);
58
+ // tick.json should point to __tick
59
+ const tickTag = files.find(f => f.path.includes('tick.json'));
60
+ expect(tickTag).toBeDefined();
61
+ expect(JSON.parse(tickTag.content).values).toContain('mypack:__tick');
62
+ // __tick.mcfunction should call the game_loop function
63
+ const tickFn = files.find(f => f.path.includes('__tick.mcfunction'));
64
+ expect(tickFn).toBeDefined();
65
+ expect(tickFn.content).toContain('function mypack:game_loop');
66
+ });
67
+ it('generates conditional branches with execute if/unless', () => {
68
+ const mod = {
69
+ namespace: 'mypack',
70
+ globals: [],
71
+ functions: [{
72
+ name: 'check',
73
+ params: [],
74
+ locals: ['cond'],
75
+ blocks: [
76
+ {
77
+ label: 'entry',
78
+ instrs: [
79
+ { op: 'assign', dst: 'cond', src: { kind: 'const', value: 1 } },
80
+ ],
81
+ term: { op: 'jump_if', cond: 'cond', then: 'then_block', else_: 'else_block' },
82
+ },
83
+ {
84
+ label: 'then_block',
85
+ instrs: [{ op: 'raw', cmd: 'say hello' }],
86
+ term: { op: 'return' },
87
+ },
88
+ {
89
+ label: 'else_block',
90
+ instrs: [{ op: 'raw', cmd: 'say goodbye' }],
91
+ term: { op: 'return' },
92
+ },
93
+ ],
94
+ }],
95
+ };
96
+ const files = (0, mcfunction_1.generateDatapack)(mod);
97
+ const entry = files.find(f => f.path.endsWith('check.mcfunction'));
98
+ expect(entry?.content).toContain('execute if score $cond rs matches 1..');
99
+ expect(entry?.content).toContain('execute if score $cond rs matches ..0');
100
+ });
101
+ it('generates advancement json for event decorators', () => {
102
+ const mod = {
103
+ namespace: 'mypack',
104
+ globals: [],
105
+ functions: [{
106
+ name: 'on_mine_diamond',
107
+ params: [],
108
+ locals: [],
109
+ blocks: [{ label: 'entry', instrs: [], term: { op: 'return' } }],
110
+ eventTrigger: { kind: 'advancement', value: 'story/mine_diamond' },
111
+ }],
112
+ };
113
+ const result = (0, mcfunction_1.generateDatapackWithStats)(mod);
114
+ const advancement = result.advancements.find(f => f.path === 'data/mypack/advancements/on_advancement_on_mine_diamond.json');
115
+ expect(advancement).toBeDefined();
116
+ const json = JSON.parse(advancement.content);
117
+ expect(json.criteria.trigger.trigger).toBe('minecraft:story/mine_diamond');
118
+ expect(json.rewards.function).toBe('mypack:on_mine_diamond');
119
+ });
120
+ });
121
+ //# sourceMappingURL=codegen.test.js.map
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Diagnostics Tests
3
+ */
4
+ export {};
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ /**
3
+ * Diagnostics Tests
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const diagnostics_1 = require("../diagnostics");
7
+ const compile_1 = require("../compile");
8
+ describe('DiagnosticError', () => {
9
+ describe('formatError', () => {
10
+ it('formats source context with a caret pointer', () => {
11
+ const source = [
12
+ 'fn main() {',
13
+ ' let x = foo(',
14
+ '}',
15
+ ].join('\n');
16
+ const error = new diagnostics_1.DiagnosticError('TypeError', 'Unknown function: foo', { line: 2, col: 11 }, source.split('\n'));
17
+ expect((0, diagnostics_1.formatError)(error, source)).toBe([
18
+ 'Error at line 2, col 11:',
19
+ ' let x = foo(',
20
+ ' ^',
21
+ 'Unknown function: foo',
22
+ ].join('\n'));
23
+ });
24
+ it('includes file path when available', () => {
25
+ const source = 'let x = foo();';
26
+ const error = new diagnostics_1.DiagnosticError('TypeError', 'Unknown function: foo', { file: 'test.rs', line: 1, col: 9 }, source.split('\n'));
27
+ expect((0, diagnostics_1.formatError)(error, source)).toContain('Error in test.rs at line 1, col 9:');
28
+ });
29
+ });
30
+ describe('format', () => {
31
+ it('formats error with source line and pointer', () => {
32
+ const sourceLines = [
33
+ 'fn main() {',
34
+ ' let x = 42',
35
+ '}',
36
+ ];
37
+ const error = new diagnostics_1.DiagnosticError('ParseError', "Expected ';' after statement", { line: 2, col: 14 }, sourceLines);
38
+ const formatted = error.format();
39
+ expect(formatted).toContain('[ParseError]');
40
+ expect(formatted).toContain('line 2');
41
+ expect(formatted).toContain('col 14');
42
+ expect(formatted).toContain('let x = 42');
43
+ expect(formatted).toContain('^');
44
+ });
45
+ it('formats error with file path', () => {
46
+ const error = new diagnostics_1.DiagnosticError('LexError', 'Unexpected character', { file: 'test.rs', line: 1, col: 1 }, ['@@@']);
47
+ const formatted = error.format();
48
+ expect(formatted).toContain('test.rs:');
49
+ expect(formatted).toContain('[LexError]');
50
+ });
51
+ it('handles missing source lines gracefully', () => {
52
+ const error = new diagnostics_1.DiagnosticError('ParseError', 'Syntax error', { line: 10, col: 5 });
53
+ const formatted = error.format();
54
+ expect(formatted).toContain('[ParseError]');
55
+ expect(formatted).toContain('line 10');
56
+ });
57
+ });
58
+ });
59
+ describe('DiagnosticCollector', () => {
60
+ it('collects multiple errors', () => {
61
+ const collector = new diagnostics_1.DiagnosticCollector('line1\nline2\nline3');
62
+ collector.error('ParseError', 'First error', 1, 1);
63
+ collector.error('ParseError', 'Second error', 2, 1);
64
+ expect(collector.hasErrors()).toBe(true);
65
+ expect(collector.getErrors()).toHaveLength(2);
66
+ });
67
+ it('formats all errors', () => {
68
+ const collector = new diagnostics_1.DiagnosticCollector('let x');
69
+ collector.error('ParseError', 'Missing semicolon', 1, 6);
70
+ const formatted = collector.formatAll();
71
+ expect(formatted).toContain('Missing semicolon');
72
+ expect(formatted).toContain('let x');
73
+ });
74
+ });
75
+ describe('parseErrorMessage', () => {
76
+ it('extracts line and col from error message', () => {
77
+ const err = (0, diagnostics_1.parseErrorMessage)('ParseError', "Expected ';' at line 5, col 12", ['', '', '', '', 'let x = 42']);
78
+ expect(err.location.line).toBe(5);
79
+ expect(err.location.col).toBe(12);
80
+ expect(err.message).toBe("Expected ';'");
81
+ });
82
+ it('defaults to line 1, col 1 if no position in message', () => {
83
+ const err = (0, diagnostics_1.parseErrorMessage)('LexError', 'Unknown error');
84
+ expect(err.location.line).toBe(1);
85
+ expect(err.location.col).toBe(1);
86
+ });
87
+ });
88
+ describe('compile function', () => {
89
+ it('returns success for valid code', () => {
90
+ const result = (0, compile_1.compile)('fn main() { let x = 1; }');
91
+ expect(result.success).toBe(true);
92
+ expect(result.files).toBeDefined();
93
+ });
94
+ it('returns DiagnosticError for lex errors', () => {
95
+ const result = (0, compile_1.compile)('fn main() { let x = $ }');
96
+ expect(result.success).toBe(false);
97
+ expect(result.error).toBeInstanceOf(diagnostics_1.DiagnosticError);
98
+ expect(result.error?.kind).toBe('LexError');
99
+ });
100
+ it('returns DiagnosticError for parse errors', () => {
101
+ const result = (0, compile_1.compile)('fn main() { let x = }');
102
+ expect(result.success).toBe(false);
103
+ expect(result.error).toBeInstanceOf(diagnostics_1.DiagnosticError);
104
+ expect(result.error?.kind).toBe('ParseError');
105
+ });
106
+ it('returns DiagnosticError for missing semicolon', () => {
107
+ const result = (0, compile_1.compile)('fn main() { let x = 42 }');
108
+ expect(result.success).toBe(false);
109
+ expect(result.error?.kind).toBe('ParseError');
110
+ expect(result.error?.message).toContain("Expected ';'");
111
+ });
112
+ it('includes file path in error', () => {
113
+ const result = (0, compile_1.compile)('fn main() { }', { filePath: 'test.rs' });
114
+ // This is valid, but test that filePath is passed through
115
+ expect(result.success).toBe(true);
116
+ });
117
+ it('formats error nicely', () => {
118
+ const result = (0, compile_1.compile)('fn main() {\n let x = 42\n}');
119
+ expect(result.success).toBe(false);
120
+ const formatted = (0, compile_1.formatCompileError)(result);
121
+ expect(formatted).toContain('Error at line');
122
+ expect(formatted).toContain('^');
123
+ // Error points to } on line 3, which is where semicolon was expected
124
+ expect(formatted).toContain('}');
125
+ });
126
+ });
127
+ describe('Lexer DiagnosticError', () => {
128
+ it('throws DiagnosticError for unexpected character', () => {
129
+ const result = (0, compile_1.compile)('fn main() { let x = $ }');
130
+ expect(result.success).toBe(false);
131
+ expect(result.error?.kind).toBe('LexError');
132
+ expect(result.error?.message).toContain('Unexpected character');
133
+ });
134
+ it('throws DiagnosticError for unterminated string', () => {
135
+ const result = (0, compile_1.compile)('fn main() { let x = "hello }');
136
+ expect(result.success).toBe(false);
137
+ expect(result.error?.kind).toBe('LexError');
138
+ expect(result.error?.message).toContain('Unterminated string');
139
+ });
140
+ });
141
+ describe('Parser DiagnosticError', () => {
142
+ it('includes line and column info', () => {
143
+ const result = (0, compile_1.compile)('fn main() { return }');
144
+ expect(result.success).toBe(false);
145
+ expect(result.error?.location.line).toBeGreaterThan(0);
146
+ expect(result.error?.location.col).toBeGreaterThan(0);
147
+ });
148
+ });
149
+ //# sourceMappingURL=diagnostics.test.js.map