redscript-mc 3.0.1 → 3.0.2

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 (225) hide show
  1. package/.github/workflows/ci.yml +1 -0
  2. package/README.md +119 -313
  3. package/README.zh.md +118 -314
  4. package/ROADMAP.md +5 -5
  5. package/dist/data/impl_test/function/counter/get.mcfunction +5 -0
  6. package/dist/data/impl_test/function/counter/inc.mcfunction +7 -0
  7. package/dist/data/impl_test/function/counter/new.mcfunction +4 -0
  8. package/dist/data/impl_test/function/load.mcfunction +1 -0
  9. package/dist/data/impl_test/function/test_impl.mcfunction +10 -0
  10. package/dist/data/minecraft/tags/function/load.json +5 -0
  11. package/dist/data/playground/function/load.mcfunction +1 -0
  12. package/dist/data/playground/function/start.mcfunction +4 -0
  13. package/dist/data/playground/function/start__say_macro_t1.mcfunction +1 -0
  14. package/dist/data/playground/function/stop.mcfunction +5 -0
  15. package/dist/data/playground/function/stop__say_macro_t0.mcfunction +1 -0
  16. package/dist/data/stdlib_queue8_test/function/__queue_append_apply.mcfunction +4 -0
  17. package/dist/data/stdlib_queue8_test/function/__queue_peek_apply.mcfunction +4 -0
  18. package/dist/data/stdlib_queue8_test/function/__queue_size_raw_apply.mcfunction +4 -0
  19. package/dist/data/stdlib_queue8_test/function/load.mcfunction +1 -0
  20. package/dist/data/stdlib_queue8_test/function/queue_clear.mcfunction +6 -0
  21. package/dist/data/stdlib_queue8_test/function/queue_empty__merge_1.mcfunction +5 -0
  22. package/dist/data/stdlib_queue8_test/function/queue_empty__then_0.mcfunction +5 -0
  23. package/dist/data/stdlib_queue8_test/function/queue_peek__merge_1.mcfunction +13 -0
  24. package/dist/data/stdlib_queue8_test/function/queue_peek__then_0.mcfunction +5 -0
  25. package/dist/data/stdlib_queue8_test/function/queue_pop__merge_1.mcfunction +15 -0
  26. package/dist/data/stdlib_queue8_test/function/queue_pop__then_0.mcfunction +5 -0
  27. package/dist/data/stdlib_queue8_test/function/queue_push__const_11.mcfunction +6 -0
  28. package/dist/data/stdlib_queue8_test/function/queue_push__const_22.mcfunction +6 -0
  29. package/dist/data/stdlib_queue8_test/function/queue_size.mcfunction +13 -0
  30. package/dist/data/stdlib_queue8_test/function/test_queue_push_and_size.mcfunction +13 -0
  31. package/dist/data/test/function/load.mcfunction +1 -0
  32. package/dist/data/test/function/say_at.mcfunction +6 -0
  33. package/dist/data/test/function/test.mcfunction +4 -0
  34. package/dist/pack.mcmeta +6 -0
  35. package/dist/package.json +1 -1
  36. package/dist/src/__tests__/formatter-extra.test.d.ts +7 -0
  37. package/dist/src/__tests__/formatter-extra.test.js +123 -0
  38. package/dist/src/__tests__/global-vars.test.d.ts +13 -0
  39. package/dist/src/__tests__/global-vars.test.js +156 -0
  40. package/dist/src/__tests__/lint/new-rules.test.d.ts +9 -0
  41. package/dist/src/__tests__/lint/new-rules.test.js +402 -0
  42. package/dist/src/__tests__/lsp-rename.test.d.ts +8 -0
  43. package/dist/src/__tests__/lsp-rename.test.js +157 -0
  44. package/dist/src/__tests__/mc-integration/say-fstring.test.d.ts +11 -0
  45. package/dist/src/__tests__/mc-integration/say-fstring.test.js +220 -0
  46. package/dist/src/__tests__/mc-integration/stdlib-coverage-2.test.js +1 -1
  47. package/dist/src/__tests__/mc-integration/stdlib-coverage-3.test.js +1 -1
  48. package/dist/src/__tests__/mc-integration/stdlib-coverage-4.test.js +1 -1
  49. package/dist/src/__tests__/mc-integration/stdlib-coverage-5.test.js +1 -1
  50. package/dist/src/__tests__/mc-integration/stdlib-coverage-6.test.js +1 -1
  51. package/dist/src/__tests__/mc-integration/stdlib-coverage-7.test.js +1 -1
  52. package/dist/src/__tests__/mc-integration/stdlib-coverage-8.test.js +1 -1
  53. package/dist/src/__tests__/mc-syntax.test.js +4 -1
  54. package/dist/src/__tests__/monomorphize-coverage.test.d.ts +9 -0
  55. package/dist/src/__tests__/monomorphize-coverage.test.js +204 -0
  56. package/dist/src/__tests__/optimizer-cse.test.d.ts +7 -0
  57. package/dist/src/__tests__/optimizer-cse.test.js +226 -0
  58. package/dist/src/__tests__/parser.test.js +4 -13
  59. package/dist/src/__tests__/repl-server-extra.test.js +6 -7
  60. package/dist/src/__tests__/repl-server.test.js +5 -7
  61. package/dist/src/__tests__/stdlib/queue.test.js +6 -6
  62. package/dist/src/cli.js +0 -0
  63. package/dist/src/lexer/index.js +2 -1
  64. package/dist/src/lint/index.d.ts +12 -5
  65. package/dist/src/lint/index.js +730 -5
  66. package/dist/src/lsp/main.js +0 -0
  67. package/dist/src/mc-test/client.d.ts +21 -0
  68. package/dist/src/mc-test/client.js +34 -0
  69. package/dist/src/mir/lower.js +108 -6
  70. package/dist/src/optimizer/interprocedural.js +37 -2
  71. package/dist/src/parser/decl-parser.d.ts +19 -0
  72. package/dist/src/parser/decl-parser.js +323 -0
  73. package/dist/src/parser/expr-parser.d.ts +46 -0
  74. package/dist/src/parser/expr-parser.js +759 -0
  75. package/dist/src/parser/index.d.ts +8 -129
  76. package/dist/src/parser/index.js +13 -2262
  77. package/dist/src/parser/stmt-parser.d.ts +28 -0
  78. package/dist/src/parser/stmt-parser.js +577 -0
  79. package/dist/src/parser/type-parser.d.ts +20 -0
  80. package/dist/src/parser/type-parser.js +257 -0
  81. package/dist/src/parser/utils.d.ts +34 -0
  82. package/dist/src/parser/utils.js +141 -0
  83. package/docs/dev/README-mc-integration-tests.md +141 -0
  84. package/docs/lint-rules.md +162 -0
  85. package/docs/stdlib/bigint.md +2 -0
  86. package/editors/vscode/README.md +63 -41
  87. package/editors/vscode/out/extension.js +1881 -1776
  88. package/editors/vscode/out/lsp-server.js +4257 -3651
  89. package/editors/vscode/package-lock.json +3 -3
  90. package/editors/vscode/package.json +1 -1
  91. package/examples/loops-demo.mcrs +87 -0
  92. package/package.json +1 -1
  93. package/redscript-docs/docs/en/stdlib/advanced.md +629 -0
  94. package/redscript-docs/docs/en/stdlib/bigint.md +316 -0
  95. package/redscript-docs/docs/en/stdlib/bits.md +292 -0
  96. package/redscript-docs/docs/en/stdlib/bossbar.md +177 -0
  97. package/redscript-docs/docs/en/stdlib/calculus.md +289 -0
  98. package/redscript-docs/docs/en/stdlib/color.md +353 -0
  99. package/redscript-docs/docs/en/stdlib/combat.md +88 -0
  100. package/redscript-docs/docs/en/stdlib/cooldown.md +82 -0
  101. package/redscript-docs/docs/en/stdlib/dialog.md +155 -0
  102. package/redscript-docs/docs/en/stdlib/easing.md +558 -0
  103. package/redscript-docs/docs/en/stdlib/ecs.md +475 -0
  104. package/redscript-docs/docs/en/stdlib/effects.md +324 -0
  105. package/redscript-docs/docs/en/stdlib/events.md +3 -0
  106. package/redscript-docs/docs/en/stdlib/expr.md +45 -0
  107. package/redscript-docs/docs/en/stdlib/fft.md +141 -0
  108. package/redscript-docs/docs/en/stdlib/geometry.md +430 -0
  109. package/redscript-docs/docs/en/stdlib/graph.md +259 -0
  110. package/redscript-docs/docs/en/stdlib/heap.md +185 -0
  111. package/redscript-docs/docs/en/stdlib/interactions.md +179 -0
  112. package/redscript-docs/docs/en/stdlib/inventory.md +97 -0
  113. package/redscript-docs/docs/en/stdlib/linalg.md +557 -0
  114. package/redscript-docs/docs/en/stdlib/list.md +559 -0
  115. package/redscript-docs/docs/en/stdlib/map.md +140 -0
  116. package/redscript-docs/docs/en/stdlib/math.md +193 -0
  117. package/redscript-docs/docs/en/stdlib/math_hp.md +149 -0
  118. package/redscript-docs/docs/en/stdlib/matrix.md +403 -0
  119. package/redscript-docs/docs/en/stdlib/mobs.md +965 -0
  120. package/redscript-docs/docs/en/stdlib/noise.md +244 -0
  121. package/redscript-docs/docs/en/stdlib/ode.md +253 -0
  122. package/redscript-docs/docs/en/stdlib/parabola.md +342 -0
  123. package/redscript-docs/docs/en/stdlib/particles.md +311 -0
  124. package/redscript-docs/docs/en/stdlib/pathfind.md +255 -0
  125. package/redscript-docs/docs/en/stdlib/physics.md +493 -0
  126. package/redscript-docs/docs/en/stdlib/player.md +78 -0
  127. package/redscript-docs/docs/en/stdlib/quaternion.md +673 -0
  128. package/redscript-docs/docs/en/stdlib/queue.md +134 -0
  129. package/redscript-docs/docs/en/stdlib/random.md +223 -0
  130. package/redscript-docs/docs/en/stdlib/result.md +143 -0
  131. package/redscript-docs/docs/en/stdlib/scheduler.md +183 -0
  132. package/redscript-docs/docs/en/stdlib/set_int.md +190 -0
  133. package/redscript-docs/docs/en/stdlib/sets.md +101 -0
  134. package/redscript-docs/docs/en/stdlib/signal.md +400 -0
  135. package/redscript-docs/docs/en/stdlib/sort.md +104 -0
  136. package/redscript-docs/docs/en/stdlib/spawn.md +147 -0
  137. package/redscript-docs/docs/en/stdlib/state.md +142 -0
  138. package/redscript-docs/docs/en/stdlib/strings.md +154 -0
  139. package/redscript-docs/docs/en/stdlib/tags.md +3451 -0
  140. package/redscript-docs/docs/en/stdlib/teams.md +153 -0
  141. package/redscript-docs/docs/en/stdlib/timer.md +246 -0
  142. package/redscript-docs/docs/en/stdlib/vec.md +158 -0
  143. package/redscript-docs/docs/en/stdlib/world.md +298 -0
  144. package/redscript-docs/docs/zh/stdlib/advanced.md +615 -0
  145. package/redscript-docs/docs/zh/stdlib/bigint.md +316 -0
  146. package/redscript-docs/docs/zh/stdlib/bits.md +292 -0
  147. package/redscript-docs/docs/zh/stdlib/bossbar.md +170 -0
  148. package/redscript-docs/docs/zh/stdlib/calculus.md +287 -0
  149. package/redscript-docs/docs/zh/stdlib/color.md +353 -0
  150. package/redscript-docs/docs/zh/stdlib/combat.md +88 -0
  151. package/redscript-docs/docs/zh/stdlib/cooldown.md +84 -0
  152. package/redscript-docs/docs/zh/stdlib/dialog.md +152 -0
  153. package/redscript-docs/docs/zh/stdlib/easing.md +558 -0
  154. package/redscript-docs/docs/zh/stdlib/ecs.md +472 -0
  155. package/redscript-docs/docs/zh/stdlib/effects.md +324 -0
  156. package/redscript-docs/docs/zh/stdlib/events.md +3 -0
  157. package/redscript-docs/docs/zh/stdlib/expr.md +37 -0
  158. package/redscript-docs/docs/zh/stdlib/fft.md +128 -0
  159. package/redscript-docs/docs/zh/stdlib/geometry.md +430 -0
  160. package/redscript-docs/docs/zh/stdlib/graph.md +259 -0
  161. package/redscript-docs/docs/zh/stdlib/heap.md +185 -0
  162. package/redscript-docs/docs/zh/stdlib/interactions.md +160 -0
  163. package/redscript-docs/docs/zh/stdlib/inventory.md +94 -0
  164. package/redscript-docs/docs/zh/stdlib/linalg.md +543 -0
  165. package/redscript-docs/docs/zh/stdlib/list.md +561 -0
  166. package/redscript-docs/docs/zh/stdlib/map.md +132 -0
  167. package/redscript-docs/docs/zh/stdlib/math.md +193 -0
  168. package/redscript-docs/docs/zh/stdlib/math_hp.md +143 -0
  169. package/redscript-docs/docs/zh/stdlib/matrix.md +396 -0
  170. package/redscript-docs/docs/zh/stdlib/mobs.md +965 -0
  171. package/redscript-docs/docs/zh/stdlib/noise.md +244 -0
  172. package/redscript-docs/docs/zh/stdlib/ode.md +243 -0
  173. package/redscript-docs/docs/zh/stdlib/parabola.md +337 -0
  174. package/redscript-docs/docs/zh/stdlib/particles.md +307 -0
  175. package/redscript-docs/docs/zh/stdlib/pathfind.md +255 -0
  176. package/redscript-docs/docs/zh/stdlib/physics.md +493 -0
  177. package/redscript-docs/docs/zh/stdlib/player.md +78 -0
  178. package/redscript-docs/docs/zh/stdlib/quaternion.md +669 -0
  179. package/redscript-docs/docs/zh/stdlib/queue.md +124 -0
  180. package/redscript-docs/docs/zh/stdlib/random.md +222 -0
  181. package/redscript-docs/docs/zh/stdlib/result.md +147 -0
  182. package/redscript-docs/docs/zh/stdlib/scheduler.md +173 -0
  183. package/redscript-docs/docs/zh/stdlib/set_int.md +180 -0
  184. package/redscript-docs/docs/zh/stdlib/sets.md +107 -0
  185. package/redscript-docs/docs/zh/stdlib/signal.md +373 -0
  186. package/redscript-docs/docs/zh/stdlib/sort.md +104 -0
  187. package/redscript-docs/docs/zh/stdlib/spawn.md +142 -0
  188. package/redscript-docs/docs/zh/stdlib/state.md +134 -0
  189. package/redscript-docs/docs/zh/stdlib/strings.md +107 -0
  190. package/redscript-docs/docs/zh/stdlib/tags.md +3451 -0
  191. package/redscript-docs/docs/zh/stdlib/teams.md +150 -0
  192. package/redscript-docs/docs/zh/stdlib/timer.md +254 -0
  193. package/redscript-docs/docs/zh/stdlib/vec.md +158 -0
  194. package/redscript-docs/docs/zh/stdlib/world.md +289 -0
  195. package/src/__tests__/formatter-extra.test.ts +139 -0
  196. package/src/__tests__/global-vars.test.ts +171 -0
  197. package/src/__tests__/lint/new-rules.test.ts +437 -0
  198. package/src/__tests__/lsp-rename.test.ts +171 -0
  199. package/src/__tests__/mc-integration/say-fstring.test.ts +211 -0
  200. package/src/__tests__/mc-integration/stdlib-coverage-2.test.ts +1 -1
  201. package/src/__tests__/mc-integration/stdlib-coverage-3.test.ts +1 -1
  202. package/src/__tests__/mc-integration/stdlib-coverage-4.test.ts +1 -1
  203. package/src/__tests__/mc-integration/stdlib-coverage-5.test.ts +1 -1
  204. package/src/__tests__/mc-integration/stdlib-coverage-6.test.ts +1 -1
  205. package/src/__tests__/mc-integration/stdlib-coverage-7.test.ts +1 -1
  206. package/src/__tests__/mc-integration/stdlib-coverage-8.test.ts +1 -1
  207. package/src/__tests__/mc-syntax.test.ts +3 -0
  208. package/src/__tests__/monomorphize-coverage.test.ts +220 -0
  209. package/src/__tests__/optimizer-cse.test.ts +250 -0
  210. package/src/__tests__/parser.test.ts +4 -13
  211. package/src/__tests__/repl-server-extra.test.ts +6 -6
  212. package/src/__tests__/repl-server.test.ts +5 -6
  213. package/src/__tests__/stdlib/queue.test.ts +6 -6
  214. package/src/lexer/index.ts +2 -1
  215. package/src/lint/index.ts +713 -5
  216. package/src/mc-test/client.ts +40 -0
  217. package/src/mir/lower.ts +111 -2
  218. package/src/optimizer/interprocedural.ts +40 -2
  219. package/src/parser/decl-parser.ts +349 -0
  220. package/src/parser/expr-parser.ts +838 -0
  221. package/src/parser/index.ts +17 -2558
  222. package/src/parser/stmt-parser.ts +585 -0
  223. package/src/parser/type-parser.ts +276 -0
  224. package/src/parser/utils.ts +173 -0
  225. package/src/stdlib/queue.mcrs +19 -6
