redscript-mc 1.2.30 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (269) hide show
  1. package/.claude/commands/build-test.md +10 -0
  2. package/.claude/commands/deploy-demo.md +12 -0
  3. package/.claude/commands/stage-status.md +13 -0
  4. package/.claude/settings.json +12 -0
  5. package/.github/workflows/ci.yml +1 -0
  6. package/CLAUDE.md +231 -0
  7. package/demo.gif +0 -0
  8. package/dist/cli.js +2 -554
  9. package/dist/compile.js +2 -266
  10. package/dist/index.js +2 -159
  11. package/dist/lowering/index.js +5 -3
  12. package/dist/src/__tests__/cli.test.d.ts +1 -0
  13. package/dist/src/__tests__/cli.test.js +104 -0
  14. package/dist/src/__tests__/codegen.test.d.ts +1 -0
  15. package/dist/src/__tests__/codegen.test.js +152 -0
  16. package/dist/src/__tests__/compile-all.test.d.ts +10 -0
  17. package/dist/src/__tests__/compile-all.test.js +108 -0
  18. package/dist/src/__tests__/dce.test.d.ts +1 -0
  19. package/dist/src/__tests__/dce.test.js +102 -0
  20. package/dist/src/__tests__/diagnostics.test.d.ts +4 -0
  21. package/dist/src/__tests__/diagnostics.test.js +177 -0
  22. package/dist/src/__tests__/e2e.test.d.ts +6 -0
  23. package/dist/src/__tests__/e2e.test.js +1789 -0
  24. package/dist/src/__tests__/entity-types.test.d.ts +1 -0
  25. package/dist/src/__tests__/entity-types.test.js +203 -0
  26. package/dist/src/__tests__/formatter.test.d.ts +1 -0
  27. package/dist/src/__tests__/formatter.test.js +40 -0
  28. package/dist/src/__tests__/lexer.test.d.ts +1 -0
  29. package/dist/src/__tests__/lexer.test.js +343 -0
  30. package/dist/src/__tests__/lowering.test.d.ts +1 -0
  31. package/dist/src/__tests__/lowering.test.js +1015 -0
  32. package/dist/src/__tests__/macro.test.d.ts +8 -0
  33. package/dist/src/__tests__/macro.test.js +306 -0
  34. package/dist/src/__tests__/mc-integration.test.d.ts +12 -0
  35. package/dist/src/__tests__/mc-integration.test.js +817 -0
  36. package/dist/src/__tests__/mc-syntax.test.d.ts +1 -0
  37. package/dist/src/__tests__/mc-syntax.test.js +124 -0
  38. package/dist/src/__tests__/nbt.test.d.ts +1 -0
  39. package/dist/src/__tests__/nbt.test.js +82 -0
  40. package/dist/src/__tests__/optimizer-advanced.test.d.ts +1 -0
  41. package/dist/src/__tests__/optimizer-advanced.test.js +124 -0
  42. package/dist/src/__tests__/optimizer.test.d.ts +1 -0
  43. package/dist/src/__tests__/optimizer.test.js +149 -0
  44. package/dist/src/__tests__/parser.test.d.ts +1 -0
  45. package/dist/src/__tests__/parser.test.js +807 -0
  46. package/dist/src/__tests__/repl.test.d.ts +1 -0
  47. package/dist/src/__tests__/repl.test.js +27 -0
  48. package/dist/src/__tests__/runtime.test.d.ts +1 -0
  49. package/dist/src/__tests__/runtime.test.js +289 -0
  50. package/dist/src/__tests__/stdlib-advanced.test.d.ts +4 -0
  51. package/dist/src/__tests__/stdlib-advanced.test.js +374 -0
  52. package/dist/src/__tests__/stdlib-bigint.test.d.ts +7 -0
  53. package/dist/src/__tests__/stdlib-bigint.test.js +426 -0
  54. package/dist/src/__tests__/stdlib-math.test.d.ts +7 -0
  55. package/dist/src/__tests__/stdlib-math.test.js +351 -0
  56. package/dist/src/__tests__/stdlib-vec.test.d.ts +4 -0
  57. package/dist/src/__tests__/stdlib-vec.test.js +263 -0
  58. package/dist/src/__tests__/structure-optimizer.test.d.ts +1 -0
  59. package/dist/src/__tests__/structure-optimizer.test.js +33 -0
  60. package/dist/src/__tests__/typechecker.test.d.ts +1 -0
  61. package/dist/src/__tests__/typechecker.test.js +552 -0
  62. package/dist/src/__tests__/var-allocator.test.d.ts +1 -0
  63. package/dist/src/__tests__/var-allocator.test.js +69 -0
  64. package/dist/src/ast/types.d.ts +515 -0
  65. package/dist/src/ast/types.js +9 -0
  66. package/dist/src/builtins/metadata.d.ts +36 -0
  67. package/dist/src/builtins/metadata.js +1014 -0
  68. package/dist/src/cli.d.ts +11 -0
  69. package/dist/src/cli.js +443 -0
  70. package/dist/src/codegen/cmdblock/index.d.ts +26 -0
  71. package/dist/src/codegen/cmdblock/index.js +45 -0
  72. package/dist/src/codegen/mcfunction/index.d.ts +40 -0
  73. package/dist/src/codegen/mcfunction/index.js +606 -0
  74. package/dist/src/codegen/structure/index.d.ts +24 -0
  75. package/dist/src/codegen/structure/index.js +279 -0
  76. package/dist/src/codegen/var-allocator.d.ts +45 -0
  77. package/dist/src/codegen/var-allocator.js +104 -0
  78. package/dist/src/compile.d.ts +37 -0
  79. package/dist/src/compile.js +165 -0
  80. package/dist/src/diagnostics/index.d.ts +44 -0
  81. package/dist/src/diagnostics/index.js +140 -0
  82. package/dist/src/events/types.d.ts +35 -0
  83. package/dist/src/events/types.js +59 -0
  84. package/dist/src/formatter/index.d.ts +1 -0
  85. package/dist/src/formatter/index.js +26 -0
  86. package/dist/src/index.d.ts +22 -0
  87. package/dist/src/index.js +45 -0
  88. package/dist/src/ir/builder.d.ts +33 -0
  89. package/dist/src/ir/builder.js +99 -0
  90. package/dist/src/ir/types.d.ts +132 -0
  91. package/dist/src/ir/types.js +15 -0
  92. package/dist/src/lexer/index.d.ts +37 -0
  93. package/dist/src/lexer/index.js +569 -0
  94. package/dist/src/lowering/index.d.ts +188 -0
  95. package/dist/src/lowering/index.js +3405 -0
  96. package/dist/src/mc-test/client.d.ts +128 -0
  97. package/dist/src/mc-test/client.js +174 -0
  98. package/dist/src/mc-test/runner.d.ts +28 -0
  99. package/dist/src/mc-test/runner.js +151 -0
  100. package/dist/src/mc-test/setup.d.ts +11 -0
  101. package/dist/src/mc-test/setup.js +98 -0
  102. package/dist/src/mc-validator/index.d.ts +17 -0
  103. package/dist/src/mc-validator/index.js +322 -0
  104. package/dist/src/nbt/index.d.ts +86 -0
  105. package/dist/src/nbt/index.js +250 -0
  106. package/dist/src/optimizer/commands.d.ts +38 -0
  107. package/dist/src/optimizer/commands.js +451 -0
  108. package/dist/src/optimizer/dce.d.ts +34 -0
  109. package/dist/src/optimizer/dce.js +639 -0
  110. package/dist/src/optimizer/passes.d.ts +34 -0
  111. package/dist/src/optimizer/passes.js +243 -0
  112. package/dist/src/optimizer/structure.d.ts +9 -0
  113. package/dist/src/optimizer/structure.js +356 -0
  114. package/dist/src/parser/index.d.ts +93 -0
  115. package/dist/src/parser/index.js +1687 -0
  116. package/dist/src/repl.d.ts +16 -0
  117. package/dist/src/repl.js +165 -0
  118. package/dist/src/runtime/index.d.ts +107 -0
  119. package/dist/src/runtime/index.js +1409 -0
  120. package/dist/src/typechecker/index.d.ts +61 -0
  121. package/dist/src/typechecker/index.js +1034 -0
  122. package/dist/src/types/entity-hierarchy.d.ts +29 -0
  123. package/dist/src/types/entity-hierarchy.js +107 -0
  124. package/dist/src2/__tests__/e2e/basic.test.d.ts +8 -0
  125. package/dist/src2/__tests__/e2e/basic.test.js +140 -0
  126. package/dist/src2/__tests__/e2e/macros.test.d.ts +9 -0
  127. package/dist/src2/__tests__/e2e/macros.test.js +182 -0
  128. package/dist/src2/__tests__/e2e/migrate.test.d.ts +13 -0
  129. package/dist/src2/__tests__/e2e/migrate.test.js +2739 -0
  130. package/dist/src2/__tests__/hir/desugar.test.d.ts +1 -0
  131. package/dist/src2/__tests__/hir/desugar.test.js +234 -0
  132. package/dist/src2/__tests__/lir/lower.test.d.ts +1 -0
  133. package/dist/src2/__tests__/lir/lower.test.js +559 -0
  134. package/dist/src2/__tests__/lir/types.test.d.ts +1 -0
  135. package/dist/src2/__tests__/lir/types.test.js +185 -0
  136. package/dist/src2/__tests__/lir/verify.test.d.ts +1 -0
  137. package/dist/src2/__tests__/lir/verify.test.js +221 -0
  138. package/dist/src2/__tests__/mir/arithmetic.test.d.ts +1 -0
  139. package/dist/src2/__tests__/mir/arithmetic.test.js +130 -0
  140. package/dist/src2/__tests__/mir/control-flow.test.d.ts +1 -0
  141. package/dist/src2/__tests__/mir/control-flow.test.js +205 -0
  142. package/dist/src2/__tests__/mir/verify.test.d.ts +1 -0
  143. package/dist/src2/__tests__/mir/verify.test.js +223 -0
  144. package/dist/src2/__tests__/optimizer/block_merge.test.d.ts +1 -0
  145. package/dist/src2/__tests__/optimizer/block_merge.test.js +78 -0
  146. package/dist/src2/__tests__/optimizer/branch_simplify.test.d.ts +1 -0
  147. package/dist/src2/__tests__/optimizer/branch_simplify.test.js +58 -0
  148. package/dist/src2/__tests__/optimizer/constant_fold.test.d.ts +1 -0
  149. package/dist/src2/__tests__/optimizer/constant_fold.test.js +131 -0
  150. package/dist/src2/__tests__/optimizer/copy_prop.test.d.ts +1 -0
  151. package/dist/src2/__tests__/optimizer/copy_prop.test.js +91 -0
  152. package/dist/src2/__tests__/optimizer/dce.test.d.ts +1 -0
  153. package/dist/src2/__tests__/optimizer/dce.test.js +76 -0
  154. package/dist/src2/__tests__/optimizer/pipeline.test.d.ts +1 -0
  155. package/dist/src2/__tests__/optimizer/pipeline.test.js +102 -0
  156. package/dist/src2/emit/compile.d.ts +19 -0
  157. package/dist/src2/emit/compile.js +80 -0
  158. package/dist/src2/emit/index.d.ts +17 -0
  159. package/dist/src2/emit/index.js +172 -0
  160. package/dist/src2/hir/lower.d.ts +15 -0
  161. package/dist/src2/hir/lower.js +378 -0
  162. package/dist/src2/hir/types.d.ts +373 -0
  163. package/dist/src2/hir/types.js +16 -0
  164. package/dist/src2/lir/lower.d.ts +15 -0
  165. package/dist/src2/lir/lower.js +453 -0
  166. package/dist/src2/lir/types.d.ts +136 -0
  167. package/dist/src2/lir/types.js +11 -0
  168. package/dist/src2/lir/verify.d.ts +14 -0
  169. package/dist/src2/lir/verify.js +113 -0
  170. package/dist/src2/mir/lower.d.ts +9 -0
  171. package/dist/src2/mir/lower.js +1030 -0
  172. package/dist/src2/mir/macro.d.ts +22 -0
  173. package/dist/src2/mir/macro.js +168 -0
  174. package/dist/src2/mir/types.d.ts +183 -0
  175. package/dist/src2/mir/types.js +11 -0
  176. package/dist/src2/mir/verify.d.ts +16 -0
  177. package/dist/src2/mir/verify.js +216 -0
  178. package/dist/src2/optimizer/block_merge.d.ts +12 -0
  179. package/dist/src2/optimizer/block_merge.js +84 -0
  180. package/dist/src2/optimizer/branch_simplify.d.ts +9 -0
  181. package/dist/src2/optimizer/branch_simplify.js +28 -0
  182. package/dist/src2/optimizer/constant_fold.d.ts +10 -0
  183. package/dist/src2/optimizer/constant_fold.js +85 -0
  184. package/dist/src2/optimizer/copy_prop.d.ts +9 -0
  185. package/dist/src2/optimizer/copy_prop.js +113 -0
  186. package/dist/src2/optimizer/dce.d.ts +8 -0
  187. package/dist/src2/optimizer/dce.js +155 -0
  188. package/dist/src2/optimizer/pipeline.d.ts +10 -0
  189. package/dist/src2/optimizer/pipeline.js +42 -0
  190. package/dist/tsconfig.tsbuildinfo +1 -0
  191. package/docs/compiler-pipeline-redesign.md +2243 -0
  192. package/docs/optimization-ideas.md +1076 -0
  193. package/editors/vscode/package-lock.json +3 -3
  194. package/editors/vscode/package.json +1 -1
  195. package/jest.config.js +1 -1
  196. package/package.json +6 -5
  197. package/scripts/postbuild.js +15 -0
  198. package/src/__tests__/cli.test.ts +8 -220
  199. package/src/__tests__/dce.test.ts +11 -56
  200. package/src/__tests__/diagnostics.test.ts +59 -38
  201. package/src/__tests__/mc-integration.test.ts +1 -2
  202. package/src/ast/types.ts +6 -1
  203. package/src/cli.ts +29 -156
  204. package/src/compile.ts +6 -162
  205. package/src/index.ts +14 -178
  206. package/src/mc-test/runner.ts +4 -3
  207. package/src/parser/index.ts +1 -1
  208. package/src/repl.ts +1 -1
  209. package/src/runtime/index.ts +1 -1
  210. package/src2/__tests__/e2e/basic.test.ts +154 -0
  211. package/src2/__tests__/e2e/macros.test.ts +199 -0
  212. package/src2/__tests__/e2e/migrate.test.ts +3008 -0
  213. package/src2/__tests__/hir/desugar.test.ts +263 -0
  214. package/src2/__tests__/lir/lower.test.ts +619 -0
  215. package/src2/__tests__/lir/types.test.ts +207 -0
  216. package/src2/__tests__/lir/verify.test.ts +249 -0
  217. package/src2/__tests__/mir/arithmetic.test.ts +156 -0
  218. package/src2/__tests__/mir/control-flow.test.ts +242 -0
  219. package/src2/__tests__/mir/verify.test.ts +254 -0
  220. package/src2/__tests__/optimizer/block_merge.test.ts +84 -0
  221. package/src2/__tests__/optimizer/branch_simplify.test.ts +64 -0
  222. package/src2/__tests__/optimizer/constant_fold.test.ts +145 -0
  223. package/src2/__tests__/optimizer/copy_prop.test.ts +99 -0
  224. package/src2/__tests__/optimizer/dce.test.ts +83 -0
  225. package/src2/__tests__/optimizer/pipeline.test.ts +116 -0
  226. package/src2/emit/compile.ts +99 -0
  227. package/src2/emit/index.ts +222 -0
  228. package/src2/hir/lower.ts +428 -0
  229. package/src2/hir/types.ts +216 -0
  230. package/src2/lir/lower.ts +556 -0
  231. package/src2/lir/types.ts +109 -0
  232. package/src2/lir/verify.ts +129 -0
  233. package/src2/mir/lower.ts +1160 -0
  234. package/src2/mir/macro.ts +167 -0
  235. package/src2/mir/types.ts +106 -0
  236. package/src2/mir/verify.ts +218 -0
  237. package/src2/optimizer/block_merge.ts +93 -0
  238. package/src2/optimizer/branch_simplify.ts +27 -0
  239. package/src2/optimizer/constant_fold.ts +88 -0
  240. package/src2/optimizer/copy_prop.ts +106 -0
  241. package/src2/optimizer/dce.ts +133 -0
  242. package/src2/optimizer/pipeline.ts +44 -0
  243. package/tsconfig.json +2 -2
  244. package/src/__tests__/codegen.test.ts +0 -161
  245. package/src/__tests__/e2e.test.ts +0 -2039
  246. package/src/__tests__/entity-types.test.ts +0 -236
  247. package/src/__tests__/lowering.test.ts +0 -1185
  248. package/src/__tests__/macro.test.ts +0 -343
  249. package/src/__tests__/nbt.test.ts +0 -58
  250. package/src/__tests__/optimizer-advanced.test.ts +0 -144
  251. package/src/__tests__/optimizer.test.ts +0 -162
  252. package/src/__tests__/runtime.test.ts +0 -305
  253. package/src/__tests__/stdlib-advanced.test.ts +0 -379
  254. package/src/__tests__/stdlib-bigint.test.ts +0 -427
  255. package/src/__tests__/stdlib-math.test.ts +0 -374
  256. package/src/__tests__/stdlib-vec.test.ts +0 -259
  257. package/src/__tests__/structure-optimizer.test.ts +0 -38
  258. package/src/__tests__/var-allocator.test.ts +0 -75
  259. package/src/codegen/cmdblock/index.ts +0 -63
  260. package/src/codegen/mcfunction/index.ts +0 -662
  261. package/src/codegen/structure/index.ts +0 -346
  262. package/src/codegen/var-allocator.ts +0 -104
  263. package/src/ir/builder.ts +0 -116
  264. package/src/ir/types.ts +0 -134
  265. package/src/lowering/index.ts +0 -3876
  266. package/src/optimizer/commands.ts +0 -534
  267. package/src/optimizer/dce.ts +0 -679
  268. package/src/optimizer/passes.ts +0 -250
  269. package/src/optimizer/structure.ts +0 -450
