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
@@ -4,182 +4,20 @@
4
4
  *
5
5
  * Recursive descent parser that converts tokens into an AST.
6
6
  * Uses precedence climbing for expression parsing.
7
+ *
8
+ * The Parser class extends a chain of sub-parsers:
9
+ * Parser → DeclParser → StmtParser → ExprParser → TypeParser → ParserBase
10
+ *
11
+ * Each layer adds methods for its domain; the full Parser assembles them
12
+ * into the top-level `parse()` entry point.
7
13
  */
8
14
  Object.defineProperty(exports, "__esModule", { value: true });
9
15
  exports.Parser = void 0;
10
- const lexer_1 = require("../lexer");
11
16
  const diagnostics_1 = require("../diagnostics");
12
- // ---------------------------------------------------------------------------
13
- // Operator Precedence (higher = binds tighter)
14
- // ---------------------------------------------------------------------------
15
- const PRECEDENCE = {
16
- '||': 1,
17
- '&&': 2,
18
- '==': 3, '!=': 3,
19
- '<': 4, '<=': 4, '>': 4, '>=': 4, 'is': 4,
20
- '+': 5, '-': 5,
21
- '*': 6, '/': 6, '%': 6,
22
- };
23
- const BINARY_OPS = new Set(['||', '&&', '==', '!=', '<', '<=', '>', '>=', 'is', '+', '-', '*', '/', '%']);
24
- const ENTITY_TYPE_NAMES = new Set([
25
- 'entity',
26
- 'Player',
27
- 'Mob',
28
- 'HostileMob',
29
- 'PassiveMob',
30
- 'Zombie',
31
- 'Skeleton',
32
- 'Creeper',
33
- 'Spider',
34
- 'Enderman',
35
- 'Blaze',
36
- 'Witch',
37
- 'Slime',
38
- 'ZombieVillager',
39
- 'Husk',
40
- 'Drowned',
41
- 'Stray',
42
- 'WitherSkeleton',
43
- 'CaveSpider',
44
- 'Pig',
45
- 'Cow',
46
- 'Sheep',
47
- 'Chicken',
48
- 'Villager',
49
- 'WanderingTrader',
50
- 'ArmorStand',
51
- 'Item',
52
- 'Arrow',
53
- ]);
54
- function computeIsSingle(raw) {
55
- if (/^@[spr](\[|$)/.test(raw))
56
- return true;
57
- if (/[\[,\s]limit=1[,\]\s]/.test(raw))
58
- return true;
59
- return false;
60
- }
61
- // ---------------------------------------------------------------------------
62
- // Parser Class
63
- // ---------------------------------------------------------------------------
64
- class Parser {
65
- constructor(tokens, source, filePath) {
66
- this.pos = 0;
67
- /** Set to true once `module library;` is seen — all subsequent fn declarations
68
- * will be marked isLibraryFn=true. When library sources are parsed via the
69
- * `librarySources` compile option, each source is parsed by its own fresh
70
- * Parser instance, so this flag never bleeds into user code. */
71
- this.inLibraryMode = false;
72
- /** Warnings accumulated during parsing (e.g. deprecated keyword usage). */
73
- this.warnings = [];
74
- /** Parse errors collected during error-recovery mode. */
75
- this.parseErrors = [];
76
- this.tokens = tokens;
77
- this.sourceLines = source?.split('\n') ?? [];
78
- this.filePath = filePath;
79
- }
80
- // -------------------------------------------------------------------------
81
- // Utilities
82
- // -------------------------------------------------------------------------
83
- peek(offset = 0) {
84
- const idx = this.pos + offset;
85
- if (idx >= this.tokens.length) {
86
- return this.tokens[this.tokens.length - 1]; // eof
87
- }
88
- return this.tokens[idx];
89
- }
90
- advance() {
91
- const token = this.tokens[this.pos];
92
- if (token.kind !== 'eof')
93
- this.pos++;
94
- return token;
95
- }
96
- check(kind) {
97
- return this.peek().kind === kind;
98
- }
99
- match(...kinds) {
100
- for (const kind of kinds) {
101
- if (this.check(kind)) {
102
- this.advance();
103
- return true;
104
- }
105
- }
106
- return false;
107
- }
108
- expect(kind) {
109
- const token = this.peek();
110
- if (token.kind !== kind) {
111
- throw new diagnostics_1.DiagnosticError('ParseError', `Expected '${kind}' but got '${token.kind}'`, { file: this.filePath, line: token.line, col: token.col }, this.sourceLines);
112
- }
113
- return this.advance();
114
- }
115
- error(message) {
116
- const token = this.peek();
117
- throw new diagnostics_1.DiagnosticError('ParseError', message, { file: this.filePath, line: token.line, col: token.col }, this.sourceLines);
118
- }
119
- withLoc(node, token) {
120
- const span = { line: token.line, col: token.col };
121
- Object.defineProperty(node, 'span', {
122
- value: span,
123
- enumerable: false,
124
- configurable: true,
125
- writable: true,
126
- });
127
- return node;
128
- }
129
- getLocToken(node) {
130
- const span = node.span;
131
- if (!span) {
132
- return null;
133
- }
134
- return { kind: 'eof', value: '', line: span.line, col: span.col };
135
- }
136
- // -------------------------------------------------------------------------
137
- // Error Recovery
138
- // -------------------------------------------------------------------------
139
- /**
140
- * Synchronize to the next top-level declaration boundary after a parse error.
141
- * Skips tokens until we find a keyword that starts a top-level declaration,
142
- * or a `}` (end of a block), or EOF.
143
- */
144
- syncToNextDecl() {
145
- const TOP_LEVEL_KEYWORDS = new Set([
146
- 'fn', 'struct', 'impl', 'enum', 'const', 'let', 'export', 'declare', 'import', 'namespace', 'module'
147
- ]);
148
- while (!this.check('eof')) {
149
- const kind = this.peek().kind;
150
- if (kind === '}') {
151
- this.advance(); // consume the stray `}`
152
- return;
153
- }
154
- if (TOP_LEVEL_KEYWORDS.has(kind)) {
155
- return;
156
- }
157
- // Also recover on a plain ident that could be 'import' keyword used as ident
158
- if (kind === 'ident' && this.peek().value === 'import') {
159
- return;
160
- }
161
- this.advance();
162
- }
163
- }
164
- /**
165
- * Synchronize to the next statement boundary inside a block after a parse error.
166
- * Skips tokens until we reach `;`, `}`, or EOF.
167
- */
168
- syncToNextStmt() {
169
- while (!this.check('eof')) {
170
- const kind = this.peek().kind;
171
- if (kind === ';') {
172
- this.advance(); // consume the `;`
173
- return;
174
- }
175
- if (kind === '}') {
176
- return; // leave `}` for parseBlock to consume
177
- }
178
- this.advance();
179
- }
180
- }
17
+ const decl_parser_1 = require("./decl-parser");
18
+ class Parser extends decl_parser_1.DeclParser {
181
19
  // -------------------------------------------------------------------------
182
- // Program
20
+ // Program (top-level entry point)
183
21
  // -------------------------------------------------------------------------
184
22
  parse(defaultNamespace = 'redscript') {
185
23
  let namespace = defaultNamespace;
@@ -193,17 +31,12 @@ class Parser {
193
31
  const interfaces = [];
194
32
  let isLibrary = false;
195
33
  let moduleName;
196
- // Check for namespace declaration
197
34
  if (this.check('namespace')) {
198
35
  this.advance();
199
36
  const name = this.expect('ident');
200
37
  namespace = name.value;
201
38
  this.match(';');
202
39
  }
203
- // Check for module declaration: `module library;` or `module <name>;`
204
- // Library-mode: all functions parsed from this point are marked isLibraryFn=true.
205
- // When using the `librarySources` compile option, each library source is parsed
206
- // by its own fresh Parser — so this flag never bleeds into user code.
207
40
  if (this.check('module')) {
208
41
  this.advance();
209
42
  const modKind = this.expect('ident');
@@ -212,16 +45,13 @@ class Parser {
212
45
  this.inLibraryMode = true;
213
46
  }
214
47
  else {
215
- // Named module declaration: `module math;`
216
48
  moduleName = modKind.value;
217
49
  }
218
50
  this.match(';');
219
51
  }
220
- // Parse struct, function, and import declarations
221
52
  while (!this.check('eof')) {
222
53
  try {
223
54
  if (this.check('decorator') && this.peek().value.startsWith('@config')) {
224
- // @config decorator on a global let
225
55
  const decorToken = this.advance();
226
56
  const decorator = this.parseDecoratorValue(decorToken.value);
227
57
  if (!this.check('let')) {
@@ -236,8 +66,7 @@ class Parser {
236
66
  globals.push(this.parseGlobalDecl(true));
237
67
  }
238
68
  else if (this.check('decorator') && this.peek().value === '@singleton') {
239
- // @singleton decorator on a struct
240
- this.advance(); // consume '@singleton'
69
+ this.advance();
241
70
  if (!this.check('struct')) {
242
71
  this.error('@singleton decorator must be followed by a struct declaration');
243
72
  }
@@ -261,21 +90,18 @@ class Parser {
261
90
  consts.push(this.parseConstDecl());
262
91
  }
263
92
  else if (this.check('declare')) {
264
- // Declaration-only stub (e.g. from builtins.d.mcrs) — parse and discard
265
- this.advance(); // consume 'declare'
93
+ this.advance();
266
94
  this.parseDeclareStub();
267
95
  }
268
96
  else if (this.check('export')) {
269
97
  declarations.push(this.parseExportedFnDecl());
270
98
  }
271
99
  else if (this.check('import') || (this.check('ident') && this.peek().value === 'import')) {
272
- // `import math::sin;` or `import math::*;` or `import player_utils;` (whole-module file import)
273
- this.advance(); // consume 'import' (keyword or ident)
100
+ this.advance();
274
101
  const importToken = this.peek();
275
102
  const modName = this.expect('ident').value;
276
- // Check for `::` — if present, this is a symbol import; otherwise, whole-module import
277
103
  if (this.check('::')) {
278
- this.advance(); // consume '::'
104
+ this.advance();
279
105
  let symbol;
280
106
  if (this.check('*')) {
281
107
  this.advance();
@@ -288,7 +114,6 @@ class Parser {
288
114
  imports.push(this.withLoc({ moduleName: modName, symbol }, importToken));
289
115
  }
290
116
  else {
291
- // Whole-module import: `import player_utils;`
292
117
  this.match(';');
293
118
  imports.push(this.withLoc({ moduleName: modName, symbol: undefined }, importToken));
294
119
  }
@@ -309,2080 +134,6 @@ class Parser {
309
134
  }
310
135
  return { namespace, moduleName, globals, declarations, structs, implBlocks, enums, consts, imports, interfaces, isLibrary };
311
136
  }
312
- // -------------------------------------------------------------------------
313
- // Struct Declaration
314
- // -------------------------------------------------------------------------
315
- parseStructDecl() {
316
- const structToken = this.expect('struct');
317
- const name = this.expect('ident').value;
318
- const extendsName = this.match('extends') ? this.expect('ident').value : undefined;
319
- this.expect('{');
320
- const fields = [];
321
- while (!this.check('}') && !this.check('eof')) {
322
- const fieldName = this.expect('ident').value;
323
- this.expect(':');
324
- const fieldType = this.parseType();
325
- fields.push({ name: fieldName, type: fieldType });
326
- // Allow optional comma or semicolon between fields
327
- this.match(',');
328
- }
329
- this.expect('}');
330
- return this.withLoc({ name, extends: extendsName, fields }, structToken);
331
- }
332
- parseEnumDecl() {
333
- const enumToken = this.expect('enum');
334
- const name = this.expect('ident').value;
335
- this.expect('{');
336
- const variants = [];
337
- let nextValue = 0;
338
- while (!this.check('}') && !this.check('eof')) {
339
- const variantToken = this.expect('ident');
340
- const variant = { name: variantToken.value };
341
- // Payload fields: Variant(field: Type, ...)
342
- if (this.check('(')) {
343
- this.advance(); // consume '('
344
- const fields = [];
345
- while (!this.check(')') && !this.check('eof')) {
346
- const fieldName = this.expect('ident').value;
347
- this.expect(':');
348
- const fieldType = this.parseType();
349
- fields.push({ name: fieldName, type: fieldType });
350
- if (!this.match(','))
351
- break;
352
- }
353
- this.expect(')');
354
- variant.fields = fields;
355
- }
356
- if (this.match('=')) {
357
- const valueToken = this.expect('int_lit');
358
- variant.value = parseInt(valueToken.value, 10);
359
- nextValue = variant.value + 1;
360
- }
361
- else {
362
- variant.value = nextValue++;
363
- }
364
- variants.push(variant);
365
- if (!this.match(',')) {
366
- break;
367
- }
368
- }
369
- this.expect('}');
370
- return this.withLoc({ name, variants }, enumToken);
371
- }
372
- parseImplBlock() {
373
- const implToken = this.expect('impl');
374
- let traitName;
375
- let typeName;
376
- const firstName = this.expect('ident').value;
377
- if (this.match('for')) {
378
- traitName = firstName;
379
- typeName = this.expect('ident').value;
380
- }
381
- else {
382
- typeName = firstName;
383
- }
384
- this.expect('{');
385
- const methods = [];
386
- while (!this.check('}') && !this.check('eof')) {
387
- methods.push(this.parseFnDecl(typeName));
388
- }
389
- this.expect('}');
390
- return this.withLoc({ kind: 'impl_block', traitName, typeName, methods }, implToken);
391
- }
392
- /**
393
- * Parse an interface declaration:
394
- * interface <Name> {
395
- * fn <method>(<params>): <retType>
396
- * ...
397
- * }
398
- * Method signatures have no body — they are prototype-only.
399
- */
400
- parseInterfaceDecl() {
401
- const ifaceToken = this.expect('interface');
402
- const name = this.expect('ident').value;
403
- this.expect('{');
404
- const methods = [];
405
- while (!this.check('}') && !this.check('eof')) {
406
- const fnToken = this.expect('fn');
407
- const methodName = this.expect('ident').value;
408
- this.expect('(');
409
- const params = this.parseInterfaceParams();
410
- this.expect(')');
411
- let returnType;
412
- if (this.match(':')) {
413
- returnType = this.parseType();
414
- }
415
- // No body — interface methods are signature-only
416
- methods.push(this.withLoc({ name: methodName, params, returnType }, fnToken));
417
- }
418
- this.expect('}');
419
- return this.withLoc({ name, methods }, ifaceToken);
420
- }
421
- /**
422
- * Parse interface method params — like parseParams but allows bare `self`
423
- * (no `:` required for the first param named 'self').
424
- */
425
- parseInterfaceParams() {
426
- const params = [];
427
- if (!this.check(')')) {
428
- do {
429
- const paramToken = this.expect('ident');
430
- const paramName = paramToken.value;
431
- let type;
432
- if (params.length === 0 && paramName === 'self' && !this.check(':')) {
433
- // self without type annotation — use a sentinel struct type
434
- type = { kind: 'named', name: 'void' };
435
- }
436
- else {
437
- this.expect(':');
438
- type = this.parseType();
439
- }
440
- params.push(this.withLoc({ name: paramName, type }, paramToken));
441
- } while (this.match(','));
442
- }
443
- return params;
444
- }
445
- parseConstDecl() {
446
- const constToken = this.expect('const');
447
- const name = this.expect('ident').value;
448
- let type;
449
- if (this.match(':')) {
450
- type = this.parseType();
451
- }
452
- this.expect('=');
453
- const value = this.parseLiteralExpr();
454
- this.match(';');
455
- // Infer type from value if not provided
456
- const inferredType = type ?? (value.kind === 'str_lit' ? { kind: 'named', name: 'string' } :
457
- value.kind === 'bool_lit' ? { kind: 'named', name: 'bool' } :
458
- value.kind === 'float_lit' ? { kind: 'named', name: 'fixed' } :
459
- { kind: 'named', name: 'int' });
460
- return this.withLoc({ name, type: inferredType, value }, constToken);
461
- }
462
- parseGlobalDecl(mutable) {
463
- const token = this.advance(); // consume 'let'
464
- const name = this.expect('ident').value;
465
- this.expect(':');
466
- const type = this.parseType();
467
- let init;
468
- if (this.match('=')) {
469
- init = this.parseExpr();
470
- }
471
- else {
472
- // No init — valid only for @config-decorated globals (resolved later)
473
- // Use a placeholder zero literal; will be replaced in compile step
474
- init = { kind: 'int_lit', value: 0 };
475
- }
476
- this.match(';');
477
- return this.withLoc({ kind: 'global', name, type, init, mutable }, token);
478
- }
479
- // -------------------------------------------------------------------------
480
- // Function Declaration
481
- // -------------------------------------------------------------------------
482
- /** Parse `export fn name(...)` — marks the function as exported (survives DCE). */
483
- parseExportedFnDecl() {
484
- this.expect('export');
485
- const fn = this.parseFnDecl();
486
- fn.isExported = true;
487
- return fn;
488
- }
489
- parseFnDecl(implTypeName) {
490
- const decorators = this.parseDecorators();
491
- const watchObjective = decorators.find(decorator => decorator.name === 'watch')?.args?.objective;
492
- // Map @keep decorator to isExported flag (backward compat)
493
- let isExported;
494
- const filteredDecorators = decorators.filter(d => {
495
- if (d.name === 'keep') {
496
- isExported = true;
497
- return false;
498
- }
499
- return true;
500
- });
501
- const fnToken = this.expect('fn');
502
- const name = this.expect('ident').value;
503
- // Parse optional generic type parameters: fn max<T>(...)
504
- let typeParams;
505
- if (this.check('<')) {
506
- this.advance(); // consume '<'
507
- typeParams = [];
508
- do {
509
- typeParams.push(this.expect('ident').value);
510
- } while (this.match(','));
511
- this.expect('>');
512
- }
513
- this.expect('(');
514
- const params = this.parseParams(implTypeName);
515
- this.expect(')');
516
- let returnType = { kind: 'named', name: 'void' };
517
- if (this.match('->') || this.match(':')) {
518
- returnType = this.parseType();
519
- }
520
- const body = this.parseBlock();
521
- // Record the closing '}' line as endLine for accurate LSP scope detection
522
- const closingBraceLine = this.tokens[this.pos - 1]?.line;
523
- const fn = this.withLoc({ name, typeParams, params, returnType, decorators: filteredDecorators, body,
524
- isLibraryFn: this.inLibraryMode || undefined, isExported, watchObjective }, fnToken);
525
- if (fn.span && closingBraceLine)
526
- fn.span.endLine = closingBraceLine;
527
- return fn;
528
- }
529
- /** Parse a `declare fn name(params): returnType;` stub — no body, just discard. */
530
- parseDeclareStub() {
531
- this.expect('fn');
532
- this.expect('ident'); // name
533
- this.expect('(');
534
- // consume params until ')'
535
- let depth = 1;
536
- while (!this.check('eof') && depth > 0) {
537
- const t = this.advance();
538
- if (t.kind === '(')
539
- depth++;
540
- else if (t.kind === ')')
541
- depth--;
542
- }
543
- // optional return type annotation `: type` or `-> type`
544
- if (this.match(':') || this.match('->')) {
545
- this.parseType();
546
- }
547
- this.match(';'); // consume trailing semicolon
548
- }
549
- parseDecorators() {
550
- const decorators = [];
551
- while (this.check('decorator')) {
552
- const token = this.advance();
553
- const decorator = this.parseDecoratorValue(token.value);
554
- decorators.push(decorator);
555
- }
556
- return decorators;
557
- }
558
- parseDecoratorValue(value) {
559
- // Parse @tick, @on(PlayerDeath), @on_trigger("name"), or @deprecated("msg with ) parens")
560
- // Use a greedy match for args that allows any content inside the outermost parens.
561
- const match = value.match(/^@([A-Za-z_][A-Za-z0-9_-]*)(?:\((.*)\))?$/s);
562
- if (!match) {
563
- this.error(`Invalid decorator: ${value}`);
564
- }
565
- const name = match[1];
566
- const argsStr = match[2];
567
- if (!argsStr) {
568
- return { name };
569
- }
570
- if (name === 'profile' || name === 'benchmark' || name === 'memoize') {
571
- this.error(`@${name} decorator does not accept arguments`);
572
- }
573
- const args = {};
574
- if (name === 'on') {
575
- const eventTypeMatch = argsStr.match(/^([A-Za-z_][A-Za-z0-9_]*)$/);
576
- if (eventTypeMatch) {
577
- args.eventType = eventTypeMatch[1];
578
- return { name, args };
579
- }
580
- }
581
- // Handle @watch("objective"), @on_trigger("name"), @on_advancement("id"), @on_craft("item"), @on_join_team("team")
582
- if (name === 'watch' || name === 'on_trigger' || name === 'on_advancement' || name === 'on_craft' || name === 'on_join_team') {
583
- const strMatch = argsStr.match(/^"([^"]*)"$/);
584
- if (strMatch) {
585
- if (name === 'watch') {
586
- args.objective = strMatch[1];
587
- }
588
- else if (name === 'on_trigger') {
589
- args.trigger = strMatch[1];
590
- }
591
- else if (name === 'on_advancement') {
592
- args.advancement = strMatch[1];
593
- }
594
- else if (name === 'on_craft') {
595
- args.item = strMatch[1];
596
- }
597
- else if (name === 'on_join_team') {
598
- args.team = strMatch[1];
599
- }
600
- return { name, args };
601
- }
602
- }
603
- // Handle @config("key", default: value)
604
- if (name === 'config') {
605
- // Format: @config("key_name", default: 42)
606
- const configMatch = argsStr.match(/^"([^"]+)"\s*,\s*default\s*:\s*(-?\d+(?:\.\d+)?)$/);
607
- if (configMatch) {
608
- return { name, args: { configKey: configMatch[1], configDefault: parseFloat(configMatch[2]) } };
609
- }
610
- // Format: @config("key_name") — no default
611
- const keyOnlyMatch = argsStr.match(/^"([^"]+)"$/);
612
- if (keyOnlyMatch) {
613
- return { name, args: { configKey: keyOnlyMatch[1] } };
614
- }
615
- this.error(`Invalid @config syntax. Expected: @config("key", default: value) or @config("key")`);
616
- }
617
- // Handle @deprecated("message")
618
- if (name === 'deprecated') {
619
- const strMatch = argsStr.match(/^"([^"]*)"$/);
620
- if (strMatch) {
621
- return { name, args: { message: strMatch[1] } };
622
- }
623
- // @deprecated with no message string
624
- return { name, args: {} };
625
- }
626
- // @test("label") — marks a test function with a human-readable label
627
- if (name === 'test') {
628
- const strMatch = argsStr.match(/^"([^"]*)"$/);
629
- if (strMatch) {
630
- return { name, args: { testLabel: strMatch[1] } };
631
- }
632
- // @test with no label — use empty string
633
- return { name, args: { testLabel: '' } };
634
- }
635
- // @require_on_load(fn_name) — when this fn is used, fn_name is called from __load.
636
- // Accepts bare identifiers (with optional leading _) or quoted strings.
637
- if (name === 'require_on_load') {
638
- const rawArgs = [];
639
- for (const part of argsStr.split(',')) {
640
- const trimmed = part.trim();
641
- // Bare identifier: @require_on_load(_math_init)
642
- const identMatch = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)$/);
643
- if (identMatch) {
644
- rawArgs.push({ kind: 'string', value: identMatch[1] });
645
- }
646
- else {
647
- // Quoted string fallback: @require_on_load("_math_init")
648
- const strMatch = trimmed.match(/^"([^"]*)"$/);
649
- if (strMatch) {
650
- rawArgs.push({ kind: 'string', value: strMatch[1] });
651
- }
652
- }
653
- }
654
- return { name, rawArgs };
655
- }
656
- // Handle key=value format (e.g., rate=20, batch=10, onDone=fn_name)
657
- for (const part of argsStr.split(',')) {
658
- const [key, val] = part.split('=').map(s => s.trim());
659
- if (key === 'rate') {
660
- args.rate = parseInt(val, 10);
661
- }
662
- else if (key === 'ticks') {
663
- args.ticks = parseInt(val, 10);
664
- }
665
- else if (key === 'batch') {
666
- args.batch = parseInt(val, 10);
667
- }
668
- else if (key === 'onDone') {
669
- args.onDone = val.replace(/^["']|["']$/g, '');
670
- }
671
- else if (key === 'trigger') {
672
- args.trigger = val;
673
- }
674
- else if (key === 'advancement') {
675
- args.advancement = val;
676
- }
677
- else if (key === 'item') {
678
- args.item = val;
679
- }
680
- else if (key === 'team') {
681
- args.team = val;
682
- }
683
- else if (key === 'max') {
684
- args.max = parseInt(val, 10);
685
- }
686
- }
687
- return { name, args };
688
- }
689
- parseParams(implTypeName) {
690
- const params = [];
691
- if (!this.check(')')) {
692
- do {
693
- const paramToken = this.expect('ident');
694
- const name = paramToken.value;
695
- let type;
696
- if (implTypeName && params.length === 0 && name === 'self' && !this.check(':')) {
697
- type = { kind: 'struct', name: implTypeName };
698
- }
699
- else {
700
- this.expect(':');
701
- type = this.parseType();
702
- }
703
- let defaultValue;
704
- if (this.match('=')) {
705
- defaultValue = this.parseExpr();
706
- }
707
- params.push(this.withLoc({ name, type, default: defaultValue }, paramToken));
708
- } while (this.match(','));
709
- }
710
- return params;
711
- }
712
- parseType() {
713
- const token = this.peek();
714
- let type;
715
- if (token.kind === '(') {
716
- // Disambiguate: tuple type `(T, T)` vs function type `(T) -> R`
717
- // Look ahead: parse elements, then check if '->' follows.
718
- const saved = this.pos;
719
- this.advance(); // consume '('
720
- const elements = [];
721
- if (!this.check(')')) {
722
- do {
723
- elements.push(this.parseType());
724
- } while (this.match(','));
725
- }
726
- this.expect(')');
727
- if (this.check('->')) {
728
- // It's a function type — restore and use existing parseFunctionType
729
- this.pos = saved;
730
- return this.parseFunctionType();
731
- }
732
- // It's a tuple type
733
- return { kind: 'tuple', elements };
734
- }
735
- if (token.kind === 'float') {
736
- this.advance();
737
- const filePart = this.filePath ? `${this.filePath}:` : '';
738
- this.warnings.push(`[DeprecatedType] ${filePart}line ${token.line}, col ${token.col}: 'float' is deprecated, use 'fixed' instead (×10000 fixed-point)`);
739
- type = { kind: 'named', name: 'float' };
740
- }
741
- else if (token.kind === 'int' || token.kind === 'bool' ||
742
- token.kind === 'fixed' || token.kind === 'string' || token.kind === 'void' ||
743
- token.kind === 'BlockPos') {
744
- this.advance();
745
- type = { kind: 'named', name: token.kind };
746
- }
747
- else if (token.kind === 'ident') {
748
- this.advance();
749
- if (token.value === 'selector' && this.check('<')) {
750
- this.advance(); // consume <
751
- const entityType = this.expect('ident').value;
752
- this.expect('>');
753
- type = { kind: 'selector', entityType };
754
- }
755
- else if (token.value === 'selector') {
756
- type = { kind: 'selector' };
757
- }
758
- else if (token.value === 'Option' && this.check('<')) {
759
- this.advance(); // consume <
760
- const inner = this.parseType();
761
- this.expect('>');
762
- type = { kind: 'option', inner };
763
- }
764
- else if (token.value === 'double' || token.value === 'byte' ||
765
- token.value === 'short' || token.value === 'long' ||
766
- token.value === 'format_string') {
767
- type = { kind: 'named', name: token.value };
768
- }
769
- else {
770
- type = { kind: 'struct', name: token.value };
771
- }
772
- }
773
- else {
774
- this.error(`Expected type, got '${token.kind}'`);
775
- }
776
- while (this.match('[')) {
777
- this.expect(']');
778
- type = { kind: 'array', elem: type };
779
- }
780
- return type;
781
- }
782
- parseFunctionType() {
783
- this.expect('(');
784
- const params = [];
785
- if (!this.check(')')) {
786
- do {
787
- params.push(this.parseType());
788
- } while (this.match(','));
789
- }
790
- this.expect(')');
791
- this.expect('->');
792
- const returnType = this.parseType();
793
- return { kind: 'function_type', params, return: returnType };
794
- }
795
- // -------------------------------------------------------------------------
796
- // Block & Statements
797
- // -------------------------------------------------------------------------
798
- parseBlock() {
799
- this.expect('{');
800
- const stmts = [];
801
- while (!this.check('}') && !this.check('eof')) {
802
- try {
803
- stmts.push(this.parseStmt());
804
- }
805
- catch (err) {
806
- if (err instanceof diagnostics_1.DiagnosticError) {
807
- this.parseErrors.push(err);
808
- this.syncToNextStmt();
809
- }
810
- else {
811
- throw err;
812
- }
813
- }
814
- }
815
- this.expect('}');
816
- return stmts;
817
- }
818
- parseStmt() {
819
- // Let statement
820
- if (this.check('let')) {
821
- return this.parseLetStmt();
822
- }
823
- // Const declaration (local)
824
- if (this.check('const')) {
825
- return this.parseLocalConstDecl();
826
- }
827
- // Return statement
828
- if (this.check('return')) {
829
- return this.parseReturnStmt();
830
- }
831
- // Break statement (with optional label: break outer)
832
- if (this.check('break')) {
833
- const token = this.advance();
834
- // Check if next token is an identifier (label name)
835
- if (this.check('ident')) {
836
- const labelToken = this.advance();
837
- this.match(';');
838
- return this.withLoc({ kind: 'break_label', label: labelToken.value }, token);
839
- }
840
- this.match(';');
841
- return this.withLoc({ kind: 'break' }, token);
842
- }
843
- // Continue statement (with optional label: continue outer)
844
- if (this.check('continue')) {
845
- const token = this.advance();
846
- // Check if next token is an identifier (label name)
847
- if (this.check('ident')) {
848
- const labelToken = this.advance();
849
- this.match(';');
850
- return this.withLoc({ kind: 'continue_label', label: labelToken.value }, token);
851
- }
852
- this.match(';');
853
- return this.withLoc({ kind: 'continue' }, token);
854
- }
855
- // If statement
856
- if (this.check('if')) {
857
- return this.parseIfStmt();
858
- }
859
- // Labeled loop: ident ':' (while|for|foreach|repeat)
860
- if (this.check('ident') && this.peek(1).kind === ':') {
861
- const labelToken = this.advance(); // consume ident
862
- const colonToken = this.advance(); // consume ':'
863
- // Now parse the loop body
864
- let loopStmt;
865
- if (this.check('while')) {
866
- loopStmt = this.parseWhileStmt();
867
- }
868
- else if (this.check('for')) {
869
- loopStmt = this.parseForStmt();
870
- }
871
- else if (this.check('foreach')) {
872
- loopStmt = this.parseForeachStmt();
873
- }
874
- else if (this.check('repeat')) {
875
- loopStmt = this.parseRepeatStmt();
876
- }
877
- else {
878
- throw new diagnostics_1.DiagnosticError('ParseError', `Expected loop statement after label '${labelToken.value}:', found '${this.peek().kind}'`, { line: labelToken.line, col: labelToken.col });
879
- }
880
- return this.withLoc({ kind: 'labeled_loop', label: labelToken.value, body: loopStmt }, labelToken);
881
- }
882
- // While statement
883
- if (this.check('while')) {
884
- return this.parseWhileStmt();
885
- }
886
- // Do-while statement
887
- if (this.check('do')) {
888
- return this.parseDoWhileStmt();
889
- }
890
- // Repeat N statement
891
- if (this.check('repeat')) {
892
- return this.parseRepeatStmt();
893
- }
894
- // For statement
895
- if (this.check('for')) {
896
- return this.parseForStmt();
897
- }
898
- // Foreach statement
899
- if (this.check('foreach')) {
900
- return this.parseForeachStmt();
901
- }
902
- if (this.check('match')) {
903
- return this.parseMatchStmt();
904
- }
905
- // As block
906
- if (this.check('as')) {
907
- return this.parseAsStmt();
908
- }
909
- // At block
910
- if (this.check('at')) {
911
- return this.parseAtStmt();
912
- }
913
- // Execute statement: execute as/at/if/unless/in ... run { }
914
- if (this.check('execute')) {
915
- return this.parseExecuteStmt();
916
- }
917
- // Raw command
918
- if (this.check('raw_cmd')) {
919
- const token = this.advance();
920
- const cmd = token.value;
921
- this.match(';'); // optional semicolon (raw consumes it)
922
- return this.withLoc({ kind: 'raw', cmd }, token);
923
- }
924
- // Expression statement
925
- return this.parseExprStmt();
926
- }
927
- parseLetStmt() {
928
- const letToken = this.expect('let');
929
- // Destructuring: let (a, b, c) = expr;
930
- if (this.check('(')) {
931
- this.advance(); // consume '('
932
- const names = [];
933
- do {
934
- names.push(this.expect('ident').value);
935
- } while (this.match(','));
936
- this.expect(')');
937
- let type;
938
- if (this.match(':')) {
939
- type = this.parseType();
940
- }
941
- this.expect('=');
942
- const init = this.parseExpr();
943
- this.match(';');
944
- return this.withLoc({ kind: 'let_destruct', names, type, init }, letToken);
945
- }
946
- const name = this.expect('ident').value;
947
- let type;
948
- if (this.match(':')) {
949
- type = this.parseType();
950
- }
951
- this.expect('=');
952
- const init = this.parseExpr();
953
- this.match(';');
954
- return this.withLoc({ kind: 'let', name, type, init }, letToken);
955
- }
956
- parseLocalConstDecl() {
957
- const constToken = this.expect('const');
958
- const name = this.expect('ident').value;
959
- this.expect(':');
960
- const type = this.parseType();
961
- this.expect('=');
962
- const value = this.parseExpr();
963
- this.match(';');
964
- return this.withLoc({ kind: 'const_decl', name, type, value }, constToken);
965
- }
966
- parseReturnStmt() {
967
- const returnToken = this.expect('return');
968
- let value;
969
- if (!this.check(';') && !this.check('}') && !this.check('eof')) {
970
- value = this.parseExpr();
971
- }
972
- this.match(';');
973
- return this.withLoc({ kind: 'return', value }, returnToken);
974
- }
975
- parseIfStmt() {
976
- const ifToken = this.expect('if');
977
- // if let Some(x) = expr { ... }
978
- if (this.check('let') && this.peek(1).kind === 'ident' && this.peek(1).value === 'Some') {
979
- this.advance(); // consume 'let'
980
- this.advance(); // consume 'Some'
981
- this.expect('(');
982
- const binding = this.expect('ident').value;
983
- this.expect(')');
984
- this.expect('=');
985
- const init = this.parseExpr();
986
- const then = this.parseBlock();
987
- let else_;
988
- if (this.match('else')) {
989
- if (this.check('if')) {
990
- else_ = [this.parseIfStmt()];
991
- }
992
- else {
993
- else_ = this.parseBlock();
994
- }
995
- }
996
- return this.withLoc({ kind: 'if_let_some', binding, init, then, else_ }, ifToken);
997
- }
998
- const cond = this.parseParenOptionalCond();
999
- const then = this.parseBlock();
1000
- let else_;
1001
- if (this.match('else')) {
1002
- if (this.check('if')) {
1003
- // else if
1004
- else_ = [this.parseIfStmt()];
1005
- }
1006
- else {
1007
- else_ = this.parseBlock();
1008
- }
1009
- }
1010
- return this.withLoc({ kind: 'if', cond, then, else_ }, ifToken);
1011
- }
1012
- parseWhileStmt() {
1013
- const whileToken = this.expect('while');
1014
- // while let Some(x) = expr { ... }
1015
- if (this.check('let') && this.peek(1).kind === 'ident' && this.peek(1).value === 'Some') {
1016
- this.advance(); // consume 'let'
1017
- this.advance(); // consume 'Some'
1018
- this.expect('(');
1019
- const binding = this.expect('ident').value;
1020
- this.expect(')');
1021
- this.expect('=');
1022
- const init = this.parseExpr();
1023
- const body = this.parseBlock();
1024
- return this.withLoc({ kind: 'while_let_some', binding, init, body }, whileToken);
1025
- }
1026
- const cond = this.parseParenOptionalCond();
1027
- const body = this.parseBlock();
1028
- return this.withLoc({ kind: 'while', cond, body }, whileToken);
1029
- }
1030
- parseDoWhileStmt() {
1031
- const doToken = this.expect('do');
1032
- const body = this.parseBlock();
1033
- this.expect('while');
1034
- const cond = this.parseParenOptionalCond();
1035
- this.match(';');
1036
- return this.withLoc({ kind: 'do_while', cond, body }, doToken);
1037
- }
1038
- parseRepeatStmt() {
1039
- const repeatToken = this.expect('repeat');
1040
- const countToken = this.expect('int_lit');
1041
- const count = parseInt(countToken.value, 10);
1042
- const body = this.parseBlock();
1043
- return this.withLoc({ kind: 'repeat', count, body }, repeatToken);
1044
- }
1045
- parseParenOptionalCond() {
1046
- if (this.match('(')) {
1047
- const cond = this.parseExpr();
1048
- this.expect(')');
1049
- return cond;
1050
- }
1051
- return this.parseExpr();
1052
- }
1053
- parseForStmt() {
1054
- const forToken = this.expect('for');
1055
- // Check for for-range syntax: for <ident> in <range_lit> { ... }
1056
- if (this.check('ident') && this.peek(1).kind === 'in') {
1057
- return this.parseForRangeStmt(forToken);
1058
- }
1059
- this.expect('(');
1060
- // Detect for-in-array syntax: for ( let ident in ident , lenExpr ) { ... }
1061
- if (this.check('let') && this.peek(1).kind === 'ident' && this.peek(2).kind === 'in' && this.peek(3).kind === 'ident' && this.peek(4).kind === ',') {
1062
- this.advance(); // consume 'let'
1063
- const binding = this.expect('ident').value;
1064
- this.expect('in');
1065
- const arrayName = this.expect('ident').value;
1066
- this.expect(',');
1067
- const lenExpr = this.parseExpr();
1068
- this.expect(')');
1069
- const body = this.parseBlock();
1070
- return this.withLoc({ kind: 'for_in_array', binding, arrayName, lenExpr, body }, forToken);
1071
- }
1072
- // Init: either let statement (without semicolon) or empty
1073
- let init;
1074
- if (this.check('let')) {
1075
- // Parse let without consuming semicolon here (we handle it)
1076
- const letToken = this.expect('let');
1077
- const name = this.expect('ident').value;
1078
- let type;
1079
- if (this.match(':')) {
1080
- type = this.parseType();
1081
- }
1082
- this.expect('=');
1083
- const initExpr = this.parseExpr();
1084
- const initStmt = { kind: 'let', name, type, init: initExpr };
1085
- init = this.withLoc(initStmt, letToken);
1086
- }
1087
- this.expect(';');
1088
- // Condition
1089
- const cond = this.parseExpr();
1090
- this.expect(';');
1091
- // Step expression
1092
- const step = this.parseExpr();
1093
- this.expect(')');
1094
- const body = this.parseBlock();
1095
- return this.withLoc({ kind: 'for', init, cond, step, body }, forToken);
1096
- }
1097
- parseForRangeStmt(forToken) {
1098
- const varName = this.expect('ident').value;
1099
- this.expect('in');
1100
- let start;
1101
- let end;
1102
- let inclusive = false;
1103
- if (this.check('range_lit')) {
1104
- // Literal range: 0..10, 0..count, 0..=9, 0..=count
1105
- const rangeToken = this.advance();
1106
- const raw = rangeToken.value;
1107
- // Detect inclusive: ends with = after .. (e.g. "0..=" or "..=")
1108
- const incl = raw.includes('..=');
1109
- inclusive = incl;
1110
- const range = this.parseRangeValue(raw);
1111
- start = this.withLoc({ kind: 'int_lit', value: range.min ?? 0 }, rangeToken);
1112
- if (range.max !== null && range.max !== undefined) {
1113
- // Fully numeric: 0..10 or 0..=9
1114
- end = this.withLoc({ kind: 'int_lit', value: range.max }, rangeToken);
1115
- }
1116
- else {
1117
- // Open-ended: "0.." or "0..=" — parse the end expression from next tokens
1118
- end = this.parseUnaryExpr();
1119
- }
1120
- }
1121
- else {
1122
- // Dynamic range: expr..expr or expr..=expr
1123
- // parseExpr stops before range_lit (not in BINARY_OPS), so this is safe
1124
- const arrayOrStart = this.parseExpr();
1125
- // --- for_each detection: for item in arr { ... } ---
1126
- // If after parsing the expression there is no range_lit, it's a for_each (array iteration)
1127
- if (!this.check('range_lit')) {
1128
- const body = this.parseBlock();
1129
- return this.withLoc({ kind: 'for_each', binding: varName, array: arrayOrStart, body }, forToken);
1130
- }
1131
- start = arrayOrStart;
1132
- // Consume the range_lit token which should be ".." or "..="
1133
- if (this.check('range_lit')) {
1134
- const rangeOp = this.advance();
1135
- inclusive = rangeOp.value.includes('=');
1136
- // If the range_lit captured digits after .., use them as end
1137
- const afterOp = rangeOp.value.replace(/^\.\.=?/, '');
1138
- if (afterOp.length > 0) {
1139
- end = this.withLoc({ kind: 'int_lit', value: parseInt(afterOp, 10) }, rangeOp);
1140
- }
1141
- else {
1142
- end = this.parseExpr();
1143
- }
1144
- }
1145
- else {
1146
- this.error('Expected .. or ..= in for-range expression');
1147
- start = this.withLoc({ kind: 'int_lit', value: 0 }, this.peek());
1148
- end = this.withLoc({ kind: 'int_lit', value: 0 }, this.peek());
1149
- }
1150
- }
1151
- const body = this.parseBlock();
1152
- return this.withLoc({ kind: 'for_range', varName, start, end, inclusive, body }, forToken);
1153
- }
1154
- parseForeachStmt() {
1155
- const foreachToken = this.expect('foreach');
1156
- this.expect('(');
1157
- const binding = this.expect('ident').value;
1158
- this.expect('in');
1159
- const iterable = this.parseExpr();
1160
- this.expect(')');
1161
- // Parse optional execute context modifiers (as, at, positioned, rotated, facing, etc.)
1162
- let executeContext;
1163
- // Check for execute subcommand keywords
1164
- const execIdentKeywords = ['positioned', 'rotated', 'facing', 'anchored', 'align', 'on', 'summon'];
1165
- if (this.check('as') || this.check('at') || this.check('in') || (this.check('ident') && execIdentKeywords.includes(this.peek().value))) {
1166
- // Collect everything until we hit '{'
1167
- let context = '';
1168
- while (!this.check('{') && !this.check('eof')) {
1169
- context += this.advance().value + ' ';
1170
- }
1171
- executeContext = context.trim();
1172
- }
1173
- const body = this.parseBlock();
1174
- return this.withLoc({ kind: 'foreach', binding, iterable, body, executeContext }, foreachToken);
1175
- }
1176
- parseMatchPattern() {
1177
- // Wildcard: _
1178
- if (this.check('ident') && this.peek().value === '_') {
1179
- this.advance();
1180
- return { kind: 'PatWild' };
1181
- }
1182
- // None
1183
- if (this.check('ident') && this.peek().value === 'None') {
1184
- this.advance();
1185
- return { kind: 'PatNone' };
1186
- }
1187
- // Some(x)
1188
- if (this.check('ident') && this.peek().value === 'Some') {
1189
- this.advance(); // consume 'Some'
1190
- this.expect('(');
1191
- const binding = this.expect('ident').value;
1192
- this.expect(')');
1193
- return { kind: 'PatSome', binding };
1194
- }
1195
- // Enum pattern: EnumName::Variant or EnumName::Variant(b1, b2, ...)
1196
- if (this.check('ident') && this.peek(1).kind === '::') {
1197
- const enumName = this.advance().value;
1198
- this.expect('::');
1199
- const variant = this.expect('ident').value;
1200
- const bindings = [];
1201
- if (this.check('(')) {
1202
- this.advance(); // consume '('
1203
- while (!this.check(')') && !this.check('eof')) {
1204
- bindings.push(this.expect('ident').value);
1205
- if (!this.match(','))
1206
- break;
1207
- }
1208
- this.expect(')');
1209
- }
1210
- return { kind: 'PatEnum', enumName, variant, bindings };
1211
- }
1212
- // Integer literal
1213
- if (this.check('int_lit')) {
1214
- const tok = this.advance();
1215
- return { kind: 'PatInt', value: parseInt(tok.value, 10) };
1216
- }
1217
- // Negative integer literal: -N
1218
- if (this.check('-') && this.peek(1).kind === 'int_lit') {
1219
- this.advance(); // consume '-'
1220
- const tok = this.advance();
1221
- return { kind: 'PatInt', value: -parseInt(tok.value, 10) };
1222
- }
1223
- // Legacy: range_lit or any other expression (e.g. 0..59)
1224
- const e = this.parseExpr();
1225
- return { kind: 'PatExpr', expr: e };
1226
- }
1227
- parseMatchStmt() {
1228
- const matchToken = this.expect('match');
1229
- // Support both `match (expr)` (legacy) and `match expr` (new syntax)
1230
- let expr;
1231
- if (this.check('(')) {
1232
- // Peek ahead — if it looks like `(expr)` followed by `{`, consume parens
1233
- this.advance(); // consume '('
1234
- expr = this.parseExpr();
1235
- this.expect(')');
1236
- }
1237
- else {
1238
- expr = this.parseExpr();
1239
- }
1240
- this.expect('{');
1241
- const arms = [];
1242
- while (!this.check('}') && !this.check('eof')) {
1243
- const pattern = this.parseMatchPattern();
1244
- this.expect('=>');
1245
- const body = this.parseBlock();
1246
- this.match(','); // optional trailing comma
1247
- arms.push({ pattern, body });
1248
- }
1249
- this.expect('}');
1250
- return this.withLoc({ kind: 'match', expr, arms }, matchToken);
1251
- }
1252
- parseAsStmt() {
1253
- const asToken = this.expect('as');
1254
- const as_sel = this.parseSelector();
1255
- // Check for combined as/at
1256
- if (this.match('at')) {
1257
- const at_sel = this.parseSelector();
1258
- const body = this.parseBlock();
1259
- return this.withLoc({ kind: 'as_at', as_sel, at_sel, body }, asToken);
1260
- }
1261
- const body = this.parseBlock();
1262
- return this.withLoc({ kind: 'as_block', selector: as_sel, body }, asToken);
1263
- }
1264
- parseAtStmt() {
1265
- const atToken = this.expect('at');
1266
- const selector = this.parseSelector();
1267
- const body = this.parseBlock();
1268
- return this.withLoc({ kind: 'at_block', selector, body }, atToken);
1269
- }
1270
- parseExecuteStmt() {
1271
- const executeToken = this.expect('execute');
1272
- const subcommands = [];
1273
- // Parse subcommands until we hit 'run'
1274
- while (!this.check('run') && !this.check('eof')) {
1275
- if (this.match('as')) {
1276
- const selector = this.parseSelector();
1277
- subcommands.push({ kind: 'as', selector });
1278
- }
1279
- else if (this.match('at')) {
1280
- const selector = this.parseSelector();
1281
- subcommands.push({ kind: 'at', selector });
1282
- }
1283
- else if (this.checkIdent('positioned')) {
1284
- this.advance();
1285
- if (this.match('as')) {
1286
- const selector = this.parseSelector();
1287
- subcommands.push({ kind: 'positioned_as', selector });
1288
- }
1289
- else {
1290
- const x = this.parseCoordToken();
1291
- const y = this.parseCoordToken();
1292
- const z = this.parseCoordToken();
1293
- subcommands.push({ kind: 'positioned', x, y, z });
1294
- }
1295
- }
1296
- else if (this.checkIdent('rotated')) {
1297
- this.advance();
1298
- if (this.match('as')) {
1299
- const selector = this.parseSelector();
1300
- subcommands.push({ kind: 'rotated_as', selector });
1301
- }
1302
- else {
1303
- const yaw = this.parseCoordToken();
1304
- const pitch = this.parseCoordToken();
1305
- subcommands.push({ kind: 'rotated', yaw, pitch });
1306
- }
1307
- }
1308
- else if (this.checkIdent('facing')) {
1309
- this.advance();
1310
- if (this.checkIdent('entity')) {
1311
- this.advance();
1312
- const selector = this.parseSelector();
1313
- const anchor = this.checkIdent('eyes') || this.checkIdent('feet') ? this.advance().value : 'feet';
1314
- subcommands.push({ kind: 'facing_entity', selector, anchor });
1315
- }
1316
- else {
1317
- const x = this.parseCoordToken();
1318
- const y = this.parseCoordToken();
1319
- const z = this.parseCoordToken();
1320
- subcommands.push({ kind: 'facing', x, y, z });
1321
- }
1322
- }
1323
- else if (this.checkIdent('anchored')) {
1324
- this.advance();
1325
- const anchor = this.advance().value;
1326
- subcommands.push({ kind: 'anchored', anchor });
1327
- }
1328
- else if (this.checkIdent('align')) {
1329
- this.advance();
1330
- const axes = this.advance().value;
1331
- subcommands.push({ kind: 'align', axes });
1332
- }
1333
- else if (this.checkIdent('on')) {
1334
- this.advance();
1335
- const relation = this.advance().value;
1336
- subcommands.push({ kind: 'on', relation });
1337
- }
1338
- else if (this.checkIdent('summon')) {
1339
- this.advance();
1340
- const entity = this.advance().value;
1341
- subcommands.push({ kind: 'summon', entity });
1342
- }
1343
- else if (this.checkIdent('store')) {
1344
- this.advance();
1345
- const storeType = this.advance().value; // 'result' or 'success'
1346
- if (this.checkIdent('score')) {
1347
- this.advance();
1348
- const target = this.advance().value;
1349
- const targetObj = this.advance().value;
1350
- if (storeType === 'result') {
1351
- subcommands.push({ kind: 'store_result', target, targetObj });
1352
- }
1353
- else {
1354
- subcommands.push({ kind: 'store_success', target, targetObj });
1355
- }
1356
- }
1357
- else {
1358
- this.error('store currently only supports score target');
1359
- }
1360
- }
1361
- else if (this.match('if')) {
1362
- this.parseExecuteCondition(subcommands, 'if');
1363
- }
1364
- else if (this.match('unless')) {
1365
- this.parseExecuteCondition(subcommands, 'unless');
1366
- }
1367
- else if (this.match('in')) {
1368
- // Dimension can be namespaced: minecraft:the_nether
1369
- let dim = this.advance().value;
1370
- if (this.match(':')) {
1371
- dim += ':' + this.advance().value;
1372
- }
1373
- subcommands.push({ kind: 'in', dimension: dim });
1374
- }
1375
- else {
1376
- this.error(`Unexpected token in execute statement: ${this.peek().kind} (${this.peek().value})`);
1377
- }
1378
- }
1379
- this.expect('run');
1380
- const body = this.parseBlock();
1381
- return this.withLoc({ kind: 'execute', subcommands, body }, executeToken);
1382
- }
1383
- parseExecuteCondition(subcommands, type) {
1384
- if (this.checkIdent('entity') || this.check('selector')) {
1385
- if (this.checkIdent('entity'))
1386
- this.advance();
1387
- const selectorOrVar = this.parseSelectorOrVarSelector();
1388
- subcommands.push({ kind: type === 'if' ? 'if_entity' : 'unless_entity', ...selectorOrVar });
1389
- }
1390
- else if (this.checkIdent('block')) {
1391
- this.advance();
1392
- const x = this.parseCoordToken();
1393
- const y = this.parseCoordToken();
1394
- const z = this.parseCoordToken();
1395
- const block = this.parseBlockId();
1396
- subcommands.push({ kind: type === 'if' ? 'if_block' : 'unless_block', pos: [x, y, z], block });
1397
- }
1398
- else if (this.checkIdent('score')) {
1399
- this.advance();
1400
- const target = this.advance().value;
1401
- const targetObj = this.advance().value;
1402
- // Check for range or comparison
1403
- if (this.checkIdent('matches')) {
1404
- this.advance();
1405
- const range = this.advance().value;
1406
- subcommands.push({ kind: type === 'if' ? 'if_score_range' : 'unless_score_range', target, targetObj, range });
1407
- }
1408
- else {
1409
- const op = this.advance().value; // <, <=, =, >=, >
1410
- const source = this.advance().value;
1411
- const sourceObj = this.advance().value;
1412
- subcommands.push({
1413
- kind: type === 'if' ? 'if_score' : 'unless_score',
1414
- target, targetObj, op, source, sourceObj
1415
- });
1416
- }
1417
- }
1418
- else {
1419
- this.error(`Unknown condition type after ${type}`);
1420
- }
1421
- }
1422
- parseCoordToken() {
1423
- // Handle ~, ^, numbers, relative coords like ~5, ^-3
1424
- const token = this.peek();
1425
- if (token.kind === 'rel_coord' || token.kind === 'local_coord' ||
1426
- token.kind === 'int_lit' || token.kind === 'float_lit' ||
1427
- token.kind === '-' || token.kind === 'ident') {
1428
- return this.advance().value;
1429
- }
1430
- this.error(`Expected coordinate, got ${token.kind}`);
1431
- return '~';
1432
- }
1433
- parseBlockId() {
1434
- // Parse block ID like minecraft:stone or stone
1435
- let id = this.advance().value;
1436
- if (this.match(':')) {
1437
- id += ':' + this.advance().value;
1438
- }
1439
- // Handle block states [facing=north]
1440
- if (this.check('[')) {
1441
- id += this.advance().value; // [
1442
- while (!this.check(']') && !this.check('eof')) {
1443
- id += this.advance().value;
1444
- }
1445
- id += this.advance().value; // ]
1446
- }
1447
- return id;
1448
- }
1449
- checkIdent(value) {
1450
- return this.check('ident') && this.peek().value === value;
1451
- }
1452
- parseExprStmt() {
1453
- const expr = this.parseExpr();
1454
- this.match(';');
1455
- const exprToken = this.getLocToken(expr) ?? this.peek();
1456
- return this.withLoc({ kind: 'expr', expr }, exprToken);
1457
- }
1458
- // -------------------------------------------------------------------------
1459
- // Expressions (Precedence Climbing)
1460
- // -------------------------------------------------------------------------
1461
- parseExpr() {
1462
- return this.parseAssignment();
1463
- }
1464
- parseAssignment() {
1465
- const left = this.parseBinaryExpr(1);
1466
- // Check for assignment
1467
- const token = this.peek();
1468
- if (token.kind === '=' || token.kind === '+=' || token.kind === '-=' ||
1469
- token.kind === '*=' || token.kind === '/=' || token.kind === '%=') {
1470
- const op = this.advance().kind;
1471
- if (left.kind === 'ident') {
1472
- const value = this.parseAssignment();
1473
- return this.withLoc({ kind: 'assign', target: left.name, op, value }, this.getLocToken(left) ?? token);
1474
- }
1475
- // Member assignment: p.x = 10, p.x += 5
1476
- if (left.kind === 'member') {
1477
- const value = this.parseAssignment();
1478
- return this.withLoc({ kind: 'member_assign', obj: left.obj, field: left.field, op, value }, this.getLocToken(left) ?? token);
1479
- }
1480
- // Index assignment: arr[0] = val, arr[i] = val
1481
- if (left.kind === 'index') {
1482
- const value = this.parseAssignment();
1483
- return this.withLoc({ kind: 'index_assign', obj: left.obj, index: left.index, op, value }, this.getLocToken(left) ?? token);
1484
- }
1485
- }
1486
- return left;
1487
- }
1488
- parseBinaryExpr(minPrec) {
1489
- let left = this.parseUnaryExpr();
1490
- while (true) {
1491
- const op = this.peek().kind;
1492
- if (!BINARY_OPS.has(op))
1493
- break;
1494
- const prec = PRECEDENCE[op];
1495
- if (prec < minPrec)
1496
- break;
1497
- const opToken = this.advance();
1498
- if (op === 'is') {
1499
- const entityType = this.parseEntityTypeName();
1500
- left = this.withLoc({ kind: 'is_check', expr: left, entityType }, this.getLocToken(left) ?? opToken);
1501
- continue;
1502
- }
1503
- const right = this.parseBinaryExpr(prec + 1); // left associative
1504
- left = this.withLoc({ kind: 'binary', op: op, left, right }, this.getLocToken(left) ?? opToken);
1505
- }
1506
- return left;
1507
- }
1508
- parseUnaryExpr() {
1509
- if (this.match('!')) {
1510
- const bangToken = this.tokens[this.pos - 1];
1511
- const operand = this.parseUnaryExpr();
1512
- return this.withLoc({ kind: 'unary', op: '!', operand }, bangToken);
1513
- }
1514
- if (this.check('-') && !this.isSubtraction()) {
1515
- const minusToken = this.advance();
1516
- const operand = this.parseUnaryExpr();
1517
- return this.withLoc({ kind: 'unary', op: '-', operand }, minusToken);
1518
- }
1519
- return this.parsePostfixExpr();
1520
- }
1521
- parseEntityTypeName() {
1522
- const token = this.expect('ident');
1523
- if (ENTITY_TYPE_NAMES.has(token.value)) {
1524
- return token.value;
1525
- }
1526
- this.error(`Unknown entity type '${token.value}'`);
1527
- }
1528
- isSubtraction() {
1529
- // Check if this minus is binary (subtraction) by looking at previous token
1530
- // If previous was a value (literal, ident, ), ]) it's subtraction
1531
- if (this.pos === 0)
1532
- return false;
1533
- const prev = this.tokens[this.pos - 1];
1534
- return ['int_lit', 'float_lit', 'ident', ')', ']'].includes(prev.kind);
1535
- }
1536
- /**
1537
- * Try to parse `<Type, ...>` as explicit generic type arguments.
1538
- * Returns the parsed type list if successful, null if this looks like a comparison.
1539
- * Does NOT consume any tokens if it returns null.
1540
- */
1541
- tryParseTypeArgs() {
1542
- const saved = this.pos;
1543
- this.advance(); // consume '<'
1544
- const typeArgs = [];
1545
- try {
1546
- do {
1547
- typeArgs.push(this.parseType());
1548
- } while (this.match(','));
1549
- if (!this.check('>')) {
1550
- this.pos = saved;
1551
- return null;
1552
- }
1553
- this.advance(); // consume '>'
1554
- return typeArgs;
1555
- }
1556
- catch {
1557
- this.pos = saved;
1558
- return null;
1559
- }
1560
- }
1561
- parsePostfixExpr() {
1562
- let expr = this.parsePrimaryExpr();
1563
- while (true) {
1564
- // Generic call: ident<Type>(args) — check before regular '(' handling
1565
- if (expr.kind === 'ident' && this.check('<')) {
1566
- const typeArgs = this.tryParseTypeArgs();
1567
- if (typeArgs !== null && this.check('(')) {
1568
- const openParenToken = this.peek();
1569
- this.advance(); // consume '('
1570
- const args = this.parseArgs();
1571
- this.expect(')');
1572
- expr = this.withLoc({ kind: 'call', fn: expr.name, args, typeArgs }, this.getLocToken(expr) ?? openParenToken);
1573
- continue;
1574
- }
1575
- // Not a generic call — fall through to normal expression handling
1576
- }
1577
- // Function call
1578
- if (this.match('(')) {
1579
- const openParenToken = this.tokens[this.pos - 1];
1580
- if (expr.kind === 'ident') {
1581
- const args = this.parseArgs();
1582
- this.expect(')');
1583
- expr = this.withLoc({ kind: 'call', fn: expr.name, args }, this.getLocToken(expr) ?? openParenToken);
1584
- continue;
1585
- }
1586
- // Member call: entity.tag("name") → __entity_tag(entity, "name")
1587
- // Also handle arr.push(val) and arr.length
1588
- if (expr.kind === 'member') {
1589
- // Option.unwrap_or(default) → unwrap_or AST node
1590
- if (expr.field === 'unwrap_or') {
1591
- const defaultExpr = this.parseExpr();
1592
- this.expect(')');
1593
- expr = this.withLoc({ kind: 'unwrap_or', opt: expr.obj, default_: defaultExpr }, this.getLocToken(expr) ?? openParenToken);
1594
- continue;
1595
- }
1596
- const methodMap = {
1597
- 'tag': '__entity_tag',
1598
- 'untag': '__entity_untag',
1599
- 'has_tag': '__entity_has_tag',
1600
- 'push': '__array_push',
1601
- 'pop': '__array_pop',
1602
- 'add': 'set_add',
1603
- 'contains': 'set_contains',
1604
- 'remove': 'set_remove',
1605
- 'clear': 'set_clear',
1606
- };
1607
- const internalFn = methodMap[expr.field];
1608
- if (internalFn) {
1609
- const args = this.parseArgs();
1610
- this.expect(')');
1611
- expr = this.withLoc({ kind: 'call', fn: internalFn, args: [expr.obj, ...args] }, this.getLocToken(expr) ?? openParenToken);
1612
- continue;
1613
- }
1614
- // Generic method sugar: obj.method(args) → method(obj, args)
1615
- const args = this.parseArgs();
1616
- this.expect(')');
1617
- expr = this.withLoc({ kind: 'call', fn: expr.field, args: [expr.obj, ...args] }, this.getLocToken(expr) ?? openParenToken);
1618
- continue;
1619
- }
1620
- const args = this.parseArgs();
1621
- this.expect(')');
1622
- expr = this.withLoc({ kind: 'invoke', callee: expr, args }, this.getLocToken(expr) ?? openParenToken);
1623
- continue;
1624
- }
1625
- // Array index access: arr[0]
1626
- if (this.match('[')) {
1627
- const index = this.parseExpr();
1628
- this.expect(']');
1629
- expr = this.withLoc({ kind: 'index', obj: expr, index }, this.getLocToken(expr) ?? this.tokens[this.pos - 1]);
1630
- continue;
1631
- }
1632
- // Member access
1633
- if (this.match('.')) {
1634
- const field = this.expect('ident').value;
1635
- expr = this.withLoc({ kind: 'member', obj: expr, field }, this.getLocToken(expr) ?? this.tokens[this.pos - 1]);
1636
- continue;
1637
- }
1638
- // Type cast: expr as Type
1639
- // Only parse 'as' as a cast when followed by a type token (not a selector like @a)
1640
- if (this.check('as') && this.isTypeCastAs()) {
1641
- const asToken = this.advance(); // consume 'as'
1642
- const targetType = this.parseType();
1643
- expr = this.withLoc({ kind: 'type_cast', expr, targetType }, this.getLocToken(expr) ?? asToken);
1644
- continue;
1645
- }
1646
- break;
1647
- }
1648
- return expr;
1649
- }
1650
- /** Returns true if the current 'as' token is a type cast (not a context block) */
1651
- isTypeCastAs() {
1652
- // Look ahead past 'as' to see if the next token looks like a type
1653
- const next = this.tokens[this.pos + 1];
1654
- if (!next)
1655
- return false;
1656
- const typeStartTokens = new Set(['int', 'bool', 'float', 'fixed', 'string', 'void', 'BlockPos', '(']);
1657
- if (typeStartTokens.has(next.kind))
1658
- return true;
1659
- if (next.kind === 'ident' && (next.value === 'double' || next.value === 'byte' || next.value === 'short' ||
1660
- next.value === 'long' || next.value === 'selector' || next.value === 'Option'))
1661
- return true;
1662
- return false;
1663
- }
1664
- parseArgs() {
1665
- const args = [];
1666
- if (!this.check(')')) {
1667
- do {
1668
- args.push(this.parseExpr());
1669
- } while (this.match(','));
1670
- }
1671
- return args;
1672
- }
1673
- parsePrimaryExpr() {
1674
- const token = this.peek();
1675
- if (token.kind === 'ident' && this.peek(1).kind === '::') {
1676
- const typeToken = this.advance();
1677
- this.expect('::');
1678
- const memberToken = this.expect('ident');
1679
- if (this.check('(')) {
1680
- // Peek inside: if first non-'(' token is `ident :` it's enum construction with named args.
1681
- // We only treat it as enum_construct when there are actual named args (not empty parens),
1682
- // because empty `()` is ambiguous and most commonly means a static method call.
1683
- const isNamedArgs = this.peek(1).kind === 'ident' && this.peek(2).kind === ':';
1684
- if (isNamedArgs) {
1685
- // Enum variant construction: EnumName::Variant(field: expr, ...)
1686
- this.advance(); // consume '('
1687
- const args = [];
1688
- while (!this.check(')') && !this.check('eof')) {
1689
- const fieldName = this.expect('ident').value;
1690
- this.expect(':');
1691
- const value = this.parseExpr();
1692
- args.push({ name: fieldName, value });
1693
- if (!this.match(','))
1694
- break;
1695
- }
1696
- this.expect(')');
1697
- return this.withLoc({ kind: 'enum_construct', enumName: typeToken.value, variant: memberToken.value, args }, typeToken);
1698
- }
1699
- // Static method call: Type::method(args)
1700
- this.advance(); // consume '('
1701
- const args = this.parseArgs();
1702
- this.expect(')');
1703
- return this.withLoc({ kind: 'static_call', type: typeToken.value, method: memberToken.value, args }, typeToken);
1704
- }
1705
- // Enum variant access: Enum::Variant
1706
- return this.withLoc({ kind: 'path_expr', enumName: typeToken.value, variant: memberToken.value }, typeToken);
1707
- }
1708
- if (token.kind === 'ident' && this.peek(1).kind === '=>') {
1709
- return this.parseSingleParamLambda();
1710
- }
1711
- // Integer literal
1712
- if (token.kind === 'int_lit') {
1713
- this.advance();
1714
- return this.withLoc({ kind: 'int_lit', value: parseInt(token.value, 10) }, token);
1715
- }
1716
- // Float literal
1717
- if (token.kind === 'float_lit') {
1718
- this.advance();
1719
- return this.withLoc({ kind: 'float_lit', value: parseFloat(token.value) }, token);
1720
- }
1721
- // Relative coordinate: ~ ~5 ~-3 ~0.5
1722
- if (token.kind === 'rel_coord') {
1723
- this.advance();
1724
- return this.withLoc({ kind: 'rel_coord', value: token.value }, token);
1725
- }
1726
- // Local coordinate: ^ ^5 ^-3 ^0.5
1727
- if (token.kind === 'local_coord') {
1728
- this.advance();
1729
- return this.withLoc({ kind: 'local_coord', value: token.value }, token);
1730
- }
1731
- // NBT suffix literals
1732
- if (token.kind === 'byte_lit') {
1733
- this.advance();
1734
- return this.withLoc({ kind: 'byte_lit', value: parseInt(token.value.slice(0, -1), 10) }, token);
1735
- }
1736
- if (token.kind === 'short_lit') {
1737
- this.advance();
1738
- return this.withLoc({ kind: 'short_lit', value: parseInt(token.value.slice(0, -1), 10) }, token);
1739
- }
1740
- if (token.kind === 'long_lit') {
1741
- this.advance();
1742
- return this.withLoc({ kind: 'long_lit', value: parseInt(token.value.slice(0, -1), 10) }, token);
1743
- }
1744
- if (token.kind === 'double_lit') {
1745
- this.advance();
1746
- return this.withLoc({ kind: 'double_lit', value: parseFloat(token.value.slice(0, -1)) }, token);
1747
- }
1748
- // String literal
1749
- if (token.kind === 'string_lit') {
1750
- this.advance();
1751
- return this.parseStringExpr(token);
1752
- }
1753
- if (token.kind === 'f_string') {
1754
- this.advance();
1755
- return this.parseFStringExpr(token);
1756
- }
1757
- // MC name literal: #health → mc_name node (value = "health", without #)
1758
- if (token.kind === 'mc_name') {
1759
- this.advance();
1760
- return this.withLoc({ kind: 'mc_name', value: token.value.slice(1) }, token);
1761
- }
1762
- // Boolean literal
1763
- if (token.kind === 'true') {
1764
- this.advance();
1765
- return this.withLoc({ kind: 'bool_lit', value: true }, token);
1766
- }
1767
- if (token.kind === 'false') {
1768
- this.advance();
1769
- return this.withLoc({ kind: 'bool_lit', value: false }, token);
1770
- }
1771
- // Range literal
1772
- if (token.kind === 'range_lit') {
1773
- this.advance();
1774
- return this.withLoc({ kind: 'range_lit', range: this.parseRangeValue(token.value) }, token);
1775
- }
1776
- // Selector
1777
- if (token.kind === 'selector') {
1778
- this.advance();
1779
- return this.withLoc({
1780
- kind: 'selector',
1781
- raw: token.value,
1782
- isSingle: computeIsSingle(token.value),
1783
- sel: this.parseSelectorValue(token.value),
1784
- }, token);
1785
- }
1786
- // Named struct literal: TypeName { field: value, ... }
1787
- // Require at least one field (ident + :) to avoid ambiguity with blocks.
1788
- if (token.kind === 'ident' && this.peek(1).kind === '{' &&
1789
- this.peek(2).kind === 'ident' && this.peek(3).kind === ':') {
1790
- this.advance(); // consume type name (used only for disambiguation, dropped from AST)
1791
- return this.parseStructLit();
1792
- }
1793
- // Some(expr) — Option constructor
1794
- if (token.kind === 'ident' && token.value === 'Some' && this.peek(1).kind === '(') {
1795
- this.advance(); // consume 'Some'
1796
- this.advance(); // consume '('
1797
- const value = this.parseExpr();
1798
- this.expect(')');
1799
- return this.withLoc({ kind: 'some_lit', value }, token);
1800
- }
1801
- // None — Option empty constructor
1802
- if (token.kind === 'ident' && token.value === 'None') {
1803
- this.advance();
1804
- return this.withLoc({ kind: 'none_lit' }, token);
1805
- }
1806
- // Identifier
1807
- if (token.kind === 'ident') {
1808
- this.advance();
1809
- return this.withLoc({ kind: 'ident', name: token.value }, token);
1810
- }
1811
- // Grouped expression or tuple literal
1812
- if (token.kind === '(') {
1813
- if (this.isBlockPosLiteral()) {
1814
- return this.parseBlockPos();
1815
- }
1816
- if (this.isLambdaStart()) {
1817
- return this.parseLambdaExpr();
1818
- }
1819
- this.advance();
1820
- const first = this.parseExpr();
1821
- // If followed by a comma, it's a tuple literal
1822
- if (this.match(',')) {
1823
- const elements = [first];
1824
- if (!this.check(')')) {
1825
- do {
1826
- elements.push(this.parseExpr());
1827
- } while (this.match(','));
1828
- }
1829
- this.expect(')');
1830
- return this.withLoc({ kind: 'tuple_lit', elements }, token);
1831
- }
1832
- this.expect(')');
1833
- return first;
1834
- }
1835
- // Struct literal or block: { x: 10, y: 20 }
1836
- if (token.kind === '{') {
1837
- return this.parseStructLit();
1838
- }
1839
- // Array literal: [1, 2, 3] or []
1840
- if (token.kind === '[') {
1841
- return this.parseArrayLit();
1842
- }
1843
- this.error(`Unexpected token '${token.kind}'`);
1844
- }
1845
- parseLiteralExpr() {
1846
- // Support negative literals: -5, -3.14
1847
- if (this.check('-')) {
1848
- this.advance();
1849
- const token = this.peek();
1850
- if (token.kind === 'int_lit') {
1851
- this.advance();
1852
- return this.withLoc({ kind: 'int_lit', value: -Number(token.value) }, token);
1853
- }
1854
- if (token.kind === 'float_lit') {
1855
- this.advance();
1856
- return this.withLoc({ kind: 'float_lit', value: -Number(token.value) }, token);
1857
- }
1858
- this.error('Expected number after unary -');
1859
- }
1860
- const expr = this.parsePrimaryExpr();
1861
- if (expr.kind === 'int_lit' ||
1862
- expr.kind === 'float_lit' ||
1863
- expr.kind === 'bool_lit' ||
1864
- expr.kind === 'str_lit') {
1865
- return expr;
1866
- }
1867
- this.error('Const value must be a literal');
1868
- }
1869
- parseSingleParamLambda() {
1870
- const paramToken = this.expect('ident');
1871
- const params = [{ name: paramToken.value }];
1872
- this.expect('=>');
1873
- return this.finishLambdaExpr(params, paramToken);
1874
- }
1875
- parseLambdaExpr() {
1876
- const openParenToken = this.expect('(');
1877
- const params = [];
1878
- if (!this.check(')')) {
1879
- do {
1880
- const name = this.expect('ident').value;
1881
- let type;
1882
- if (this.match(':')) {
1883
- type = this.parseType();
1884
- }
1885
- params.push({ name, type });
1886
- } while (this.match(','));
1887
- }
1888
- this.expect(')');
1889
- let returnType;
1890
- if (this.match('->')) {
1891
- returnType = this.parseType();
1892
- }
1893
- this.expect('=>');
1894
- return this.finishLambdaExpr(params, openParenToken, returnType);
1895
- }
1896
- finishLambdaExpr(params, token, returnType) {
1897
- const body = this.check('{') ? this.parseBlock() : this.parseExpr();
1898
- return this.withLoc({ kind: 'lambda', params, returnType, body }, token);
1899
- }
1900
- parseStringExpr(token) {
1901
- if (!token.value.includes('${')) {
1902
- return this.withLoc({ kind: 'str_lit', value: token.value }, token);
1903
- }
1904
- const parts = [];
1905
- let current = '';
1906
- let index = 0;
1907
- while (index < token.value.length) {
1908
- if (token.value[index] === '$' && token.value[index + 1] === '{') {
1909
- if (current) {
1910
- parts.push(current);
1911
- current = '';
1912
- }
1913
- index += 2;
1914
- let depth = 1;
1915
- let exprSource = '';
1916
- let inString = false;
1917
- while (index < token.value.length && depth > 0) {
1918
- const char = token.value[index];
1919
- if (char === '"' && token.value[index - 1] !== '\\') {
1920
- inString = !inString;
1921
- }
1922
- if (!inString) {
1923
- if (char === '{') {
1924
- depth++;
1925
- }
1926
- else if (char === '}') {
1927
- depth--;
1928
- if (depth === 0) {
1929
- index++;
1930
- break;
1931
- }
1932
- }
1933
- }
1934
- if (depth > 0) {
1935
- exprSource += char;
1936
- }
1937
- index++;
1938
- }
1939
- if (depth !== 0) {
1940
- this.error('Unterminated string interpolation');
1941
- }
1942
- parts.push(this.parseEmbeddedExpr(exprSource));
1943
- continue;
1944
- }
1945
- current += token.value[index];
1946
- index++;
1947
- }
1948
- if (current) {
1949
- parts.push(current);
1950
- }
1951
- return this.withLoc({ kind: 'str_interp', parts }, token);
1952
- }
1953
- parseFStringExpr(token) {
1954
- const parts = [];
1955
- let current = '';
1956
- let index = 0;
1957
- while (index < token.value.length) {
1958
- if (token.value[index] === '{') {
1959
- if (current) {
1960
- parts.push({ kind: 'text', value: current });
1961
- current = '';
1962
- }
1963
- index++;
1964
- let depth = 1;
1965
- let exprSource = '';
1966
- let inString = false;
1967
- while (index < token.value.length && depth > 0) {
1968
- const char = token.value[index];
1969
- if (char === '"' && token.value[index - 1] !== '\\') {
1970
- inString = !inString;
1971
- }
1972
- if (!inString) {
1973
- if (char === '{') {
1974
- depth++;
1975
- }
1976
- else if (char === '}') {
1977
- depth--;
1978
- if (depth === 0) {
1979
- index++;
1980
- break;
1981
- }
1982
- }
1983
- }
1984
- if (depth > 0) {
1985
- exprSource += char;
1986
- }
1987
- index++;
1988
- }
1989
- if (depth !== 0) {
1990
- this.error('Unterminated f-string interpolation');
1991
- }
1992
- parts.push({ kind: 'expr', expr: this.parseEmbeddedExpr(exprSource) });
1993
- continue;
1994
- }
1995
- current += token.value[index];
1996
- index++;
1997
- }
1998
- if (current) {
1999
- parts.push({ kind: 'text', value: current });
2000
- }
2001
- return this.withLoc({ kind: 'f_string', parts }, token);
2002
- }
2003
- parseEmbeddedExpr(source) {
2004
- const tokens = new lexer_1.Lexer(source, this.filePath).tokenize();
2005
- const parser = new Parser(tokens, source, this.filePath);
2006
- const expr = parser.parseExpr();
2007
- if (!parser.check('eof')) {
2008
- parser.error(`Unexpected token '${parser.peek().kind}' in string interpolation`);
2009
- }
2010
- return expr;
2011
- }
2012
- parseStructLit() {
2013
- const braceToken = this.expect('{');
2014
- const fields = [];
2015
- if (!this.check('}')) {
2016
- do {
2017
- const name = this.expect('ident').value;
2018
- this.expect(':');
2019
- const value = this.parseExpr();
2020
- fields.push({ name, value });
2021
- } while (this.match(','));
2022
- }
2023
- this.expect('}');
2024
- return this.withLoc({ kind: 'struct_lit', fields }, braceToken);
2025
- }
2026
- parseArrayLit() {
2027
- const bracketToken = this.expect('[');
2028
- const elements = [];
2029
- if (!this.check(']')) {
2030
- do {
2031
- elements.push(this.parseExpr());
2032
- } while (this.match(','));
2033
- }
2034
- this.expect(']');
2035
- return this.withLoc({ kind: 'array_lit', elements }, bracketToken);
2036
- }
2037
- isLambdaStart() {
2038
- if (!this.check('('))
2039
- return false;
2040
- let offset = 1;
2041
- if (this.peek(offset).kind !== ')') {
2042
- while (true) {
2043
- if (this.peek(offset).kind !== 'ident') {
2044
- return false;
2045
- }
2046
- offset += 1;
2047
- if (this.peek(offset).kind === ':') {
2048
- offset += 1;
2049
- const consumed = this.typeTokenLength(offset);
2050
- if (consumed === 0) {
2051
- return false;
2052
- }
2053
- offset += consumed;
2054
- }
2055
- if (this.peek(offset).kind === ',') {
2056
- offset += 1;
2057
- continue;
2058
- }
2059
- break;
2060
- }
2061
- }
2062
- if (this.peek(offset).kind !== ')') {
2063
- return false;
2064
- }
2065
- offset += 1;
2066
- if (this.peek(offset).kind === '=>') {
2067
- return true;
2068
- }
2069
- if (this.peek(offset).kind === '->') {
2070
- offset += 1;
2071
- const consumed = this.typeTokenLength(offset);
2072
- if (consumed === 0) {
2073
- return false;
2074
- }
2075
- offset += consumed;
2076
- return this.peek(offset).kind === '=>';
2077
- }
2078
- return false;
2079
- }
2080
- typeTokenLength(offset) {
2081
- const token = this.peek(offset);
2082
- if (token.kind === '(') {
2083
- let inner = offset + 1;
2084
- if (this.peek(inner).kind !== ')') {
2085
- while (true) {
2086
- const consumed = this.typeTokenLength(inner);
2087
- if (consumed === 0) {
2088
- return 0;
2089
- }
2090
- inner += consumed;
2091
- if (this.peek(inner).kind === ',') {
2092
- inner += 1;
2093
- continue;
2094
- }
2095
- break;
2096
- }
2097
- }
2098
- if (this.peek(inner).kind !== ')') {
2099
- return 0;
2100
- }
2101
- inner += 1;
2102
- if (this.peek(inner).kind !== '->') {
2103
- return 0;
2104
- }
2105
- inner += 1;
2106
- const returnLen = this.typeTokenLength(inner);
2107
- return returnLen === 0 ? 0 : inner + returnLen - offset;
2108
- }
2109
- const isNamedType = token.kind === 'int' ||
2110
- token.kind === 'bool' ||
2111
- token.kind === 'float' ||
2112
- token.kind === 'fixed' ||
2113
- token.kind === 'string' ||
2114
- token.kind === 'void' ||
2115
- token.kind === 'BlockPos' ||
2116
- token.kind === 'ident';
2117
- if (!isNamedType) {
2118
- return 0;
2119
- }
2120
- let length = 1;
2121
- while (this.peek(offset + length).kind === '[' && this.peek(offset + length + 1).kind === ']') {
2122
- length += 2;
2123
- }
2124
- return length;
2125
- }
2126
- isBlockPosLiteral() {
2127
- if (!this.check('('))
2128
- return false;
2129
- let offset = 1;
2130
- for (let i = 0; i < 3; i++) {
2131
- const consumed = this.coordComponentTokenLength(offset);
2132
- if (consumed === 0)
2133
- return false;
2134
- offset += consumed;
2135
- if (i < 2) {
2136
- if (this.peek(offset).kind !== ',')
2137
- return false;
2138
- offset += 1;
2139
- }
2140
- }
2141
- return this.peek(offset).kind === ')';
2142
- }
2143
- coordComponentTokenLength(offset) {
2144
- const token = this.peek(offset);
2145
- if (token.kind === 'int_lit') {
2146
- return 1;
2147
- }
2148
- if (token.kind === '-') {
2149
- return this.peek(offset + 1).kind === 'int_lit' ? 2 : 0;
2150
- }
2151
- // rel_coord (~, ~5, ~-3) and local_coord (^, ^5, ^-3) are single tokens now
2152
- if (token.kind === 'rel_coord' || token.kind === 'local_coord') {
2153
- return 1;
2154
- }
2155
- return 0;
2156
- }
2157
- parseBlockPos() {
2158
- const openParenToken = this.expect('(');
2159
- const x = this.parseCoordComponent();
2160
- this.expect(',');
2161
- const y = this.parseCoordComponent();
2162
- this.expect(',');
2163
- const z = this.parseCoordComponent();
2164
- this.expect(')');
2165
- return this.withLoc({ kind: 'blockpos', x, y, z }, openParenToken);
2166
- }
2167
- parseCoordComponent() {
2168
- const token = this.peek();
2169
- // Handle rel_coord (~, ~5, ~-3) and local_coord (^, ^5, ^-3) tokens
2170
- if (token.kind === 'rel_coord') {
2171
- this.advance();
2172
- // Parse the offset from the token value (e.g., "~5" -> 5, "~" -> 0, "~-3" -> -3)
2173
- const offset = this.parseCoordOffsetFromValue(token.value.slice(1));
2174
- return { kind: 'relative', offset };
2175
- }
2176
- if (token.kind === 'local_coord') {
2177
- this.advance();
2178
- const offset = this.parseCoordOffsetFromValue(token.value.slice(1));
2179
- return { kind: 'local', offset };
2180
- }
2181
- return { kind: 'absolute', value: this.parseSignedCoordOffset(true) };
2182
- }
2183
- parseCoordOffsetFromValue(value) {
2184
- if (value === '' || value === undefined)
2185
- return 0;
2186
- return parseFloat(value);
2187
- }
2188
- parseSignedCoordOffset(requireValue = false) {
2189
- let sign = 1;
2190
- if (this.match('-')) {
2191
- sign = -1;
2192
- }
2193
- if (this.check('int_lit')) {
2194
- return sign * parseInt(this.advance().value, 10);
2195
- }
2196
- if (requireValue) {
2197
- this.error('Expected integer coordinate component');
2198
- }
2199
- return 0;
2200
- }
2201
- // -------------------------------------------------------------------------
2202
- // Selector Parsing
2203
- // -------------------------------------------------------------------------
2204
- parseSelector() {
2205
- const token = this.expect('selector');
2206
- return this.parseSelectorValue(token.value);
2207
- }
2208
- // Parse either a selector (@a[...]) or a variable with filters (p[...])
2209
- // Returns { selector } for selectors or { varName, filters } for variables
2210
- parseSelectorOrVarSelector() {
2211
- if (this.check('selector')) {
2212
- return { selector: this.parseSelector() };
2213
- }
2214
- // Must be an identifier (variable) possibly with filters
2215
- const varToken = this.expect('ident');
2216
- const varName = varToken.value;
2217
- // Check for optional filters [...]
2218
- if (this.check('[')) {
2219
- this.advance(); // consume '['
2220
- // Collect everything until ']'
2221
- let filterStr = '';
2222
- let depth = 1;
2223
- while (depth > 0 && !this.check('eof')) {
2224
- if (this.check('['))
2225
- depth++;
2226
- else if (this.check(']'))
2227
- depth--;
2228
- if (depth > 0) {
2229
- filterStr += this.peek().value ?? this.peek().kind;
2230
- this.advance();
2231
- }
2232
- }
2233
- this.expect(']');
2234
- const filters = this.parseSelectorFilters(filterStr);
2235
- return { varName, filters };
2236
- }
2237
- return { varName };
2238
- }
2239
- parseSelectorValue(value) {
2240
- // Parse @e[type=zombie, distance=..5]
2241
- const bracketIndex = value.indexOf('[');
2242
- if (bracketIndex === -1) {
2243
- return { kind: value };
2244
- }
2245
- const kind = value.slice(0, bracketIndex);
2246
- const paramsStr = value.slice(bracketIndex + 1, -1); // Remove [ and ]
2247
- const filters = this.parseSelectorFilters(paramsStr);
2248
- return { kind, filters };
2249
- }
2250
- parseSelectorFilters(paramsStr) {
2251
- const filters = {};
2252
- const parts = this.splitSelectorParams(paramsStr);
2253
- for (const part of parts) {
2254
- const eqIndex = part.indexOf('=');
2255
- if (eqIndex === -1)
2256
- continue;
2257
- const key = part.slice(0, eqIndex).trim();
2258
- const val = part.slice(eqIndex + 1).trim();
2259
- switch (key) {
2260
- case 'type':
2261
- filters.type = val;
2262
- break;
2263
- case 'distance':
2264
- filters.distance = this.parseRangeValue(val);
2265
- break;
2266
- case 'tag':
2267
- if (val.startsWith('!')) {
2268
- filters.notTag = filters.notTag ?? [];
2269
- filters.notTag.push(val.slice(1));
2270
- }
2271
- else {
2272
- filters.tag = filters.tag ?? [];
2273
- filters.tag.push(val);
2274
- }
2275
- break;
2276
- case 'limit':
2277
- filters.limit = parseInt(val, 10);
2278
- break;
2279
- case 'sort':
2280
- filters.sort = val;
2281
- break;
2282
- case 'nbt':
2283
- filters.nbt = val;
2284
- break;
2285
- case 'gamemode':
2286
- filters.gamemode = val;
2287
- break;
2288
- case 'scores':
2289
- filters.scores = this.parseScoresFilter(val);
2290
- break;
2291
- case 'x':
2292
- filters.x = this.parseRangeValue(val);
2293
- break;
2294
- case 'y':
2295
- filters.y = this.parseRangeValue(val);
2296
- break;
2297
- case 'z':
2298
- filters.z = this.parseRangeValue(val);
2299
- break;
2300
- case 'x_rotation':
2301
- filters.x_rotation = this.parseRangeValue(val);
2302
- break;
2303
- case 'y_rotation':
2304
- filters.y_rotation = this.parseRangeValue(val);
2305
- break;
2306
- }
2307
- }
2308
- return filters;
2309
- }
2310
- splitSelectorParams(str) {
2311
- const parts = [];
2312
- let current = '';
2313
- let depth = 0;
2314
- for (const char of str) {
2315
- if (char === '{' || char === '[')
2316
- depth++;
2317
- else if (char === '}' || char === ']')
2318
- depth--;
2319
- else if (char === ',' && depth === 0) {
2320
- parts.push(current.trim());
2321
- current = '';
2322
- continue;
2323
- }
2324
- current += char;
2325
- }
2326
- if (current.trim()) {
2327
- parts.push(current.trim());
2328
- }
2329
- return parts;
2330
- }
2331
- parseScoresFilter(val) {
2332
- // Parse {kills=1.., deaths=..5}
2333
- const scores = {};
2334
- const inner = val.slice(1, -1); // Remove { and }
2335
- const parts = inner.split(',');
2336
- for (const part of parts) {
2337
- const [name, range] = part.split('=').map(s => s.trim());
2338
- scores[name] = this.parseRangeValue(range);
2339
- }
2340
- return scores;
2341
- }
2342
- parseRangeValue(value) {
2343
- // ..5 → { max: 5 }
2344
- // ..=5 → { max: 5 }
2345
- // 1.. → { min: 1 }
2346
- // 1..= → { min: 1 } (open-ended inclusive, end parsed separately)
2347
- // 1..10 → { min: 1, max: 10 }
2348
- // 1..=10 → { min: 1, max: 10 }
2349
- // 5 → { min: 5, max: 5 } (exact match)
2350
- if (value.startsWith('..=')) {
2351
- const rest = value.slice(3);
2352
- if (!rest)
2353
- return {}; // open upper bound, no max
2354
- const max = parseInt(rest, 10);
2355
- return { max };
2356
- }
2357
- if (value.startsWith('..')) {
2358
- const rest = value.slice(2);
2359
- if (!rest)
2360
- return {}; // open upper bound, no max
2361
- const max = parseInt(rest, 10);
2362
- return { max };
2363
- }
2364
- const inclIdx = value.indexOf('..=');
2365
- if (inclIdx !== -1) {
2366
- const min = parseInt(value.slice(0, inclIdx), 10);
2367
- const rest = value.slice(inclIdx + 3);
2368
- if (!rest)
2369
- return { min }; // open-ended inclusive
2370
- const max = parseInt(rest, 10);
2371
- return { min, max };
2372
- }
2373
- const dotIndex = value.indexOf('..');
2374
- if (dotIndex !== -1) {
2375
- const min = parseInt(value.slice(0, dotIndex), 10);
2376
- const rest = value.slice(dotIndex + 2);
2377
- if (!rest)
2378
- return { min }; // open-ended
2379
- const max = parseInt(rest, 10);
2380
- return { min, max };
2381
- }
2382
- // Exact value
2383
- const val = parseInt(value, 10);
2384
- return { min: val, max: val };
2385
- }
2386
137
  }
2387
138
  exports.Parser = Parser;
2388
139
  //# sourceMappingURL=index.js.map