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,737 @@
1
+ /**
2
+ * RedScript Type Checker
3
+ *
4
+ * Performs basic type checking between Parser and Lowering phases.
5
+ * Collects errors but doesn't block compilation (warn mode).
6
+ */
7
+
8
+ import type { Program, FnDecl, Stmt, Expr, TypeNode, Block } from '../ast/types'
9
+ import { DiagnosticError, DiagnosticCollector } from '../diagnostics'
10
+
11
+ interface ScopeSymbol {
12
+ type: TypeNode
13
+ mutable: boolean
14
+ }
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Type Checker
18
+ // ---------------------------------------------------------------------------
19
+
20
+ export class TypeChecker {
21
+ private collector: DiagnosticCollector
22
+ private functions: Map<string, FnDecl> = new Map()
23
+ private structs: Map<string, Map<string, TypeNode>> = new Map()
24
+ private enums: Map<string, Map<string, number>> = new Map()
25
+ private consts: Map<string, TypeNode> = new Map()
26
+ private currentFn: FnDecl | null = null
27
+ private currentReturnType: TypeNode | null = null
28
+ private scope: Map<string, ScopeSymbol> = new Map()
29
+
30
+ constructor(source?: string, filePath?: string) {
31
+ this.collector = new DiagnosticCollector(source, filePath)
32
+ }
33
+
34
+ private getNodeLocation(node: unknown): { line: number; col: number } {
35
+ const span = (node as { span?: { line: number; col: number } } | undefined)?.span
36
+ return {
37
+ line: span?.line ?? 1,
38
+ col: span?.col ?? 1,
39
+ }
40
+ }
41
+
42
+ private report(message: string, node?: unknown): void {
43
+ const { line, col } = this.getNodeLocation(node)
44
+ this.collector.error('TypeError', message, line, col)
45
+ }
46
+
47
+ /**
48
+ * Type check a program. Returns collected errors.
49
+ */
50
+ check(program: Program): DiagnosticError[] {
51
+ // First pass: collect function and struct declarations
52
+ for (const fn of program.declarations) {
53
+ this.functions.set(fn.name, fn)
54
+ }
55
+
56
+ for (const struct of program.structs ?? []) {
57
+ const fields = new Map<string, TypeNode>()
58
+ for (const field of struct.fields) {
59
+ fields.set(field.name, field.type)
60
+ }
61
+ this.structs.set(struct.name, fields)
62
+ }
63
+
64
+ for (const enumDecl of program.enums ?? []) {
65
+ const variants = new Map<string, number>()
66
+ for (const variant of enumDecl.variants) {
67
+ variants.set(variant.name, variant.value ?? 0)
68
+ }
69
+ this.enums.set(enumDecl.name, variants)
70
+ }
71
+
72
+ for (const constDecl of program.consts ?? []) {
73
+ const constType = this.normalizeType(constDecl.type)
74
+ const actualType = this.inferType(constDecl.value)
75
+ if (!this.typesMatch(constType, actualType)) {
76
+ this.report(
77
+ `Type mismatch: expected ${this.typeToString(constType)}, got ${this.typeToString(actualType)}`,
78
+ constDecl.value
79
+ )
80
+ }
81
+ this.consts.set(constDecl.name, constType)
82
+ }
83
+
84
+ // Second pass: type check function bodies
85
+ for (const fn of program.declarations) {
86
+ this.checkFunction(fn)
87
+ }
88
+
89
+ return this.collector.getErrors()
90
+ }
91
+
92
+ private checkFunction(fn: FnDecl): void {
93
+ this.currentFn = fn
94
+ this.currentReturnType = this.normalizeType(fn.returnType)
95
+ this.scope = new Map()
96
+ let seenDefault = false
97
+
98
+ for (const [name, type] of this.consts.entries()) {
99
+ this.scope.set(name, { type, mutable: false })
100
+ }
101
+
102
+ // Add parameters to scope
103
+ for (const param of fn.params) {
104
+ this.scope.set(param.name, { type: this.normalizeType(param.type), mutable: true })
105
+ if (param.default) {
106
+ seenDefault = true
107
+ this.checkExpr(param.default)
108
+ const defaultType = this.inferType(param.default)
109
+ const paramType = this.normalizeType(param.type)
110
+ if (!this.typesMatch(paramType, defaultType)) {
111
+ this.report(
112
+ `Default value for '${param.name}' must be ${this.typeToString(paramType)}, got ${this.typeToString(defaultType)}`,
113
+ param.default
114
+ )
115
+ }
116
+ } else if (seenDefault) {
117
+ this.report(`Parameter '${param.name}' cannot follow a default parameter`, param)
118
+ }
119
+ }
120
+
121
+ // Check body
122
+ this.checkBlock(fn.body)
123
+
124
+ this.currentFn = null
125
+ this.currentReturnType = null
126
+ }
127
+
128
+ private checkBlock(stmts: Block): void {
129
+ for (const stmt of stmts) {
130
+ this.checkStmt(stmt)
131
+ }
132
+ }
133
+
134
+ private checkStmt(stmt: Stmt): void {
135
+ switch (stmt.kind) {
136
+ case 'let':
137
+ this.checkLetStmt(stmt)
138
+ break
139
+ case 'return':
140
+ this.checkReturnStmt(stmt)
141
+ break
142
+ case 'if':
143
+ this.checkExpr(stmt.cond)
144
+ this.checkBlock(stmt.then)
145
+ if (stmt.else_) this.checkBlock(stmt.else_)
146
+ break
147
+ case 'while':
148
+ this.checkExpr(stmt.cond)
149
+ this.checkBlock(stmt.body)
150
+ break
151
+ case 'for':
152
+ if (stmt.init) this.checkStmt(stmt.init)
153
+ this.checkExpr(stmt.cond)
154
+ this.checkExpr(stmt.step)
155
+ this.checkBlock(stmt.body)
156
+ break
157
+ case 'foreach':
158
+ this.checkExpr(stmt.iterable)
159
+ if (stmt.iterable.kind === 'selector') {
160
+ this.scope.set(stmt.binding, { type: { kind: 'named', name: 'void' }, mutable: true }) // Entity marker
161
+ } else {
162
+ const iterableType = this.inferType(stmt.iterable)
163
+ if (iterableType.kind === 'array') {
164
+ this.scope.set(stmt.binding, { type: iterableType.elem, mutable: true })
165
+ } else {
166
+ this.scope.set(stmt.binding, { type: { kind: 'named', name: 'void' }, mutable: true })
167
+ }
168
+ }
169
+ this.checkBlock(stmt.body)
170
+ break
171
+ case 'match':
172
+ this.checkExpr(stmt.expr)
173
+ for (const arm of stmt.arms) {
174
+ if (arm.pattern) {
175
+ this.checkExpr(arm.pattern)
176
+ if (!this.typesMatch(this.inferType(stmt.expr), this.inferType(arm.pattern))) {
177
+ this.report('Match arm pattern type must match subject type', arm.pattern)
178
+ }
179
+ }
180
+ this.checkBlock(arm.body)
181
+ }
182
+ break
183
+ case 'as_block':
184
+ case 'at_block':
185
+ this.checkBlock(stmt.body)
186
+ break
187
+ case 'as_at':
188
+ this.checkBlock(stmt.body)
189
+ break
190
+ case 'execute':
191
+ this.checkBlock(stmt.body)
192
+ break
193
+ case 'expr':
194
+ this.checkExpr(stmt.expr)
195
+ break
196
+ case 'raw':
197
+ // Raw commands are not type checked
198
+ break
199
+ }
200
+ }
201
+
202
+ private checkLetStmt(stmt: Extract<Stmt, { kind: 'let' }>): void {
203
+ // Check initializer
204
+ const expectedType = stmt.type ? this.normalizeType(stmt.type) : undefined
205
+ this.checkExpr(stmt.init, expectedType)
206
+
207
+ // Add variable to scope
208
+ const type = expectedType ?? this.inferType(stmt.init)
209
+ this.scope.set(stmt.name, { type, mutable: true })
210
+
211
+ const actualType = this.inferType(stmt.init, expectedType)
212
+ if (
213
+ expectedType &&
214
+ stmt.init.kind !== 'struct_lit' &&
215
+ stmt.init.kind !== 'array_lit' &&
216
+ !(actualType.kind === 'named' && actualType.name === 'void') &&
217
+ !this.typesMatch(expectedType, actualType)
218
+ ) {
219
+ this.report(
220
+ `Type mismatch: expected ${this.typeToString(expectedType)}, got ${this.typeToString(actualType)}`,
221
+ stmt
222
+ )
223
+ }
224
+ }
225
+
226
+ private checkReturnStmt(stmt: Extract<Stmt, { kind: 'return' }>): void {
227
+ if (!this.currentReturnType) return
228
+
229
+ const expectedType = this.currentReturnType
230
+
231
+ if (stmt.value) {
232
+ const actualType = this.inferType(stmt.value, expectedType)
233
+ this.checkExpr(stmt.value, expectedType)
234
+
235
+ if (!this.typesMatch(expectedType, actualType)) {
236
+ this.report(
237
+ `Return type mismatch: expected ${this.typeToString(expectedType)}, got ${this.typeToString(actualType)}`,
238
+ stmt
239
+ )
240
+ }
241
+ } else {
242
+ // No return value
243
+ if (expectedType.kind !== 'named' || expectedType.name !== 'void') {
244
+ this.report(`Missing return value: expected ${this.typeToString(expectedType)}`, stmt)
245
+ }
246
+ }
247
+ }
248
+
249
+ private checkExpr(expr: Expr, expectedType?: TypeNode): void {
250
+ switch (expr.kind) {
251
+ case 'ident':
252
+ if (!this.scope.has(expr.name)) {
253
+ this.report(`Variable '${expr.name}' used before declaration`, expr)
254
+ }
255
+ break
256
+
257
+ case 'call':
258
+ this.checkCallExpr(expr)
259
+ break
260
+
261
+ case 'invoke':
262
+ this.checkInvokeExpr(expr)
263
+ break
264
+
265
+ case 'member':
266
+ this.checkMemberExpr(expr)
267
+ break
268
+
269
+ case 'binary':
270
+ this.checkExpr(expr.left)
271
+ this.checkExpr(expr.right)
272
+ break
273
+
274
+ case 'unary':
275
+ this.checkExpr(expr.operand)
276
+ break
277
+
278
+ case 'assign':
279
+ if (!this.scope.has(expr.target)) {
280
+ this.report(`Variable '${expr.target}' used before declaration`, expr)
281
+ } else if (!this.scope.get(expr.target)?.mutable) {
282
+ this.report(`Cannot assign to const '${expr.target}'`, expr)
283
+ }
284
+ this.checkExpr(expr.value, this.scope.get(expr.target)?.type)
285
+ break
286
+
287
+ case 'member_assign':
288
+ this.checkExpr(expr.obj)
289
+ this.checkExpr(expr.value)
290
+ break
291
+
292
+ case 'index':
293
+ this.checkExpr(expr.obj)
294
+ this.checkExpr(expr.index)
295
+ const indexType = this.inferType(expr.index)
296
+ if (indexType.kind !== 'named' || indexType.name !== 'int') {
297
+ this.report('Array index must be int', expr.index)
298
+ }
299
+ break
300
+
301
+ case 'struct_lit':
302
+ for (const field of expr.fields) {
303
+ this.checkExpr(field.value)
304
+ }
305
+ break
306
+
307
+ case 'str_interp':
308
+ for (const part of expr.parts) {
309
+ if (typeof part !== 'string') {
310
+ this.checkExpr(part)
311
+ }
312
+ }
313
+ break
314
+
315
+ case 'array_lit':
316
+ for (const elem of expr.elements) {
317
+ this.checkExpr(elem)
318
+ }
319
+ break
320
+
321
+ case 'lambda':
322
+ this.checkLambdaExpr(expr, expectedType)
323
+ break
324
+
325
+ case 'blockpos':
326
+ break
327
+
328
+ case 'static_call':
329
+ for (const arg of expr.args) {
330
+ this.checkExpr(arg)
331
+ }
332
+ break
333
+
334
+ // Literals don't need checking
335
+ case 'int_lit':
336
+ case 'float_lit':
337
+ case 'bool_lit':
338
+ case 'str_lit':
339
+ case 'mc_name':
340
+ case 'range_lit':
341
+ case 'selector':
342
+ case 'byte_lit':
343
+ case 'short_lit':
344
+ case 'long_lit':
345
+ case 'double_lit':
346
+ break
347
+ }
348
+ }
349
+
350
+ private checkCallExpr(expr: Extract<Expr, { kind: 'call' }>): void {
351
+ if (expr.fn === 'tp' || expr.fn === 'tp_to') {
352
+ this.checkTpCall(expr)
353
+ }
354
+
355
+ // Check if function exists and arg count matches
356
+ const fn = this.functions.get(expr.fn)
357
+ if (fn) {
358
+ const requiredParams = fn.params.filter(param => !param.default).length
359
+ if (expr.args.length < requiredParams || expr.args.length > fn.params.length) {
360
+ const expectedRange = requiredParams === fn.params.length
361
+ ? `${fn.params.length}`
362
+ : `${requiredParams}-${fn.params.length}`
363
+ this.report(
364
+ `Function '${expr.fn}' expects ${expectedRange} arguments, got ${expr.args.length}`,
365
+ expr
366
+ )
367
+ }
368
+ for (let i = 0; i < expr.args.length; i++) {
369
+ const paramType = fn.params[i] ? this.normalizeType(fn.params[i].type) : undefined
370
+ if (paramType) {
371
+ this.checkExpr(expr.args[i], paramType)
372
+ }
373
+ const argType = this.inferType(expr.args[i], paramType)
374
+ if (paramType && !this.typesMatch(paramType, argType)) {
375
+ this.report(
376
+ `Argument ${i + 1} of '${expr.fn}' expects ${this.typeToString(paramType)}, got ${this.typeToString(argType)}`,
377
+ expr.args[i]
378
+ )
379
+ }
380
+ }
381
+ return
382
+ }
383
+
384
+ const varType = this.scope.get(expr.fn)?.type
385
+ if (varType?.kind === 'function_type') {
386
+ this.checkFunctionCallArgs(expr.args, varType.params, expr.fn, expr)
387
+ return
388
+ }
389
+
390
+ for (const arg of expr.args) {
391
+ this.checkExpr(arg)
392
+ }
393
+ // Built-in functions are not checked for arg count
394
+ }
395
+
396
+ private checkInvokeExpr(expr: Extract<Expr, { kind: 'invoke' }>): void {
397
+ this.checkExpr(expr.callee)
398
+ const calleeType = this.inferType(expr.callee)
399
+ if (calleeType.kind !== 'function_type') {
400
+ this.report('Attempted to call a non-function value', expr.callee)
401
+ for (const arg of expr.args) {
402
+ this.checkExpr(arg)
403
+ }
404
+ return
405
+ }
406
+
407
+ this.checkFunctionCallArgs(expr.args, calleeType.params, 'lambda', expr)
408
+ }
409
+
410
+ private checkFunctionCallArgs(
411
+ args: Expr[],
412
+ params: TypeNode[],
413
+ calleeName: string,
414
+ node: Expr
415
+ ): void {
416
+ if (args.length !== params.length) {
417
+ this.report(`Function '${calleeName}' expects ${params.length} arguments, got ${args.length}`, node)
418
+ }
419
+
420
+ for (let i = 0; i < args.length; i++) {
421
+ const paramType = params[i]
422
+ if (!paramType) {
423
+ this.checkExpr(args[i])
424
+ continue
425
+ }
426
+ this.checkExpr(args[i], paramType)
427
+ const argType = this.inferType(args[i], paramType)
428
+ if (!this.typesMatch(paramType, argType)) {
429
+ this.report(
430
+ `Argument ${i + 1} of '${calleeName}' expects ${this.typeToString(paramType)}, got ${this.typeToString(argType)}`,
431
+ args[i]
432
+ )
433
+ }
434
+ }
435
+ }
436
+
437
+ private checkTpCall(expr: Extract<Expr, { kind: 'call' }>): void {
438
+ const dest = expr.args[1]
439
+ if (!dest) {
440
+ return
441
+ }
442
+
443
+ const destType = this.inferType(dest)
444
+ if (destType.kind === 'named' && destType.name === 'BlockPos') {
445
+ return
446
+ }
447
+
448
+ if (dest.kind === 'selector' && !dest.isSingle) {
449
+ this.report(
450
+ 'tp destination must be a single-entity selector (@s, @p, @r, or limit=1)',
451
+ dest
452
+ )
453
+ }
454
+ }
455
+
456
+ private checkMemberExpr(expr: Extract<Expr, { kind: 'member' }>): void {
457
+ if (!(expr.obj.kind === 'ident' && this.enums.has(expr.obj.name))) {
458
+ this.checkExpr(expr.obj)
459
+ }
460
+
461
+ // Check if accessing member on appropriate type
462
+ if (expr.obj.kind === 'ident') {
463
+ if (this.enums.has(expr.obj.name)) {
464
+ const enumVariants = this.enums.get(expr.obj.name)!
465
+ if (!enumVariants.has(expr.field)) {
466
+ this.report(`Enum '${expr.obj.name}' has no variant '${expr.field}'`, expr)
467
+ }
468
+ return
469
+ }
470
+
471
+ const varSymbol = this.scope.get(expr.obj.name)
472
+ const varType = varSymbol?.type
473
+ if (varType) {
474
+ // Allow member access on struct types
475
+ if (varType.kind === 'struct') {
476
+ const structFields = this.structs.get(varType.name)
477
+ if (structFields && !structFields.has(expr.field)) {
478
+ this.report(`Struct '${varType.name}' has no field '${expr.field}'`, expr)
479
+ }
480
+ } else if (varType.kind === 'array') {
481
+ if (expr.field !== 'len' && expr.field !== 'push' && expr.field !== 'pop') {
482
+ this.report(`Array has no field '${expr.field}'`, expr)
483
+ }
484
+ } else if (varType.kind === 'named') {
485
+ // Entity marker (void) - allow all members
486
+ if (varType.name !== 'void') {
487
+ // Only warn for primitive types
488
+ if (['int', 'bool', 'float', 'string', 'byte', 'short', 'long', 'double'].includes(varType.name)) {
489
+ this.report(
490
+ `Cannot access member '${expr.field}' on ${this.typeToString(varType)}`,
491
+ expr
492
+ )
493
+ }
494
+ }
495
+ }
496
+ }
497
+ }
498
+ }
499
+
500
+ private checkLambdaExpr(expr: Extract<Expr, { kind: 'lambda' }>, expectedType?: TypeNode): void {
501
+ const normalizedExpected = expectedType ? this.normalizeType(expectedType) : undefined
502
+ const expectedFnType = normalizedExpected?.kind === 'function_type' ? normalizedExpected : undefined
503
+ const lambdaType = this.inferLambdaType(expr, expectedFnType)
504
+
505
+ if (expectedFnType && !this.typesMatch(expectedFnType, lambdaType)) {
506
+ this.report(
507
+ `Type mismatch: expected ${this.typeToString(expectedFnType)}, got ${this.typeToString(lambdaType)}`,
508
+ expr
509
+ )
510
+ return
511
+ }
512
+
513
+ const outerScope = this.scope
514
+ const outerReturnType = this.currentReturnType
515
+ const lambdaScope = new Map(this.scope)
516
+ const paramTypes = expectedFnType?.params ?? lambdaType.params
517
+
518
+ for (let i = 0; i < expr.params.length; i++) {
519
+ lambdaScope.set(expr.params[i].name, {
520
+ type: paramTypes[i] ?? { kind: 'named', name: 'void' },
521
+ mutable: true,
522
+ })
523
+ }
524
+
525
+ this.scope = lambdaScope
526
+ this.currentReturnType = expr.returnType
527
+ ? this.normalizeType(expr.returnType)
528
+ : (expectedFnType?.return ?? lambdaType.return)
529
+
530
+ if (Array.isArray(expr.body)) {
531
+ this.checkBlock(expr.body)
532
+ } else {
533
+ this.checkExpr(expr.body, this.currentReturnType)
534
+ const actualType = this.inferType(expr.body, this.currentReturnType)
535
+ if (!this.typesMatch(this.currentReturnType, actualType)) {
536
+ this.report(
537
+ `Return type mismatch: expected ${this.typeToString(this.currentReturnType)}, got ${this.typeToString(actualType)}`,
538
+ expr.body
539
+ )
540
+ }
541
+ }
542
+
543
+ this.scope = outerScope
544
+ this.currentReturnType = outerReturnType
545
+ }
546
+
547
+ private inferType(expr: Expr, expectedType?: TypeNode): TypeNode {
548
+ switch (expr.kind) {
549
+ case 'int_lit':
550
+ return { kind: 'named', name: 'int' }
551
+ case 'float_lit':
552
+ return { kind: 'named', name: 'float' }
553
+ case 'byte_lit':
554
+ return { kind: 'named', name: 'byte' }
555
+ case 'short_lit':
556
+ return { kind: 'named', name: 'short' }
557
+ case 'long_lit':
558
+ return { kind: 'named', name: 'long' }
559
+ case 'double_lit':
560
+ return { kind: 'named', name: 'double' }
561
+ case 'bool_lit':
562
+ return { kind: 'named', name: 'bool' }
563
+ case 'str_lit':
564
+ case 'mc_name':
565
+ return { kind: 'named', name: 'string' }
566
+ case 'str_interp':
567
+ for (const part of expr.parts) {
568
+ if (typeof part !== 'string') {
569
+ this.checkExpr(part)
570
+ }
571
+ }
572
+ return { kind: 'named', name: 'string' }
573
+ case 'blockpos':
574
+ return { kind: 'named', name: 'BlockPos' }
575
+ case 'ident':
576
+ return this.scope.get(expr.name)?.type ?? { kind: 'named', name: 'void' }
577
+ case 'call': {
578
+ if (expr.fn === '__array_push') {
579
+ return { kind: 'named', name: 'void' }
580
+ }
581
+ if (expr.fn === '__array_pop') {
582
+ const target = expr.args[0]
583
+ if (target && target.kind === 'ident') {
584
+ const targetType = this.scope.get(target.name)?.type
585
+ if (targetType?.kind === 'array') return targetType.elem
586
+ }
587
+ return { kind: 'named', name: 'int' }
588
+ }
589
+ if (expr.fn === 'bossbar_get_value') {
590
+ return { kind: 'named', name: 'int' }
591
+ }
592
+ if (expr.fn === 'random_sequence') {
593
+ return { kind: 'named', name: 'void' }
594
+ }
595
+ const varType = this.scope.get(expr.fn)?.type
596
+ if (varType?.kind === 'function_type') {
597
+ return varType.return
598
+ }
599
+ const fn = this.functions.get(expr.fn)
600
+ return fn?.returnType ?? { kind: 'named', name: 'int' }
601
+ }
602
+ case 'invoke': {
603
+ const calleeType = this.inferType(expr.callee)
604
+ if (calleeType.kind === 'function_type') {
605
+ return calleeType.return
606
+ }
607
+ return { kind: 'named', name: 'void' }
608
+ }
609
+ case 'member':
610
+ if (expr.obj.kind === 'ident' && this.enums.has(expr.obj.name)) {
611
+ return { kind: 'enum', name: expr.obj.name }
612
+ }
613
+ if (expr.obj.kind === 'ident') {
614
+ const objTypeNode = this.scope.get(expr.obj.name)?.type
615
+ if (objTypeNode?.kind === 'array' && expr.field === 'len') {
616
+ return { kind: 'named', name: 'int' }
617
+ }
618
+ }
619
+ return { kind: 'named', name: 'void' }
620
+ case 'index': {
621
+ const objType = this.inferType(expr.obj)
622
+ if (objType.kind === 'array') return objType.elem
623
+ return { kind: 'named', name: 'void' }
624
+ }
625
+ case 'binary':
626
+ if (['==', '!=', '<', '<=', '>', '>=', '&&', '||'].includes(expr.op)) {
627
+ return { kind: 'named', name: 'bool' }
628
+ }
629
+ return this.inferType(expr.left)
630
+ case 'unary':
631
+ if (expr.op === '!') return { kind: 'named', name: 'bool' }
632
+ return this.inferType(expr.operand)
633
+ case 'array_lit':
634
+ if (expr.elements.length > 0) {
635
+ return { kind: 'array', elem: this.inferType(expr.elements[0]) }
636
+ }
637
+ return { kind: 'array', elem: { kind: 'named', name: 'int' } }
638
+ case 'lambda':
639
+ return this.inferLambdaType(
640
+ expr,
641
+ expectedType && this.normalizeType(expectedType).kind === 'function_type'
642
+ ? this.normalizeType(expectedType) as Extract<TypeNode, { kind: 'function_type' }>
643
+ : undefined
644
+ )
645
+ default:
646
+ return { kind: 'named', name: 'void' }
647
+ }
648
+ }
649
+
650
+ private inferLambdaType(
651
+ expr: Extract<Expr, { kind: 'lambda' }>,
652
+ expectedType?: Extract<TypeNode, { kind: 'function_type' }>
653
+ ): Extract<TypeNode, { kind: 'function_type' }> {
654
+ const params: TypeNode[] = expr.params.map((param, index) => {
655
+ if (param.type) {
656
+ return this.normalizeType(param.type)
657
+ }
658
+ const inferred = expectedType?.params[index]
659
+ if (inferred) {
660
+ return inferred
661
+ }
662
+ this.report(`Lambda parameter '${param.name}' requires a type annotation`, expr)
663
+ return { kind: 'named', name: 'void' }
664
+ })
665
+
666
+ let returnType: TypeNode | undefined = expr.returnType
667
+ ? this.normalizeType(expr.returnType)
668
+ : expectedType?.return
669
+ if (!returnType) {
670
+ returnType = Array.isArray(expr.body) ? { kind: 'named', name: 'void' } : this.inferType(expr.body)
671
+ }
672
+
673
+ return { kind: 'function_type', params, return: returnType }
674
+ }
675
+
676
+ private typesMatch(expected: TypeNode, actual: TypeNode): boolean {
677
+ if (expected.kind !== actual.kind) return false
678
+
679
+ if (expected.kind === 'named' && actual.kind === 'named') {
680
+ // void matches anything (for inferred types)
681
+ if (actual.name === 'void') return true
682
+ return expected.name === actual.name
683
+ }
684
+
685
+ if (expected.kind === 'array' && actual.kind === 'array') {
686
+ return this.typesMatch(expected.elem, actual.elem)
687
+ }
688
+
689
+ if (expected.kind === 'struct' && actual.kind === 'struct') {
690
+ return expected.name === actual.name
691
+ }
692
+
693
+ if (expected.kind === 'enum' && actual.kind === 'enum') {
694
+ return expected.name === actual.name
695
+ }
696
+
697
+ if (expected.kind === 'function_type' && actual.kind === 'function_type') {
698
+ return expected.params.length === actual.params.length &&
699
+ expected.params.every((param, index) => this.typesMatch(param, actual.params[index])) &&
700
+ this.typesMatch(expected.return, actual.return)
701
+ }
702
+
703
+ return false
704
+ }
705
+
706
+ private typeToString(type: TypeNode): string {
707
+ switch (type.kind) {
708
+ case 'named':
709
+ return type.name
710
+ case 'array':
711
+ return `${this.typeToString(type.elem)}[]`
712
+ case 'struct':
713
+ return type.name
714
+ case 'enum':
715
+ return type.name
716
+ case 'function_type':
717
+ return `(${type.params.map(param => this.typeToString(param)).join(', ')}) -> ${this.typeToString(type.return)}`
718
+ }
719
+ }
720
+
721
+ private normalizeType(type: TypeNode): TypeNode {
722
+ if (type.kind === 'array') {
723
+ return { kind: 'array', elem: this.normalizeType(type.elem) }
724
+ }
725
+ if (type.kind === 'function_type') {
726
+ return {
727
+ kind: 'function_type',
728
+ params: type.params.map(param => this.normalizeType(param)),
729
+ return: this.normalizeType(type.return),
730
+ }
731
+ }
732
+ if ((type.kind === 'struct' || type.kind === 'enum') && this.enums.has(type.name)) {
733
+ return { kind: 'enum', name: type.name }
734
+ }
735
+ return type
736
+ }
737
+ }