redscript-mc 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (272) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +40 -0
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +31 -0
  3. package/.github/ISSUE_TEMPLATE/wrong_output.md +33 -0
  4. package/.github/PULL_REQUEST_TEMPLATE.md +34 -0
  5. package/.github/workflows/ci.yml +29 -0
  6. package/.github/workflows/publish-extension.yml +35 -0
  7. package/LICENSE +21 -0
  8. package/README.md +261 -0
  9. package/README.zh.md +261 -0
  10. package/dist/__tests__/cli.test.d.ts +1 -0
  11. package/dist/__tests__/cli.test.js +140 -0
  12. package/dist/__tests__/codegen.test.d.ts +1 -0
  13. package/dist/__tests__/codegen.test.js +121 -0
  14. package/dist/__tests__/diagnostics.test.d.ts +4 -0
  15. package/dist/__tests__/diagnostics.test.js +149 -0
  16. package/dist/__tests__/e2e.test.d.ts +6 -0
  17. package/dist/__tests__/e2e.test.js +1528 -0
  18. package/dist/__tests__/lexer.test.d.ts +1 -0
  19. package/dist/__tests__/lexer.test.js +316 -0
  20. package/dist/__tests__/lowering.test.d.ts +1 -0
  21. package/dist/__tests__/lowering.test.js +819 -0
  22. package/dist/__tests__/mc-integration.test.d.ts +12 -0
  23. package/dist/__tests__/mc-integration.test.js +395 -0
  24. package/dist/__tests__/mc-syntax.test.d.ts +1 -0
  25. package/dist/__tests__/mc-syntax.test.js +112 -0
  26. package/dist/__tests__/nbt.test.d.ts +1 -0
  27. package/dist/__tests__/nbt.test.js +82 -0
  28. package/dist/__tests__/optimizer-advanced.test.d.ts +1 -0
  29. package/dist/__tests__/optimizer-advanced.test.js +124 -0
  30. package/dist/__tests__/optimizer.test.d.ts +1 -0
  31. package/dist/__tests__/optimizer.test.js +118 -0
  32. package/dist/__tests__/parser.test.d.ts +1 -0
  33. package/dist/__tests__/parser.test.js +717 -0
  34. package/dist/__tests__/repl.test.d.ts +1 -0
  35. package/dist/__tests__/repl.test.js +27 -0
  36. package/dist/__tests__/runtime.test.d.ts +1 -0
  37. package/dist/__tests__/runtime.test.js +276 -0
  38. package/dist/__tests__/structure-optimizer.test.d.ts +1 -0
  39. package/dist/__tests__/structure-optimizer.test.js +33 -0
  40. package/dist/__tests__/typechecker.test.d.ts +1 -0
  41. package/dist/__tests__/typechecker.test.js +364 -0
  42. package/dist/ast/types.d.ts +357 -0
  43. package/dist/ast/types.js +9 -0
  44. package/dist/cli.d.ts +11 -0
  45. package/dist/cli.js +407 -0
  46. package/dist/codegen/cmdblock/index.d.ts +26 -0
  47. package/dist/codegen/cmdblock/index.js +45 -0
  48. package/dist/codegen/mcfunction/index.d.ts +34 -0
  49. package/dist/codegen/mcfunction/index.js +413 -0
  50. package/dist/codegen/structure/index.d.ts +18 -0
  51. package/dist/codegen/structure/index.js +249 -0
  52. package/dist/compile.d.ts +30 -0
  53. package/dist/compile.js +152 -0
  54. package/dist/data/arena/function/__load.mcfunction +6 -0
  55. package/dist/data/arena/function/__tick.mcfunction +2 -0
  56. package/dist/data/arena/function/announce_leaders/else_1.mcfunction +3 -0
  57. package/dist/data/arena/function/announce_leaders/foreach_0/merge_2.mcfunction +1 -0
  58. package/dist/data/arena/function/announce_leaders/foreach_0/then_0.mcfunction +3 -0
  59. package/dist/data/arena/function/announce_leaders/foreach_0.mcfunction +7 -0
  60. package/dist/data/arena/function/announce_leaders/foreach_1/merge_2.mcfunction +1 -0
  61. package/dist/data/arena/function/announce_leaders/foreach_1/then_0.mcfunction +4 -0
  62. package/dist/data/arena/function/announce_leaders/foreach_1.mcfunction +6 -0
  63. package/dist/data/arena/function/announce_leaders/merge_2.mcfunction +1 -0
  64. package/dist/data/arena/function/announce_leaders/then_0.mcfunction +4 -0
  65. package/dist/data/arena/function/announce_leaders.mcfunction +6 -0
  66. package/dist/data/arena/function/arena_tick/merge_2.mcfunction +1 -0
  67. package/dist/data/arena/function/arena_tick/then_0.mcfunction +4 -0
  68. package/dist/data/arena/function/arena_tick.mcfunction +11 -0
  69. package/dist/data/counter/function/__load.mcfunction +5 -0
  70. package/dist/data/counter/function/__tick.mcfunction +2 -0
  71. package/dist/data/counter/function/counter_tick/merge_2.mcfunction +1 -0
  72. package/dist/data/counter/function/counter_tick/then_0.mcfunction +3 -0
  73. package/dist/data/counter/function/counter_tick.mcfunction +11 -0
  74. package/dist/data/minecraft/tags/function/load.json +5 -0
  75. package/dist/data/minecraft/tags/function/tick.json +5 -0
  76. package/dist/data/quiz/function/__load.mcfunction +16 -0
  77. package/dist/data/quiz/function/__tick.mcfunction +6 -0
  78. package/dist/data/quiz/function/__trigger_quiz_a_dispatch.mcfunction +4 -0
  79. package/dist/data/quiz/function/__trigger_quiz_b_dispatch.mcfunction +4 -0
  80. package/dist/data/quiz/function/__trigger_quiz_c_dispatch.mcfunction +4 -0
  81. package/dist/data/quiz/function/__trigger_quiz_start_dispatch.mcfunction +4 -0
  82. package/dist/data/quiz/function/answer_a.mcfunction +4 -0
  83. package/dist/data/quiz/function/answer_b.mcfunction +4 -0
  84. package/dist/data/quiz/function/answer_c.mcfunction +4 -0
  85. package/dist/data/quiz/function/ask_question/else_1.mcfunction +5 -0
  86. package/dist/data/quiz/function/ask_question/else_4.mcfunction +5 -0
  87. package/dist/data/quiz/function/ask_question/else_7.mcfunction +4 -0
  88. package/dist/data/quiz/function/ask_question/merge_2.mcfunction +1 -0
  89. package/dist/data/quiz/function/ask_question/merge_5.mcfunction +2 -0
  90. package/dist/data/quiz/function/ask_question/merge_8.mcfunction +2 -0
  91. package/dist/data/quiz/function/ask_question/then_0.mcfunction +4 -0
  92. package/dist/data/quiz/function/ask_question/then_3.mcfunction +4 -0
  93. package/dist/data/quiz/function/ask_question/then_6.mcfunction +4 -0
  94. package/dist/data/quiz/function/ask_question.mcfunction +7 -0
  95. package/dist/data/quiz/function/finish_quiz.mcfunction +6 -0
  96. package/dist/data/quiz/function/handle_answer/else_1.mcfunction +5 -0
  97. package/dist/data/quiz/function/handle_answer/else_10.mcfunction +3 -0
  98. package/dist/data/quiz/function/handle_answer/else_16.mcfunction +3 -0
  99. package/dist/data/quiz/function/handle_answer/else_4.mcfunction +3 -0
  100. package/dist/data/quiz/function/handle_answer/else_7.mcfunction +5 -0
  101. package/dist/data/quiz/function/handle_answer/merge_11.mcfunction +2 -0
  102. package/dist/data/quiz/function/handle_answer/merge_14.mcfunction +2 -0
  103. package/dist/data/quiz/function/handle_answer/merge_17.mcfunction +2 -0
  104. package/dist/data/quiz/function/handle_answer/merge_2.mcfunction +8 -0
  105. package/dist/data/quiz/function/handle_answer/merge_5.mcfunction +2 -0
  106. package/dist/data/quiz/function/handle_answer/merge_8.mcfunction +2 -0
  107. package/dist/data/quiz/function/handle_answer/then_0.mcfunction +5 -0
  108. package/dist/data/quiz/function/handle_answer/then_12.mcfunction +5 -0
  109. package/dist/data/quiz/function/handle_answer/then_15.mcfunction +6 -0
  110. package/dist/data/quiz/function/handle_answer/then_3.mcfunction +6 -0
  111. package/dist/data/quiz/function/handle_answer/then_6.mcfunction +5 -0
  112. package/dist/data/quiz/function/handle_answer/then_9.mcfunction +6 -0
  113. package/dist/data/quiz/function/handle_answer.mcfunction +11 -0
  114. package/dist/data/quiz/function/start_quiz.mcfunction +5 -0
  115. package/dist/data/shop/function/__load.mcfunction +7 -0
  116. package/dist/data/shop/function/__tick.mcfunction +3 -0
  117. package/dist/data/shop/function/__trigger_shop_buy_dispatch.mcfunction +4 -0
  118. package/dist/data/shop/function/complete_purchase/else_1.mcfunction +5 -0
  119. package/dist/data/shop/function/complete_purchase/else_4.mcfunction +5 -0
  120. package/dist/data/shop/function/complete_purchase/else_7.mcfunction +3 -0
  121. package/dist/data/shop/function/complete_purchase/merge_2.mcfunction +2 -0
  122. package/dist/data/shop/function/complete_purchase/merge_5.mcfunction +2 -0
  123. package/dist/data/shop/function/complete_purchase/merge_8.mcfunction +2 -0
  124. package/dist/data/shop/function/complete_purchase/then_0.mcfunction +4 -0
  125. package/dist/data/shop/function/complete_purchase/then_3.mcfunction +4 -0
  126. package/dist/data/shop/function/complete_purchase/then_6.mcfunction +4 -0
  127. package/dist/data/shop/function/complete_purchase.mcfunction +7 -0
  128. package/dist/data/shop/function/handle_shop_trigger.mcfunction +3 -0
  129. package/dist/data/turret/function/__load.mcfunction +5 -0
  130. package/dist/data/turret/function/__tick.mcfunction +4 -0
  131. package/dist/data/turret/function/__trigger_deploy_turret_dispatch.mcfunction +4 -0
  132. package/dist/data/turret/function/deploy_turret.mcfunction +8 -0
  133. package/dist/data/turret/function/turret_tick/at_1.mcfunction +2 -0
  134. package/dist/data/turret/function/turret_tick/foreach_0.mcfunction +2 -0
  135. package/dist/data/turret/function/turret_tick/foreach_2.mcfunction +2 -0
  136. package/dist/data/turret/function/turret_tick/tick_body.mcfunction +3 -0
  137. package/dist/data/turret/function/turret_tick/tick_skip.mcfunction +1 -0
  138. package/dist/data/turret/function/turret_tick.mcfunction +5 -0
  139. package/dist/diagnostics/index.d.ts +44 -0
  140. package/dist/diagnostics/index.js +140 -0
  141. package/dist/index.d.ts +53 -0
  142. package/dist/index.js +126 -0
  143. package/dist/ir/builder.d.ts +32 -0
  144. package/dist/ir/builder.js +99 -0
  145. package/dist/ir/types.d.ts +117 -0
  146. package/dist/ir/types.js +15 -0
  147. package/dist/lexer/index.d.ts +36 -0
  148. package/dist/lexer/index.js +458 -0
  149. package/dist/lowering/index.d.ts +106 -0
  150. package/dist/lowering/index.js +2041 -0
  151. package/dist/mc-test/client.d.ts +128 -0
  152. package/dist/mc-test/client.js +174 -0
  153. package/dist/mc-test/runner.d.ts +28 -0
  154. package/dist/mc-test/runner.js +150 -0
  155. package/dist/mc-test/setup.d.ts +11 -0
  156. package/dist/mc-test/setup.js +98 -0
  157. package/dist/mc-validator/index.d.ts +17 -0
  158. package/dist/mc-validator/index.js +322 -0
  159. package/dist/nbt/index.d.ts +86 -0
  160. package/dist/nbt/index.js +250 -0
  161. package/dist/optimizer/commands.d.ts +36 -0
  162. package/dist/optimizer/commands.js +349 -0
  163. package/dist/optimizer/passes.d.ts +34 -0
  164. package/dist/optimizer/passes.js +227 -0
  165. package/dist/optimizer/structure.d.ts +8 -0
  166. package/dist/optimizer/structure.js +344 -0
  167. package/dist/pack.mcmeta +6 -0
  168. package/dist/parser/index.d.ts +76 -0
  169. package/dist/parser/index.js +1193 -0
  170. package/dist/repl.d.ts +16 -0
  171. package/dist/repl.js +165 -0
  172. package/dist/runtime/index.d.ts +101 -0
  173. package/dist/runtime/index.js +1288 -0
  174. package/dist/typechecker/index.d.ts +42 -0
  175. package/dist/typechecker/index.js +629 -0
  176. package/docs/COMPILATION_STATS.md +142 -0
  177. package/docs/IMPLEMENTATION_GUIDE.md +512 -0
  178. package/docs/LANGUAGE_REFERENCE.md +415 -0
  179. package/docs/MC_MAPPING.md +280 -0
  180. package/docs/STRUCTURE_TARGET.md +80 -0
  181. package/docs/mc-reference/commands.md +259 -0
  182. package/editors/vscode/.vscodeignore +10 -0
  183. package/editors/vscode/LICENSE +21 -0
  184. package/editors/vscode/README.md +78 -0
  185. package/editors/vscode/build.mjs +28 -0
  186. package/editors/vscode/icon.png +0 -0
  187. package/editors/vscode/mcfunction-language-configuration.json +28 -0
  188. package/editors/vscode/out/extension.js +7236 -0
  189. package/editors/vscode/package-lock.json +566 -0
  190. package/editors/vscode/package.json +137 -0
  191. package/editors/vscode/redscript-language-configuration.json +28 -0
  192. package/editors/vscode/snippets/redscript.json +114 -0
  193. package/editors/vscode/src/codeactions.ts +89 -0
  194. package/editors/vscode/src/completion.ts +130 -0
  195. package/editors/vscode/src/extension.ts +239 -0
  196. package/editors/vscode/src/hover.ts +1120 -0
  197. package/editors/vscode/src/symbols.ts +207 -0
  198. package/editors/vscode/syntaxes/mcfunction.tmLanguage.json +740 -0
  199. package/editors/vscode/syntaxes/redscript.tmLanguage.json +357 -0
  200. package/editors/vscode/tsconfig.json +13 -0
  201. package/jest.config.js +5 -0
  202. package/package.json +38 -0
  203. package/src/__tests__/cli.test.ts +130 -0
  204. package/src/__tests__/codegen.test.ts +128 -0
  205. package/src/__tests__/diagnostics.test.ts +195 -0
  206. package/src/__tests__/e2e.test.ts +1721 -0
  207. package/src/__tests__/fixtures/mc-commands-1.21.4.json +18734 -0
  208. package/src/__tests__/formatter.test.ts +46 -0
  209. package/src/__tests__/lexer.test.ts +356 -0
  210. package/src/__tests__/lowering.test.ts +962 -0
  211. package/src/__tests__/mc-integration.test.ts +409 -0
  212. package/src/__tests__/mc-syntax.test.ts +96 -0
  213. package/src/__tests__/nbt.test.ts +58 -0
  214. package/src/__tests__/optimizer-advanced.test.ts +144 -0
  215. package/src/__tests__/optimizer.test.ts +129 -0
  216. package/src/__tests__/parser.test.ts +800 -0
  217. package/src/__tests__/repl.test.ts +33 -0
  218. package/src/__tests__/runtime.test.ts +289 -0
  219. package/src/__tests__/structure-optimizer.test.ts +38 -0
  220. package/src/__tests__/typechecker.test.ts +395 -0
  221. package/src/ast/types.ts +248 -0
  222. package/src/cli.ts +445 -0
  223. package/src/codegen/cmdblock/index.ts +63 -0
  224. package/src/codegen/mcfunction/index.ts +471 -0
  225. package/src/codegen/structure/index.ts +305 -0
  226. package/src/compile.ts +188 -0
  227. package/src/diagnostics/index.ts +186 -0
  228. package/src/examples/README.md +77 -0
  229. package/src/examples/SHOWCASE_GAME.md +43 -0
  230. package/src/examples/arena.rs +44 -0
  231. package/src/examples/counter.rs +12 -0
  232. package/src/examples/pvp_arena.rs +131 -0
  233. package/src/examples/quiz.rs +90 -0
  234. package/src/examples/rpg.rs +13 -0
  235. package/src/examples/shop.rs +30 -0
  236. package/src/examples/showcase_game.rs +552 -0
  237. package/src/examples/stdlib_demo.rs +181 -0
  238. package/src/examples/turret.rs +27 -0
  239. package/src/examples/world_manager.rs +23 -0
  240. package/src/formatter/index.ts +22 -0
  241. package/src/index.ts +161 -0
  242. package/src/ir/builder.ts +114 -0
  243. package/src/ir/types.ts +119 -0
  244. package/src/lexer/index.ts +555 -0
  245. package/src/lowering/index.ts +2406 -0
  246. package/src/mc-test/client.ts +259 -0
  247. package/src/mc-test/runner.ts +140 -0
  248. package/src/mc-test/setup.ts +70 -0
  249. package/src/mc-validator/index.ts +367 -0
  250. package/src/nbt/index.ts +321 -0
  251. package/src/optimizer/commands.ts +416 -0
  252. package/src/optimizer/passes.ts +233 -0
  253. package/src/optimizer/structure.ts +441 -0
  254. package/src/parser/index.ts +1437 -0
  255. package/src/repl.ts +165 -0
  256. package/src/runtime/index.ts +1403 -0
  257. package/src/stdlib/README.md +156 -0
  258. package/src/stdlib/combat.rs +20 -0
  259. package/src/stdlib/cooldown.rs +45 -0
  260. package/src/stdlib/math.rs +49 -0
  261. package/src/stdlib/mobs.rs +99 -0
  262. package/src/stdlib/player.rs +29 -0
  263. package/src/stdlib/strings.rs +7 -0
  264. package/src/stdlib/timer.rs +51 -0
  265. package/src/templates/README.md +126 -0
  266. package/src/templates/combat.rs +96 -0
  267. package/src/templates/economy.rs +40 -0
  268. package/src/templates/mini-game-framework.rs +117 -0
  269. package/src/templates/quest.rs +78 -0
  270. package/src/test_programs/zombie_game.rs +25 -0
  271. package/src/typechecker/index.ts +737 -0
  272. package/tsconfig.json +16 -0
