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
@@ -0,0 +1,357 @@
1
+ {
2
+ "name": "RedScript",
3
+ "scopeName": "source.redscript",
4
+ "fileTypes": ["rs"],
5
+ "patterns": [{ "include": "#root" }],
6
+ "repository": {
7
+ "root": {
8
+ "patterns": [
9
+ { "include": "#comments" },
10
+ { "include": "#decorators" },
11
+ { "include": "#string-interpolation" },
12
+ { "include": "#strings" },
13
+ { "include": "#keywords" },
14
+ { "include": "#selectors" },
15
+ { "include": "#const-definition" },
16
+ { "include": "#constants-upper" },
17
+ { "include": "#fn-definition" },
18
+ { "include": "#struct-definition" },
19
+ { "include": "#enum-definition" },
20
+ { "include": "#fn-call" },
21
+ { "include": "#types" },
22
+ { "include": "#numbers" },
23
+ { "include": "#blockpos" },
24
+ { "include": "#mc-name" },
25
+ { "include": "#operators" },
26
+ { "include": "#punctuation" }
27
+ ]
28
+ },
29
+
30
+ "comments": {
31
+ "patterns": [
32
+ {
33
+ "name": "comment.line.double-slash.redscript",
34
+ "match": "//.*$"
35
+ },
36
+ {
37
+ "comment": "JSDoc block comment /** ... */",
38
+ "name": "comment.block.documentation.redscript",
39
+ "begin": "/\\*\\*",
40
+ "end": "\\*/",
41
+ "patterns": [
42
+ {
43
+ "name": "storage.type.class.jsdoc",
44
+ "match": "@(?:param|returns?|type|throws?|example|deprecated|since|see|todo)\\b"
45
+ },
46
+ {
47
+ "name": "variable.other.jsdoc",
48
+ "match": "\\{[^}]+\\}"
49
+ }
50
+ ]
51
+ },
52
+ {
53
+ "name": "comment.block.redscript",
54
+ "begin": "/\\*",
55
+ "end": "\\*/"
56
+ }
57
+ ]
58
+ },
59
+
60
+ "decorators": {
61
+ "patterns": [
62
+ {
63
+ "comment": "Decorator with arguments: @tick(rate=20)",
64
+ "begin": "(@(?:tick|on_advancement|on_craft|on_death|on_trigger|on_join_team|on_login))\\s*(\\()",
65
+ "beginCaptures": {
66
+ "1": { "name": "entity.name.function.decorator.redscript" },
67
+ "2": { "name": "punctuation.definition.parameters.redscript" }
68
+ },
69
+ "end": "\\)",
70
+ "endCaptures": {
71
+ "0": { "name": "punctuation.definition.parameters.redscript" }
72
+ },
73
+ "patterns": [
74
+ { "include": "#strings" },
75
+ { "include": "#numbers" },
76
+ {
77
+ "name": "variable.parameter.decorator.redscript",
78
+ "match": "\\b[a-z_][a-zA-Z0-9_]*\\b"
79
+ },
80
+ {
81
+ "name": "keyword.operator.assignment.redscript",
82
+ "match": "="
83
+ }
84
+ ]
85
+ },
86
+ {
87
+ "comment": "Bare decorator: @on_death",
88
+ "name": "entity.name.function.decorator.redscript",
89
+ "match": "@(?:tick|on_advancement|on_craft|on_death|on_trigger|on_join_team|on_login)\\b"
90
+ }
91
+ ]
92
+ },
93
+
94
+ "string-interpolation": {
95
+ "comment": "Interpolated string with ${expr} — highlight the expression inside",
96
+ "begin": "\"(?=[^\"]*\\$\\{)",
97
+ "end": "\"",
98
+ "name": "string.quoted.double.redscript",
99
+ "patterns": [
100
+ {
101
+ "begin": "\\$\\{",
102
+ "end": "\\}",
103
+ "beginCaptures": { "0": { "name": "punctuation.definition.template-expression.begin.redscript" } },
104
+ "endCaptures": { "0": { "name": "punctuation.definition.template-expression.end.redscript" } },
105
+ "contentName": "meta.embedded.expression.redscript",
106
+ "patterns": [
107
+ { "include": "#selectors" },
108
+ { "include": "#numbers" },
109
+ {
110
+ "name": "variable.other.redscript",
111
+ "match": "[a-zA-Z_][a-zA-Z0-9_]*"
112
+ }
113
+ ]
114
+ },
115
+ { "name": "constant.character.escape.redscript", "match": "\\\\." }
116
+ ]
117
+ },
118
+
119
+ "strings": {
120
+ "patterns": [
121
+ {
122
+ "name": "string.quoted.double.redscript",
123
+ "begin": "\"",
124
+ "end": "\"",
125
+ "patterns": [
126
+ { "name": "constant.character.escape.redscript", "match": "\\\\." }
127
+ ]
128
+ }
129
+ ]
130
+ },
131
+
132
+ "keywords": {
133
+ "patterns": [
134
+ {
135
+ "name": "keyword.control.redscript",
136
+ "match": "\\b(if|else|while|for|foreach|return|match|in|execute|as|at|unless|run)\\b"
137
+ },
138
+ {
139
+ "name": "keyword.declaration.redscript",
140
+ "match": "\\b(fn|let|struct|enum|import|namespace|trigger)\\b"
141
+ },
142
+ {
143
+ "name": "keyword.declaration.const.redscript",
144
+ "match": "\\bconst\\b"
145
+ },
146
+ {
147
+ "name": "constant.language.boolean.redscript",
148
+ "match": "\\b(true|false)\\b"
149
+ }
150
+ ]
151
+ },
152
+
153
+ "types": {
154
+ "patterns": [
155
+ {
156
+ "comment": "Primitive types",
157
+ "name": "support.type.primitive.redscript",
158
+ "match": "\\b(int|float|bool|string|void|BlockPos|selector)\\b"
159
+ },
160
+ {
161
+ "comment": "Array type T[]",
162
+ "match": "\\b([A-Za-z][A-Za-z0-9_]*)\\s*(\\[\\])",
163
+ "captures": {
164
+ "1": { "name": "support.type.redscript" },
165
+ "2": { "name": "punctuation.definition.type.redscript" }
166
+ }
167
+ },
168
+ {
169
+ "comment": "User-defined types (PascalCase: starts uppercase, second char lowercase)",
170
+ "name": "entity.name.type.redscript",
171
+ "match": "\\b[A-Z][a-z][A-Za-z0-9]*\\b"
172
+ }
173
+ ]
174
+ },
175
+
176
+ "selectors": {
177
+ "patterns": [
178
+ {
179
+ "comment": "Selector WITH arguments: @e[type=minecraft:zombie,distance=..8]",
180
+ "begin": "(@[aesprnAESPRN])(\\[)",
181
+ "beginCaptures": {
182
+ "1": { "name": "keyword.other.selector.redscript" },
183
+ "2": { "name": "punctuation.section.selector.begin.redscript" }
184
+ },
185
+ "end": "\\]",
186
+ "endCaptures": {
187
+ "0": { "name": "punctuation.section.selector.end.redscript" }
188
+ },
189
+ "patterns": [
190
+ {
191
+ "comment": "Selector argument keys",
192
+ "name": "support.type.property-name.redscript",
193
+ "match": "\\b(type|name|tag|team|scores|nbt|predicate|gamemode|distance|level|x_rotation|y_rotation|x|y|z|dx|dy|dz|limit|sort|advancements|selector)\\b"
194
+ },
195
+ {
196
+ "comment": "Range operator: ..8, 1.., 1..10",
197
+ "name": "keyword.operator.range.selector.redscript",
198
+ "match": "\\.\\."
199
+ },
200
+ {
201
+ "comment": "Namespaced value e.g. minecraft:zombie",
202
+ "match": "\\b([a-z0-9_]+):([a-z0-9_./]+)",
203
+ "captures": {
204
+ "1": { "name": "entity.name.namespace.redscript" },
205
+ "2": { "name": "entity.name.type.entity.redscript" }
206
+ }
207
+ },
208
+ { "include": "#strings" },
209
+ { "include": "#numbers" },
210
+ {
211
+ "name": "keyword.operator.assignment.redscript",
212
+ "match": "="
213
+ },
214
+ {
215
+ "name": "keyword.operator.logical.redscript",
216
+ "match": "!"
217
+ }
218
+ ]
219
+ },
220
+ {
221
+ "comment": "Bare selector: @s @a @e @p @r @n (no arguments)",
222
+ "name": "keyword.other.selector.redscript",
223
+ "match": "@[aesprnAESPRN]\\b"
224
+ }
225
+ ]
226
+ },
227
+
228
+ "fn-definition": {
229
+ "comment": "fn name(...) — highlight function name",
230
+ "match": "\\b(fn)\\s+([a-z_][a-zA-Z0-9_]*)",
231
+ "captures": {
232
+ "1": { "name": "keyword.declaration.redscript" },
233
+ "2": { "name": "entity.name.function.redscript" }
234
+ }
235
+ },
236
+
237
+ "struct-definition": {
238
+ "comment": "struct Name { ... }",
239
+ "match": "\\b(struct)\\s+([A-Z][A-Za-z0-9_]*)",
240
+ "captures": {
241
+ "1": { "name": "keyword.declaration.redscript" },
242
+ "2": { "name": "entity.name.type.redscript" }
243
+ }
244
+ },
245
+
246
+ "enum-definition": {
247
+ "comment": "enum Name { ... }",
248
+ "match": "\\b(enum)\\s+([A-Z][A-Za-z0-9_]*)",
249
+ "captures": {
250
+ "1": { "name": "keyword.declaration.redscript" },
251
+ "2": { "name": "entity.name.type.redscript" }
252
+ }
253
+ },
254
+
255
+ "const-definition": {
256
+ "comment": "const NAME: type = value — highlight const name",
257
+ "match": "\\b(const)\\s+([A-Z_][A-Z0-9_]*)",
258
+ "captures": {
259
+ "1": { "name": "keyword.declaration.const.redscript" },
260
+ "2": { "name": "variable.other.constant.redscript" }
261
+ }
262
+ },
263
+
264
+ "fn-call": {
265
+ "comment": "Function call: name(...) — highlight callee",
266
+ "patterns": [
267
+ {
268
+ "comment": "Builtin functions",
269
+ "name": "support.function.builtin.redscript",
270
+ "match": "\\b(say|tell|announce|title|subtitle|actionbar|title_times|give|kill|effect|clear|kick|xp_add|xp_set|tp|tp_to|setblock|fill|clone|summon|particle|playsound|weather|time_set|time_add|gamerule|difficulty|tag_add|tag_remove|scoreboard_get|scoreboard_set|scoreboard_add|scoreboard_display|scoreboard_hide|scoreboard_add_objective|scoreboard_remove_objective|score|random|random_native|random_sequence|data_get|str_len|push|pop|bossbar_add|bossbar_set_value|bossbar_set_max|bossbar_set_color|bossbar_set_style|bossbar_set_visible|bossbar_set_players|bossbar_remove|bossbar_get_value|team_add|team_remove|team_join|team_leave|team_option|spawn_object|if_entity|unless_entity)(?=\\s*\\()"
271
+ },
272
+ {
273
+ "comment": "User-defined function calls",
274
+ "name": "entity.name.function.redscript",
275
+ "match": "\\b([a-z_][a-zA-Z0-9_]*)(?=\\s*\\()"
276
+ }
277
+ ]
278
+ },
279
+
280
+ "constants-upper": {
281
+ "comment": "SCREAMING_SNAKE_CASE constants and 2+ char ALL_CAPS (e.g. GAME_TIME, MAX, PI)",
282
+ "name": "variable.other.constant.redscript",
283
+ "match": "\\b(?:[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+|[A-Z]{2,})\\b"
284
+ },
285
+
286
+ "numbers": {
287
+ "patterns": [
288
+ {
289
+ "name": "constant.numeric.float.redscript",
290
+ "match": "-?\\b\\d+\\.\\d+\\b"
291
+ },
292
+ {
293
+ "name": "constant.numeric.integer.redscript",
294
+ "match": "-?\\b\\d+\\b"
295
+ }
296
+ ]
297
+ },
298
+
299
+ "blockpos": {
300
+ "comment": "BlockPos coordinates: ~5, ^3, ~-1",
301
+ "name": "constant.numeric.blockpos.redscript",
302
+ "match": "[~^]-?\\d*(?:\\.\\d+)?"
303
+ },
304
+
305
+ "mc-name": {
306
+ "comment": "#objective, #tag, #team — unquoted MC identifier literal",
307
+ "name": "variable.other.mc-name.redscript",
308
+ "match": "#[a-zA-Z_][a-zA-Z0-9_]*"
309
+ },
310
+
311
+ "operators": {
312
+ "patterns": [
313
+ {
314
+ "name": "keyword.operator.comparison.redscript",
315
+ "match": "(==|!=|<=|>=|<|>)"
316
+ },
317
+ {
318
+ "name": "keyword.operator.logical.redscript",
319
+ "match": "(&&|\\|\\||!(?!=))"
320
+ },
321
+ {
322
+ "name": "keyword.operator.assignment.redscript",
323
+ "match": "(\\+=|-=|\\*=|/=|%=|=(?!=))"
324
+ },
325
+ {
326
+ "name": "keyword.operator.arithmetic.redscript",
327
+ "match": "(\\+|-(?!>)|\\*|/|%)"
328
+ },
329
+ {
330
+ "name": "keyword.operator.arrow.redscript",
331
+ "match": "->"
332
+ },
333
+ {
334
+ "name": "keyword.operator.scope.redscript",
335
+ "match": "::"
336
+ },
337
+ {
338
+ "name": "keyword.operator.range.redscript",
339
+ "match": "\\.\\."
340
+ }
341
+ ]
342
+ },
343
+
344
+ "punctuation": {
345
+ "patterns": [
346
+ { "name": "punctuation.terminator.statement.redscript", "match": ";" },
347
+ { "name": "punctuation.separator.comma.redscript", "match": "," },
348
+ { "name": "punctuation.separator.colon.redscript", "match": ":" },
349
+ { "name": "punctuation.accessor.dot.redscript", "match": "\\." },
350
+ { "name": "punctuation.definition.block.begin.redscript", "match": "\\{" },
351
+ { "name": "punctuation.definition.block.end.redscript", "match": "\\}" },
352
+ { "name": "punctuation.definition.parameters.begin.redscript", "match": "\\(" },
353
+ { "name": "punctuation.definition.parameters.end.redscript", "match": "\\)" }
354
+ ]
355
+ }
356
+ }
357
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "commonjs",
4
+ "target": "ES2020",
5
+ "outDir": "out",
6
+ "rootDir": "src",
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true
10
+ },
11
+ "include": ["src/**/*.ts"],
12
+ "exclude": ["node_modules", "out"]
13
+ }
package/jest.config.js ADDED
@@ -0,0 +1,5 @@
1
+ module.exports = {
2
+ preset: 'ts-jest',
3
+ testEnvironment: 'node',
4
+ roots: ['<rootDir>/src'],
5
+ };
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "redscript-mc",
3
+ "version": "1.0.0",
4
+ "description": "A high-level programming language that compiles to Minecraft datapacks",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "redscript": "dist/cli.js",
8
+ "rsc": "dist/cli.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsc -w",
13
+ "test": "jest",
14
+ "cli": "ts-node src/cli.ts",
15
+ "validate-mc": "jest src/__tests__/mc-syntax.test.ts --verbose"
16
+ },
17
+ "devDependencies": {
18
+ "@types/jest": "^29.5.0",
19
+ "@types/node": "^20.0.0",
20
+ "jest": "^29.7.0",
21
+ "ts-jest": "^29.1.0",
22
+ "ts-node": "^10.9.0",
23
+ "typescript": "^5.4.0"
24
+ },
25
+ "license": "MIT",
26
+ "keywords": [
27
+ "minecraft",
28
+ "datapack",
29
+ "mcfunction",
30
+ "compiler",
31
+ "redscript"
32
+ ],
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "https://github.com/bkmashiro/redscript.git"
36
+ },
37
+ "author": "bkmashiro"
38
+ }
@@ -0,0 +1,130 @@
1
+ import { compile, check } from '../index'
2
+ import * as fs from 'fs'
3
+ import * as os from 'os'
4
+ import * as path from 'path'
5
+ import { execFileSync } from 'child_process'
6
+
7
+ // Note: watch command is tested manually as it's an interactive long-running process
8
+
9
+ describe('CLI API', () => {
10
+ describe('imports', () => {
11
+ it('compiles a file with imported helpers', () => {
12
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'redscript-imports-'))
13
+ const libPath = path.join(tempDir, 'lib.rs')
14
+ const mainPath = path.join(tempDir, 'main.rs')
15
+
16
+ fs.writeFileSync(libPath, 'fn double(x: int) -> int { return x + x; }\n')
17
+ fs.writeFileSync(mainPath, 'import "./lib.rs"\n\nfn main() { let value: int = double(2); }\n')
18
+
19
+ const source = fs.readFileSync(mainPath, 'utf-8')
20
+ const result = compile(source, { namespace: 'imports', filePath: mainPath })
21
+
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)
25
+ })
26
+
27
+ it('deduplicates circular imports', () => {
28
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'redscript-circular-'))
29
+ const aPath = path.join(tempDir, 'a.rs')
30
+ const bPath = path.join(tempDir, 'b.rs')
31
+ const mainPath = path.join(tempDir, 'main.rs')
32
+
33
+ fs.writeFileSync(aPath, 'import "./b.rs"\n\nfn from_a() -> int { return 1; }\n')
34
+ fs.writeFileSync(bPath, 'import "./a.rs"\n\nfn from_b() -> int { return from_a(); }\n')
35
+ fs.writeFileSync(mainPath, 'import "./a.rs"\n\nfn main() { let value: int = from_b(); }\n')
36
+
37
+ const source = fs.readFileSync(mainPath, 'utf-8')
38
+ const result = compile(source, { namespace: 'circular', filePath: mainPath })
39
+
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
+
45
+ describe('compile()', () => {
46
+ it('compiles simple source', () => {
47
+ const source = 'fn test() { say("hello"); }'
48
+ const result = compile(source, { namespace: 'mypack' })
49
+ expect(result.files.length).toBeGreaterThan(0)
50
+ expect(result.ast.namespace).toBe('mypack')
51
+ expect(result.ir.functions.length).toBe(1)
52
+ })
53
+
54
+ it('uses default namespace', () => {
55
+ const source = 'fn test() {}'
56
+ const result = compile(source)
57
+ expect(result.ast.namespace).toBe('redscript')
58
+ })
59
+
60
+ it('generates correct file structure', () => {
61
+ const source = 'fn test() { say("hello"); }'
62
+ const result = compile(source, { namespace: 'game' })
63
+
64
+ const paths = result.files.map(f => f.path)
65
+ expect(paths).toContain('pack.mcmeta')
66
+ expect(paths).toContain('data/game/function/__load.mcfunction')
67
+ expect(paths.some(p => p.includes('test.mcfunction'))).toBe(true)
68
+ })
69
+
70
+ it('collects optimizer stats', () => {
71
+ const source = `
72
+ fn build() {
73
+ foreach (turret in @e[tag=turret]) {
74
+ let range: int = scoreboard_get("config", "turret_range");
75
+ if (range > 0) {
76
+ if (range > -1) {
77
+ say("ready");
78
+ }
79
+ }
80
+ }
81
+ }
82
+ `
83
+
84
+ const result = compile(source, { namespace: 'stats' })
85
+ expect(result.stats?.licmHoists).toBeGreaterThan(0)
86
+ expect(result.stats?.totalCommandsBefore).toBeGreaterThan(result.stats?.totalCommandsAfter ?? 0)
87
+ expect(result.stats?.deadCodeRemoved).toBeGreaterThanOrEqual(0)
88
+ })
89
+ })
90
+
91
+ describe('--stats flag', () => {
92
+ it('prints optimizer statistics', () => {
93
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'redscript-stats-'))
94
+ const inputPath = path.join(tempDir, 'input.rs')
95
+ const outputDir = path.join(tempDir, 'out')
96
+
97
+ fs.writeFileSync(inputPath, 'fn build() { setblock((0, 64, 0), "minecraft:stone"); setblock((1, 64, 0), "minecraft:stone"); }')
98
+
99
+ const stdout = execFileSync(
100
+ process.execPath,
101
+ ['-r', 'ts-node/register', path.join(process.cwd(), 'src/cli.ts'), 'compile', inputPath, '-o', outputDir, '--stats'],
102
+ { cwd: process.cwd(), encoding: 'utf-8' }
103
+ )
104
+
105
+ expect(stdout).toContain('Optimizations applied:')
106
+ expect(stdout).toContain('setblock batching:')
107
+ expect(stdout).toContain('Total mcfunction commands:')
108
+ })
109
+ })
110
+
111
+ describe('check()', () => {
112
+ it('returns null for valid source', () => {
113
+ const source = 'fn test() { say("hello"); }'
114
+ const error = check(source)
115
+ expect(error).toBeNull()
116
+ })
117
+
118
+ it('returns error for invalid source', () => {
119
+ const source = 'fn test( { say("hello"); }' // Missing )
120
+ const error = check(source)
121
+ expect(error).toBeInstanceOf(Error)
122
+ })
123
+
124
+ it('returns error for syntax errors', () => {
125
+ const source = 'fn test() { let x = ; }' // Missing value
126
+ const error = check(source)
127
+ expect(error).toBeInstanceOf(Error)
128
+ })
129
+ })
130
+ })
@@ -0,0 +1,128 @@
1
+ import { generateDatapack, generateDatapackWithStats } from '../codegen/mcfunction'
2
+ import type { IRModule } from '../ir/types'
3
+
4
+ describe('generateDatapack', () => {
5
+ it('generates pack.mcmeta', () => {
6
+ const mod: IRModule = { namespace: 'test', functions: [], globals: [] }
7
+ const files = 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
+
13
+ it('generates __load.mcfunction with objective setup', () => {
14
+ const mod: IRModule = { namespace: 'mypack', functions: [], globals: ['counter'] }
15
+ const files = generateDatapack(mod)
16
+ const load = files.find(f => f.path.includes('__load.mcfunction'))
17
+ expect(load?.content).toContain('scoreboard objectives add rs dummy')
18
+ expect(load?.content).toContain('scoreboard players set $counter rs 0')
19
+ })
20
+
21
+ it('generates function file for simple add(a, b)', () => {
22
+ const mod: IRModule = {
23
+ namespace: 'mypack',
24
+ globals: [],
25
+ functions: [{
26
+ name: 'add',
27
+ params: ['a', 'b'],
28
+ locals: ['a', 'b', 'result'],
29
+ blocks: [{
30
+ label: 'entry',
31
+ instrs: [
32
+ { op: 'binop', dst: 'result', lhs: { kind: 'var', name: 'a' }, bop: '+', rhs: { kind: 'var', name: 'b' } },
33
+ ],
34
+ term: { op: 'return', value: { kind: 'var', name: 'result' } },
35
+ }],
36
+ }],
37
+ }
38
+ const files = generateDatapack(mod)
39
+ const fn = files.find(f => f.path.includes('add.mcfunction'))
40
+ expect(fn).toBeDefined()
41
+ // Should have param setup
42
+ expect(fn!.content).toContain('scoreboard players operation $a rs = $p0 rs')
43
+ expect(fn!.content).toContain('scoreboard players operation $b rs = $p1 rs')
44
+ // Should have add operation
45
+ expect(fn!.content).toContain('+=')
46
+ })
47
+
48
+ it('generates tick tag for tick loop function', () => {
49
+ const mod: IRModule = {
50
+ namespace: 'mypack',
51
+ globals: [],
52
+ functions: [{
53
+ name: 'game_loop',
54
+ params: [],
55
+ locals: [],
56
+ blocks: [{ label: 'entry', instrs: [], term: { op: 'return' } }],
57
+ isTickLoop: true,
58
+ }],
59
+ }
60
+ const files = generateDatapack(mod)
61
+
62
+ // tick.json should point to __tick
63
+ const tickTag = files.find(f => f.path.includes('tick.json'))
64
+ expect(tickTag).toBeDefined()
65
+ expect(JSON.parse(tickTag!.content).values).toContain('mypack:__tick')
66
+
67
+ // __tick.mcfunction should call the game_loop function
68
+ const tickFn = files.find(f => f.path.includes('__tick.mcfunction'))
69
+ expect(tickFn).toBeDefined()
70
+ expect(tickFn!.content).toContain('function mypack:game_loop')
71
+ })
72
+
73
+ it('generates conditional branches with execute if/unless', () => {
74
+ const mod: IRModule = {
75
+ namespace: 'mypack',
76
+ globals: [],
77
+ functions: [{
78
+ name: 'check',
79
+ params: [],
80
+ locals: ['cond'],
81
+ blocks: [
82
+ {
83
+ label: 'entry',
84
+ instrs: [
85
+ { op: 'assign', dst: 'cond', src: { kind: 'const', value: 1 } },
86
+ ],
87
+ term: { op: 'jump_if', cond: 'cond', then: 'then_block', else_: 'else_block' },
88
+ },
89
+ {
90
+ label: 'then_block',
91
+ instrs: [{ op: 'raw', cmd: 'say hello' }],
92
+ term: { op: 'return' },
93
+ },
94
+ {
95
+ label: 'else_block',
96
+ instrs: [{ op: 'raw', cmd: 'say goodbye' }],
97
+ term: { op: 'return' },
98
+ },
99
+ ],
100
+ }],
101
+ }
102
+ const files = generateDatapack(mod)
103
+ const entry = files.find(f => f.path.endsWith('check.mcfunction'))
104
+ expect(entry?.content).toContain('execute if score $cond rs matches 1..')
105
+ expect(entry?.content).toContain('execute if score $cond rs matches ..0')
106
+ })
107
+
108
+ it('generates advancement json for event decorators', () => {
109
+ const mod: IRModule = {
110
+ namespace: 'mypack',
111
+ globals: [],
112
+ functions: [{
113
+ name: 'on_mine_diamond',
114
+ params: [],
115
+ locals: [],
116
+ blocks: [{ label: 'entry', instrs: [], term: { op: 'return' } }],
117
+ eventTrigger: { kind: 'advancement', value: 'story/mine_diamond' },
118
+ }],
119
+ }
120
+
121
+ const result = generateDatapackWithStats(mod)
122
+ const advancement = result.advancements.find(f => f.path === 'data/mypack/advancements/on_advancement_on_mine_diamond.json')
123
+ expect(advancement).toBeDefined()
124
+ const json = JSON.parse(advancement!.content)
125
+ expect(json.criteria.trigger.trigger).toBe('minecraft:story/mine_diamond')
126
+ expect(json.rewards.function).toBe('mypack:on_mine_diamond')
127
+ })
128
+ })