@@ -0,0 +1,585 @@
1
+ /**
2
+ * StmtParser — statement parsing (if/while/for/match/execute/etc).
3
+ * Extends ExprParser so statement methods can call expression methods.
4
+ */
5
+
6
+ import { DiagnosticError } from '../diagnostics'
7
+ import type { Stmt, Block, TypeNode, Expr, MatchPattern, ExecuteSubcommand } from '../ast/types'
8
+ import { ExprParser } from './expr-parser'
9
+
10
+ export class StmtParser extends ExprParser {
11
+ // -------------------------------------------------------------------------
12
+ // Block
13
+ // -------------------------------------------------------------------------
14
+
15
+ parseBlock(): Block {
16
+ this.expect('{')
17
+ const stmts: Stmt[] = []
18
+
19
+ while (!this.check('}') && !this.check('eof')) {
20
+ try {
21
+ stmts.push(this.parseStmt())
22
+ } catch (err) {
23
+ if (err instanceof DiagnosticError) {
24
+ this.parseErrors.push(err)
25
+ this.syncToNextStmt()
26
+ } else {
27
+ throw err
28
+ }
29
+ }
30
+ }
31
+
32
+ this.expect('}')
33
+ return stmts
34
+ }
35
+
36
+ // -------------------------------------------------------------------------
37
+ // Statement dispatch
38
+ // -------------------------------------------------------------------------
39
+
40
+ parseStmt(): Stmt {
41
+ if (this.check('let')) return this.parseLetStmt()
42
+ if (this.check('const')) return this.parseLocalConstDecl()
43
+ if (this.check('return')) return this.parseReturnStmt()
44
+
45
+ if (this.check('break')) {
46
+ const token = this.advance()
47
+ if (this.check('ident')) {
48
+ const labelToken = this.advance()
49
+ this.match(';')
50
+ return this.withLoc({ kind: 'break_label', label: labelToken.value }, token)
51
+ }
52
+ this.match(';')
53
+ return this.withLoc({ kind: 'break' }, token)
54
+ }
55
+
56
+ if (this.check('continue')) {
57
+ const token = this.advance()
58
+ if (this.check('ident')) {
59
+ const labelToken = this.advance()
60
+ this.match(';')
61
+ return this.withLoc({ kind: 'continue_label', label: labelToken.value }, token)
62
+ }
63
+ this.match(';')
64
+ return this.withLoc({ kind: 'continue' }, token)
65
+ }
66
+
67
+ if (this.check('if')) return this.parseIfStmt()
68
+
69
+ // Labeled loop: ident ':' (while|for|foreach|repeat)
70
+ if (this.check('ident') && this.peek(1).kind === ':') {
71
+ const labelToken = this.advance()
72
+ this.advance() // consume ':'
73
+ let loopStmt: Stmt
74
+ if (this.check('while')) {
75
+ loopStmt = this.parseWhileStmt()
76
+ } else if (this.check('for')) {
77
+ loopStmt = this.parseForStmt()
78
+ } else if (this.check('foreach')) {
79
+ loopStmt = this.parseForeachStmt()
80
+ } else if (this.check('repeat')) {
81
+ loopStmt = this.parseRepeatStmt()
82
+ } else {
83
+ throw new DiagnosticError(
84
+ 'ParseError',
85
+ `Expected loop statement after label '${labelToken.value}:', found '${this.peek().kind}'`,
86
+ { line: labelToken.line, col: labelToken.col },
87
+ )
88
+ }
89
+ return this.withLoc({ kind: 'labeled_loop', label: labelToken.value, body: loopStmt }, labelToken)
90
+ }
91
+
92
+ if (this.check('while')) return this.parseWhileStmt()
93
+ if (this.check('do')) return this.parseDoWhileStmt()
94
+ if (this.check('repeat')) return this.parseRepeatStmt()
95
+ if (this.check('for')) return this.parseForStmt()
96
+ if (this.check('foreach')) return this.parseForeachStmt()
97
+ if (this.check('match')) return this.parseMatchStmt()
98
+ if (this.check('as')) return this.parseAsStmt()
99
+ if (this.check('at')) return this.parseAtStmt()
100
+ if (this.check('execute')) return this.parseExecuteStmt()
101
+
102
+ if (this.check('raw_cmd')) {
103
+ const token = this.advance()
104
+ const cmd = token.value
105
+ this.match(';')
106
+ return this.withLoc({ kind: 'raw', cmd }, token)
107
+ }
108
+
109
+ return this.parseExprStmt()
110
+ }
111
+
112
+ // -------------------------------------------------------------------------
113
+ // Individual statement parsers
114
+ // -------------------------------------------------------------------------
115
+
116
+ private parseLetStmt(): Stmt {
117
+ const letToken = this.expect('let')
118
+
119
+ if (this.check('(')) {
120
+ this.advance()
121
+ const names: string[] = []
122
+ do {
123
+ names.push(this.expect('ident').value)
124
+ } while (this.match(','))
125
+ this.expect(')')
126
+ let type: TypeNode | undefined
127
+ if (this.match(':')) type = this.parseType()
128
+ this.expect('=')
129
+ const init = this.parseExpr()
130
+ this.match(';')
131
+ return this.withLoc({ kind: 'let_destruct', names, type, init }, letToken)
132
+ }
133
+
134
+ const name = this.expect('ident').value
135
+ let type: TypeNode | undefined
136
+ if (this.match(':')) type = this.parseType()
137
+ this.expect('=')
138
+ const init = this.parseExpr()
139
+ this.match(';')
140
+ return this.withLoc({ kind: 'let', name, type, init }, letToken)
141
+ }
142
+
143
+ private parseLocalConstDecl(): Stmt {
144
+ const constToken = this.expect('const')
145
+ const name = this.expect('ident').value
146
+ this.expect(':')
147
+ const type = this.parseType()
148
+ this.expect('=')
149
+ const value = this.parseExpr()
150
+ this.match(';')
151
+ return this.withLoc({ kind: 'const_decl', name, type, value }, constToken)
152
+ }
153
+
154
+ private parseReturnStmt(): Stmt {
155
+ const returnToken = this.expect('return')
156
+ let value: Expr | undefined
157
+ if (!this.check(';') && !this.check('}') && !this.check('eof')) {
158
+ value = this.parseExpr()
159
+ }
160
+ this.match(';')
161
+ return this.withLoc({ kind: 'return', value }, returnToken)
162
+ }
163
+
164
+ private parseIfStmt(): Stmt {
165
+ const ifToken = this.expect('if')
166
+
167
+ // if let Some(x) = expr { ... }
168
+ if (this.check('let') && this.peek(1).kind === 'ident' && this.peek(1).value === 'Some') {
169
+ this.advance()
170
+ this.advance()
171
+ this.expect('(')
172
+ const binding = this.expect('ident').value
173
+ this.expect(')')
174
+ this.expect('=')
175
+ const init = this.parseExpr()
176
+ const then = this.parseBlock()
177
+ let else_: Block | undefined
178
+ if (this.match('else')) {
179
+ else_ = this.check('if') ? [this.parseIfStmt()] : this.parseBlock()
180
+ }
181
+ return this.withLoc({ kind: 'if_let_some', binding, init, then, else_ }, ifToken)
182
+ }
183
+
184
+ const cond = this.parseParenOptionalCond()
185
+ const then = this.parseBlock()
186
+ let else_: Block | undefined
187
+ if (this.match('else')) {
188
+ else_ = this.check('if') ? [this.parseIfStmt()] : this.parseBlock()
189
+ }
190
+ return this.withLoc({ kind: 'if', cond, then, else_ }, ifToken)
191
+ }
192
+
193
+ private parseWhileStmt(): Stmt {
194
+ const whileToken = this.expect('while')
195
+
196
+ if (this.check('let') && this.peek(1).kind === 'ident' && this.peek(1).value === 'Some') {
197
+ this.advance()
198
+ this.advance()
199
+ this.expect('(')
200
+ const binding = this.expect('ident').value
201
+ this.expect(')')
202
+ this.expect('=')
203
+ const init = this.parseExpr()
204
+ const body = this.parseBlock()
205
+ return this.withLoc({ kind: 'while_let_some', binding, init, body }, whileToken)
206
+ }
207
+
208
+ const cond = this.parseParenOptionalCond()
209
+ const body = this.parseBlock()
210
+ return this.withLoc({ kind: 'while', cond, body }, whileToken)
211
+ }
212
+
213
+ private parseDoWhileStmt(): Stmt {
214
+ const doToken = this.expect('do')
215
+ const body = this.parseBlock()
216
+ this.expect('while')
217
+ const cond = this.parseParenOptionalCond()
218
+ this.match(';')
219
+ return this.withLoc({ kind: 'do_while', cond, body }, doToken)
220
+ }
221
+
222
+ private parseRepeatStmt(): Stmt {
223
+ const repeatToken = this.expect('repeat')
224
+ const countToken = this.expect('int_lit')
225
+ const count = parseInt(countToken.value, 10)
226
+ const body = this.parseBlock()
227
+ return this.withLoc({ kind: 'repeat', count, body }, repeatToken)
228
+ }
229
+
230
+ private parseParenOptionalCond(): Expr {
231
+ if (this.match('(')) {
232
+ const cond = this.parseExpr()
233
+ this.expect(')')
234
+ return cond
235
+ }
236
+ return this.parseExpr()
237
+ }
238
+
239
+ private parseForStmt(): Stmt {
240
+ const forToken = this.expect('for')
241
+
242
+ if (this.check('ident') && this.peek(1).kind === 'in') {
243
+ return this.parseForRangeStmt(forToken)
244
+ }
245
+
246
+ this.expect('(')
247
+
248
+ if (this.check('let') && this.peek(1).kind === 'ident' && this.peek(2).kind === 'in' && this.peek(3).kind === 'ident' && this.peek(4).kind === ',') {
249
+ this.advance()
250
+ const binding = this.expect('ident').value
251
+ this.expect('in')
252
+ const arrayName = this.expect('ident').value
253
+ this.expect(',')
254
+ const lenExpr = this.parseExpr()
255
+ this.expect(')')
256
+ const body = this.parseBlock()
257
+ return this.withLoc({ kind: 'for_in_array', binding, arrayName, lenExpr, body }, forToken)
258
+ }
259
+
260
+ let init: Stmt | undefined
261
+ if (this.check('let')) {
262
+ const letToken = this.expect('let')
263
+ const name = this.expect('ident').value
264
+ let type: TypeNode | undefined
265
+ if (this.match(':')) type = this.parseType()
266
+ this.expect('=')
267
+ const initExpr = this.parseExpr()
268
+ const initStmt: Stmt = { kind: 'let', name, type, init: initExpr }
269
+ init = this.withLoc(initStmt, letToken)
270
+ }
271
+ this.expect(';')
272
+
273
+ const cond = this.parseExpr()
274
+ this.expect(';')
275
+
276
+ const step = this.parseExpr()
277
+ this.expect(')')
278
+
279
+ const body = this.parseBlock()
280
+ return this.withLoc({ kind: 'for', init, cond, step, body }, forToken)
281
+ }
282
+
283
+ private parseForRangeStmt(forToken: import('../lexer').Token): Stmt {
284
+ const varName = this.expect('ident').value
285
+ this.expect('in')
286
+
287
+ let start: Expr
288
+ let end: Expr
289
+ let inclusive = false
290
+
291
+ if (this.check('range_lit')) {
292
+ const rangeToken = this.advance()
293
+ const raw = rangeToken.value
294
+ inclusive = raw.includes('..=')
295
+ const range = this.parseRangeValue(raw)
296
+ start = this.withLoc({ kind: 'int_lit', value: range.min ?? 0 }, rangeToken)
297
+ if (range.max !== null && range.max !== undefined) {
298
+ end = this.withLoc({ kind: 'int_lit', value: range.max }, rangeToken)
299
+ } else {
300
+ end = this.parseUnaryExpr()
301
+ }
302
+ } else {
303
+ const arrayOrStart = this.parseExpr()
304
+
305
+ if (!this.check('range_lit')) {
306
+ const body = this.parseBlock()
307
+ return this.withLoc({ kind: 'for_each', binding: varName, array: arrayOrStart, body }, forToken)
308
+ }
309
+
310
+ start = arrayOrStart
311
+ if (this.check('range_lit')) {
312
+ const rangeOp = this.advance()
313
+ inclusive = rangeOp.value.includes('=')
314
+ const afterOp = rangeOp.value.replace(/^\.\.=?/, '')
315
+ if (afterOp.length > 0) {
316
+ end = this.withLoc({ kind: 'int_lit', value: parseInt(afterOp, 10) }, rangeOp)
317
+ } else {
318
+ end = this.parseExpr()
319
+ }
320
+ } else {
321
+ this.error('Expected .. or ..= in for-range expression. Example: for i in 0..10 { ... }')
322
+ }
323
+ }
324
+
325
+ const body = this.parseBlock()
326
+ return this.withLoc({ kind: 'for_range', varName, start, end, inclusive, body }, forToken)
327
+ }
328
+
329
+ private parseForeachStmt(): Stmt {
330
+ const foreachToken = this.expect('foreach')
331
+ this.expect('(')
332
+ const binding = this.expect('ident').value
333
+ this.expect('in')
334
+ const iterable = this.parseExpr()
335
+ this.expect(')')
336
+
337
+ let executeContext: string | undefined
338
+ const execIdentKeywords = ['positioned', 'rotated', 'facing', 'anchored', 'align', 'on', 'summon']
339
+ if (this.check('as') || this.check('at') || this.check('in') || (this.check('ident') && execIdentKeywords.includes(this.peek().value))) {
340
+ let context = ''
341
+ while (!this.check('{') && !this.check('eof')) {
342
+ context += this.advance().value + ' '
343
+ }
344
+ executeContext = context.trim()
345
+ }
346
+
347
+ const body = this.parseBlock()
348
+ return this.withLoc({ kind: 'foreach', binding, iterable, body, executeContext }, foreachToken)
349
+ }
350
+
351
+ // -------------------------------------------------------------------------
352
+ // Match
353
+ // -------------------------------------------------------------------------
354
+
355
+ private parseMatchPattern(): MatchPattern {
356
+ if (this.check('ident') && this.peek().value === '_') {
357
+ this.advance()
358
+ return { kind: 'PatWild' }
359
+ }
360
+ if (this.check('ident') && this.peek().value === 'None') {
361
+ this.advance()
362
+ return { kind: 'PatNone' }
363
+ }
364
+ if (this.check('ident') && this.peek().value === 'Some') {
365
+ this.advance()
366
+ this.expect('(')
367
+ const binding = this.expect('ident').value
368
+ this.expect(')')
369
+ return { kind: 'PatSome', binding }
370
+ }
371
+ if (this.check('ident') && this.peek(1).kind === '::') {
372
+ const enumName = this.advance().value
373
+ this.expect('::')
374
+ const variant = this.expect('ident').value
375
+ const bindings: string[] = []
376
+ if (this.check('(')) {
377
+ this.advance()
378
+ while (!this.check(')') && !this.check('eof')) {
379
+ bindings.push(this.expect('ident').value)
380
+ if (!this.match(',')) break
381
+ }
382
+ this.expect(')')
383
+ }
384
+ return { kind: 'PatEnum', enumName, variant, bindings }
385
+ }
386
+ if (this.check('int_lit')) {
387
+ const tok = this.advance()
388
+ return { kind: 'PatInt', value: parseInt(tok.value, 10) }
389
+ }
390
+ if (this.check('-') && this.peek(1).kind === 'int_lit') {
391
+ this.advance()
392
+ const tok = this.advance()
393
+ return { kind: 'PatInt', value: -parseInt(tok.value, 10) }
394
+ }
395
+ const e = this.parseExpr()
396
+ return { kind: 'PatExpr', expr: e }
397
+ }
398
+
399
+ private parseMatchStmt(): Stmt {
400
+ const matchToken = this.expect('match')
401
+ let expr: Expr
402
+ if (this.check('(')) {
403
+ this.advance()
404
+ expr = this.parseExpr()
405
+ this.expect(')')
406
+ } else {
407
+ expr = this.parseExpr()
408
+ }
409
+ this.expect('{')
410
+
411
+ const arms: Array<{ pattern: MatchPattern; body: Block }> = []
412
+ while (!this.check('}') && !this.check('eof')) {
413
+ const pattern = this.parseMatchPattern()
414
+ this.expect('=>')
415
+ const body = this.parseBlock()
416
+ this.match(',')
417
+ arms.push({ pattern, body })
418
+ }
419
+
420
+ this.expect('}')
421
+ return this.withLoc({ kind: 'match', expr, arms }, matchToken)
422
+ }
423
+
424
+ // -------------------------------------------------------------------------
425
+ // As / At / Execute
426
+ // -------------------------------------------------------------------------
427
+
428
+ private parseAsStmt(): Stmt {
429
+ const asToken = this.expect('as')
430
+ const as_sel = this.parseSelector()
431
+ if (this.match('at')) {
432
+ const at_sel = this.parseSelector()
433
+ const body = this.parseBlock()
434
+ return this.withLoc({ kind: 'as_at', as_sel, at_sel, body }, asToken)
435
+ }
436
+ const body = this.parseBlock()
437
+ return this.withLoc({ kind: 'as_block', selector: as_sel, body }, asToken)
438
+ }
439
+
440
+ private parseAtStmt(): Stmt {
441
+ const atToken = this.expect('at')
442
+ const selector = this.parseSelector()
443
+ const body = this.parseBlock()
444
+ return this.withLoc({ kind: 'at_block', selector, body }, atToken)
445
+ }
446
+
447
+ private parseExecuteStmt(): Stmt {
448
+ const executeToken = this.expect('execute')
449
+ const subcommands: ExecuteSubcommand[] = []
450
+
451
+ while (!this.check('run') && !this.check('eof')) {
452
+ if (this.match('as')) {
453
+ const selector = this.parseSelector()
454
+ subcommands.push({ kind: 'as', selector })
455
+ } else if (this.match('at')) {
456
+ const selector = this.parseSelector()
457
+ subcommands.push({ kind: 'at', selector })
458
+ } else if (this.checkIdent('positioned')) {
459
+ this.advance()
460
+ if (this.match('as')) {
461
+ const selector = this.parseSelector()
462
+ subcommands.push({ kind: 'positioned_as', selector })
463
+ } else {
464
+ const x = this.parseCoordToken()
465
+ const y = this.parseCoordToken()
466
+ const z = this.parseCoordToken()
467
+ subcommands.push({ kind: 'positioned', x, y, z })
468
+ }
469
+ } else if (this.checkIdent('rotated')) {
470
+ this.advance()
471
+ if (this.match('as')) {
472
+ const selector = this.parseSelector()
473
+ subcommands.push({ kind: 'rotated_as', selector })
474
+ } else {
475
+ const yaw = this.parseCoordToken()
476
+ const pitch = this.parseCoordToken()
477
+ subcommands.push({ kind: 'rotated', yaw, pitch })
478
+ }
479
+ } else if (this.checkIdent('facing')) {
480
+ this.advance()
481
+ if (this.checkIdent('entity')) {
482
+ this.advance()
483
+ const selector = this.parseSelector()
484
+ const anchor = this.checkIdent('eyes') || this.checkIdent('feet') ? this.advance().value as 'eyes' | 'feet' : 'feet'
485
+ subcommands.push({ kind: 'facing_entity', selector, anchor })
486
+ } else {
487
+ const x = this.parseCoordToken()
488
+ const y = this.parseCoordToken()
489
+ const z = this.parseCoordToken()
490
+ subcommands.push({ kind: 'facing', x, y, z })
491
+ }
492
+ } else if (this.checkIdent('anchored')) {
493
+ this.advance()
494
+ const anchor = this.advance().value as 'eyes' | 'feet'
495
+ subcommands.push({ kind: 'anchored', anchor })
496
+ } else if (this.checkIdent('align')) {
497
+ this.advance()
498
+ const axes = this.advance().value
499
+ subcommands.push({ kind: 'align', axes })
500
+ } else if (this.checkIdent('on')) {
501
+ this.advance()
502
+ const relation = this.advance().value
503
+ subcommands.push({ kind: 'on', relation })
504
+ } else if (this.checkIdent('summon')) {
505
+ this.advance()
506
+ const entity = this.advance().value
507
+ subcommands.push({ kind: 'summon', entity })
508
+ } else if (this.checkIdent('store')) {
509
+ this.advance()
510
+ const storeType = this.advance().value
511
+ if (this.checkIdent('score')) {
512
+ this.advance()
513
+ const target = this.advance().value
514
+ const targetObj = this.advance().value
515
+ if (storeType === 'result') {
516
+ subcommands.push({ kind: 'store_result', target, targetObj })
517
+ } else {
518
+ subcommands.push({ kind: 'store_success', target, targetObj })
519
+ }
520
+ } else {
521
+ this.error('store currently only supports score target')
522
+ }
523
+ } else if (this.match('if')) {
524
+ this.parseExecuteCondition(subcommands, 'if')
525
+ } else if (this.match('unless')) {
526
+ this.parseExecuteCondition(subcommands, 'unless')
527
+ } else if (this.match('in')) {
528
+ let dim = this.advance().value
529
+ if (this.match(':')) dim += ':' + this.advance().value
530
+ subcommands.push({ kind: 'in', dimension: dim })
531
+ } else {
532
+ this.error(`Unexpected token in execute statement: '${this.peek().value || this.peek().kind}'. Valid subcommands: as, at, positioned, align, facing, rotated, anchored, if, unless, in, store`)
533
+ }
534
+ }
535
+
536
+ this.expect('run')
537
+ const body = this.parseBlock()
538
+ return this.withLoc({ kind: 'execute', subcommands, body }, executeToken)
539
+ }
540
+
541
+ private parseExecuteCondition(subcommands: ExecuteSubcommand[], type: 'if' | 'unless'): void {
542
+ if (this.checkIdent('entity') || this.check('selector')) {
543
+ if (this.checkIdent('entity')) this.advance()
544
+ const selectorOrVar = this.parseSelectorOrVarSelector()
545
+ subcommands.push({ kind: type === 'if' ? 'if_entity' : 'unless_entity', ...selectorOrVar })
546
+ } else if (this.checkIdent('block')) {
547
+ this.advance()
548
+ const x = this.parseCoordToken()
549
+ const y = this.parseCoordToken()
550
+ const z = this.parseCoordToken()
551
+ const block = this.parseBlockId()
552
+ subcommands.push({ kind: type === 'if' ? 'if_block' : 'unless_block', pos: [x, y, z], block })
553
+ } else if (this.checkIdent('score')) {
554
+ this.advance()
555
+ const target = this.advance().value
556
+ const targetObj = this.advance().value
557
+ if (this.checkIdent('matches')) {
558
+ this.advance()
559
+ const range = this.advance().value
560
+ subcommands.push({ kind: type === 'if' ? 'if_score_range' : 'unless_score_range', target, targetObj, range })
561
+ } else {
562
+ const op = this.advance().value
563
+ const source = this.advance().value
564
+ const sourceObj = this.advance().value
565
+ subcommands.push({
566
+ kind: type === 'if' ? 'if_score' : 'unless_score',
567
+ target, targetObj, op, source, sourceObj
568
+ })
569
+ }
570
+ } else {
571
+ this.error(`Unknown condition type after ${type}`)
572
+ }
573
+ }
574
+
575
+ // -------------------------------------------------------------------------
576
+ // Expression statement
577
+ // -------------------------------------------------------------------------
578
+
579
+ private parseExprStmt(): Stmt {
580
+ const expr = this.parseExpr()
581
+ this.match(';')
582
+ const exprToken = this.getLocToken(expr) ?? this.peek()
583
+ return this.withLoc({ kind: 'expr', expr }, exprToken)
584
+ }
585
+ }