@@ -0,0 +1,1687 @@
1
+ "use strict";
2
+ /**
3
+ * RedScript Parser
4
+ *
5
+ * Recursive descent parser that converts tokens into an AST.
6
+ * Uses precedence climbing for expression parsing.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.Parser = void 0;
10
+ const lexer_1 = require("../lexer");
11
+ 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
+ this.tokens = tokens;
73
+ this.sourceLines = source?.split('\n') ?? [];
74
+ this.filePath = filePath;
75
+ }
76
+ // -------------------------------------------------------------------------
77
+ // Utilities
78
+ // -------------------------------------------------------------------------
79
+ peek(offset = 0) {
80
+ const idx = this.pos + offset;
81
+ if (idx >= this.tokens.length) {
82
+ return this.tokens[this.tokens.length - 1]; // eof
83
+ }
84
+ return this.tokens[idx];
85
+ }
86
+ advance() {
87
+ const token = this.tokens[this.pos];
88
+ if (token.kind !== 'eof')
89
+ this.pos++;
90
+ return token;
91
+ }
92
+ check(kind) {
93
+ return this.peek().kind === kind;
94
+ }
95
+ match(...kinds) {
96
+ for (const kind of kinds) {
97
+ if (this.check(kind)) {
98
+ this.advance();
99
+ return true;
100
+ }
101
+ }
102
+ return false;
103
+ }
104
+ expect(kind) {
105
+ const token = this.peek();
106
+ if (token.kind !== kind) {
107
+ throw new diagnostics_1.DiagnosticError('ParseError', `Expected '${kind}' but got '${token.kind}'`, { file: this.filePath, line: token.line, col: token.col }, this.sourceLines);
108
+ }
109
+ return this.advance();
110
+ }
111
+ error(message) {
112
+ const token = this.peek();
113
+ throw new diagnostics_1.DiagnosticError('ParseError', message, { file: this.filePath, line: token.line, col: token.col }, this.sourceLines);
114
+ }
115
+ withLoc(node, token) {
116
+ const span = { line: token.line, col: token.col };
117
+ Object.defineProperty(node, 'span', {
118
+ value: span,
119
+ enumerable: false,
120
+ configurable: true,
121
+ writable: true,
122
+ });
123
+ return node;
124
+ }
125
+ getLocToken(node) {
126
+ const span = node.span;
127
+ if (!span) {
128
+ return null;
129
+ }
130
+ return { kind: 'eof', value: '', line: span.line, col: span.col };
131
+ }
132
+ // -------------------------------------------------------------------------
133
+ // Program
134
+ // -------------------------------------------------------------------------
135
+ parse(defaultNamespace = 'redscript') {
136
+ let namespace = defaultNamespace;
137
+ const globals = [];
138
+ const declarations = [];
139
+ const structs = [];
140
+ const implBlocks = [];
141
+ const enums = [];
142
+ const consts = [];
143
+ let isLibrary = false;
144
+ // Check for namespace declaration
145
+ if (this.check('namespace')) {
146
+ this.advance();
147
+ const name = this.expect('ident');
148
+ namespace = name.value;
149
+ this.expect(';');
150
+ }
151
+ // Check for module declaration: `module library;`
152
+ // Library-mode: all functions parsed from this point are marked isLibraryFn=true.
153
+ // When using the `librarySources` compile option, each library source is parsed
154
+ // by its own fresh Parser — so this flag never bleeds into user code.
155
+ if (this.check('module')) {
156
+ this.advance();
157
+ const modKind = this.expect('ident');
158
+ if (modKind.value === 'library') {
159
+ isLibrary = true;
160
+ this.inLibraryMode = true;
161
+ }
162
+ this.expect(';');
163
+ }
164
+ // Parse struct and function declarations
165
+ while (!this.check('eof')) {
166
+ if (this.check('let')) {
167
+ globals.push(this.parseGlobalDecl(true));
168
+ }
169
+ else if (this.check('struct')) {
170
+ structs.push(this.parseStructDecl());
171
+ }
172
+ else if (this.check('impl')) {
173
+ implBlocks.push(this.parseImplBlock());
174
+ }
175
+ else if (this.check('enum')) {
176
+ enums.push(this.parseEnumDecl());
177
+ }
178
+ else if (this.check('const')) {
179
+ consts.push(this.parseConstDecl());
180
+ }
181
+ else if (this.check('declare')) {
182
+ // Declaration-only stub (e.g. from builtins.d.mcrs) — parse and discard
183
+ this.advance(); // consume 'declare'
184
+ this.parseDeclareStub();
185
+ }
186
+ else {
187
+ declarations.push(this.parseFnDecl());
188
+ }
189
+ }
190
+ return { namespace, globals, declarations, structs, implBlocks, enums, consts, isLibrary };
191
+ }
192
+ // -------------------------------------------------------------------------
193
+ // Struct Declaration
194
+ // -------------------------------------------------------------------------
195
+ parseStructDecl() {
196
+ const structToken = this.expect('struct');
197
+ const name = this.expect('ident').value;
198
+ this.expect('{');
199
+ const fields = [];
200
+ while (!this.check('}') && !this.check('eof')) {
201
+ const fieldName = this.expect('ident').value;
202
+ this.expect(':');
203
+ const fieldType = this.parseType();
204
+ fields.push({ name: fieldName, type: fieldType });
205
+ // Allow optional comma or semicolon between fields
206
+ this.match(',');
207
+ }
208
+ this.expect('}');
209
+ return this.withLoc({ name, fields }, structToken);
210
+ }
211
+ parseEnumDecl() {
212
+ const enumToken = this.expect('enum');
213
+ const name = this.expect('ident').value;
214
+ this.expect('{');
215
+ const variants = [];
216
+ let nextValue = 0;
217
+ while (!this.check('}') && !this.check('eof')) {
218
+ const variantToken = this.expect('ident');
219
+ const variant = { name: variantToken.value };
220
+ if (this.match('=')) {
221
+ const valueToken = this.expect('int_lit');
222
+ variant.value = parseInt(valueToken.value, 10);
223
+ nextValue = variant.value + 1;
224
+ }
225
+ else {
226
+ variant.value = nextValue++;
227
+ }
228
+ variants.push(variant);
229
+ if (!this.match(',')) {
230
+ break;
231
+ }
232
+ }
233
+ this.expect('}');
234
+ return this.withLoc({ name, variants }, enumToken);
235
+ }
236
+ parseImplBlock() {
237
+ const implToken = this.expect('impl');
238
+ const typeName = this.expect('ident').value;
239
+ this.expect('{');
240
+ const methods = [];
241
+ while (!this.check('}') && !this.check('eof')) {
242
+ methods.push(this.parseFnDecl(typeName));
243
+ }
244
+ this.expect('}');
245
+ return this.withLoc({ kind: 'impl_block', typeName, methods }, implToken);
246
+ }
247
+ parseConstDecl() {
248
+ const constToken = this.expect('const');
249
+ const name = this.expect('ident').value;
250
+ let type;
251
+ if (this.match(':')) {
252
+ type = this.parseType();
253
+ }
254
+ this.expect('=');
255
+ const value = this.parseLiteralExpr();
256
+ this.match(';');
257
+ // Infer type from value if not provided
258
+ const inferredType = type ?? (value.kind === 'str_lit' ? { kind: 'named', name: 'string' } :
259
+ value.kind === 'bool_lit' ? { kind: 'named', name: 'bool' } :
260
+ value.kind === 'float_lit' ? { kind: 'named', name: 'float' } :
261
+ { kind: 'named', name: 'int' });
262
+ return this.withLoc({ name, type: inferredType, value }, constToken);
263
+ }
264
+ parseGlobalDecl(mutable) {
265
+ const token = this.advance(); // consume 'let'
266
+ const name = this.expect('ident').value;
267
+ this.expect(':');
268
+ const type = this.parseType();
269
+ this.expect('=');
270
+ const init = this.parseExpr();
271
+ this.expect(';');
272
+ return this.withLoc({ kind: 'global', name, type, init, mutable }, token);
273
+ }
274
+ // -------------------------------------------------------------------------
275
+ // Function Declaration
276
+ // -------------------------------------------------------------------------
277
+ parseFnDecl(implTypeName) {
278
+ const decorators = this.parseDecorators();
279
+ const fnToken = this.expect('fn');
280
+ const name = this.expect('ident').value;
281
+ this.expect('(');
282
+ const params = this.parseParams(implTypeName);
283
+ this.expect(')');
284
+ let returnType = { kind: 'named', name: 'void' };
285
+ if (this.match('->') || this.match(':')) {
286
+ returnType = this.parseType();
287
+ }
288
+ const body = this.parseBlock();
289
+ const fn = this.withLoc({ name, params, returnType, decorators, body, isLibraryFn: this.inLibraryMode || undefined }, fnToken);
290
+ return fn;
291
+ }
292
+ /** Parse a `declare fn name(params): returnType;` stub — no body, just discard. */
293
+ parseDeclareStub() {
294
+ this.expect('fn');
295
+ this.expect('ident'); // name
296
+ this.expect('(');
297
+ // consume params until ')'
298
+ let depth = 1;
299
+ while (!this.check('eof') && depth > 0) {
300
+ const t = this.advance();
301
+ if (t.kind === '(')
302
+ depth++;
303
+ else if (t.kind === ')')
304
+ depth--;
305
+ }
306
+ // optional return type annotation `: type` or `-> type`
307
+ if (this.match(':') || this.match('->')) {
308
+ this.parseType();
309
+ }
310
+ this.match(';'); // consume trailing semicolon
311
+ }
312
+ parseDecorators() {
313
+ const decorators = [];
314
+ while (this.check('decorator')) {
315
+ const token = this.advance();
316
+ const decorator = this.parseDecoratorValue(token.value);
317
+ decorators.push(decorator);
318
+ }
319
+ return decorators;
320
+ }
321
+ parseDecoratorValue(value) {
322
+ // Parse @tick, @on(PlayerDeath), or @on_trigger("name")
323
+ const match = value.match(/^@(\w+)(?:\(([^)]*)\))?$/);
324
+ if (!match) {
325
+ this.error(`Invalid decorator: ${value}`);
326
+ }
327
+ const name = match[1];
328
+ const argsStr = match[2];
329
+ if (!argsStr) {
330
+ return { name };
331
+ }
332
+ const args = {};
333
+ if (name === 'on') {
334
+ const eventTypeMatch = argsStr.match(/^([A-Za-z_][A-Za-z0-9_]*)$/);
335
+ if (eventTypeMatch) {
336
+ args.eventType = eventTypeMatch[1];
337
+ return { name, args };
338
+ }
339
+ }
340
+ // Handle @on_trigger("name"), @on_advancement("id"), @on_craft("item"), @on_join_team("team")
341
+ if (name === 'on_trigger' || name === 'on_advancement' || name === 'on_craft' || name === 'on_join_team') {
342
+ const strMatch = argsStr.match(/^"([^"]*)"$/);
343
+ if (strMatch) {
344
+ if (name === 'on_trigger') {
345
+ args.trigger = strMatch[1];
346
+ }
347
+ else if (name === 'on_advancement') {
348
+ args.advancement = strMatch[1];
349
+ }
350
+ else if (name === 'on_craft') {
351
+ args.item = strMatch[1];
352
+ }
353
+ else if (name === 'on_join_team') {
354
+ args.team = strMatch[1];
355
+ }
356
+ return { name, args };
357
+ }
358
+ }
359
+ // @require_on_load(fn_name) — when this fn is used, fn_name is called from __load.
360
+ // Accepts bare identifiers (with optional leading _) or quoted strings.
361
+ if (name === 'require_on_load') {
362
+ const rawArgs = [];
363
+ for (const part of argsStr.split(',')) {
364
+ const trimmed = part.trim();
365
+ // Bare identifier: @require_on_load(_math_init)
366
+ const identMatch = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)$/);
367
+ if (identMatch) {
368
+ rawArgs.push({ kind: 'string', value: identMatch[1] });
369
+ }
370
+ else {
371
+ // Quoted string fallback: @require_on_load("_math_init")
372
+ const strMatch = trimmed.match(/^"([^"]*)"$/);
373
+ if (strMatch) {
374
+ rawArgs.push({ kind: 'string', value: strMatch[1] });
375
+ }
376
+ }
377
+ }
378
+ return { name, rawArgs };
379
+ }
380
+ // Handle key=value format (e.g., rate=20)
381
+ for (const part of argsStr.split(',')) {
382
+ const [key, val] = part.split('=').map(s => s.trim());
383
+ if (key === 'rate') {
384
+ args.rate = parseInt(val, 10);
385
+ }
386
+ else if (key === 'trigger') {
387
+ args.trigger = val;
388
+ }
389
+ else if (key === 'advancement') {
390
+ args.advancement = val;
391
+ }
392
+ else if (key === 'item') {
393
+ args.item = val;
394
+ }
395
+ else if (key === 'team') {
396
+ args.team = val;
397
+ }
398
+ }
399
+ return { name, args };
400
+ }
401
+ parseParams(implTypeName) {
402
+ const params = [];
403
+ if (!this.check(')')) {
404
+ do {
405
+ const paramToken = this.expect('ident');
406
+ const name = paramToken.value;
407
+ let type;
408
+ if (implTypeName && params.length === 0 && name === 'self' && !this.check(':')) {
409
+ type = { kind: 'struct', name: implTypeName };
410
+ }
411
+ else {
412
+ this.expect(':');
413
+ type = this.parseType();
414
+ }
415
+ let defaultValue;
416
+ if (this.match('=')) {
417
+ defaultValue = this.parseExpr();
418
+ }
419
+ params.push(this.withLoc({ name, type, default: defaultValue }, paramToken));
420
+ } while (this.match(','));
421
+ }
422
+ return params;
423
+ }
424
+ parseType() {
425
+ const token = this.peek();
426
+ let type;
427
+ if (token.kind === '(') {
428
+ return this.parseFunctionType();
429
+ }
430
+ if (token.kind === 'int' || token.kind === 'bool' ||
431
+ token.kind === 'float' || token.kind === 'string' || token.kind === 'void' ||
432
+ token.kind === 'BlockPos') {
433
+ this.advance();
434
+ type = { kind: 'named', name: token.kind };
435
+ }
436
+ else if (token.kind === 'ident') {
437
+ this.advance();
438
+ if (token.value === 'selector' && this.check('<')) {
439
+ this.advance(); // consume <
440
+ const entityType = this.expect('ident').value;
441
+ this.expect('>');
442
+ type = { kind: 'selector', entityType };
443
+ }
444
+ else if (token.value === 'selector') {
445
+ type = { kind: 'selector' };
446
+ }
447
+ else {
448
+ type = { kind: 'struct', name: token.value };
449
+ }
450
+ }
451
+ else {
452
+ this.error(`Expected type, got '${token.kind}'`);
453
+ }
454
+ while (this.match('[')) {
455
+ this.expect(']');
456
+ type = { kind: 'array', elem: type };
457
+ }
458
+ return type;
459
+ }
460
+ parseFunctionType() {
461
+ this.expect('(');
462
+ const params = [];
463
+ if (!this.check(')')) {
464
+ do {
465
+ params.push(this.parseType());
466
+ } while (this.match(','));
467
+ }
468
+ this.expect(')');
469
+ this.expect('->');
470
+ const returnType = this.parseType();
471
+ return { kind: 'function_type', params, return: returnType };
472
+ }
473
+ // -------------------------------------------------------------------------
474
+ // Block & Statements
475
+ // -------------------------------------------------------------------------
476
+ parseBlock() {
477
+ this.expect('{');
478
+ const stmts = [];
479
+ while (!this.check('}') && !this.check('eof')) {
480
+ stmts.push(this.parseStmt());
481
+ }
482
+ this.expect('}');
483
+ return stmts;
484
+ }
485
+ parseStmt() {
486
+ // Let statement
487
+ if (this.check('let')) {
488
+ return this.parseLetStmt();
489
+ }
490
+ // Return statement
491
+ if (this.check('return')) {
492
+ return this.parseReturnStmt();
493
+ }
494
+ // Break statement
495
+ if (this.check('break')) {
496
+ const token = this.advance();
497
+ this.match(';');
498
+ return this.withLoc({ kind: 'break' }, token);
499
+ }
500
+ // Continue statement
501
+ if (this.check('continue')) {
502
+ const token = this.advance();
503
+ this.match(';');
504
+ return this.withLoc({ kind: 'continue' }, token);
505
+ }
506
+ // If statement
507
+ if (this.check('if')) {
508
+ return this.parseIfStmt();
509
+ }
510
+ // While statement
511
+ if (this.check('while')) {
512
+ return this.parseWhileStmt();
513
+ }
514
+ // For statement
515
+ if (this.check('for')) {
516
+ return this.parseForStmt();
517
+ }
518
+ // Foreach statement
519
+ if (this.check('foreach')) {
520
+ return this.parseForeachStmt();
521
+ }
522
+ if (this.check('match')) {
523
+ return this.parseMatchStmt();
524
+ }
525
+ // As block
526
+ if (this.check('as')) {
527
+ return this.parseAsStmt();
528
+ }
529
+ // At block
530
+ if (this.check('at')) {
531
+ return this.parseAtStmt();
532
+ }
533
+ // Execute statement: execute as/at/if/unless/in ... run { }
534
+ if (this.check('execute')) {
535
+ return this.parseExecuteStmt();
536
+ }
537
+ // Raw command
538
+ if (this.check('raw_cmd')) {
539
+ const token = this.advance();
540
+ const cmd = token.value;
541
+ this.match(';'); // optional semicolon (raw consumes it)
542
+ return this.withLoc({ kind: 'raw', cmd }, token);
543
+ }
544
+ // Expression statement
545
+ return this.parseExprStmt();
546
+ }
547
+ parseLetStmt() {
548
+ const letToken = this.expect('let');
549
+ const name = this.expect('ident').value;
550
+ let type;
551
+ if (this.match(':')) {
552
+ type = this.parseType();
553
+ }
554
+ this.expect('=');
555
+ const init = this.parseExpr();
556
+ this.expect(';');
557
+ return this.withLoc({ kind: 'let', name, type, init }, letToken);
558
+ }
559
+ parseReturnStmt() {
560
+ const returnToken = this.expect('return');
561
+ let value;
562
+ if (!this.check(';')) {
563
+ value = this.parseExpr();
564
+ }
565
+ this.expect(';');
566
+ return this.withLoc({ kind: 'return', value }, returnToken);
567
+ }
568
+ parseIfStmt() {
569
+ const ifToken = this.expect('if');
570
+ this.expect('(');
571
+ const cond = this.parseExpr();
572
+ this.expect(')');
573
+ const then = this.parseBlock();
574
+ let else_;
575
+ if (this.match('else')) {
576
+ if (this.check('if')) {
577
+ // else if
578
+ else_ = [this.parseIfStmt()];
579
+ }
580
+ else {
581
+ else_ = this.parseBlock();
582
+ }
583
+ }
584
+ return this.withLoc({ kind: 'if', cond, then, else_ }, ifToken);
585
+ }
586
+ parseWhileStmt() {
587
+ const whileToken = this.expect('while');
588
+ this.expect('(');
589
+ const cond = this.parseExpr();
590
+ this.expect(')');
591
+ const body = this.parseBlock();
592
+ return this.withLoc({ kind: 'while', cond, body }, whileToken);
593
+ }
594
+ parseForStmt() {
595
+ const forToken = this.expect('for');
596
+ // Check for for-range syntax: for <ident> in <range_lit> { ... }
597
+ if (this.check('ident') && this.peek(1).kind === 'in') {
598
+ return this.parseForRangeStmt(forToken);
599
+ }
600
+ this.expect('(');
601
+ // Init: either let statement (without semicolon) or empty
602
+ let init;
603
+ if (this.check('let')) {
604
+ // Parse let without consuming semicolon here (we handle it)
605
+ const letToken = this.expect('let');
606
+ const name = this.expect('ident').value;
607
+ let type;
608
+ if (this.match(':')) {
609
+ type = this.parseType();
610
+ }
611
+ this.expect('=');
612
+ const initExpr = this.parseExpr();
613
+ const initStmt = { kind: 'let', name, type, init: initExpr };
614
+ init = this.withLoc(initStmt, letToken);
615
+ }
616
+ this.expect(';');
617
+ // Condition
618
+ const cond = this.parseExpr();
619
+ this.expect(';');
620
+ // Step expression
621
+ const step = this.parseExpr();
622
+ this.expect(')');
623
+ const body = this.parseBlock();
624
+ return this.withLoc({ kind: 'for', init, cond, step, body }, forToken);
625
+ }
626
+ parseForRangeStmt(forToken) {
627
+ const varName = this.expect('ident').value;
628
+ this.expect('in');
629
+ let start;
630
+ let end;
631
+ if (this.check('range_lit')) {
632
+ // Literal range: 0..10, 0..count, 0..=9
633
+ const rangeToken = this.advance();
634
+ const range = this.parseRangeValue(rangeToken.value);
635
+ start = this.withLoc({ kind: 'int_lit', value: range.min ?? 0 }, rangeToken);
636
+ if (range.max !== null && range.max !== undefined) {
637
+ // Fully numeric: 0..10
638
+ end = this.withLoc({ kind: 'int_lit', value: range.max }, rangeToken);
639
+ }
640
+ else {
641
+ // Open-ended: "0.." — parse the end expression from next tokens
642
+ end = this.parseUnaryExpr();
643
+ }
644
+ }
645
+ else {
646
+ // Dynamic range: expr..expr (e.g. start..end) — not yet supported
647
+ // Fall back to: parse as int_lit 0..0 (safe default)
648
+ start = this.withLoc({ kind: 'int_lit', value: 0 }, this.peek());
649
+ end = this.withLoc({ kind: 'int_lit', value: 0 }, this.peek());
650
+ this.error('Dynamic range start requires a literal integer (e.g. 0..count)');
651
+ }
652
+ const body = this.parseBlock();
653
+ return this.withLoc({ kind: 'for_range', varName, start, end, body }, forToken);
654
+ }
655
+ parseForeachStmt() {
656
+ const foreachToken = this.expect('foreach');
657
+ this.expect('(');
658
+ const binding = this.expect('ident').value;
659
+ this.expect('in');
660
+ const iterable = this.parseExpr();
661
+ this.expect(')');
662
+ // Parse optional execute context modifiers (as, at, positioned, rotated, facing, etc.)
663
+ let executeContext;
664
+ // Check for execute subcommand keywords
665
+ const execIdentKeywords = ['positioned', 'rotated', 'facing', 'anchored', 'align', 'on', 'summon'];
666
+ if (this.check('as') || this.check('at') || this.check('in') || (this.check('ident') && execIdentKeywords.includes(this.peek().value))) {
667
+ // Collect everything until we hit '{'
668
+ let context = '';
669
+ while (!this.check('{') && !this.check('eof')) {
670
+ context += this.advance().value + ' ';
671
+ }
672
+ executeContext = context.trim();
673
+ }
674
+ const body = this.parseBlock();
675
+ return this.withLoc({ kind: 'foreach', binding, iterable, body, executeContext }, foreachToken);
676
+ }
677
+ parseMatchStmt() {
678
+ const matchToken = this.expect('match');
679
+ this.expect('(');
680
+ const expr = this.parseExpr();
681
+ this.expect(')');
682
+ this.expect('{');
683
+ const arms = [];
684
+ while (!this.check('}') && !this.check('eof')) {
685
+ let pattern;
686
+ if (this.check('ident') && this.peek().value === '_') {
687
+ this.advance();
688
+ pattern = null;
689
+ }
690
+ else {
691
+ pattern = this.parseExpr();
692
+ }
693
+ this.expect('=>');
694
+ const body = this.parseBlock();
695
+ arms.push({ pattern, body });
696
+ }
697
+ this.expect('}');
698
+ return this.withLoc({ kind: 'match', expr, arms }, matchToken);
699
+ }
700
+ parseAsStmt() {
701
+ const asToken = this.expect('as');
702
+ const as_sel = this.parseSelector();
703
+ // Check for combined as/at
704
+ if (this.match('at')) {
705
+ const at_sel = this.parseSelector();
706
+ const body = this.parseBlock();
707
+ return this.withLoc({ kind: 'as_at', as_sel, at_sel, body }, asToken);
708
+ }
709
+ const body = this.parseBlock();
710
+ return this.withLoc({ kind: 'as_block', selector: as_sel, body }, asToken);
711
+ }
712
+ parseAtStmt() {
713
+ const atToken = this.expect('at');
714
+ const selector = this.parseSelector();
715
+ const body = this.parseBlock();
716
+ return this.withLoc({ kind: 'at_block', selector, body }, atToken);
717
+ }
718
+ parseExecuteStmt() {
719
+ const executeToken = this.expect('execute');
720
+ const subcommands = [];
721
+ // Parse subcommands until we hit 'run'
722
+ while (!this.check('run') && !this.check('eof')) {
723
+ if (this.match('as')) {
724
+ const selector = this.parseSelector();
725
+ subcommands.push({ kind: 'as', selector });
726
+ }
727
+ else if (this.match('at')) {
728
+ const selector = this.parseSelector();
729
+ subcommands.push({ kind: 'at', selector });
730
+ }
731
+ else if (this.checkIdent('positioned')) {
732
+ this.advance();
733
+ if (this.match('as')) {
734
+ const selector = this.parseSelector();
735
+ subcommands.push({ kind: 'positioned_as', selector });
736
+ }
737
+ else {
738
+ const x = this.parseCoordToken();
739
+ const y = this.parseCoordToken();
740
+ const z = this.parseCoordToken();
741
+ subcommands.push({ kind: 'positioned', x, y, z });
742
+ }
743
+ }
744
+ else if (this.checkIdent('rotated')) {
745
+ this.advance();
746
+ if (this.match('as')) {
747
+ const selector = this.parseSelector();
748
+ subcommands.push({ kind: 'rotated_as', selector });
749
+ }
750
+ else {
751
+ const yaw = this.parseCoordToken();
752
+ const pitch = this.parseCoordToken();
753
+ subcommands.push({ kind: 'rotated', yaw, pitch });
754
+ }
755
+ }
756
+ else if (this.checkIdent('facing')) {
757
+ this.advance();
758
+ if (this.checkIdent('entity')) {
759
+ this.advance();
760
+ const selector = this.parseSelector();
761
+ const anchor = this.checkIdent('eyes') || this.checkIdent('feet') ? this.advance().value : 'feet';
762
+ subcommands.push({ kind: 'facing_entity', selector, anchor });
763
+ }
764
+ else {
765
+ const x = this.parseCoordToken();
766
+ const y = this.parseCoordToken();
767
+ const z = this.parseCoordToken();
768
+ subcommands.push({ kind: 'facing', x, y, z });
769
+ }
770
+ }
771
+ else if (this.checkIdent('anchored')) {
772
+ this.advance();
773
+ const anchor = this.advance().value;
774
+ subcommands.push({ kind: 'anchored', anchor });
775
+ }
776
+ else if (this.checkIdent('align')) {
777
+ this.advance();
778
+ const axes = this.advance().value;
779
+ subcommands.push({ kind: 'align', axes });
780
+ }
781
+ else if (this.checkIdent('on')) {
782
+ this.advance();
783
+ const relation = this.advance().value;
784
+ subcommands.push({ kind: 'on', relation });
785
+ }
786
+ else if (this.checkIdent('summon')) {
787
+ this.advance();
788
+ const entity = this.advance().value;
789
+ subcommands.push({ kind: 'summon', entity });
790
+ }
791
+ else if (this.checkIdent('store')) {
792
+ this.advance();
793
+ const storeType = this.advance().value; // 'result' or 'success'
794
+ if (this.checkIdent('score')) {
795
+ this.advance();
796
+ const target = this.advance().value;
797
+ const targetObj = this.advance().value;
798
+ if (storeType === 'result') {
799
+ subcommands.push({ kind: 'store_result', target, targetObj });
800
+ }
801
+ else {
802
+ subcommands.push({ kind: 'store_success', target, targetObj });
803
+ }
804
+ }
805
+ else {
806
+ this.error('store currently only supports score target');
807
+ }
808
+ }
809
+ else if (this.match('if')) {
810
+ this.parseExecuteCondition(subcommands, 'if');
811
+ }
812
+ else if (this.match('unless')) {
813
+ this.parseExecuteCondition(subcommands, 'unless');
814
+ }
815
+ else if (this.match('in')) {
816
+ // Dimension can be namespaced: minecraft:the_nether
817
+ let dim = this.advance().value;
818
+ if (this.match(':')) {
819
+ dim += ':' + this.advance().value;
820
+ }
821
+ subcommands.push({ kind: 'in', dimension: dim });
822
+ }
823
+ else {
824
+ this.error(`Unexpected token in execute statement: ${this.peek().kind} (${this.peek().value})`);
825
+ }
826
+ }
827
+ this.expect('run');
828
+ const body = this.parseBlock();
829
+ return this.withLoc({ kind: 'execute', subcommands, body }, executeToken);
830
+ }
831
+ parseExecuteCondition(subcommands, type) {
832
+ if (this.checkIdent('entity') || this.check('selector')) {
833
+ if (this.checkIdent('entity'))
834
+ this.advance();
835
+ const selectorOrVar = this.parseSelectorOrVarSelector();
836
+ subcommands.push({ kind: type === 'if' ? 'if_entity' : 'unless_entity', ...selectorOrVar });
837
+ }
838
+ else if (this.checkIdent('block')) {
839
+ this.advance();
840
+ const x = this.parseCoordToken();
841
+ const y = this.parseCoordToken();
842
+ const z = this.parseCoordToken();
843
+ const block = this.parseBlockId();
844
+ subcommands.push({ kind: type === 'if' ? 'if_block' : 'unless_block', pos: [x, y, z], block });
845
+ }
846
+ else if (this.checkIdent('score')) {
847
+ this.advance();
848
+ const target = this.advance().value;
849
+ const targetObj = this.advance().value;
850
+ // Check for range or comparison
851
+ if (this.checkIdent('matches')) {
852
+ this.advance();
853
+ const range = this.advance().value;
854
+ subcommands.push({ kind: type === 'if' ? 'if_score_range' : 'unless_score_range', target, targetObj, range });
855
+ }
856
+ else {
857
+ const op = this.advance().value; // <, <=, =, >=, >
858
+ const source = this.advance().value;
859
+ const sourceObj = this.advance().value;
860
+ subcommands.push({
861
+ kind: type === 'if' ? 'if_score' : 'unless_score',
862
+ target, targetObj, op, source, sourceObj
863
+ });
864
+ }
865
+ }
866
+ else {
867
+ this.error(`Unknown condition type after ${type}`);
868
+ }
869
+ }
870
+ parseCoordToken() {
871
+ // Handle ~, ^, numbers, relative coords like ~5, ^-3
872
+ const token = this.peek();
873
+ if (token.kind === 'rel_coord' || token.kind === 'local_coord' ||
874
+ token.kind === 'int_lit' || token.kind === 'float_lit' ||
875
+ token.kind === '-' || token.kind === 'ident') {
876
+ return this.advance().value;
877
+ }
878
+ this.error(`Expected coordinate, got ${token.kind}`);
879
+ return '~';
880
+ }
881
+ parseBlockId() {
882
+ // Parse block ID like minecraft:stone or stone
883
+ let id = this.advance().value;
884
+ if (this.match(':')) {
885
+ id += ':' + this.advance().value;
886
+ }
887
+ // Handle block states [facing=north]
888
+ if (this.check('[')) {
889
+ id += this.advance().value; // [
890
+ while (!this.check(']') && !this.check('eof')) {
891
+ id += this.advance().value;
892
+ }
893
+ id += this.advance().value; // ]
894
+ }
895
+ return id;
896
+ }
897
+ checkIdent(value) {
898
+ return this.check('ident') && this.peek().value === value;
899
+ }
900
+ parseExprStmt() {
901
+ const expr = this.parseExpr();
902
+ this.expect(';');
903
+ const exprToken = this.getLocToken(expr) ?? this.peek();
904
+ return this.withLoc({ kind: 'expr', expr }, exprToken);
905
+ }
906
+ // -------------------------------------------------------------------------
907
+ // Expressions (Precedence Climbing)
908
+ // -------------------------------------------------------------------------
909
+ parseExpr() {
910
+ return this.parseAssignment();
911
+ }
912
+ parseAssignment() {
913
+ const left = this.parseBinaryExpr(1);
914
+ // Check for assignment
915
+ const token = this.peek();
916
+ if (token.kind === '=' || token.kind === '+=' || token.kind === '-=' ||
917
+ token.kind === '*=' || token.kind === '/=' || token.kind === '%=') {
918
+ const op = this.advance().kind;
919
+ if (left.kind === 'ident') {
920
+ const value = this.parseAssignment();
921
+ return this.withLoc({ kind: 'assign', target: left.name, op, value }, this.getLocToken(left) ?? token);
922
+ }
923
+ // Member assignment: p.x = 10, p.x += 5
924
+ if (left.kind === 'member') {
925
+ const value = this.parseAssignment();
926
+ return this.withLoc({ kind: 'member_assign', obj: left.obj, field: left.field, op, value }, this.getLocToken(left) ?? token);
927
+ }
928
+ }
929
+ return left;
930
+ }
931
+ parseBinaryExpr(minPrec) {
932
+ let left = this.parseUnaryExpr();
933
+ while (true) {
934
+ const op = this.peek().kind;
935
+ if (!BINARY_OPS.has(op))
936
+ break;
937
+ const prec = PRECEDENCE[op];
938
+ if (prec < minPrec)
939
+ break;
940
+ const opToken = this.advance();
941
+ if (op === 'is') {
942
+ const entityType = this.parseEntityTypeName();
943
+ left = this.withLoc({ kind: 'is_check', expr: left, entityType }, this.getLocToken(left) ?? opToken);
944
+ continue;
945
+ }
946
+ const right = this.parseBinaryExpr(prec + 1); // left associative
947
+ left = this.withLoc({ kind: 'binary', op: op, left, right }, this.getLocToken(left) ?? opToken);
948
+ }
949
+ return left;
950
+ }
951
+ parseUnaryExpr() {
952
+ if (this.match('!')) {
953
+ const bangToken = this.tokens[this.pos - 1];
954
+ const operand = this.parseUnaryExpr();
955
+ return this.withLoc({ kind: 'unary', op: '!', operand }, bangToken);
956
+ }
957
+ if (this.check('-') && !this.isSubtraction()) {
958
+ const minusToken = this.advance();
959
+ const operand = this.parseUnaryExpr();
960
+ return this.withLoc({ kind: 'unary', op: '-', operand }, minusToken);
961
+ }
962
+ return this.parsePostfixExpr();
963
+ }
964
+ parseEntityTypeName() {
965
+ const token = this.expect('ident');
966
+ if (ENTITY_TYPE_NAMES.has(token.value)) {
967
+ return token.value;
968
+ }
969
+ this.error(`Unknown entity type '${token.value}'`);
970
+ }
971
+ isSubtraction() {
972
+ // Check if this minus is binary (subtraction) by looking at previous token
973
+ // If previous was a value (literal, ident, ), ]) it's subtraction
974
+ if (this.pos === 0)
975
+ return false;
976
+ const prev = this.tokens[this.pos - 1];
977
+ return ['int_lit', 'float_lit', 'ident', ')', ']'].includes(prev.kind);
978
+ }
979
+ parsePostfixExpr() {
980
+ let expr = this.parsePrimaryExpr();
981
+ while (true) {
982
+ // Function call
983
+ if (this.match('(')) {
984
+ const openParenToken = this.tokens[this.pos - 1];
985
+ if (expr.kind === 'ident') {
986
+ const args = this.parseArgs();
987
+ this.expect(')');
988
+ expr = this.withLoc({ kind: 'call', fn: expr.name, args }, this.getLocToken(expr) ?? openParenToken);
989
+ continue;
990
+ }
991
+ // Member call: entity.tag("name") → __entity_tag(entity, "name")
992
+ // Also handle arr.push(val) and arr.length
993
+ if (expr.kind === 'member') {
994
+ const methodMap = {
995
+ 'tag': '__entity_tag',
996
+ 'untag': '__entity_untag',
997
+ 'has_tag': '__entity_has_tag',
998
+ 'push': '__array_push',
999
+ 'pop': '__array_pop',
1000
+ 'add': 'set_add',
1001
+ 'contains': 'set_contains',
1002
+ 'remove': 'set_remove',
1003
+ 'clear': 'set_clear',
1004
+ };
1005
+ const internalFn = methodMap[expr.field];
1006
+ if (internalFn) {
1007
+ const args = this.parseArgs();
1008
+ this.expect(')');
1009
+ expr = this.withLoc({ kind: 'call', fn: internalFn, args: [expr.obj, ...args] }, this.getLocToken(expr) ?? openParenToken);
1010
+ continue;
1011
+ }
1012
+ // Generic method sugar: obj.method(args) → method(obj, args)
1013
+ const args = this.parseArgs();
1014
+ this.expect(')');
1015
+ expr = this.withLoc({ kind: 'call', fn: expr.field, args: [expr.obj, ...args] }, this.getLocToken(expr) ?? openParenToken);
1016
+ continue;
1017
+ }
1018
+ const args = this.parseArgs();
1019
+ this.expect(')');
1020
+ expr = this.withLoc({ kind: 'invoke', callee: expr, args }, this.getLocToken(expr) ?? openParenToken);
1021
+ continue;
1022
+ }
1023
+ // Array index access: arr[0]
1024
+ if (this.match('[')) {
1025
+ const index = this.parseExpr();
1026
+ this.expect(']');
1027
+ expr = this.withLoc({ kind: 'index', obj: expr, index }, this.getLocToken(expr) ?? this.tokens[this.pos - 1]);
1028
+ continue;
1029
+ }
1030
+ // Member access
1031
+ if (this.match('.')) {
1032
+ const field = this.expect('ident').value;
1033
+ expr = this.withLoc({ kind: 'member', obj: expr, field }, this.getLocToken(expr) ?? this.tokens[this.pos - 1]);
1034
+ continue;
1035
+ }
1036
+ break;
1037
+ }
1038
+ return expr;
1039
+ }
1040
+ parseArgs() {
1041
+ const args = [];
1042
+ if (!this.check(')')) {
1043
+ do {
1044
+ args.push(this.parseExpr());
1045
+ } while (this.match(','));
1046
+ }
1047
+ return args;
1048
+ }
1049
+ parsePrimaryExpr() {
1050
+ const token = this.peek();
1051
+ if (token.kind === 'ident' && this.peek(1).kind === '::') {
1052
+ const typeToken = this.advance();
1053
+ this.expect('::');
1054
+ const methodToken = this.expect('ident');
1055
+ this.expect('(');
1056
+ const args = this.parseArgs();
1057
+ this.expect(')');
1058
+ return this.withLoc({ kind: 'static_call', type: typeToken.value, method: methodToken.value, args }, typeToken);
1059
+ }
1060
+ if (token.kind === 'ident' && this.peek(1).kind === '=>') {
1061
+ return this.parseSingleParamLambda();
1062
+ }
1063
+ // Integer literal
1064
+ if (token.kind === 'int_lit') {
1065
+ this.advance();
1066
+ return this.withLoc({ kind: 'int_lit', value: parseInt(token.value, 10) }, token);
1067
+ }
1068
+ // Float literal
1069
+ if (token.kind === 'float_lit') {
1070
+ this.advance();
1071
+ return this.withLoc({ kind: 'float_lit', value: parseFloat(token.value) }, token);
1072
+ }
1073
+ // Relative coordinate: ~ ~5 ~-3 ~0.5
1074
+ if (token.kind === 'rel_coord') {
1075
+ this.advance();
1076
+ return this.withLoc({ kind: 'rel_coord', value: token.value }, token);
1077
+ }
1078
+ // Local coordinate: ^ ^5 ^-3 ^0.5
1079
+ if (token.kind === 'local_coord') {
1080
+ this.advance();
1081
+ return this.withLoc({ kind: 'local_coord', value: token.value }, token);
1082
+ }
1083
+ // NBT suffix literals
1084
+ if (token.kind === 'byte_lit') {
1085
+ this.advance();
1086
+ return this.withLoc({ kind: 'byte_lit', value: parseInt(token.value.slice(0, -1), 10) }, token);
1087
+ }
1088
+ if (token.kind === 'short_lit') {
1089
+ this.advance();
1090
+ return this.withLoc({ kind: 'short_lit', value: parseInt(token.value.slice(0, -1), 10) }, token);
1091
+ }
1092
+ if (token.kind === 'long_lit') {
1093
+ this.advance();
1094
+ return this.withLoc({ kind: 'long_lit', value: parseInt(token.value.slice(0, -1), 10) }, token);
1095
+ }
1096
+ if (token.kind === 'double_lit') {
1097
+ this.advance();
1098
+ return this.withLoc({ kind: 'double_lit', value: parseFloat(token.value.slice(0, -1)) }, token);
1099
+ }
1100
+ // String literal
1101
+ if (token.kind === 'string_lit') {
1102
+ this.advance();
1103
+ return this.parseStringExpr(token);
1104
+ }
1105
+ if (token.kind === 'f_string') {
1106
+ this.advance();
1107
+ return this.parseFStringExpr(token);
1108
+ }
1109
+ // MC name literal: #health → mc_name node (value = "health", without #)
1110
+ if (token.kind === 'mc_name') {
1111
+ this.advance();
1112
+ return this.withLoc({ kind: 'mc_name', value: token.value.slice(1) }, token);
1113
+ }
1114
+ // Boolean literal
1115
+ if (token.kind === 'true') {
1116
+ this.advance();
1117
+ return this.withLoc({ kind: 'bool_lit', value: true }, token);
1118
+ }
1119
+ if (token.kind === 'false') {
1120
+ this.advance();
1121
+ return this.withLoc({ kind: 'bool_lit', value: false }, token);
1122
+ }
1123
+ // Range literal
1124
+ if (token.kind === 'range_lit') {
1125
+ this.advance();
1126
+ return this.withLoc({ kind: 'range_lit', range: this.parseRangeValue(token.value) }, token);
1127
+ }
1128
+ // Selector
1129
+ if (token.kind === 'selector') {
1130
+ this.advance();
1131
+ return this.withLoc({
1132
+ kind: 'selector',
1133
+ raw: token.value,
1134
+ isSingle: computeIsSingle(token.value),
1135
+ sel: this.parseSelectorValue(token.value),
1136
+ }, token);
1137
+ }
1138
+ // Identifier
1139
+ if (token.kind === 'ident') {
1140
+ this.advance();
1141
+ return this.withLoc({ kind: 'ident', name: token.value }, token);
1142
+ }
1143
+ // Grouped expression
1144
+ if (token.kind === '(') {
1145
+ if (this.isBlockPosLiteral()) {
1146
+ return this.parseBlockPos();
1147
+ }
1148
+ if (this.isLambdaStart()) {
1149
+ return this.parseLambdaExpr();
1150
+ }
1151
+ this.advance();
1152
+ const expr = this.parseExpr();
1153
+ this.expect(')');
1154
+ return expr;
1155
+ }
1156
+ // Struct literal or block: { x: 10, y: 20 }
1157
+ if (token.kind === '{') {
1158
+ return this.parseStructLit();
1159
+ }
1160
+ // Array literal: [1, 2, 3] or []
1161
+ if (token.kind === '[') {
1162
+ return this.parseArrayLit();
1163
+ }
1164
+ this.error(`Unexpected token '${token.kind}'`);
1165
+ }
1166
+ parseLiteralExpr() {
1167
+ // Support negative literals: -5, -3.14
1168
+ if (this.check('-')) {
1169
+ this.advance();
1170
+ const token = this.peek();
1171
+ if (token.kind === 'int_lit') {
1172
+ this.advance();
1173
+ return this.withLoc({ kind: 'int_lit', value: -Number(token.value) }, token);
1174
+ }
1175
+ if (token.kind === 'float_lit') {
1176
+ this.advance();
1177
+ return this.withLoc({ kind: 'float_lit', value: -Number(token.value) }, token);
1178
+ }
1179
+ this.error('Expected number after unary -');
1180
+ }
1181
+ const expr = this.parsePrimaryExpr();
1182
+ if (expr.kind === 'int_lit' ||
1183
+ expr.kind === 'float_lit' ||
1184
+ expr.kind === 'bool_lit' ||
1185
+ expr.kind === 'str_lit') {
1186
+ return expr;
1187
+ }
1188
+ this.error('Const value must be a literal');
1189
+ }
1190
+ parseSingleParamLambda() {
1191
+ const paramToken = this.expect('ident');
1192
+ const params = [{ name: paramToken.value }];
1193
+ this.expect('=>');
1194
+ return this.finishLambdaExpr(params, paramToken);
1195
+ }
1196
+ parseLambdaExpr() {
1197
+ const openParenToken = this.expect('(');
1198
+ const params = [];
1199
+ if (!this.check(')')) {
1200
+ do {
1201
+ const name = this.expect('ident').value;
1202
+ let type;
1203
+ if (this.match(':')) {
1204
+ type = this.parseType();
1205
+ }
1206
+ params.push({ name, type });
1207
+ } while (this.match(','));
1208
+ }
1209
+ this.expect(')');
1210
+ let returnType;
1211
+ if (this.match('->')) {
1212
+ returnType = this.parseType();
1213
+ }
1214
+ this.expect('=>');
1215
+ return this.finishLambdaExpr(params, openParenToken, returnType);
1216
+ }
1217
+ finishLambdaExpr(params, token, returnType) {
1218
+ const body = this.check('{') ? this.parseBlock() : this.parseExpr();
1219
+ return this.withLoc({ kind: 'lambda', params, returnType, body }, token);
1220
+ }
1221
+ parseStringExpr(token) {
1222
+ if (!token.value.includes('${')) {
1223
+ return this.withLoc({ kind: 'str_lit', value: token.value }, token);
1224
+ }
1225
+ const parts = [];
1226
+ let current = '';
1227
+ let index = 0;
1228
+ while (index < token.value.length) {
1229
+ if (token.value[index] === '$' && token.value[index + 1] === '{') {
1230
+ if (current) {
1231
+ parts.push(current);
1232
+ current = '';
1233
+ }
1234
+ index += 2;
1235
+ let depth = 1;
1236
+ let exprSource = '';
1237
+ let inString = false;
1238
+ while (index < token.value.length && depth > 0) {
1239
+ const char = token.value[index];
1240
+ if (char === '"' && token.value[index - 1] !== '\\') {
1241
+ inString = !inString;
1242
+ }
1243
+ if (!inString) {
1244
+ if (char === '{') {
1245
+ depth++;
1246
+ }
1247
+ else if (char === '}') {
1248
+ depth--;
1249
+ if (depth === 0) {
1250
+ index++;
1251
+ break;
1252
+ }
1253
+ }
1254
+ }
1255
+ if (depth > 0) {
1256
+ exprSource += char;
1257
+ }
1258
+ index++;
1259
+ }
1260
+ if (depth !== 0) {
1261
+ this.error('Unterminated string interpolation');
1262
+ }
1263
+ parts.push(this.parseEmbeddedExpr(exprSource));
1264
+ continue;
1265
+ }
1266
+ current += token.value[index];
1267
+ index++;
1268
+ }
1269
+ if (current) {
1270
+ parts.push(current);
1271
+ }
1272
+ return this.withLoc({ kind: 'str_interp', parts }, token);
1273
+ }
1274
+ parseFStringExpr(token) {
1275
+ const parts = [];
1276
+ let current = '';
1277
+ let index = 0;
1278
+ while (index < token.value.length) {
1279
+ if (token.value[index] === '{') {
1280
+ if (current) {
1281
+ parts.push({ kind: 'text', value: current });
1282
+ current = '';
1283
+ }
1284
+ index++;
1285
+ let depth = 1;
1286
+ let exprSource = '';
1287
+ let inString = false;
1288
+ while (index < token.value.length && depth > 0) {
1289
+ const char = token.value[index];
1290
+ if (char === '"' && token.value[index - 1] !== '\\') {
1291
+ inString = !inString;
1292
+ }
1293
+ if (!inString) {
1294
+ if (char === '{') {
1295
+ depth++;
1296
+ }
1297
+ else if (char === '}') {
1298
+ depth--;
1299
+ if (depth === 0) {
1300
+ index++;
1301
+ break;
1302
+ }
1303
+ }
1304
+ }
1305
+ if (depth > 0) {
1306
+ exprSource += char;
1307
+ }
1308
+ index++;
1309
+ }
1310
+ if (depth !== 0) {
1311
+ this.error('Unterminated f-string interpolation');
1312
+ }
1313
+ parts.push({ kind: 'expr', expr: this.parseEmbeddedExpr(exprSource) });
1314
+ continue;
1315
+ }
1316
+ current += token.value[index];
1317
+ index++;
1318
+ }
1319
+ if (current) {
1320
+ parts.push({ kind: 'text', value: current });
1321
+ }
1322
+ return this.withLoc({ kind: 'f_string', parts }, token);
1323
+ }
1324
+ parseEmbeddedExpr(source) {
1325
+ const tokens = new lexer_1.Lexer(source, this.filePath).tokenize();
1326
+ const parser = new Parser(tokens, source, this.filePath);
1327
+ const expr = parser.parseExpr();
1328
+ if (!parser.check('eof')) {
1329
+ parser.error(`Unexpected token '${parser.peek().kind}' in string interpolation`);
1330
+ }
1331
+ return expr;
1332
+ }
1333
+ parseStructLit() {
1334
+ const braceToken = this.expect('{');
1335
+ const fields = [];
1336
+ if (!this.check('}')) {
1337
+ do {
1338
+ const name = this.expect('ident').value;
1339
+ this.expect(':');
1340
+ const value = this.parseExpr();
1341
+ fields.push({ name, value });
1342
+ } while (this.match(','));
1343
+ }
1344
+ this.expect('}');
1345
+ return this.withLoc({ kind: 'struct_lit', fields }, braceToken);
1346
+ }
1347
+ parseArrayLit() {
1348
+ const bracketToken = this.expect('[');
1349
+ const elements = [];
1350
+ if (!this.check(']')) {
1351
+ do {
1352
+ elements.push(this.parseExpr());
1353
+ } while (this.match(','));
1354
+ }
1355
+ this.expect(']');
1356
+ return this.withLoc({ kind: 'array_lit', elements }, bracketToken);
1357
+ }
1358
+ isLambdaStart() {
1359
+ if (!this.check('('))
1360
+ return false;
1361
+ let offset = 1;
1362
+ if (this.peek(offset).kind !== ')') {
1363
+ while (true) {
1364
+ if (this.peek(offset).kind !== 'ident') {
1365
+ return false;
1366
+ }
1367
+ offset += 1;
1368
+ if (this.peek(offset).kind === ':') {
1369
+ offset += 1;
1370
+ const consumed = this.typeTokenLength(offset);
1371
+ if (consumed === 0) {
1372
+ return false;
1373
+ }
1374
+ offset += consumed;
1375
+ }
1376
+ if (this.peek(offset).kind === ',') {
1377
+ offset += 1;
1378
+ continue;
1379
+ }
1380
+ break;
1381
+ }
1382
+ }
1383
+ if (this.peek(offset).kind !== ')') {
1384
+ return false;
1385
+ }
1386
+ offset += 1;
1387
+ if (this.peek(offset).kind === '=>') {
1388
+ return true;
1389
+ }
1390
+ if (this.peek(offset).kind === '->') {
1391
+ offset += 1;
1392
+ const consumed = this.typeTokenLength(offset);
1393
+ if (consumed === 0) {
1394
+ return false;
1395
+ }
1396
+ offset += consumed;
1397
+ return this.peek(offset).kind === '=>';
1398
+ }
1399
+ return false;
1400
+ }
1401
+ typeTokenLength(offset) {
1402
+ const token = this.peek(offset);
1403
+ if (token.kind === '(') {
1404
+ let inner = offset + 1;
1405
+ if (this.peek(inner).kind !== ')') {
1406
+ while (true) {
1407
+ const consumed = this.typeTokenLength(inner);
1408
+ if (consumed === 0) {
1409
+ return 0;
1410
+ }
1411
+ inner += consumed;
1412
+ if (this.peek(inner).kind === ',') {
1413
+ inner += 1;
1414
+ continue;
1415
+ }
1416
+ break;
1417
+ }
1418
+ }
1419
+ if (this.peek(inner).kind !== ')') {
1420
+ return 0;
1421
+ }
1422
+ inner += 1;
1423
+ if (this.peek(inner).kind !== '->') {
1424
+ return 0;
1425
+ }
1426
+ inner += 1;
1427
+ const returnLen = this.typeTokenLength(inner);
1428
+ return returnLen === 0 ? 0 : inner + returnLen - offset;
1429
+ }
1430
+ const isNamedType = token.kind === 'int' ||
1431
+ token.kind === 'bool' ||
1432
+ token.kind === 'float' ||
1433
+ token.kind === 'string' ||
1434
+ token.kind === 'void' ||
1435
+ token.kind === 'BlockPos' ||
1436
+ token.kind === 'ident';
1437
+ if (!isNamedType) {
1438
+ return 0;
1439
+ }
1440
+ let length = 1;
1441
+ while (this.peek(offset + length).kind === '[' && this.peek(offset + length + 1).kind === ']') {
1442
+ length += 2;
1443
+ }
1444
+ return length;
1445
+ }
1446
+ isBlockPosLiteral() {
1447
+ if (!this.check('('))
1448
+ return false;
1449
+ let offset = 1;
1450
+ for (let i = 0; i < 3; i++) {
1451
+ const consumed = this.coordComponentTokenLength(offset);
1452
+ if (consumed === 0)
1453
+ return false;
1454
+ offset += consumed;
1455
+ if (i < 2) {
1456
+ if (this.peek(offset).kind !== ',')
1457
+ return false;
1458
+ offset += 1;
1459
+ }
1460
+ }
1461
+ return this.peek(offset).kind === ')';
1462
+ }
1463
+ coordComponentTokenLength(offset) {
1464
+ const token = this.peek(offset);
1465
+ if (token.kind === 'int_lit') {
1466
+ return 1;
1467
+ }
1468
+ if (token.kind === '-') {
1469
+ return this.peek(offset + 1).kind === 'int_lit' ? 2 : 0;
1470
+ }
1471
+ // rel_coord (~, ~5, ~-3) and local_coord (^, ^5, ^-3) are single tokens now
1472
+ if (token.kind === 'rel_coord' || token.kind === 'local_coord') {
1473
+ return 1;
1474
+ }
1475
+ return 0;
1476
+ }
1477
+ parseBlockPos() {
1478
+ const openParenToken = this.expect('(');
1479
+ const x = this.parseCoordComponent();
1480
+ this.expect(',');
1481
+ const y = this.parseCoordComponent();
1482
+ this.expect(',');
1483
+ const z = this.parseCoordComponent();
1484
+ this.expect(')');
1485
+ return this.withLoc({ kind: 'blockpos', x, y, z }, openParenToken);
1486
+ }
1487
+ parseCoordComponent() {
1488
+ const token = this.peek();
1489
+ // Handle rel_coord (~, ~5, ~-3) and local_coord (^, ^5, ^-3) tokens
1490
+ if (token.kind === 'rel_coord') {
1491
+ this.advance();
1492
+ // Parse the offset from the token value (e.g., "~5" -> 5, "~" -> 0, "~-3" -> -3)
1493
+ const offset = this.parseCoordOffsetFromValue(token.value.slice(1));
1494
+ return { kind: 'relative', offset };
1495
+ }
1496
+ if (token.kind === 'local_coord') {
1497
+ this.advance();
1498
+ const offset = this.parseCoordOffsetFromValue(token.value.slice(1));
1499
+ return { kind: 'local', offset };
1500
+ }
1501
+ return { kind: 'absolute', value: this.parseSignedCoordOffset(true) };
1502
+ }
1503
+ parseCoordOffsetFromValue(value) {
1504
+ if (value === '' || value === undefined)
1505
+ return 0;
1506
+ return parseFloat(value);
1507
+ }
1508
+ parseSignedCoordOffset(requireValue = false) {
1509
+ let sign = 1;
1510
+ if (this.match('-')) {
1511
+ sign = -1;
1512
+ }
1513
+ if (this.check('int_lit')) {
1514
+ return sign * parseInt(this.advance().value, 10);
1515
+ }
1516
+ if (requireValue) {
1517
+ this.error('Expected integer coordinate component');
1518
+ }
1519
+ return 0;
1520
+ }
1521
+ // -------------------------------------------------------------------------
1522
+ // Selector Parsing
1523
+ // -------------------------------------------------------------------------
1524
+ parseSelector() {
1525
+ const token = this.expect('selector');
1526
+ return this.parseSelectorValue(token.value);
1527
+ }
1528
+ // Parse either a selector (@a[...]) or a variable with filters (p[...])
1529
+ // Returns { selector } for selectors or { varName, filters } for variables
1530
+ parseSelectorOrVarSelector() {
1531
+ if (this.check('selector')) {
1532
+ return { selector: this.parseSelector() };
1533
+ }
1534
+ // Must be an identifier (variable) possibly with filters
1535
+ const varToken = this.expect('ident');
1536
+ const varName = varToken.value;
1537
+ // Check for optional filters [...]
1538
+ if (this.check('[')) {
1539
+ this.advance(); // consume '['
1540
+ // Collect everything until ']'
1541
+ let filterStr = '';
1542
+ let depth = 1;
1543
+ while (depth > 0 && !this.check('eof')) {
1544
+ if (this.check('['))
1545
+ depth++;
1546
+ else if (this.check(']'))
1547
+ depth--;
1548
+ if (depth > 0) {
1549
+ filterStr += this.peek().value ?? this.peek().kind;
1550
+ this.advance();
1551
+ }
1552
+ }
1553
+ this.expect(']');
1554
+ const filters = this.parseSelectorFilters(filterStr);
1555
+ return { varName, filters };
1556
+ }
1557
+ return { varName };
1558
+ }
1559
+ parseSelectorValue(value) {
1560
+ // Parse @e[type=zombie, distance=..5]
1561
+ const bracketIndex = value.indexOf('[');
1562
+ if (bracketIndex === -1) {
1563
+ return { kind: value };
1564
+ }
1565
+ const kind = value.slice(0, bracketIndex);
1566
+ const paramsStr = value.slice(bracketIndex + 1, -1); // Remove [ and ]
1567
+ const filters = this.parseSelectorFilters(paramsStr);
1568
+ return { kind, filters };
1569
+ }
1570
+ parseSelectorFilters(paramsStr) {
1571
+ const filters = {};
1572
+ const parts = this.splitSelectorParams(paramsStr);
1573
+ for (const part of parts) {
1574
+ const eqIndex = part.indexOf('=');
1575
+ if (eqIndex === -1)
1576
+ continue;
1577
+ const key = part.slice(0, eqIndex).trim();
1578
+ const val = part.slice(eqIndex + 1).trim();
1579
+ switch (key) {
1580
+ case 'type':
1581
+ filters.type = val;
1582
+ break;
1583
+ case 'distance':
1584
+ filters.distance = this.parseRangeValue(val);
1585
+ break;
1586
+ case 'tag':
1587
+ if (val.startsWith('!')) {
1588
+ filters.notTag = filters.notTag ?? [];
1589
+ filters.notTag.push(val.slice(1));
1590
+ }
1591
+ else {
1592
+ filters.tag = filters.tag ?? [];
1593
+ filters.tag.push(val);
1594
+ }
1595
+ break;
1596
+ case 'limit':
1597
+ filters.limit = parseInt(val, 10);
1598
+ break;
1599
+ case 'sort':
1600
+ filters.sort = val;
1601
+ break;
1602
+ case 'nbt':
1603
+ filters.nbt = val;
1604
+ break;
1605
+ case 'gamemode':
1606
+ filters.gamemode = val;
1607
+ break;
1608
+ case 'scores':
1609
+ filters.scores = this.parseScoresFilter(val);
1610
+ break;
1611
+ case 'x':
1612
+ filters.x = this.parseRangeValue(val);
1613
+ break;
1614
+ case 'y':
1615
+ filters.y = this.parseRangeValue(val);
1616
+ break;
1617
+ case 'z':
1618
+ filters.z = this.parseRangeValue(val);
1619
+ break;
1620
+ case 'x_rotation':
1621
+ filters.x_rotation = this.parseRangeValue(val);
1622
+ break;
1623
+ case 'y_rotation':
1624
+ filters.y_rotation = this.parseRangeValue(val);
1625
+ break;
1626
+ }
1627
+ }
1628
+ return filters;
1629
+ }
1630
+ splitSelectorParams(str) {
1631
+ const parts = [];
1632
+ let current = '';
1633
+ let depth = 0;
1634
+ for (const char of str) {
1635
+ if (char === '{' || char === '[')
1636
+ depth++;
1637
+ else if (char === '}' || char === ']')
1638
+ depth--;
1639
+ else if (char === ',' && depth === 0) {
1640
+ parts.push(current.trim());
1641
+ current = '';
1642
+ continue;
1643
+ }
1644
+ current += char;
1645
+ }
1646
+ if (current.trim()) {
1647
+ parts.push(current.trim());
1648
+ }
1649
+ return parts;
1650
+ }
1651
+ parseScoresFilter(val) {
1652
+ // Parse {kills=1.., deaths=..5}
1653
+ const scores = {};
1654
+ const inner = val.slice(1, -1); // Remove { and }
1655
+ const parts = inner.split(',');
1656
+ for (const part of parts) {
1657
+ const [name, range] = part.split('=').map(s => s.trim());
1658
+ scores[name] = this.parseRangeValue(range);
1659
+ }
1660
+ return scores;
1661
+ }
1662
+ parseRangeValue(value) {
1663
+ // ..5 → { max: 5 }
1664
+ // 1.. → { min: 1 }
1665
+ // 1..10 → { min: 1, max: 10 }
1666
+ // 5 → { min: 5, max: 5 } (exact match)
1667
+ if (value.startsWith('..')) {
1668
+ const max = parseInt(value.slice(2), 10);
1669
+ return { max };
1670
+ }
1671
+ if (value.endsWith('..')) {
1672
+ const min = parseInt(value.slice(0, -2), 10);
1673
+ return { min };
1674
+ }
1675
+ const dotIndex = value.indexOf('..');
1676
+ if (dotIndex !== -1) {
1677
+ const min = parseInt(value.slice(0, dotIndex), 10);
1678
+ const max = parseInt(value.slice(dotIndex + 2), 10);
1679
+ return { min, max };
1680
+ }
1681
+ // Exact value
1682
+ const val = parseInt(value, 10);
1683
+ return { min: val, max: val };
1684
+ }
1685
+ }
1686
+ exports.Parser = Parser;
1687
+ //# sourceMappingURL=index.js.map