@@ -0,0 +1,1193 @@
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,
20
+ '+': 5, '-': 5,
21
+ '*': 6, '/': 6, '%': 6,
22
+ };
23
+ const BINARY_OPS = new Set(['||', '&&', '==', '!=', '<', '<=', '>', '>=', '+', '-', '*', '/', '%']);
24
+ function computeIsSingle(raw) {
25
+ if (/^@[spr](\[|$)/.test(raw))
26
+ return true;
27
+ if (/[\[,\s]limit=1[,\]\s]/.test(raw))
28
+ return true;
29
+ return false;
30
+ }
31
+ // ---------------------------------------------------------------------------
32
+ // Parser Class
33
+ // ---------------------------------------------------------------------------
34
+ class Parser {
35
+ constructor(tokens, source, filePath) {
36
+ this.pos = 0;
37
+ this.tokens = tokens;
38
+ this.sourceLines = source?.split('\n') ?? [];
39
+ this.filePath = filePath;
40
+ }
41
+ // -------------------------------------------------------------------------
42
+ // Utilities
43
+ // -------------------------------------------------------------------------
44
+ peek(offset = 0) {
45
+ const idx = this.pos + offset;
46
+ if (idx >= this.tokens.length) {
47
+ return this.tokens[this.tokens.length - 1]; // eof
48
+ }
49
+ return this.tokens[idx];
50
+ }
51
+ advance() {
52
+ const token = this.tokens[this.pos];
53
+ if (token.kind !== 'eof')
54
+ this.pos++;
55
+ return token;
56
+ }
57
+ check(kind) {
58
+ return this.peek().kind === kind;
59
+ }
60
+ match(...kinds) {
61
+ for (const kind of kinds) {
62
+ if (this.check(kind)) {
63
+ this.advance();
64
+ return true;
65
+ }
66
+ }
67
+ return false;
68
+ }
69
+ expect(kind) {
70
+ const token = this.peek();
71
+ if (token.kind !== kind) {
72
+ throw new diagnostics_1.DiagnosticError('ParseError', `Expected '${kind}' but got '${token.kind}'`, { file: this.filePath, line: token.line, col: token.col }, this.sourceLines);
73
+ }
74
+ return this.advance();
75
+ }
76
+ error(message) {
77
+ const token = this.peek();
78
+ throw new diagnostics_1.DiagnosticError('ParseError', message, { file: this.filePath, line: token.line, col: token.col }, this.sourceLines);
79
+ }
80
+ withLoc(node, token) {
81
+ const span = { line: token.line, col: token.col };
82
+ Object.defineProperty(node, 'span', {
83
+ value: span,
84
+ enumerable: false,
85
+ configurable: true,
86
+ writable: true,
87
+ });
88
+ return node;
89
+ }
90
+ getLocToken(node) {
91
+ const span = node.span;
92
+ if (!span) {
93
+ return null;
94
+ }
95
+ return { kind: 'eof', value: '', line: span.line, col: span.col };
96
+ }
97
+ // -------------------------------------------------------------------------
98
+ // Program
99
+ // -------------------------------------------------------------------------
100
+ parse(defaultNamespace = 'redscript') {
101
+ let namespace = defaultNamespace;
102
+ const declarations = [];
103
+ const structs = [];
104
+ const enums = [];
105
+ const consts = [];
106
+ // Check for namespace declaration
107
+ if (this.check('namespace')) {
108
+ this.advance();
109
+ const name = this.expect('ident');
110
+ namespace = name.value;
111
+ this.expect(';');
112
+ }
113
+ // Parse struct and function declarations
114
+ while (!this.check('eof')) {
115
+ if (this.check('struct')) {
116
+ structs.push(this.parseStructDecl());
117
+ }
118
+ else if (this.check('enum')) {
119
+ enums.push(this.parseEnumDecl());
120
+ }
121
+ else if (this.check('const')) {
122
+ consts.push(this.parseConstDecl());
123
+ }
124
+ else {
125
+ declarations.push(this.parseFnDecl());
126
+ }
127
+ }
128
+ return { namespace, declarations, structs, enums, consts };
129
+ }
130
+ // -------------------------------------------------------------------------
131
+ // Struct Declaration
132
+ // -------------------------------------------------------------------------
133
+ parseStructDecl() {
134
+ const structToken = this.expect('struct');
135
+ const name = this.expect('ident').value;
136
+ this.expect('{');
137
+ const fields = [];
138
+ while (!this.check('}') && !this.check('eof')) {
139
+ const fieldName = this.expect('ident').value;
140
+ this.expect(':');
141
+ const fieldType = this.parseType();
142
+ fields.push({ name: fieldName, type: fieldType });
143
+ // Allow optional comma or semicolon between fields
144
+ this.match(',');
145
+ }
146
+ this.expect('}');
147
+ return this.withLoc({ name, fields }, structToken);
148
+ }
149
+ parseEnumDecl() {
150
+ const enumToken = this.expect('enum');
151
+ const name = this.expect('ident').value;
152
+ this.expect('{');
153
+ const variants = [];
154
+ let nextValue = 0;
155
+ while (!this.check('}') && !this.check('eof')) {
156
+ const variantToken = this.expect('ident');
157
+ const variant = { name: variantToken.value };
158
+ if (this.match('=')) {
159
+ const valueToken = this.expect('int_lit');
160
+ variant.value = parseInt(valueToken.value, 10);
161
+ nextValue = variant.value + 1;
162
+ }
163
+ else {
164
+ variant.value = nextValue++;
165
+ }
166
+ variants.push(variant);
167
+ if (!this.match(',')) {
168
+ break;
169
+ }
170
+ }
171
+ this.expect('}');
172
+ return this.withLoc({ name, variants }, enumToken);
173
+ }
174
+ parseConstDecl() {
175
+ const constToken = this.expect('const');
176
+ const name = this.expect('ident').value;
177
+ this.expect(':');
178
+ const type = this.parseType();
179
+ this.expect('=');
180
+ const value = this.parseLiteralExpr();
181
+ this.match(';');
182
+ return this.withLoc({ name, type, value }, constToken);
183
+ }
184
+ // -------------------------------------------------------------------------
185
+ // Function Declaration
186
+ // -------------------------------------------------------------------------
187
+ parseFnDecl() {
188
+ const decorators = this.parseDecorators();
189
+ const fnToken = this.expect('fn');
190
+ const name = this.expect('ident').value;
191
+ this.expect('(');
192
+ const params = this.parseParams();
193
+ this.expect(')');
194
+ let returnType = { kind: 'named', name: 'void' };
195
+ if (this.match('->')) {
196
+ returnType = this.parseType();
197
+ }
198
+ const body = this.parseBlock();
199
+ return this.withLoc({ name, params, returnType, decorators, body }, fnToken);
200
+ }
201
+ parseDecorators() {
202
+ const decorators = [];
203
+ while (this.check('decorator')) {
204
+ const token = this.advance();
205
+ const decorator = this.parseDecoratorValue(token.value);
206
+ decorators.push(decorator);
207
+ }
208
+ return decorators;
209
+ }
210
+ parseDecoratorValue(value) {
211
+ // Parse @tick or @on_trigger("name") or @on_advancement("story/mine_diamond")
212
+ const match = value.match(/^@(\w+)(?:\(([^)]*)\))?$/);
213
+ if (!match) {
214
+ this.error(`Invalid decorator: ${value}`);
215
+ }
216
+ const name = match[1];
217
+ const argsStr = match[2];
218
+ if (!argsStr) {
219
+ return { name };
220
+ }
221
+ const args = {};
222
+ // Handle @on_trigger("name"), @on_advancement("id"), @on_craft("item"), @on_join_team("team")
223
+ if (name === 'on_trigger' || name === 'on_advancement' || name === 'on_craft' || name === 'on_join_team') {
224
+ const strMatch = argsStr.match(/^"([^"]*)"$/);
225
+ if (strMatch) {
226
+ if (name === 'on_trigger') {
227
+ args.trigger = strMatch[1];
228
+ }
229
+ else if (name === 'on_advancement') {
230
+ args.advancement = strMatch[1];
231
+ }
232
+ else if (name === 'on_craft') {
233
+ args.item = strMatch[1];
234
+ }
235
+ else if (name === 'on_join_team') {
236
+ args.team = strMatch[1];
237
+ }
238
+ return { name, args };
239
+ }
240
+ }
241
+ // Handle key=value format (e.g., rate=20)
242
+ for (const part of argsStr.split(',')) {
243
+ const [key, val] = part.split('=').map(s => s.trim());
244
+ if (key === 'rate') {
245
+ args.rate = parseInt(val, 10);
246
+ }
247
+ else if (key === 'trigger') {
248
+ args.trigger = val;
249
+ }
250
+ else if (key === 'advancement') {
251
+ args.advancement = val;
252
+ }
253
+ else if (key === 'item') {
254
+ args.item = val;
255
+ }
256
+ else if (key === 'team') {
257
+ args.team = val;
258
+ }
259
+ }
260
+ return { name, args };
261
+ }
262
+ parseParams() {
263
+ const params = [];
264
+ if (!this.check(')')) {
265
+ do {
266
+ const paramToken = this.expect('ident');
267
+ const name = paramToken.value;
268
+ this.expect(':');
269
+ const type = this.parseType();
270
+ let defaultValue;
271
+ if (this.match('=')) {
272
+ defaultValue = this.parseExpr();
273
+ }
274
+ params.push(this.withLoc({ name, type, default: defaultValue }, paramToken));
275
+ } while (this.match(','));
276
+ }
277
+ return params;
278
+ }
279
+ parseType() {
280
+ const token = this.peek();
281
+ let type;
282
+ if (token.kind === '(') {
283
+ return this.parseFunctionType();
284
+ }
285
+ if (token.kind === 'int' || token.kind === 'bool' ||
286
+ token.kind === 'float' || token.kind === 'string' || token.kind === 'void' ||
287
+ token.kind === 'BlockPos') {
288
+ this.advance();
289
+ type = { kind: 'named', name: token.kind };
290
+ }
291
+ else if (token.kind === 'ident') {
292
+ this.advance();
293
+ type = { kind: 'struct', name: token.value };
294
+ }
295
+ else {
296
+ this.error(`Expected type, got '${token.kind}'`);
297
+ }
298
+ while (this.match('[')) {
299
+ this.expect(']');
300
+ type = { kind: 'array', elem: type };
301
+ }
302
+ return type;
303
+ }
304
+ parseFunctionType() {
305
+ this.expect('(');
306
+ const params = [];
307
+ if (!this.check(')')) {
308
+ do {
309
+ params.push(this.parseType());
310
+ } while (this.match(','));
311
+ }
312
+ this.expect(')');
313
+ this.expect('->');
314
+ const returnType = this.parseType();
315
+ return { kind: 'function_type', params, return: returnType };
316
+ }
317
+ // -------------------------------------------------------------------------
318
+ // Block & Statements
319
+ // -------------------------------------------------------------------------
320
+ parseBlock() {
321
+ this.expect('{');
322
+ const stmts = [];
323
+ while (!this.check('}') && !this.check('eof')) {
324
+ stmts.push(this.parseStmt());
325
+ }
326
+ this.expect('}');
327
+ return stmts;
328
+ }
329
+ parseStmt() {
330
+ // Let statement
331
+ if (this.check('let')) {
332
+ return this.parseLetStmt();
333
+ }
334
+ // Return statement
335
+ if (this.check('return')) {
336
+ return this.parseReturnStmt();
337
+ }
338
+ // If statement
339
+ if (this.check('if')) {
340
+ return this.parseIfStmt();
341
+ }
342
+ // While statement
343
+ if (this.check('while')) {
344
+ return this.parseWhileStmt();
345
+ }
346
+ // For statement
347
+ if (this.check('for')) {
348
+ return this.parseForStmt();
349
+ }
350
+ // Foreach statement
351
+ if (this.check('foreach')) {
352
+ return this.parseForeachStmt();
353
+ }
354
+ if (this.check('match')) {
355
+ return this.parseMatchStmt();
356
+ }
357
+ // As block
358
+ if (this.check('as')) {
359
+ return this.parseAsStmt();
360
+ }
361
+ // At block
362
+ if (this.check('at')) {
363
+ return this.parseAtStmt();
364
+ }
365
+ // Execute statement: execute as/at/if/unless/in ... run { }
366
+ if (this.check('execute')) {
367
+ return this.parseExecuteStmt();
368
+ }
369
+ // Raw command
370
+ if (this.check('raw_cmd')) {
371
+ const token = this.advance();
372
+ const cmd = token.value;
373
+ this.match(';'); // optional semicolon (raw consumes it)
374
+ return this.withLoc({ kind: 'raw', cmd }, token);
375
+ }
376
+ // Expression statement
377
+ return this.parseExprStmt();
378
+ }
379
+ parseLetStmt() {
380
+ const letToken = this.expect('let');
381
+ const name = this.expect('ident').value;
382
+ let type;
383
+ if (this.match(':')) {
384
+ type = this.parseType();
385
+ }
386
+ this.expect('=');
387
+ const init = this.parseExpr();
388
+ this.expect(';');
389
+ return this.withLoc({ kind: 'let', name, type, init }, letToken);
390
+ }
391
+ parseReturnStmt() {
392
+ const returnToken = this.expect('return');
393
+ let value;
394
+ if (!this.check(';')) {
395
+ value = this.parseExpr();
396
+ }
397
+ this.expect(';');
398
+ return this.withLoc({ kind: 'return', value }, returnToken);
399
+ }
400
+ parseIfStmt() {
401
+ const ifToken = this.expect('if');
402
+ this.expect('(');
403
+ const cond = this.parseExpr();
404
+ this.expect(')');
405
+ const then = this.parseBlock();
406
+ let else_;
407
+ if (this.match('else')) {
408
+ if (this.check('if')) {
409
+ // else if
410
+ else_ = [this.parseIfStmt()];
411
+ }
412
+ else {
413
+ else_ = this.parseBlock();
414
+ }
415
+ }
416
+ return this.withLoc({ kind: 'if', cond, then, else_ }, ifToken);
417
+ }
418
+ parseWhileStmt() {
419
+ const whileToken = this.expect('while');
420
+ this.expect('(');
421
+ const cond = this.parseExpr();
422
+ this.expect(')');
423
+ const body = this.parseBlock();
424
+ return this.withLoc({ kind: 'while', cond, body }, whileToken);
425
+ }
426
+ parseForStmt() {
427
+ const forToken = this.expect('for');
428
+ // Check for for-range syntax: for <ident> in <range_lit> { ... }
429
+ if (this.check('ident') && this.peek(1).kind === 'in') {
430
+ return this.parseForRangeStmt(forToken);
431
+ }
432
+ this.expect('(');
433
+ // Init: either let statement (without semicolon) or empty
434
+ let init;
435
+ if (this.check('let')) {
436
+ // Parse let without consuming semicolon here (we handle it)
437
+ const letToken = this.expect('let');
438
+ const name = this.expect('ident').value;
439
+ let type;
440
+ if (this.match(':')) {
441
+ type = this.parseType();
442
+ }
443
+ this.expect('=');
444
+ const initExpr = this.parseExpr();
445
+ const initStmt = { kind: 'let', name, type, init: initExpr };
446
+ init = this.withLoc(initStmt, letToken);
447
+ }
448
+ this.expect(';');
449
+ // Condition
450
+ const cond = this.parseExpr();
451
+ this.expect(';');
452
+ // Step expression
453
+ const step = this.parseExpr();
454
+ this.expect(')');
455
+ const body = this.parseBlock();
456
+ return this.withLoc({ kind: 'for', init, cond, step, body }, forToken);
457
+ }
458
+ parseForRangeStmt(forToken) {
459
+ const varName = this.expect('ident').value;
460
+ this.expect('in');
461
+ const rangeToken = this.expect('range_lit');
462
+ const range = this.parseRangeValue(rangeToken.value);
463
+ const start = this.withLoc({ kind: 'int_lit', value: range.min ?? 0 }, rangeToken);
464
+ const end = this.withLoc({ kind: 'int_lit', value: range.max ?? 0 }, rangeToken);
465
+ const body = this.parseBlock();
466
+ return this.withLoc({ kind: 'for_range', varName, start, end, body }, forToken);
467
+ }
468
+ parseForeachStmt() {
469
+ const foreachToken = this.expect('foreach');
470
+ this.expect('(');
471
+ const binding = this.expect('ident').value;
472
+ this.expect('in');
473
+ const iterable = this.parseExpr();
474
+ this.expect(')');
475
+ const body = this.parseBlock();
476
+ return this.withLoc({ kind: 'foreach', binding, iterable, body }, foreachToken);
477
+ }
478
+ parseMatchStmt() {
479
+ const matchToken = this.expect('match');
480
+ this.expect('(');
481
+ const expr = this.parseExpr();
482
+ this.expect(')');
483
+ this.expect('{');
484
+ const arms = [];
485
+ while (!this.check('}') && !this.check('eof')) {
486
+ let pattern;
487
+ if (this.check('ident') && this.peek().value === '_') {
488
+ this.advance();
489
+ pattern = null;
490
+ }
491
+ else {
492
+ pattern = this.parseExpr();
493
+ }
494
+ this.expect('=>');
495
+ const body = this.parseBlock();
496
+ arms.push({ pattern, body });
497
+ }
498
+ this.expect('}');
499
+ return this.withLoc({ kind: 'match', expr, arms }, matchToken);
500
+ }
501
+ parseAsStmt() {
502
+ const asToken = this.expect('as');
503
+ const as_sel = this.parseSelector();
504
+ // Check for combined as/at
505
+ if (this.match('at')) {
506
+ const at_sel = this.parseSelector();
507
+ const body = this.parseBlock();
508
+ return this.withLoc({ kind: 'as_at', as_sel, at_sel, body }, asToken);
509
+ }
510
+ const body = this.parseBlock();
511
+ return this.withLoc({ kind: 'as_block', selector: as_sel, body }, asToken);
512
+ }
513
+ parseAtStmt() {
514
+ const atToken = this.expect('at');
515
+ const selector = this.parseSelector();
516
+ const body = this.parseBlock();
517
+ return this.withLoc({ kind: 'at_block', selector, body }, atToken);
518
+ }
519
+ parseExecuteStmt() {
520
+ const executeToken = this.expect('execute');
521
+ const subcommands = [];
522
+ // Parse subcommands until we hit 'run'
523
+ while (!this.check('run') && !this.check('eof')) {
524
+ if (this.match('as')) {
525
+ const selector = this.parseSelector();
526
+ subcommands.push({ kind: 'as', selector });
527
+ }
528
+ else if (this.match('at')) {
529
+ const selector = this.parseSelector();
530
+ subcommands.push({ kind: 'at', selector });
531
+ }
532
+ else if (this.match('if')) {
533
+ // Expect 'entity' keyword (as ident) or just parse selector directly
534
+ if (this.peek().kind === 'ident' && this.peek().value === 'entity') {
535
+ this.advance(); // consume 'entity'
536
+ }
537
+ const selector = this.parseSelector();
538
+ subcommands.push({ kind: 'if_entity', selector });
539
+ }
540
+ else if (this.match('unless')) {
541
+ // Expect 'entity' keyword (as ident) or just parse selector directly
542
+ if (this.peek().kind === 'ident' && this.peek().value === 'entity') {
543
+ this.advance(); // consume 'entity'
544
+ }
545
+ const selector = this.parseSelector();
546
+ subcommands.push({ kind: 'unless_entity', selector });
547
+ }
548
+ else if (this.match('in')) {
549
+ const dim = this.expect('ident').value;
550
+ subcommands.push({ kind: 'in', dimension: dim });
551
+ }
552
+ else {
553
+ this.error(`Unexpected token in execute statement: ${this.peek().kind}`);
554
+ }
555
+ }
556
+ this.expect('run');
557
+ const body = this.parseBlock();
558
+ return this.withLoc({ kind: 'execute', subcommands, body }, executeToken);
559
+ }
560
+ parseExprStmt() {
561
+ const expr = this.parseExpr();
562
+ this.expect(';');
563
+ const exprToken = this.getLocToken(expr) ?? this.peek();
564
+ return this.withLoc({ kind: 'expr', expr }, exprToken);
565
+ }
566
+ // -------------------------------------------------------------------------
567
+ // Expressions (Precedence Climbing)
568
+ // -------------------------------------------------------------------------
569
+ parseExpr() {
570
+ return this.parseAssignment();
571
+ }
572
+ parseAssignment() {
573
+ const left = this.parseBinaryExpr(1);
574
+ // Check for assignment
575
+ const token = this.peek();
576
+ if (token.kind === '=' || token.kind === '+=' || token.kind === '-=' ||
577
+ token.kind === '*=' || token.kind === '/=' || token.kind === '%=') {
578
+ const op = this.advance().kind;
579
+ if (left.kind === 'ident') {
580
+ const value = this.parseAssignment();
581
+ return this.withLoc({ kind: 'assign', target: left.name, op, value }, this.getLocToken(left) ?? token);
582
+ }
583
+ // Member assignment: p.x = 10, p.x += 5
584
+ if (left.kind === 'member') {
585
+ const value = this.parseAssignment();
586
+ return this.withLoc({ kind: 'member_assign', obj: left.obj, field: left.field, op, value }, this.getLocToken(left) ?? token);
587
+ }
588
+ }
589
+ return left;
590
+ }
591
+ parseBinaryExpr(minPrec) {
592
+ let left = this.parseUnaryExpr();
593
+ while (true) {
594
+ const op = this.peek().kind;
595
+ if (!BINARY_OPS.has(op))
596
+ break;
597
+ const prec = PRECEDENCE[op];
598
+ if (prec < minPrec)
599
+ break;
600
+ const opToken = this.advance();
601
+ const right = this.parseBinaryExpr(prec + 1); // left associative
602
+ left = this.withLoc({ kind: 'binary', op: op, left, right }, this.getLocToken(left) ?? opToken);
603
+ }
604
+ return left;
605
+ }
606
+ parseUnaryExpr() {
607
+ if (this.match('!')) {
608
+ const bangToken = this.tokens[this.pos - 1];
609
+ const operand = this.parseUnaryExpr();
610
+ return this.withLoc({ kind: 'unary', op: '!', operand }, bangToken);
611
+ }
612
+ if (this.check('-') && !this.isSubtraction()) {
613
+ const minusToken = this.advance();
614
+ const operand = this.parseUnaryExpr();
615
+ return this.withLoc({ kind: 'unary', op: '-', operand }, minusToken);
616
+ }
617
+ return this.parsePostfixExpr();
618
+ }
619
+ isSubtraction() {
620
+ // Check if this minus is binary (subtraction) by looking at previous token
621
+ // If previous was a value (literal, ident, ), ]) it's subtraction
622
+ if (this.pos === 0)
623
+ return false;
624
+ const prev = this.tokens[this.pos - 1];
625
+ return ['int_lit', 'float_lit', 'ident', ')', ']'].includes(prev.kind);
626
+ }
627
+ parsePostfixExpr() {
628
+ let expr = this.parsePrimaryExpr();
629
+ while (true) {
630
+ // Function call
631
+ if (this.match('(')) {
632
+ const openParenToken = this.tokens[this.pos - 1];
633
+ if (expr.kind === 'ident') {
634
+ const args = this.parseArgs();
635
+ this.expect(')');
636
+ expr = this.withLoc({ kind: 'call', fn: expr.name, args }, this.getLocToken(expr) ?? openParenToken);
637
+ continue;
638
+ }
639
+ // Member call: entity.tag("name") → __entity_tag(entity, "name")
640
+ // Also handle arr.push(val) and arr.length
641
+ if (expr.kind === 'member') {
642
+ const methodMap = {
643
+ 'tag': '__entity_tag',
644
+ 'untag': '__entity_untag',
645
+ 'has_tag': '__entity_has_tag',
646
+ 'push': '__array_push',
647
+ 'pop': '__array_pop',
648
+ };
649
+ const internalFn = methodMap[expr.field];
650
+ if (internalFn) {
651
+ const args = this.parseArgs();
652
+ this.expect(')');
653
+ expr = this.withLoc({ kind: 'call', fn: internalFn, args: [expr.obj, ...args] }, this.getLocToken(expr) ?? openParenToken);
654
+ continue;
655
+ }
656
+ this.error(`Unknown method '${expr.field}'`);
657
+ }
658
+ const args = this.parseArgs();
659
+ this.expect(')');
660
+ expr = this.withLoc({ kind: 'invoke', callee: expr, args }, this.getLocToken(expr) ?? openParenToken);
661
+ continue;
662
+ }
663
+ // Array index access: arr[0]
664
+ if (this.match('[')) {
665
+ const index = this.parseExpr();
666
+ this.expect(']');
667
+ expr = this.withLoc({ kind: 'index', obj: expr, index }, this.getLocToken(expr) ?? this.tokens[this.pos - 1]);
668
+ continue;
669
+ }
670
+ // Member access
671
+ if (this.match('.')) {
672
+ const field = this.expect('ident').value;
673
+ expr = this.withLoc({ kind: 'member', obj: expr, field }, this.getLocToken(expr) ?? this.tokens[this.pos - 1]);
674
+ continue;
675
+ }
676
+ break;
677
+ }
678
+ return expr;
679
+ }
680
+ parseArgs() {
681
+ const args = [];
682
+ if (!this.check(')')) {
683
+ do {
684
+ args.push(this.parseExpr());
685
+ } while (this.match(','));
686
+ }
687
+ return args;
688
+ }
689
+ parsePrimaryExpr() {
690
+ const token = this.peek();
691
+ if (token.kind === 'ident' && this.peek(1).kind === '=>') {
692
+ return this.parseSingleParamLambda();
693
+ }
694
+ // Integer literal
695
+ if (token.kind === 'int_lit') {
696
+ this.advance();
697
+ return this.withLoc({ kind: 'int_lit', value: parseInt(token.value, 10) }, token);
698
+ }
699
+ // Float literal
700
+ if (token.kind === 'float_lit') {
701
+ this.advance();
702
+ return this.withLoc({ kind: 'float_lit', value: parseFloat(token.value) }, token);
703
+ }
704
+ // NBT suffix literals
705
+ if (token.kind === 'byte_lit') {
706
+ this.advance();
707
+ return this.withLoc({ kind: 'byte_lit', value: parseInt(token.value.slice(0, -1), 10) }, token);
708
+ }
709
+ if (token.kind === 'short_lit') {
710
+ this.advance();
711
+ return this.withLoc({ kind: 'short_lit', value: parseInt(token.value.slice(0, -1), 10) }, token);
712
+ }
713
+ if (token.kind === 'long_lit') {
714
+ this.advance();
715
+ return this.withLoc({ kind: 'long_lit', value: parseInt(token.value.slice(0, -1), 10) }, token);
716
+ }
717
+ if (token.kind === 'double_lit') {
718
+ this.advance();
719
+ return this.withLoc({ kind: 'double_lit', value: parseFloat(token.value.slice(0, -1)) }, token);
720
+ }
721
+ // String literal
722
+ if (token.kind === 'string_lit') {
723
+ this.advance();
724
+ return this.parseStringExpr(token);
725
+ }
726
+ // MC name literal: #health → mc_name node (value = "health", without #)
727
+ if (token.kind === 'mc_name') {
728
+ this.advance();
729
+ return this.withLoc({ kind: 'mc_name', value: token.value.slice(1) }, token);
730
+ }
731
+ // Boolean literal
732
+ if (token.kind === 'true') {
733
+ this.advance();
734
+ return this.withLoc({ kind: 'bool_lit', value: true }, token);
735
+ }
736
+ if (token.kind === 'false') {
737
+ this.advance();
738
+ return this.withLoc({ kind: 'bool_lit', value: false }, token);
739
+ }
740
+ // Range literal
741
+ if (token.kind === 'range_lit') {
742
+ this.advance();
743
+ return this.withLoc({ kind: 'range_lit', range: this.parseRangeValue(token.value) }, token);
744
+ }
745
+ // Selector
746
+ if (token.kind === 'selector') {
747
+ this.advance();
748
+ return this.withLoc({
749
+ kind: 'selector',
750
+ raw: token.value,
751
+ isSingle: computeIsSingle(token.value),
752
+ sel: this.parseSelectorValue(token.value),
753
+ }, token);
754
+ }
755
+ // Identifier
756
+ if (token.kind === 'ident') {
757
+ this.advance();
758
+ return this.withLoc({ kind: 'ident', name: token.value }, token);
759
+ }
760
+ // Grouped expression
761
+ if (token.kind === '(') {
762
+ if (this.isBlockPosLiteral()) {
763
+ return this.parseBlockPos();
764
+ }
765
+ if (this.isLambdaStart()) {
766
+ return this.parseLambdaExpr();
767
+ }
768
+ this.advance();
769
+ const expr = this.parseExpr();
770
+ this.expect(')');
771
+ return expr;
772
+ }
773
+ // Struct literal or block: { x: 10, y: 20 }
774
+ if (token.kind === '{') {
775
+ return this.parseStructLit();
776
+ }
777
+ // Array literal: [1, 2, 3] or []
778
+ if (token.kind === '[') {
779
+ return this.parseArrayLit();
780
+ }
781
+ this.error(`Unexpected token '${token.kind}'`);
782
+ }
783
+ parseLiteralExpr() {
784
+ const expr = this.parsePrimaryExpr();
785
+ if (expr.kind === 'int_lit' ||
786
+ expr.kind === 'float_lit' ||
787
+ expr.kind === 'bool_lit' ||
788
+ expr.kind === 'str_lit') {
789
+ return expr;
790
+ }
791
+ this.error('Const value must be a literal');
792
+ }
793
+ parseSingleParamLambda() {
794
+ const paramToken = this.expect('ident');
795
+ const params = [{ name: paramToken.value }];
796
+ this.expect('=>');
797
+ return this.finishLambdaExpr(params, paramToken);
798
+ }
799
+ parseLambdaExpr() {
800
+ const openParenToken = this.expect('(');
801
+ const params = [];
802
+ if (!this.check(')')) {
803
+ do {
804
+ const name = this.expect('ident').value;
805
+ let type;
806
+ if (this.match(':')) {
807
+ type = this.parseType();
808
+ }
809
+ params.push({ name, type });
810
+ } while (this.match(','));
811
+ }
812
+ this.expect(')');
813
+ let returnType;
814
+ if (this.match('->')) {
815
+ returnType = this.parseType();
816
+ }
817
+ this.expect('=>');
818
+ return this.finishLambdaExpr(params, openParenToken, returnType);
819
+ }
820
+ finishLambdaExpr(params, token, returnType) {
821
+ const body = this.check('{') ? this.parseBlock() : this.parseExpr();
822
+ return this.withLoc({ kind: 'lambda', params, returnType, body }, token);
823
+ }
824
+ parseStringExpr(token) {
825
+ if (!token.value.includes('${')) {
826
+ return this.withLoc({ kind: 'str_lit', value: token.value }, token);
827
+ }
828
+ const parts = [];
829
+ let current = '';
830
+ let index = 0;
831
+ while (index < token.value.length) {
832
+ if (token.value[index] === '$' && token.value[index + 1] === '{') {
833
+ if (current) {
834
+ parts.push(current);
835
+ current = '';
836
+ }
837
+ index += 2;
838
+ let depth = 1;
839
+ let exprSource = '';
840
+ let inString = false;
841
+ while (index < token.value.length && depth > 0) {
842
+ const char = token.value[index];
843
+ if (char === '"' && token.value[index - 1] !== '\\') {
844
+ inString = !inString;
845
+ }
846
+ if (!inString) {
847
+ if (char === '{') {
848
+ depth++;
849
+ }
850
+ else if (char === '}') {
851
+ depth--;
852
+ if (depth === 0) {
853
+ index++;
854
+ break;
855
+ }
856
+ }
857
+ }
858
+ if (depth > 0) {
859
+ exprSource += char;
860
+ }
861
+ index++;
862
+ }
863
+ if (depth !== 0) {
864
+ this.error('Unterminated string interpolation');
865
+ }
866
+ parts.push(this.parseEmbeddedExpr(exprSource));
867
+ continue;
868
+ }
869
+ current += token.value[index];
870
+ index++;
871
+ }
872
+ if (current) {
873
+ parts.push(current);
874
+ }
875
+ return this.withLoc({ kind: 'str_interp', parts }, token);
876
+ }
877
+ parseEmbeddedExpr(source) {
878
+ const tokens = new lexer_1.Lexer(source, this.filePath).tokenize();
879
+ const parser = new Parser(tokens, source, this.filePath);
880
+ const expr = parser.parseExpr();
881
+ if (!parser.check('eof')) {
882
+ parser.error(`Unexpected token '${parser.peek().kind}' in string interpolation`);
883
+ }
884
+ return expr;
885
+ }
886
+ parseStructLit() {
887
+ const braceToken = this.expect('{');
888
+ const fields = [];
889
+ if (!this.check('}')) {
890
+ do {
891
+ const name = this.expect('ident').value;
892
+ this.expect(':');
893
+ const value = this.parseExpr();
894
+ fields.push({ name, value });
895
+ } while (this.match(','));
896
+ }
897
+ this.expect('}');
898
+ return this.withLoc({ kind: 'struct_lit', fields }, braceToken);
899
+ }
900
+ parseArrayLit() {
901
+ const bracketToken = this.expect('[');
902
+ const elements = [];
903
+ if (!this.check(']')) {
904
+ do {
905
+ elements.push(this.parseExpr());
906
+ } while (this.match(','));
907
+ }
908
+ this.expect(']');
909
+ return this.withLoc({ kind: 'array_lit', elements }, bracketToken);
910
+ }
911
+ isLambdaStart() {
912
+ if (!this.check('('))
913
+ return false;
914
+ let offset = 1;
915
+ if (this.peek(offset).kind !== ')') {
916
+ while (true) {
917
+ if (this.peek(offset).kind !== 'ident') {
918
+ return false;
919
+ }
920
+ offset += 1;
921
+ if (this.peek(offset).kind === ':') {
922
+ offset += 1;
923
+ const consumed = this.typeTokenLength(offset);
924
+ if (consumed === 0) {
925
+ return false;
926
+ }
927
+ offset += consumed;
928
+ }
929
+ if (this.peek(offset).kind === ',') {
930
+ offset += 1;
931
+ continue;
932
+ }
933
+ break;
934
+ }
935
+ }
936
+ if (this.peek(offset).kind !== ')') {
937
+ return false;
938
+ }
939
+ offset += 1;
940
+ if (this.peek(offset).kind === '=>') {
941
+ return true;
942
+ }
943
+ if (this.peek(offset).kind === '->') {
944
+ offset += 1;
945
+ const consumed = this.typeTokenLength(offset);
946
+ if (consumed === 0) {
947
+ return false;
948
+ }
949
+ offset += consumed;
950
+ return this.peek(offset).kind === '=>';
951
+ }
952
+ return false;
953
+ }
954
+ typeTokenLength(offset) {
955
+ const token = this.peek(offset);
956
+ if (token.kind === '(') {
957
+ let inner = offset + 1;
958
+ if (this.peek(inner).kind !== ')') {
959
+ while (true) {
960
+ const consumed = this.typeTokenLength(inner);
961
+ if (consumed === 0) {
962
+ return 0;
963
+ }
964
+ inner += consumed;
965
+ if (this.peek(inner).kind === ',') {
966
+ inner += 1;
967
+ continue;
968
+ }
969
+ break;
970
+ }
971
+ }
972
+ if (this.peek(inner).kind !== ')') {
973
+ return 0;
974
+ }
975
+ inner += 1;
976
+ if (this.peek(inner).kind !== '->') {
977
+ return 0;
978
+ }
979
+ inner += 1;
980
+ const returnLen = this.typeTokenLength(inner);
981
+ return returnLen === 0 ? 0 : inner + returnLen - offset;
982
+ }
983
+ const isNamedType = token.kind === 'int' ||
984
+ token.kind === 'bool' ||
985
+ token.kind === 'float' ||
986
+ token.kind === 'string' ||
987
+ token.kind === 'void' ||
988
+ token.kind === 'BlockPos' ||
989
+ token.kind === 'ident';
990
+ if (!isNamedType) {
991
+ return 0;
992
+ }
993
+ let length = 1;
994
+ while (this.peek(offset + length).kind === '[' && this.peek(offset + length + 1).kind === ']') {
995
+ length += 2;
996
+ }
997
+ return length;
998
+ }
999
+ isBlockPosLiteral() {
1000
+ if (!this.check('('))
1001
+ return false;
1002
+ let offset = 1;
1003
+ for (let i = 0; i < 3; i++) {
1004
+ const consumed = this.coordComponentTokenLength(offset);
1005
+ if (consumed === 0)
1006
+ return false;
1007
+ offset += consumed;
1008
+ if (i < 2) {
1009
+ if (this.peek(offset).kind !== ',')
1010
+ return false;
1011
+ offset += 1;
1012
+ }
1013
+ }
1014
+ return this.peek(offset).kind === ')';
1015
+ }
1016
+ coordComponentTokenLength(offset) {
1017
+ const token = this.peek(offset);
1018
+ if (token.kind === 'int_lit') {
1019
+ return 1;
1020
+ }
1021
+ if (token.kind === '-') {
1022
+ return this.peek(offset + 1).kind === 'int_lit' ? 2 : 0;
1023
+ }
1024
+ if (token.kind !== '~' && token.kind !== '^') {
1025
+ return 0;
1026
+ }
1027
+ const next = this.peek(offset + 1);
1028
+ if (next.kind === ',' || next.kind === ')') {
1029
+ return 1;
1030
+ }
1031
+ if (next.kind === 'int_lit') {
1032
+ return 2;
1033
+ }
1034
+ if (next.kind === '-' && this.peek(offset + 2).kind === 'int_lit') {
1035
+ return 3;
1036
+ }
1037
+ return 0;
1038
+ }
1039
+ parseBlockPos() {
1040
+ const openParenToken = this.expect('(');
1041
+ const x = this.parseCoordComponent();
1042
+ this.expect(',');
1043
+ const y = this.parseCoordComponent();
1044
+ this.expect(',');
1045
+ const z = this.parseCoordComponent();
1046
+ this.expect(')');
1047
+ return this.withLoc({ kind: 'blockpos', x, y, z }, openParenToken);
1048
+ }
1049
+ parseCoordComponent() {
1050
+ const token = this.peek();
1051
+ if (token.kind === '~' || token.kind === '^') {
1052
+ this.advance();
1053
+ const offset = this.parseSignedCoordOffset();
1054
+ return token.kind === '~'
1055
+ ? { kind: 'relative', offset }
1056
+ : { kind: 'local', offset };
1057
+ }
1058
+ return { kind: 'absolute', value: this.parseSignedCoordOffset(true) };
1059
+ }
1060
+ parseSignedCoordOffset(requireValue = false) {
1061
+ let sign = 1;
1062
+ if (this.match('-')) {
1063
+ sign = -1;
1064
+ }
1065
+ if (this.check('int_lit')) {
1066
+ return sign * parseInt(this.advance().value, 10);
1067
+ }
1068
+ if (requireValue) {
1069
+ this.error('Expected integer coordinate component');
1070
+ }
1071
+ return 0;
1072
+ }
1073
+ // -------------------------------------------------------------------------
1074
+ // Selector Parsing
1075
+ // -------------------------------------------------------------------------
1076
+ parseSelector() {
1077
+ const token = this.expect('selector');
1078
+ return this.parseSelectorValue(token.value);
1079
+ }
1080
+ parseSelectorValue(value) {
1081
+ // Parse @e[type=zombie, distance=..5]
1082
+ const bracketIndex = value.indexOf('[');
1083
+ if (bracketIndex === -1) {
1084
+ return { kind: value };
1085
+ }
1086
+ const kind = value.slice(0, bracketIndex);
1087
+ const paramsStr = value.slice(bracketIndex + 1, -1); // Remove [ and ]
1088
+ const filters = this.parseSelectorFilters(paramsStr);
1089
+ return { kind, filters };
1090
+ }
1091
+ parseSelectorFilters(paramsStr) {
1092
+ const filters = {};
1093
+ const parts = this.splitSelectorParams(paramsStr);
1094
+ for (const part of parts) {
1095
+ const eqIndex = part.indexOf('=');
1096
+ if (eqIndex === -1)
1097
+ continue;
1098
+ const key = part.slice(0, eqIndex).trim();
1099
+ const val = part.slice(eqIndex + 1).trim();
1100
+ switch (key) {
1101
+ case 'type':
1102
+ filters.type = val;
1103
+ break;
1104
+ case 'distance':
1105
+ filters.distance = this.parseRangeValue(val);
1106
+ break;
1107
+ case 'tag':
1108
+ if (val.startsWith('!')) {
1109
+ filters.notTag = filters.notTag ?? [];
1110
+ filters.notTag.push(val.slice(1));
1111
+ }
1112
+ else {
1113
+ filters.tag = filters.tag ?? [];
1114
+ filters.tag.push(val);
1115
+ }
1116
+ break;
1117
+ case 'limit':
1118
+ filters.limit = parseInt(val, 10);
1119
+ break;
1120
+ case 'sort':
1121
+ filters.sort = val;
1122
+ break;
1123
+ case 'nbt':
1124
+ filters.nbt = val;
1125
+ break;
1126
+ case 'gamemode':
1127
+ filters.gamemode = val;
1128
+ break;
1129
+ case 'scores':
1130
+ filters.scores = this.parseScoresFilter(val);
1131
+ break;
1132
+ }
1133
+ }
1134
+ return filters;
1135
+ }
1136
+ splitSelectorParams(str) {
1137
+ const parts = [];
1138
+ let current = '';
1139
+ let depth = 0;
1140
+ for (const char of str) {
1141
+ if (char === '{' || char === '[')
1142
+ depth++;
1143
+ else if (char === '}' || char === ']')
1144
+ depth--;
1145
+ else if (char === ',' && depth === 0) {
1146
+ parts.push(current.trim());
1147
+ current = '';
1148
+ continue;
1149
+ }
1150
+ current += char;
1151
+ }
1152
+ if (current.trim()) {
1153
+ parts.push(current.trim());
1154
+ }
1155
+ return parts;
1156
+ }
1157
+ parseScoresFilter(val) {
1158
+ // Parse {kills=1.., deaths=..5}
1159
+ const scores = {};
1160
+ const inner = val.slice(1, -1); // Remove { and }
1161
+ const parts = inner.split(',');
1162
+ for (const part of parts) {
1163
+ const [name, range] = part.split('=').map(s => s.trim());
1164
+ scores[name] = this.parseRangeValue(range);
1165
+ }
1166
+ return scores;
1167
+ }
1168
+ parseRangeValue(value) {
1169
+ // ..5 → { max: 5 }
1170
+ // 1.. → { min: 1 }
1171
+ // 1..10 → { min: 1, max: 10 }
1172
+ // 5 → { min: 5, max: 5 } (exact match)
1173
+ if (value.startsWith('..')) {
1174
+ const max = parseInt(value.slice(2), 10);
1175
+ return { max };
1176
+ }
1177
+ if (value.endsWith('..')) {
1178
+ const min = parseInt(value.slice(0, -2), 10);
1179
+ return { min };
1180
+ }
1181
+ const dotIndex = value.indexOf('..');
1182
+ if (dotIndex !== -1) {
1183
+ const min = parseInt(value.slice(0, dotIndex), 10);
1184
+ const max = parseInt(value.slice(dotIndex + 2), 10);
1185
+ return { min, max };
1186
+ }
1187
+ // Exact value
1188
+ const val = parseInt(value, 10);
1189
+ return { min: val, max: val };
1190
+ }
1191
+ }
1192
+ exports.Parser = Parser;
1193
+ //# sourceMappingURL=index.js.map