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,28 @@
1
+ {
2
+ "comments": {
3
+ "lineComment": "//",
4
+ "blockComment": ["/*", "*/"]
5
+ },
6
+ "brackets": [
7
+ ["{", "}"],
8
+ ["[", "]"],
9
+ ["(", ")"]
10
+ ],
11
+ "autoClosingPairs": [
12
+ { "open": "{", "close": "}" },
13
+ { "open": "[", "close": "]" },
14
+ { "open": "(", "close": ")" },
15
+ { "open": "\"", "close": "\"" }
16
+ ],
17
+ "surroundingPairs": [
18
+ ["{", "}"],
19
+ ["[", "]"],
20
+ ["(", ")"],
21
+ ["\"", "\""]
22
+ ],
23
+ "indentationRules": {
24
+ "increaseIndentPattern": "\\{\\s*$",
25
+ "decreaseIndentPattern": "^\\s*\\}"
26
+ },
27
+ "wordPattern": "[a-zA-Z_][a-zA-Z0-9_]*"
28
+ }
@@ -0,0 +1,114 @@
1
+ {
2
+ "Function": {
3
+ "prefix": "fn",
4
+ "body": [
5
+ "fn ${1:name}(${2:params})${3: -> ${4:ReturnType}} {",
6
+ " $0",
7
+ "}"
8
+ ],
9
+ "description": "Define a function"
10
+ },
11
+ "Tick Function": {
12
+ "prefix": "fntick",
13
+ "body": [
14
+ "@tick",
15
+ "fn ${1:name}(${2:params})${3: -> ${4:void}} {",
16
+ " $0",
17
+ "}"
18
+ ],
19
+ "description": "Define an @tick function"
20
+ },
21
+ "Load Function": {
22
+ "prefix": "fnload",
23
+ "body": [
24
+ "@on_load",
25
+ "fn ${1:name}(${2:params})${3: -> ${4:void}} {",
26
+ " $0",
27
+ "}"
28
+ ],
29
+ "description": "Define an @on_load function"
30
+ },
31
+ "If Statement": {
32
+ "prefix": "if",
33
+ "body": [
34
+ "if (${1:condition}) {",
35
+ " $0",
36
+ "}"
37
+ ],
38
+ "description": "Insert an if statement"
39
+ },
40
+ "If Else Statement": {
41
+ "prefix": "ife",
42
+ "body": [
43
+ "if (${1:condition}) {",
44
+ " ${2}",
45
+ "} else {",
46
+ " $0",
47
+ "}"
48
+ ],
49
+ "description": "Insert an if-else statement"
50
+ },
51
+ "Match Expression": {
52
+ "prefix": "match",
53
+ "body": [
54
+ "match (${1:value}) {",
55
+ " ${2:pattern} => {",
56
+ " ${3}",
57
+ " }",
58
+ " _ => {",
59
+ " $0",
60
+ " }",
61
+ "}"
62
+ ],
63
+ "description": "Insert a match expression"
64
+ },
65
+ "Foreach Selector": {
66
+ "prefix": "foreach",
67
+ "body": [
68
+ "foreach (${1:entity} in ${2:@a}) {",
69
+ " $0",
70
+ "}"
71
+ ],
72
+ "description": "Iterate over a selector or iterable"
73
+ },
74
+ "Foreach Selector Body": {
75
+ "prefix": "fors",
76
+ "body": [
77
+ "foreach (${1:entity} in ${2:@a}) {",
78
+ " ${3}",
79
+ " $0",
80
+ "}"
81
+ ],
82
+ "description": "Iterate over a selector with a prepared body"
83
+ },
84
+ "Let Declaration": {
85
+ "prefix": "let",
86
+ "body": [
87
+ "let ${1:name}: ${2:Type} = ${0:value};"
88
+ ],
89
+ "description": "Declare a typed local variable"
90
+ },
91
+ "Const Declaration": {
92
+ "prefix": "const",
93
+ "body": [
94
+ "const ${1:NAME}: ${2:Type} = ${0:value}"
95
+ ],
96
+ "description": "Declare a constant"
97
+ },
98
+ "Struct Definition": {
99
+ "prefix": "struct",
100
+ "body": [
101
+ "struct ${1:Name} {",
102
+ " ${2:field}: ${3:Type}",
103
+ "}"
104
+ ],
105
+ "description": "Define a struct"
106
+ },
107
+ "Enum Definition": {
108
+ "prefix": "enum",
109
+ "body": [
110
+ "enum ${1:Name} { ${2:VariantA}, ${3:VariantB} }"
111
+ ],
112
+ "description": "Define an enum"
113
+ }
114
+ }
@@ -0,0 +1,89 @@
1
+ import * as vscode from 'vscode'
2
+
3
+ /**
4
+ * Code action provider for RedScript.
5
+ * Currently provides:
6
+ * - "Add minecraft: namespace" quick fix for unnamespaced entity types
7
+ * e.g. type=zombie → type=minecraft:zombie
8
+ */
9
+ export function registerCodeActions(context: vscode.ExtensionContext): void {
10
+ context.subscriptions.push(
11
+ vscode.languages.registerCodeActionsProvider(
12
+ { language: 'redscript', scheme: 'file' },
13
+ new RedScriptCodeActionProvider(),
14
+ { providedCodeActionKinds: [vscode.CodeActionKind.QuickFix] }
15
+ )
16
+ )
17
+ }
18
+
19
+ class RedScriptCodeActionProvider implements vscode.CodeActionProvider {
20
+ provideCodeActions(
21
+ document: vscode.TextDocument,
22
+ range: vscode.Range,
23
+ context: vscode.CodeActionContext
24
+ ): vscode.CodeAction[] {
25
+ const actions: vscode.CodeAction[] = []
26
+
27
+ for (const diag of context.diagnostics) {
28
+ if (diag.source !== 'redscript') continue
29
+
30
+ // W_UNNAMESPACED_TYPE: e.g. 'Unnamespaced entity type "zombie"'
31
+ if (diag.code === 'W_UNNAMESPACED_TYPE') {
32
+ const m = diag.message.match(/Unnamespaced entity type "([^"]+)"/)
33
+ if (!m) continue
34
+ const typeName = m[1]
35
+
36
+ // Find and replace in the diagnostic range area
37
+ const fix = new vscode.CodeAction(
38
+ `Add namespace: type=minecraft:${typeName}`,
39
+ vscode.CodeActionKind.QuickFix
40
+ )
41
+ fix.diagnostics = [diag]
42
+ fix.isPreferred = true
43
+
44
+ // Search the document for type=<typeName> (without namespace)
45
+ const text = document.getText()
46
+ const re = new RegExp(`\\btype=${escapeRe(typeName)}(?![a-zA-Z0-9_:.])`, 'g')
47
+ const edit = new vscode.WorkspaceEdit()
48
+ let match: RegExpExecArray | null
49
+ while ((match = re.exec(text)) !== null) {
50
+ const start = document.positionAt(match.index + 'type='.length)
51
+ const end = document.positionAt(match.index + 'type='.length + typeName.length)
52
+ edit.replace(document.uri, new vscode.Range(start, end), `minecraft:${typeName}`)
53
+ }
54
+ fix.edit = edit
55
+ actions.push(fix)
56
+ }
57
+ }
58
+
59
+ // Also scan the current line for unnamespaced type= patterns
60
+ // even without a diagnostic (as a proactive suggestion)
61
+ const lineText = document.lineAt(range.start.line).text
62
+ const lineTypeRe = /\btype=([a-z][a-z0-9_]*)(?!\s*[:a-z0-9_])/g
63
+ let lm: RegExpExecArray | null
64
+ while ((lm = lineTypeRe.exec(lineText)) !== null) {
65
+ const typeName = lm[1]
66
+ // Skip already-namespaced or already have a fix above
67
+ if (typeName.includes(':')) continue
68
+ if (actions.some(a => a.title.includes(typeName))) continue
69
+
70
+ const fix = new vscode.CodeAction(
71
+ `Add namespace: type=minecraft:${typeName}`,
72
+ vscode.CodeActionKind.QuickFix
73
+ )
74
+ const col = lm.index + 'type='.length
75
+ const start = new vscode.Position(range.start.line, col)
76
+ const end = new vscode.Position(range.start.line, col + typeName.length)
77
+ const edit = new vscode.WorkspaceEdit()
78
+ edit.replace(document.uri, new vscode.Range(start, end), `minecraft:${typeName}`)
79
+ fix.edit = edit
80
+ actions.push(fix)
81
+ }
82
+
83
+ return actions
84
+ }
85
+ }
86
+
87
+ function escapeRe(s: string): string {
88
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
89
+ }
@@ -0,0 +1,130 @@
1
+ import * as vscode from 'vscode'
2
+
3
+ interface BuiltinFunction {
4
+ name: string
5
+ detail: string
6
+ doc: string
7
+ insertText?: string
8
+ kind?: vscode.CompletionItemKind
9
+ }
10
+
11
+ const BUILTIN_FUNCTIONS: BuiltinFunction[] = [
12
+ { name: 'say', detail: 'say(msg: string)', doc: 'Broadcast a message to all players as the server.' },
13
+ { name: 'tell', detail: 'tell(target: selector, msg: string)', doc: 'Send a private message to a player or selector.' },
14
+ { name: 'announce', detail: 'announce(msg: string)', doc: 'Send a message to all players in chat.' },
15
+ { name: 'title', detail: 'title(target: selector, msg: string)', doc: 'Show a large title on screen for target players.' },
16
+ { name: 'subtitle', detail: 'subtitle(target: selector, msg: string)', doc: 'Show subtitle text below the title.' },
17
+ { name: 'actionbar', detail: 'actionbar(target: selector, msg: string)', doc: 'Show text in the action bar (above hotbar).' },
18
+ { name: 'title_times', detail: 'title_times(target: selector, fadeIn: int, stay: int, fadeOut: int)', doc: 'Set title display timing (in ticks).' },
19
+ { name: 'give', detail: 'give(target: selector, item: string, count?: int)', doc: 'Give item(s) to a player.' },
20
+ { name: 'kill', detail: 'kill(target?: selector)', doc: 'Kill entity/entities. Defaults to @s.' },
21
+ { name: 'effect', detail: 'effect(target: selector, effect: string, duration?: int, amplifier?: int)', doc: 'Apply a status effect.' },
22
+ { name: 'clear', detail: 'clear(target: selector, item?: string)', doc: 'Remove items from inventory.' },
23
+ { name: 'kick', detail: 'kick(player: selector, reason?: string)', doc: 'Kick a player from the server.' },
24
+ { name: 'xp_add', detail: 'xp_add(target: selector, amount: int, type?: string)', doc: 'Add experience to a player.' },
25
+ { name: 'xp_set', detail: 'xp_set(target: selector, amount: int, type?: string)', doc: "Set a player's experience." },
26
+ { name: 'tp', detail: 'tp(target: selector, destination: selector | BlockPos)', doc: 'Teleport entity to a player or coordinates.' },
27
+ { name: 'setblock', detail: 'setblock(pos: BlockPos, block: string)', doc: 'Place a block at coordinates.' },
28
+ { name: 'fill', detail: 'fill(from: BlockPos, to: BlockPos, block: string)', doc: 'Fill a region with blocks.' },
29
+ { name: 'clone', detail: 'clone(from: BlockPos, to: BlockPos, dest: BlockPos)', doc: 'Clone a region of blocks to a new location.' },
30
+ { name: 'summon', detail: 'summon(type: string, pos: BlockPos)', doc: 'Spawn an entity at a location.' },
31
+ { name: 'weather', detail: 'weather(type: string)', doc: 'Set the weather.' },
32
+ { name: 'time_set', detail: 'time_set(value: int | string)', doc: 'Set the world time.' },
33
+ { name: 'time_add', detail: 'time_add(ticks: int)', doc: 'Advance world time by ticks.' },
34
+ { name: 'gamerule', detail: 'gamerule(rule: string, value: bool | int)', doc: 'Set a gamerule value.' },
35
+ { name: 'difficulty', detail: 'difficulty(level: string)', doc: 'Set the game difficulty.' },
36
+ { name: 'particle', detail: 'particle(name: string, pos: BlockPos)', doc: 'Spawn a particle effect.' },
37
+ { name: 'playsound', detail: 'playsound(sound: string, source: string, target: selector, pos?: BlockPos, volume?: float, pitch?: float)', doc: 'Play a sound for a player.' },
38
+ { name: 'tag_add', detail: 'tag_add(target: selector, tag: string)', doc: 'Add an entity tag.' },
39
+ { name: 'tag_remove', detail: 'tag_remove(target: selector, tag: string)', doc: 'Remove an entity tag.' },
40
+ { name: 'scoreboard_get', detail: 'scoreboard_get(target: selector | string, objective: string) -> int', doc: 'Read a scoreboard value.' },
41
+ { name: 'score', detail: 'score(target: selector | string, objective: string) -> int', doc: 'Alias for scoreboard_get. Read a scoreboard value.' },
42
+ { name: 'scoreboard_set', detail: 'scoreboard_set(target: selector | string, objective: string, value: int)', doc: 'Set a scoreboard value.' },
43
+ { name: 'scoreboard_add', detail: 'scoreboard_add(target: selector | string, objective: string, amount: int)', doc: 'Add to a scoreboard value.' },
44
+ { name: 'scoreboard_display', detail: 'scoreboard_display(slot: string, objective: string)', doc: 'Display a scoreboard objective in a slot.' },
45
+ { name: 'scoreboard_add_objective', detail: 'scoreboard_add_objective(name: string, criteria: string)', doc: 'Create a new scoreboard objective.' },
46
+ { name: 'scoreboard_remove_objective', detail: 'scoreboard_remove_objective(name: string)', doc: 'Remove a scoreboard objective.' },
47
+ { name: 'scoreboard_hide', detail: 'scoreboard_hide(slot: string)', doc: 'Clear the display in a scoreboard slot.' },
48
+ { name: 'random', detail: 'random(min: int, max: int) -> int', doc: 'Generate a random integer in range [min, max] using scoreboard arithmetic.' },
49
+ { name: 'random_native', detail: 'random_native(min: int, max: int) -> int', doc: 'Generate a random integer using /random command (MC 1.20.3+). Faster than random().' },
50
+ { name: 'str_len', detail: 'str_len(s: string) -> int', doc: 'Get the length of a string (stored in NBT storage).' },
51
+ { name: 'push', detail: 'push(arr: T[], value: T)', doc: 'Append a value to the end of an array.' },
52
+ { name: 'pop', detail: 'pop(arr: T[]) -> T', doc: 'Remove and return the last element of an array.' },
53
+ { name: 'len', detail: 'arr.len', doc: 'Get the number of elements in an array (property access, not a function call).', kind: vscode.CompletionItemKind.Property },
54
+ { name: 'data_get', detail: 'data_get(target: string, path: string) -> int', doc: 'Read NBT data from entity/block/storage.' },
55
+ { name: 'bossbar_add', detail: 'bossbar_add(id: string, name: string)', doc: 'Create a new boss bar.' },
56
+ { name: 'bossbar_set_value', detail: 'bossbar_set_value(id: string, value: int)', doc: 'Set boss bar current value.' },
57
+ { name: 'bossbar_set_max', detail: 'bossbar_set_max(id: string, max: int)', doc: 'Set boss bar maximum value.' },
58
+ { name: 'bossbar_remove', detail: 'bossbar_remove(id: string)', doc: 'Remove a boss bar.' },
59
+ { name: 'bossbar_set_players', detail: 'bossbar_set_players(id: string, target: selector)', doc: 'Set which players see the boss bar.' },
60
+ { name: 'bossbar_set_color', detail: 'bossbar_set_color(id: string, color: string)', doc: 'Set boss bar color.' },
61
+ { name: 'bossbar_set_style', detail: 'bossbar_set_style(id: string, style: string)', doc: 'Set boss bar segmentation style.' },
62
+ { name: 'bossbar_set_visible', detail: 'bossbar_set_visible(id: string, visible: bool)', doc: 'Show or hide a boss bar.' },
63
+ { name: 'bossbar_get_value', detail: 'bossbar_get_value(id: string) -> int', doc: 'Get the current value of a boss bar.' },
64
+ { name: 'team_add', detail: 'team_add(name: string)', doc: 'Create a new team.' },
65
+ { name: 'team_remove', detail: 'team_remove(name: string)', doc: 'Remove a team.' },
66
+ { name: 'team_join', detail: 'team_join(name: string, target: selector)', doc: 'Add entities to a team.' },
67
+ { name: 'team_leave', detail: 'team_leave(target: selector)', doc: 'Remove entities from their team.' },
68
+ { name: 'team_option', detail: 'team_option(name: string, option: string, value: string)', doc: 'Set a team option.' },
69
+ { name: 'tick', detail: '@tick | @tick(rate: int)', doc: 'Run this function every tick (rate=1) or every N ticks.', insertText: '@tick', kind: vscode.CompletionItemKind.Event },
70
+ { name: 'on_advancement', detail: '@on_advancement(id: string)', doc: 'Trigger when a player earns an advancement.', insertText: '@on_advancement', kind: vscode.CompletionItemKind.Event },
71
+ { name: 'on_death', detail: '@on_death', doc: 'Trigger when the executing entity dies.', insertText: '@on_death', kind: vscode.CompletionItemKind.Event },
72
+ { name: 'on_craft', detail: '@on_craft(item: string)', doc: 'Trigger when a player crafts an item.', insertText: '@on_craft', kind: vscode.CompletionItemKind.Event },
73
+ ]
74
+
75
+ const KEYWORDS = [
76
+ 'fn',
77
+ 'let',
78
+ 'const',
79
+ 'if',
80
+ 'else',
81
+ 'match',
82
+ 'foreach',
83
+ 'in',
84
+ 'return',
85
+ 'struct',
86
+ 'enum',
87
+ 'execute',
88
+ 'as',
89
+ 'at',
90
+ 'true',
91
+ 'false',
92
+ ]
93
+
94
+ const TYPES = ['int', 'float', 'string', 'bool', 'void', 'BlockPos', 'selector']
95
+ const TRIGGER_CHARACTERS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_@'.split('')
96
+
97
+ export function registerCompletionProvider(context: vscode.ExtensionContext): void {
98
+ const provider = vscode.languages.registerCompletionItemProvider(
99
+ { language: 'redscript', scheme: 'file' },
100
+ {
101
+ provideCompletionItems() {
102
+ const items: vscode.CompletionItem[] = []
103
+
104
+ for (const builtin of BUILTIN_FUNCTIONS) {
105
+ const item = new vscode.CompletionItem(
106
+ builtin.name,
107
+ builtin.kind ?? vscode.CompletionItemKind.Function,
108
+ )
109
+ item.detail = builtin.detail
110
+ item.documentation = builtin.doc
111
+ item.insertText = builtin.insertText ?? builtin.name
112
+ items.push(item)
113
+ }
114
+
115
+ for (const keyword of KEYWORDS) {
116
+ items.push(new vscode.CompletionItem(keyword, vscode.CompletionItemKind.Keyword))
117
+ }
118
+
119
+ for (const type of TYPES) {
120
+ items.push(new vscode.CompletionItem(type, vscode.CompletionItemKind.TypeParameter))
121
+ }
122
+
123
+ return items
124
+ },
125
+ },
126
+ ...TRIGGER_CHARACTERS,
127
+ )
128
+
129
+ context.subscriptions.push(provider)
130
+ }
@@ -0,0 +1,239 @@
1
+ import * as vscode from 'vscode'
2
+ import { registerHoverProvider } from './hover'
3
+ import { registerCodeActions } from './codeactions'
4
+ import { registerCompletionProvider } from './completion'
5
+ import { registerSymbolProviders } from './symbols'
6
+ // The compiler is bundled directly into this extension by esbuild.
7
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
8
+ const { compile: _compile } = require('redscript') as {
9
+ compile: (source: string, opts?: { filePath?: string }) => {
10
+ files: { path: string; content: string }[]
11
+ warnings: { message: string; code: string; line?: number; col?: number }[]
12
+ }
13
+ }
14
+
15
+ function getCompile() {
16
+ return _compile ?? null
17
+ }
18
+
19
+ const DEBOUNCE_MS = 600
20
+
21
+ export function activate(context: vscode.ExtensionContext): void {
22
+ const diagnostics = vscode.languages.createDiagnosticCollection('redscript')
23
+ context.subscriptions.push(diagnostics)
24
+
25
+ // Debounce timer per document URI
26
+ const timers = new Map<string, NodeJS.Timeout>()
27
+
28
+ function scheduleValidation(doc: vscode.TextDocument) {
29
+ if (doc.languageId !== 'redscript') return
30
+ const key = doc.uri.toString()
31
+ const existing = timers.get(key)
32
+ if (existing) clearTimeout(existing)
33
+ timers.set(key, setTimeout(() => {
34
+ validateDocument(doc, diagnostics)
35
+ timers.delete(key)
36
+ }, DEBOUNCE_MS))
37
+ }
38
+
39
+ // Validate on open
40
+ context.subscriptions.push(
41
+ vscode.workspace.onDidOpenTextDocument(doc => scheduleValidation(doc))
42
+ )
43
+
44
+ // Validate on change
45
+ context.subscriptions.push(
46
+ vscode.workspace.onDidChangeTextDocument(e => scheduleValidation(e.document))
47
+ )
48
+
49
+ // Clear diagnostics on close
50
+ context.subscriptions.push(
51
+ vscode.workspace.onDidCloseTextDocument(doc => {
52
+ diagnostics.delete(doc.uri)
53
+ const key = doc.uri.toString()
54
+ const t = timers.get(key)
55
+ if (t) { clearTimeout(t); timers.delete(key) }
56
+ })
57
+ )
58
+
59
+ // Validate all already-open .rs files
60
+ vscode.workspace.textDocuments
61
+ .filter(d => d.languageId === 'redscript')
62
+ .forEach(d => scheduleValidation(d))
63
+
64
+ // Register hover documentation
65
+ registerHoverProvider(context)
66
+
67
+ // Register completions
68
+ registerCompletionProvider(context)
69
+
70
+ // Register code actions (quick fixes)
71
+ registerCodeActions(context)
72
+
73
+ // Register Go-to-Definition and Find-References
74
+ registerSymbolProviders(context)
75
+
76
+ // Status bar item to show compilation state
77
+ const statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left, 10)
78
+ statusBar.text = '$(pass) RedScript'
79
+ statusBar.tooltip = 'RedScript compiler'
80
+ statusBar.show()
81
+ context.subscriptions.push(statusBar)
82
+
83
+ // Track the active editor's compile state
84
+ context.subscriptions.push(
85
+ vscode.window.onDidChangeActiveTextEditor(editor => {
86
+ if (editor?.document.languageId === 'redscript') {
87
+ statusBar.show()
88
+ } else {
89
+ statusBar.hide()
90
+ }
91
+ })
92
+ )
93
+ }
94
+
95
+ function validateDocument(
96
+ doc: vscode.TextDocument,
97
+ collection: vscode.DiagnosticCollection
98
+ ): void {
99
+ const compile = getCompile()
100
+ if (!compile) {
101
+ // Compiler not available — show a one-time info message
102
+ collection.set(doc.uri, [{
103
+ message: 'RedScript compiler not found. Run `npm install -g redscript` to enable diagnostics.',
104
+ range: new vscode.Range(0, 0, 0, 0),
105
+ severity: vscode.DiagnosticSeverity.Information,
106
+ source: 'redscript'
107
+ }])
108
+ return
109
+ }
110
+
111
+ const source = doc.getText()
112
+ const docDiagnostics: vscode.Diagnostic[] = []
113
+
114
+ try {
115
+ const result = compile(source, { filePath: doc.uri.fsPath })
116
+
117
+ // Convert warnings to VS Code diagnostics
118
+ for (const w of result.warnings ?? []) {
119
+ // Use real line/col from AST span when available, fall back to text search
120
+ const range = (w.line && w.col)
121
+ ? new vscode.Range(w.line - 1, w.col - 1, w.line - 1, w.col - 1 + 20)
122
+ : findWarningRange(w.message, w.code, source, doc)
123
+ docDiagnostics.push({
124
+ message: w.message,
125
+ range,
126
+ severity: vscode.DiagnosticSeverity.Warning,
127
+ source: 'redscript',
128
+ code: w.code
129
+ })
130
+ }
131
+ } catch (err: unknown) {
132
+ const msg = err instanceof Error ? err.message : String(err)
133
+
134
+ // Try to get location from DiagnosticError.location first
135
+ let range: vscode.Range
136
+ const loc = (err as { location?: { line?: number; col?: number } }).location
137
+ if (loc?.line && loc?.col) {
138
+ const l = Math.max(0, loc.line - 1)
139
+ const c = Math.max(0, loc.col - 1)
140
+ range = new vscode.Range(l, c, l, c + 20)
141
+ } else {
142
+ // Fallback: parse the error message for line/column info
143
+ range = extractRange(msg, doc)
144
+ }
145
+
146
+ docDiagnostics.push({
147
+ message: msg,
148
+ range,
149
+ severity: vscode.DiagnosticSeverity.Error,
150
+ source: 'redscript'
151
+ })
152
+ }
153
+
154
+ collection.set(doc.uri, docDiagnostics)
155
+ }
156
+
157
+ /**
158
+ * For warnings without position info, search the source for the relevant token
159
+ * mentioned in the warning message.
160
+ */
161
+ function findWarningRange(
162
+ message: string,
163
+ code: string | undefined,
164
+ source: string,
165
+ doc: vscode.TextDocument
166
+ ): vscode.Range {
167
+ // W_UNNAMESPACED_TYPE: message contains the unqualified type name in quotes
168
+ // e.g. 'Unnamespaced entity type "zombie"'
169
+ if (code === 'W_UNNAMESPACED_TYPE') {
170
+ const m = message.match(/"([^"]+)"/)
171
+ if (m) return searchToken(source, doc, `type=${m[1]}`) ?? searchToken(source, doc, m[1]) ?? topLine(doc)
172
+ }
173
+
174
+ // W_QUOTED_SELECTOR: message contains the quoted selector
175
+ // e.g. 'Quoted selector "@a" is deprecated'
176
+ if (code === 'W_QUOTED_SELECTOR') {
177
+ const m = message.match(/"(@[^"]+)"/)
178
+ if (m) return searchToken(source, doc, `"${m[1]}"`) ?? topLine(doc)
179
+ }
180
+
181
+ // W_DEPRECATED: usually about tp_to
182
+ if (code === 'W_DEPRECATED') {
183
+ const m = message.match(/^(\w+) is deprecated/)
184
+ if (m) return searchToken(source, doc, m[1]) ?? topLine(doc)
185
+ }
186
+
187
+ return topLine(doc)
188
+ }
189
+
190
+ /** Search source for a literal string, return range of first match. */
191
+ function searchToken(
192
+ source: string,
193
+ doc: vscode.TextDocument,
194
+ token: string
195
+ ): vscode.Range | null {
196
+ const idx = source.indexOf(token)
197
+ if (idx < 0) return null
198
+ const pos = doc.positionAt(idx)
199
+ return new vscode.Range(pos, doc.positionAt(idx + token.length))
200
+ }
201
+
202
+ function topLine(doc: vscode.TextDocument): vscode.Range {
203
+ return new vscode.Range(0, 0, 0, doc.lineAt(0).text.length)
204
+ }
205
+
206
+ /**
207
+ * Try to extract line/column from common error formats:
208
+ * "Error at line 5, column 12: ..."
209
+ * "5:12: ..."
210
+ * "[line 5] ..."
211
+ */
212
+ function extractRange(msg: string, doc: vscode.TextDocument): vscode.Range {
213
+ // "line N, column M"
214
+ let m = msg.match(/line[: ]+(\d+)[,\s]+col(?:umn)?[: ]+(\d+)/i)
215
+ if (m) {
216
+ const l = Math.max(0, parseInt(m[1]) - 1)
217
+ const c = Math.max(0, parseInt(m[2]) - 1)
218
+ return new vscode.Range(l, c, l, c + 80)
219
+ }
220
+ // "N:M"
221
+ m = msg.match(/^(\d+):(\d+)/)
222
+ if (m) {
223
+ const l = Math.max(0, parseInt(m[1]) - 1)
224
+ const c = Math.max(0, parseInt(m[2]) - 1)
225
+ return new vscode.Range(l, c, l, c + 80)
226
+ }
227
+ // "[line N]"
228
+ m = msg.match(/\[line (\d+)\]/i)
229
+ if (m) {
230
+ const l = Math.max(0, parseInt(m[1]) - 1)
231
+ return new vscode.Range(l, 0, l, 200)
232
+ }
233
+ // Fallback: highlight first line
234
+ return new vscode.Range(0, 0, 0, doc.lineAt(0).text.length)
235
+ }
236
+
237
+ export function deactivate(): void {
238
+ // nothing
239
+ }