redscript-mc 3.0.1 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/.github/workflows/ci.yml +1 -0
  2. package/README.md +119 -313
  3. package/README.zh.md +118 -314
  4. package/ROADMAP.md +5 -5
  5. package/dist/data/impl_test/function/counter/get.mcfunction +5 -0
  6. package/dist/data/impl_test/function/counter/inc.mcfunction +7 -0
  7. package/dist/data/impl_test/function/counter/new.mcfunction +4 -0
  8. package/dist/data/impl_test/function/load.mcfunction +1 -0
  9. package/dist/data/impl_test/function/test_impl.mcfunction +10 -0
  10. package/dist/data/minecraft/tags/function/load.json +5 -0
  11. package/dist/data/playground/function/load.mcfunction +1 -0
  12. package/dist/data/playground/function/start.mcfunction +4 -0
  13. package/dist/data/playground/function/start__say_macro_t1.mcfunction +1 -0
  14. package/dist/data/playground/function/stop.mcfunction +5 -0
  15. package/dist/data/playground/function/stop__say_macro_t0.mcfunction +1 -0
  16. package/dist/data/stdlib_queue8_test/function/__queue_append_apply.mcfunction +4 -0
  17. package/dist/data/stdlib_queue8_test/function/__queue_peek_apply.mcfunction +4 -0
  18. package/dist/data/stdlib_queue8_test/function/__queue_size_raw_apply.mcfunction +4 -0
  19. package/dist/data/stdlib_queue8_test/function/load.mcfunction +1 -0
  20. package/dist/data/stdlib_queue8_test/function/queue_clear.mcfunction +6 -0
  21. package/dist/data/stdlib_queue8_test/function/queue_empty__merge_1.mcfunction +5 -0
  22. package/dist/data/stdlib_queue8_test/function/queue_empty__then_0.mcfunction +5 -0
  23. package/dist/data/stdlib_queue8_test/function/queue_peek__merge_1.mcfunction +13 -0
  24. package/dist/data/stdlib_queue8_test/function/queue_peek__then_0.mcfunction +5 -0
  25. package/dist/data/stdlib_queue8_test/function/queue_pop__merge_1.mcfunction +15 -0
  26. package/dist/data/stdlib_queue8_test/function/queue_pop__then_0.mcfunction +5 -0
  27. package/dist/data/stdlib_queue8_test/function/queue_push__const_11.mcfunction +6 -0
  28. package/dist/data/stdlib_queue8_test/function/queue_push__const_22.mcfunction +6 -0
  29. package/dist/data/stdlib_queue8_test/function/queue_size.mcfunction +13 -0
  30. package/dist/data/stdlib_queue8_test/function/test_queue_push_and_size.mcfunction +13 -0
  31. package/dist/data/test/function/load.mcfunction +1 -0
  32. package/dist/data/test/function/say_at.mcfunction +6 -0
  33. package/dist/data/test/function/test.mcfunction +4 -0
  34. package/dist/pack.mcmeta +6 -0
  35. package/dist/package.json +1 -1
  36. package/dist/src/__tests__/formatter-extra.test.d.ts +7 -0
  37. package/dist/src/__tests__/formatter-extra.test.js +123 -0
  38. package/dist/src/__tests__/global-vars.test.d.ts +13 -0
  39. package/dist/src/__tests__/global-vars.test.js +156 -0
  40. package/dist/src/__tests__/lint/new-rules.test.d.ts +9 -0
  41. package/dist/src/__tests__/lint/new-rules.test.js +402 -0
  42. package/dist/src/__tests__/lsp-rename.test.d.ts +8 -0
  43. package/dist/src/__tests__/lsp-rename.test.js +157 -0
  44. package/dist/src/__tests__/mc-integration/say-fstring.test.d.ts +11 -0
  45. package/dist/src/__tests__/mc-integration/say-fstring.test.js +220 -0
  46. package/dist/src/__tests__/mc-integration/stdlib-coverage-2.test.js +1 -1
  47. package/dist/src/__tests__/mc-integration/stdlib-coverage-3.test.js +1 -1
  48. package/dist/src/__tests__/mc-integration/stdlib-coverage-4.test.js +1 -1
  49. package/dist/src/__tests__/mc-integration/stdlib-coverage-5.test.js +1 -1
  50. package/dist/src/__tests__/mc-integration/stdlib-coverage-6.test.js +1 -1
  51. package/dist/src/__tests__/mc-integration/stdlib-coverage-7.test.js +1 -1
  52. package/dist/src/__tests__/mc-integration/stdlib-coverage-8.test.js +1 -1
  53. package/dist/src/__tests__/mc-syntax.test.js +4 -1
  54. package/dist/src/__tests__/monomorphize-coverage.test.d.ts +9 -0
  55. package/dist/src/__tests__/monomorphize-coverage.test.js +204 -0
  56. package/dist/src/__tests__/optimizer-cse.test.d.ts +7 -0
  57. package/dist/src/__tests__/optimizer-cse.test.js +226 -0
  58. package/dist/src/__tests__/parser.test.js +4 -13
  59. package/dist/src/__tests__/repl-server-extra.test.js +6 -7
  60. package/dist/src/__tests__/repl-server.test.js +5 -7
  61. package/dist/src/__tests__/stdlib/queue.test.js +6 -6
  62. package/dist/src/cli.js +0 -0
  63. package/dist/src/lexer/index.js +2 -1
  64. package/dist/src/lint/index.d.ts +12 -5
  65. package/dist/src/lint/index.js +730 -5
  66. package/dist/src/lsp/main.js +0 -0
  67. package/dist/src/mc-test/client.d.ts +21 -0
  68. package/dist/src/mc-test/client.js +34 -0
  69. package/dist/src/mir/lower.js +108 -6
  70. package/dist/src/optimizer/interprocedural.js +37 -2
  71. package/dist/src/parser/decl-parser.d.ts +19 -0
  72. package/dist/src/parser/decl-parser.js +323 -0
  73. package/dist/src/parser/expr-parser.d.ts +46 -0
  74. package/dist/src/parser/expr-parser.js +759 -0
  75. package/dist/src/parser/index.d.ts +8 -129
  76. package/dist/src/parser/index.js +13 -2262
  77. package/dist/src/parser/stmt-parser.d.ts +28 -0
  78. package/dist/src/parser/stmt-parser.js +577 -0
  79. package/dist/src/parser/type-parser.d.ts +20 -0
  80. package/dist/src/parser/type-parser.js +257 -0
  81. package/dist/src/parser/utils.d.ts +34 -0
  82. package/dist/src/parser/utils.js +141 -0
  83. package/docs/dev/README-mc-integration-tests.md +141 -0
  84. package/docs/lint-rules.md +162 -0
  85. package/docs/stdlib/bigint.md +2 -0
  86. package/editors/vscode/README.md +63 -41
  87. package/editors/vscode/out/extension.js +1881 -1776
  88. package/editors/vscode/out/lsp-server.js +4257 -3651
  89. package/editors/vscode/package-lock.json +3 -3
  90. package/editors/vscode/package.json +1 -1
  91. package/examples/loops-demo.mcrs +87 -0
  92. package/package.json +1 -1
  93. package/redscript-docs/docs/en/stdlib/advanced.md +629 -0
  94. package/redscript-docs/docs/en/stdlib/bigint.md +316 -0
  95. package/redscript-docs/docs/en/stdlib/bits.md +292 -0
  96. package/redscript-docs/docs/en/stdlib/bossbar.md +177 -0
  97. package/redscript-docs/docs/en/stdlib/calculus.md +289 -0
  98. package/redscript-docs/docs/en/stdlib/color.md +353 -0
  99. package/redscript-docs/docs/en/stdlib/combat.md +88 -0
  100. package/redscript-docs/docs/en/stdlib/cooldown.md +82 -0
  101. package/redscript-docs/docs/en/stdlib/dialog.md +155 -0
  102. package/redscript-docs/docs/en/stdlib/easing.md +558 -0
  103. package/redscript-docs/docs/en/stdlib/ecs.md +475 -0
  104. package/redscript-docs/docs/en/stdlib/effects.md +324 -0
  105. package/redscript-docs/docs/en/stdlib/events.md +3 -0
  106. package/redscript-docs/docs/en/stdlib/expr.md +45 -0
  107. package/redscript-docs/docs/en/stdlib/fft.md +141 -0
  108. package/redscript-docs/docs/en/stdlib/geometry.md +430 -0
  109. package/redscript-docs/docs/en/stdlib/graph.md +259 -0
  110. package/redscript-docs/docs/en/stdlib/heap.md +185 -0
  111. package/redscript-docs/docs/en/stdlib/interactions.md +179 -0
  112. package/redscript-docs/docs/en/stdlib/inventory.md +97 -0
  113. package/redscript-docs/docs/en/stdlib/linalg.md +557 -0
  114. package/redscript-docs/docs/en/stdlib/list.md +559 -0
  115. package/redscript-docs/docs/en/stdlib/map.md +140 -0
  116. package/redscript-docs/docs/en/stdlib/math.md +193 -0
  117. package/redscript-docs/docs/en/stdlib/math_hp.md +149 -0
  118. package/redscript-docs/docs/en/stdlib/matrix.md +403 -0
  119. package/redscript-docs/docs/en/stdlib/mobs.md +965 -0
  120. package/redscript-docs/docs/en/stdlib/noise.md +244 -0
  121. package/redscript-docs/docs/en/stdlib/ode.md +253 -0
  122. package/redscript-docs/docs/en/stdlib/parabola.md +342 -0
  123. package/redscript-docs/docs/en/stdlib/particles.md +311 -0
  124. package/redscript-docs/docs/en/stdlib/pathfind.md +255 -0
  125. package/redscript-docs/docs/en/stdlib/physics.md +493 -0
  126. package/redscript-docs/docs/en/stdlib/player.md +78 -0
  127. package/redscript-docs/docs/en/stdlib/quaternion.md +673 -0
  128. package/redscript-docs/docs/en/stdlib/queue.md +134 -0
  129. package/redscript-docs/docs/en/stdlib/random.md +223 -0
  130. package/redscript-docs/docs/en/stdlib/result.md +143 -0
  131. package/redscript-docs/docs/en/stdlib/scheduler.md +183 -0
  132. package/redscript-docs/docs/en/stdlib/set_int.md +190 -0
  133. package/redscript-docs/docs/en/stdlib/sets.md +101 -0
  134. package/redscript-docs/docs/en/stdlib/signal.md +400 -0
  135. package/redscript-docs/docs/en/stdlib/sort.md +104 -0
  136. package/redscript-docs/docs/en/stdlib/spawn.md +147 -0
  137. package/redscript-docs/docs/en/stdlib/state.md +142 -0
  138. package/redscript-docs/docs/en/stdlib/strings.md +154 -0
  139. package/redscript-docs/docs/en/stdlib/tags.md +3451 -0
  140. package/redscript-docs/docs/en/stdlib/teams.md +153 -0
  141. package/redscript-docs/docs/en/stdlib/timer.md +246 -0
  142. package/redscript-docs/docs/en/stdlib/vec.md +158 -0
  143. package/redscript-docs/docs/en/stdlib/world.md +298 -0
  144. package/redscript-docs/docs/zh/stdlib/advanced.md +615 -0
  145. package/redscript-docs/docs/zh/stdlib/bigint.md +316 -0
  146. package/redscript-docs/docs/zh/stdlib/bits.md +292 -0
  147. package/redscript-docs/docs/zh/stdlib/bossbar.md +170 -0
  148. package/redscript-docs/docs/zh/stdlib/calculus.md +287 -0
  149. package/redscript-docs/docs/zh/stdlib/color.md +353 -0
  150. package/redscript-docs/docs/zh/stdlib/combat.md +88 -0
  151. package/redscript-docs/docs/zh/stdlib/cooldown.md +84 -0
  152. package/redscript-docs/docs/zh/stdlib/dialog.md +152 -0
  153. package/redscript-docs/docs/zh/stdlib/easing.md +558 -0
  154. package/redscript-docs/docs/zh/stdlib/ecs.md +472 -0
  155. package/redscript-docs/docs/zh/stdlib/effects.md +324 -0
  156. package/redscript-docs/docs/zh/stdlib/events.md +3 -0
  157. package/redscript-docs/docs/zh/stdlib/expr.md +37 -0
  158. package/redscript-docs/docs/zh/stdlib/fft.md +128 -0
  159. package/redscript-docs/docs/zh/stdlib/geometry.md +430 -0
  160. package/redscript-docs/docs/zh/stdlib/graph.md +259 -0
  161. package/redscript-docs/docs/zh/stdlib/heap.md +185 -0
  162. package/redscript-docs/docs/zh/stdlib/interactions.md +160 -0
  163. package/redscript-docs/docs/zh/stdlib/inventory.md +94 -0
  164. package/redscript-docs/docs/zh/stdlib/linalg.md +543 -0
  165. package/redscript-docs/docs/zh/stdlib/list.md +561 -0
  166. package/redscript-docs/docs/zh/stdlib/map.md +132 -0
  167. package/redscript-docs/docs/zh/stdlib/math.md +193 -0
  168. package/redscript-docs/docs/zh/stdlib/math_hp.md +143 -0
  169. package/redscript-docs/docs/zh/stdlib/matrix.md +396 -0
  170. package/redscript-docs/docs/zh/stdlib/mobs.md +965 -0
  171. package/redscript-docs/docs/zh/stdlib/noise.md +244 -0
  172. package/redscript-docs/docs/zh/stdlib/ode.md +243 -0
  173. package/redscript-docs/docs/zh/stdlib/parabola.md +337 -0
  174. package/redscript-docs/docs/zh/stdlib/particles.md +307 -0
  175. package/redscript-docs/docs/zh/stdlib/pathfind.md +255 -0
  176. package/redscript-docs/docs/zh/stdlib/physics.md +493 -0
  177. package/redscript-docs/docs/zh/stdlib/player.md +78 -0
  178. package/redscript-docs/docs/zh/stdlib/quaternion.md +669 -0
  179. package/redscript-docs/docs/zh/stdlib/queue.md +124 -0
  180. package/redscript-docs/docs/zh/stdlib/random.md +222 -0
  181. package/redscript-docs/docs/zh/stdlib/result.md +147 -0
  182. package/redscript-docs/docs/zh/stdlib/scheduler.md +173 -0
  183. package/redscript-docs/docs/zh/stdlib/set_int.md +180 -0
  184. package/redscript-docs/docs/zh/stdlib/sets.md +107 -0
  185. package/redscript-docs/docs/zh/stdlib/signal.md +373 -0
  186. package/redscript-docs/docs/zh/stdlib/sort.md +104 -0
  187. package/redscript-docs/docs/zh/stdlib/spawn.md +142 -0
  188. package/redscript-docs/docs/zh/stdlib/state.md +134 -0
  189. package/redscript-docs/docs/zh/stdlib/strings.md +107 -0
  190. package/redscript-docs/docs/zh/stdlib/tags.md +3451 -0
  191. package/redscript-docs/docs/zh/stdlib/teams.md +150 -0
  192. package/redscript-docs/docs/zh/stdlib/timer.md +254 -0
  193. package/redscript-docs/docs/zh/stdlib/vec.md +158 -0
  194. package/redscript-docs/docs/zh/stdlib/world.md +289 -0
  195. package/src/__tests__/formatter-extra.test.ts +139 -0
  196. package/src/__tests__/global-vars.test.ts +171 -0
  197. package/src/__tests__/lint/new-rules.test.ts +437 -0
  198. package/src/__tests__/lsp-rename.test.ts +171 -0
  199. package/src/__tests__/mc-integration/say-fstring.test.ts +211 -0
  200. package/src/__tests__/mc-integration/stdlib-coverage-2.test.ts +1 -1
  201. package/src/__tests__/mc-integration/stdlib-coverage-3.test.ts +1 -1
  202. package/src/__tests__/mc-integration/stdlib-coverage-4.test.ts +1 -1
  203. package/src/__tests__/mc-integration/stdlib-coverage-5.test.ts +1 -1
  204. package/src/__tests__/mc-integration/stdlib-coverage-6.test.ts +1 -1
  205. package/src/__tests__/mc-integration/stdlib-coverage-7.test.ts +1 -1
  206. package/src/__tests__/mc-integration/stdlib-coverage-8.test.ts +1 -1
  207. package/src/__tests__/mc-syntax.test.ts +3 -0
  208. package/src/__tests__/monomorphize-coverage.test.ts +220 -0
  209. package/src/__tests__/optimizer-cse.test.ts +250 -0
  210. package/src/__tests__/parser.test.ts +4 -13
  211. package/src/__tests__/repl-server-extra.test.ts +6 -6
  212. package/src/__tests__/repl-server.test.ts +5 -6
  213. package/src/__tests__/stdlib/queue.test.ts +6 -6
  214. package/src/lexer/index.ts +2 -1
  215. package/src/lint/index.ts +713 -5
  216. package/src/mc-test/client.ts +40 -0
  217. package/src/mir/lower.ts +111 -2
  218. package/src/optimizer/interprocedural.ts +40 -2
  219. package/src/parser/decl-parser.ts +349 -0
  220. package/src/parser/expr-parser.ts +838 -0
  221. package/src/parser/index.ts +17 -2558
  222. package/src/parser/stmt-parser.ts +585 -0
  223. package/src/parser/type-parser.ts +276 -0
  224. package/src/parser/utils.ts +173 -0
  225. package/src/stdlib/queue.mcrs +19 -6
@@ -0,0 +1,838 @@
1
+ /**
2
+ * ExprParser — expression parsing (binary, unary, postfix, primary).
3
+ * Extends TypeParser so expression methods can call type methods.
4
+ */
5
+
6
+ import { Lexer } from '../lexer'
7
+ import type { Token } from '../lexer'
8
+ import type {
9
+ Expr, LiteralExpr, LambdaParam, TypeNode,
10
+ RangeExpr, SelectorFilter, EntitySelector, SelectorKind,
11
+ BlockPosExpr, CoordComponent, EntityTypeName, AssignOp,
12
+ } from '../ast/types'
13
+ import type { BinOp, CmpOp } from '../ast/types'
14
+ import { TypeParser } from './type-parser'
15
+ import { BINARY_OPS, PRECEDENCE } from './utils'
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // Entity type name set
19
+ // ---------------------------------------------------------------------------
20
+
21
+ const ENTITY_TYPE_NAMES = new Set<EntityTypeName>([
22
+ 'entity', 'Player', 'Mob', 'HostileMob', 'PassiveMob', 'Zombie', 'Skeleton',
23
+ 'Creeper', 'Spider', 'Enderman', 'Blaze', 'Witch', 'Slime', 'ZombieVillager',
24
+ 'Husk', 'Drowned', 'Stray', 'WitherSkeleton', 'CaveSpider', 'Pig', 'Cow',
25
+ 'Sheep', 'Chicken', 'Villager', 'WanderingTrader', 'ArmorStand', 'Item', 'Arrow',
26
+ ])
27
+
28
+ function computeIsSingle(raw: string): boolean {
29
+ if (/^@[spr](\[|$)/.test(raw)) return true
30
+ if (/[\[,\s]limit=1[,\]\s]/.test(raw)) return true
31
+ return false
32
+ }
33
+
34
+ export class ExprParser extends TypeParser {
35
+ // -------------------------------------------------------------------------
36
+ // Expressions (Precedence Climbing)
37
+ // -------------------------------------------------------------------------
38
+
39
+ parseExpr(): Expr {
40
+ return this.parseAssignment()
41
+ }
42
+
43
+ private parseAssignment(): Expr {
44
+ const left = this.parseBinaryExpr(1)
45
+
46
+ const token = this.peek()
47
+ if (token.kind === '=' || token.kind === '+=' || token.kind === '-=' ||
48
+ token.kind === '*=' || token.kind === '/=' || token.kind === '%=') {
49
+ const op = this.advance().kind as AssignOp
50
+
51
+ if (left.kind === 'ident') {
52
+ const value = this.parseAssignment()
53
+ return this.withLoc({ kind: 'assign', target: left.name, op, value }, this.getLocToken(left) ?? token)
54
+ }
55
+
56
+ if (left.kind === 'member') {
57
+ const value = this.parseAssignment()
58
+ return this.withLoc(
59
+ { kind: 'member_assign', obj: left.obj, field: left.field, op, value },
60
+ this.getLocToken(left) ?? token
61
+ )
62
+ }
63
+
64
+ if (left.kind === 'index') {
65
+ const value = this.parseAssignment()
66
+ return this.withLoc(
67
+ { kind: 'index_assign', obj: left.obj, index: left.index, op, value },
68
+ this.getLocToken(left) ?? token
69
+ )
70
+ }
71
+ }
72
+
73
+ return left
74
+ }
75
+
76
+ private parseBinaryExpr(minPrec: number): Expr {
77
+ let left = this.parseUnaryExpr()
78
+
79
+ while (true) {
80
+ const op = this.peek().kind
81
+ if (!BINARY_OPS.has(op)) break
82
+
83
+ const prec = PRECEDENCE[op]
84
+ if (prec < minPrec) break
85
+
86
+ const opToken = this.advance()
87
+ if (op === 'is') {
88
+ const entityType = this.parseEntityTypeName()
89
+ left = this.withLoc(
90
+ { kind: 'is_check', expr: left, entityType },
91
+ this.getLocToken(left) ?? opToken
92
+ )
93
+ continue
94
+ }
95
+
96
+ const right = this.parseBinaryExpr(prec + 1)
97
+ left = this.withLoc(
98
+ { kind: 'binary', op: op as BinOp | CmpOp | '&&' | '||', left, right },
99
+ this.getLocToken(left) ?? opToken
100
+ )
101
+ }
102
+
103
+ return left
104
+ }
105
+
106
+ parseUnaryExpr(): Expr {
107
+ if (this.match('!')) {
108
+ const bangToken = this.tokens[this.pos - 1]
109
+ const operand = this.parseUnaryExpr()
110
+ return this.withLoc({ kind: 'unary', op: '!', operand }, bangToken)
111
+ }
112
+
113
+ if (this.check('-') && !this.isSubtraction()) {
114
+ const minusToken = this.advance()
115
+ const operand = this.parseUnaryExpr()
116
+ return this.withLoc({ kind: 'unary', op: '-', operand }, minusToken)
117
+ }
118
+
119
+ return this.parsePostfixExpr()
120
+ }
121
+
122
+ private parseEntityTypeName(): EntityTypeName {
123
+ const token = this.expect('ident')
124
+ if (ENTITY_TYPE_NAMES.has(token.value as EntityTypeName)) {
125
+ return token.value as EntityTypeName
126
+ }
127
+ this.error(`Unknown entity type '${token.value}'. Valid types: ${[...ENTITY_TYPE_NAMES].slice(0, 6).join(', ')}, ...`)
128
+ }
129
+
130
+ private isSubtraction(): boolean {
131
+ if (this.pos === 0) return false
132
+ const prev = this.tokens[this.pos - 1]
133
+ return ['int_lit', 'float_lit', 'ident', ')', ']'].includes(prev.kind)
134
+ }
135
+
136
+ private parsePostfixExpr(): Expr {
137
+ let expr = this.parsePrimaryExpr()
138
+
139
+ while (true) {
140
+ // Generic call: ident<Type>(args)
141
+ if (expr.kind === 'ident' && this.check('<')) {
142
+ const typeArgs = this.tryParseTypeArgs()
143
+ if (typeArgs !== null && this.check('(')) {
144
+ const openParenToken = this.peek()
145
+ this.advance() // consume '('
146
+ const args = this.parseArgs()
147
+ this.expect(')')
148
+ expr = this.withLoc(
149
+ { kind: 'call', fn: expr.name, args, typeArgs },
150
+ this.getLocToken(expr) ?? openParenToken
151
+ )
152
+ continue
153
+ }
154
+ }
155
+
156
+ // Function call
157
+ if (this.match('(')) {
158
+ const openParenToken = this.tokens[this.pos - 1]
159
+ if (expr.kind === 'ident') {
160
+ const args = this.parseArgs()
161
+ this.expect(')')
162
+ expr = this.withLoc({ kind: 'call', fn: expr.name, args }, this.getLocToken(expr) ?? openParenToken)
163
+ continue
164
+ }
165
+ if (expr.kind === 'member') {
166
+ if (expr.field === 'unwrap_or') {
167
+ const defaultExpr = this.parseExpr()
168
+ this.expect(')')
169
+ expr = this.withLoc(
170
+ { kind: 'unwrap_or', opt: expr.obj, default_: defaultExpr },
171
+ this.getLocToken(expr) ?? openParenToken
172
+ )
173
+ continue
174
+ }
175
+
176
+ const methodMap: Record<string, string> = {
177
+ 'tag': '__entity_tag',
178
+ 'untag': '__entity_untag',
179
+ 'has_tag': '__entity_has_tag',
180
+ 'push': '__array_push',
181
+ 'pop': '__array_pop',
182
+ 'add': 'set_add',
183
+ 'contains': 'set_contains',
184
+ 'remove': 'set_remove',
185
+ 'clear': 'set_clear',
186
+ }
187
+ const internalFn = methodMap[expr.field]
188
+ if (internalFn) {
189
+ const args = this.parseArgs()
190
+ this.expect(')')
191
+ expr = this.withLoc(
192
+ { kind: 'call', fn: internalFn, args: [expr.obj, ...args] },
193
+ this.getLocToken(expr) ?? openParenToken
194
+ )
195
+ continue
196
+ }
197
+ const args = this.parseArgs()
198
+ this.expect(')')
199
+ expr = this.withLoc(
200
+ { kind: 'call', fn: expr.field, args: [expr.obj, ...args] },
201
+ this.getLocToken(expr) ?? openParenToken
202
+ )
203
+ continue
204
+ }
205
+ const args = this.parseArgs()
206
+ this.expect(')')
207
+ expr = this.withLoc(
208
+ { kind: 'invoke', callee: expr, args },
209
+ this.getLocToken(expr) ?? openParenToken
210
+ )
211
+ continue
212
+ }
213
+
214
+ // Array index access
215
+ if (this.match('[')) {
216
+ const index = this.parseExpr()
217
+ this.expect(']')
218
+ expr = this.withLoc(
219
+ { kind: 'index', obj: expr, index },
220
+ this.getLocToken(expr) ?? this.tokens[this.pos - 1]
221
+ )
222
+ continue
223
+ }
224
+
225
+ // Member access
226
+ if (this.match('.')) {
227
+ const field = this.expect('ident').value
228
+ expr = this.withLoc(
229
+ { kind: 'member', obj: expr, field },
230
+ this.getLocToken(expr) ?? this.tokens[this.pos - 1]
231
+ )
232
+ continue
233
+ }
234
+
235
+ // Type cast: expr as Type
236
+ if (this.check('as') && this.isTypeCastAs()) {
237
+ const asToken = this.advance()
238
+ const targetType = this.parseType()
239
+ expr = this.withLoc(
240
+ { kind: 'type_cast', expr, targetType },
241
+ this.getLocToken(expr) ?? asToken
242
+ )
243
+ continue
244
+ }
245
+
246
+ break
247
+ }
248
+
249
+ return expr
250
+ }
251
+
252
+ private isTypeCastAs(): boolean {
253
+ const next = this.tokens[this.pos + 1]
254
+ if (!next) return false
255
+ const typeStartTokens = new Set(['int', 'bool', 'float', 'fixed', 'string', 'void', 'BlockPos', '('])
256
+ if (typeStartTokens.has(next.kind)) return true
257
+ if (next.kind === 'ident' && (
258
+ next.value === 'double' || next.value === 'byte' || next.value === 'short' ||
259
+ next.value === 'long' || next.value === 'selector' || next.value === 'Option'
260
+ )) return true
261
+ return false
262
+ }
263
+
264
+ parseArgs(): Expr[] {
265
+ const args: Expr[] = []
266
+ if (!this.check(')')) {
267
+ do {
268
+ args.push(this.parseExpr())
269
+ } while (this.match(','))
270
+ }
271
+ return args
272
+ }
273
+
274
+ parsePrimaryExpr(): Expr {
275
+ const token = this.peek()
276
+
277
+ if (token.kind === 'ident' && this.peek(1).kind === '::') {
278
+ const typeToken = this.advance()
279
+ this.expect('::')
280
+ const memberToken = this.expect('ident')
281
+ if (this.check('(')) {
282
+ const isNamedArgs = this.peek(1).kind === 'ident' && this.peek(2).kind === ':'
283
+ if (isNamedArgs) {
284
+ this.advance() // consume '('
285
+ const args: { name: string; value: Expr }[] = []
286
+ while (!this.check(')') && !this.check('eof')) {
287
+ const fieldName = this.expect('ident').value
288
+ this.expect(':')
289
+ const value = this.parseExpr()
290
+ args.push({ name: fieldName, value })
291
+ if (!this.match(',')) break
292
+ }
293
+ this.expect(')')
294
+ return this.withLoc({ kind: 'enum_construct', enumName: typeToken.value, variant: memberToken.value, args }, typeToken)
295
+ }
296
+ this.advance() // consume '('
297
+ const args = this.parseArgs()
298
+ this.expect(')')
299
+ return this.withLoc({ kind: 'static_call', type: typeToken.value, method: memberToken.value, args }, typeToken)
300
+ }
301
+ return this.withLoc({ kind: 'path_expr', enumName: typeToken.value, variant: memberToken.value }, typeToken)
302
+ }
303
+
304
+ if (token.kind === 'ident' && this.peek(1).kind === '=>') {
305
+ return this.parseSingleParamLambda()
306
+ }
307
+
308
+ if (token.kind === 'int_lit') {
309
+ this.advance()
310
+ return this.withLoc({ kind: 'int_lit', value: parseInt(token.value, 10) }, token)
311
+ }
312
+
313
+ if (token.kind === 'float_lit') {
314
+ this.advance()
315
+ return this.withLoc({ kind: 'float_lit', value: parseFloat(token.value) }, token)
316
+ }
317
+
318
+ if (token.kind === 'rel_coord') {
319
+ this.advance()
320
+ return this.withLoc({ kind: 'rel_coord', value: token.value }, token)
321
+ }
322
+
323
+ if (token.kind === 'local_coord') {
324
+ this.advance()
325
+ return this.withLoc({ kind: 'local_coord', value: token.value }, token)
326
+ }
327
+
328
+ if (token.kind === 'byte_lit') {
329
+ this.advance()
330
+ return this.withLoc({ kind: 'byte_lit', value: parseInt(token.value.slice(0, -1), 10) }, token)
331
+ }
332
+ if (token.kind === 'short_lit') {
333
+ this.advance()
334
+ return this.withLoc({ kind: 'short_lit', value: parseInt(token.value.slice(0, -1), 10) }, token)
335
+ }
336
+ if (token.kind === 'long_lit') {
337
+ this.advance()
338
+ return this.withLoc({ kind: 'long_lit', value: parseInt(token.value.slice(0, -1), 10) }, token)
339
+ }
340
+ if (token.kind === 'double_lit') {
341
+ this.advance()
342
+ return this.withLoc({ kind: 'double_lit', value: parseFloat(token.value.slice(0, -1)) }, token)
343
+ }
344
+
345
+ if (token.kind === 'string_lit') {
346
+ this.advance()
347
+ return this.parseStringExpr(token)
348
+ }
349
+
350
+ if (token.kind === 'f_string') {
351
+ this.advance()
352
+ return this.parseFStringExpr(token)
353
+ }
354
+
355
+ if (token.kind === 'mc_name') {
356
+ this.advance()
357
+ return this.withLoc({ kind: 'mc_name', value: token.value.slice(1) }, token)
358
+ }
359
+
360
+ if (token.kind === 'true') {
361
+ this.advance()
362
+ return this.withLoc({ kind: 'bool_lit', value: true }, token)
363
+ }
364
+ if (token.kind === 'false') {
365
+ this.advance()
366
+ return this.withLoc({ kind: 'bool_lit', value: false }, token)
367
+ }
368
+
369
+ if (token.kind === 'range_lit') {
370
+ this.advance()
371
+ return this.withLoc({ kind: 'range_lit', range: this.parseRangeValue(token.value) }, token)
372
+ }
373
+
374
+ if (token.kind === 'selector') {
375
+ this.advance()
376
+ return this.withLoc({
377
+ kind: 'selector',
378
+ raw: token.value,
379
+ isSingle: computeIsSingle(token.value),
380
+ sel: this.parseSelectorValue(token.value),
381
+ }, token)
382
+ }
383
+
384
+ if (token.kind === 'ident' && this.peek(1).kind === '{' &&
385
+ this.peek(2).kind === 'ident' && this.peek(3).kind === ':') {
386
+ this.advance()
387
+ return this.parseStructLit()
388
+ }
389
+
390
+ if (token.kind === 'ident' && token.value === 'Some' && this.peek(1).kind === '(') {
391
+ this.advance()
392
+ this.advance()
393
+ const value = this.parseExpr()
394
+ this.expect(')')
395
+ return this.withLoc({ kind: 'some_lit', value }, token)
396
+ }
397
+
398
+ if (token.kind === 'ident' && token.value === 'None') {
399
+ this.advance()
400
+ return this.withLoc({ kind: 'none_lit' }, token)
401
+ }
402
+
403
+ if (token.kind === 'ident') {
404
+ this.advance()
405
+ return this.withLoc({ kind: 'ident', name: token.value }, token)
406
+ }
407
+
408
+ if (token.kind === '(') {
409
+ if (this.isBlockPosLiteral()) {
410
+ return this.parseBlockPos()
411
+ }
412
+ if (this.isLambdaStart()) {
413
+ return this.parseLambdaExpr()
414
+ }
415
+ this.advance()
416
+ const first = this.parseExpr()
417
+ if (this.match(',')) {
418
+ const elements: Expr[] = [first]
419
+ if (!this.check(')')) {
420
+ do {
421
+ elements.push(this.parseExpr())
422
+ } while (this.match(','))
423
+ }
424
+ this.expect(')')
425
+ return this.withLoc({ kind: 'tuple_lit', elements }, token)
426
+ }
427
+ this.expect(')')
428
+ return first
429
+ }
430
+
431
+ if (token.kind === '{') {
432
+ return this.parseStructLit()
433
+ }
434
+
435
+ if (token.kind === '[') {
436
+ return this.parseArrayLit()
437
+ }
438
+
439
+ this.error(`Unexpected token '${token.value || token.kind}'. Expected an expression (identifier, literal, '(', '[', or '{')`)
440
+ }
441
+
442
+ parseLiteralExpr(): LiteralExpr {
443
+ if (this.check('-')) {
444
+ this.advance()
445
+ const token = this.peek()
446
+ if (token.kind === 'int_lit') {
447
+ this.advance()
448
+ return this.withLoc({ kind: 'int_lit', value: -Number(token.value) }, token)
449
+ }
450
+ if (token.kind === 'float_lit') {
451
+ this.advance()
452
+ return this.withLoc({ kind: 'float_lit', value: -Number(token.value) }, token)
453
+ }
454
+ this.error('Expected number after unary minus (-). Const values must be numeric or string literals')
455
+ }
456
+ const expr = this.parsePrimaryExpr()
457
+ if (
458
+ expr.kind === 'int_lit' ||
459
+ expr.kind === 'float_lit' ||
460
+ expr.kind === 'bool_lit' ||
461
+ expr.kind === 'str_lit'
462
+ ) {
463
+ return expr
464
+ }
465
+ this.error('Const value must be a literal')
466
+ }
467
+
468
+ // -------------------------------------------------------------------------
469
+ // Lambda
470
+ // -------------------------------------------------------------------------
471
+
472
+ parseSingleParamLambda(): Expr {
473
+ const paramToken = this.expect('ident')
474
+ const params: LambdaParam[] = [{ name: paramToken.value }]
475
+ this.expect('=>')
476
+ return this.finishLambdaExpr(params, paramToken)
477
+ }
478
+
479
+ parseLambdaExpr(): Expr {
480
+ const openParenToken = this.expect('(')
481
+ const params: LambdaParam[] = []
482
+
483
+ if (!this.check(')')) {
484
+ do {
485
+ const name = this.expect('ident').value
486
+ let type: TypeNode | undefined
487
+ if (this.match(':')) {
488
+ type = this.parseType()
489
+ }
490
+ params.push({ name, type })
491
+ } while (this.match(','))
492
+ }
493
+
494
+ this.expect(')')
495
+ let returnType: TypeNode | undefined
496
+ if (this.match('->')) {
497
+ returnType = this.parseType()
498
+ }
499
+ this.expect('=>')
500
+ return this.finishLambdaExpr(params, openParenToken, returnType)
501
+ }
502
+
503
+ private finishLambdaExpr(params: LambdaParam[], token: Token, returnType?: TypeNode): Expr {
504
+ const body = this.check('{') ? (this as any).parseBlock() : this.parseExpr()
505
+ return this.withLoc({ kind: 'lambda', params, returnType, body }, token)
506
+ }
507
+
508
+ // -------------------------------------------------------------------------
509
+ // String interpolation
510
+ // -------------------------------------------------------------------------
511
+
512
+ private parseStringExpr(token: Token): Expr {
513
+ // Plain string literals: no interpolation. "${...}" is treated as literal text.
514
+ // Only f"..." strings (f_string token) support {expr} interpolation.
515
+ return this.withLoc({ kind: 'str_lit', value: token.value }, token)
516
+ }
517
+
518
+ private parseFStringExpr(token: Token): Expr {
519
+ const parts: Array<{ kind: 'text'; value: string } | { kind: 'expr'; expr: Expr }> = []
520
+ let current = ''
521
+ let index = 0
522
+
523
+ while (index < token.value.length) {
524
+ if (token.value[index] === '{') {
525
+ if (current) {
526
+ parts.push({ kind: 'text', value: current })
527
+ current = ''
528
+ }
529
+
530
+ index++
531
+ let depth = 1
532
+ let exprSource = ''
533
+ let inString = false
534
+
535
+ while (index < token.value.length && depth > 0) {
536
+ const char = token.value[index]
537
+ if (char === '"' && token.value[index - 1] !== '\\') {
538
+ inString = !inString
539
+ }
540
+ if (!inString) {
541
+ if (char === '{') depth++
542
+ else if (char === '}') {
543
+ depth--
544
+ if (depth === 0) { index++; break }
545
+ }
546
+ }
547
+ if (depth > 0) exprSource += char
548
+ index++
549
+ }
550
+
551
+ if (depth !== 0) this.error('Unterminated f-string interpolation')
552
+ parts.push({ kind: 'expr', expr: this.parseEmbeddedExpr(exprSource) })
553
+ continue
554
+ }
555
+ current += token.value[index]
556
+ index++
557
+ }
558
+
559
+ if (current) parts.push({ kind: 'text', value: current })
560
+ return this.withLoc({ kind: 'f_string', parts }, token)
561
+ }
562
+
563
+ private parseEmbeddedExpr(source: string): Expr {
564
+ // Lazy import to break circular dependency at runtime — Parser extends ExprParser
565
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
566
+ const { Parser } = require('./index') as { Parser: new (tokens: import('../lexer').Token[], source?: string, filePath?: string) => ExprParser }
567
+ const tokens = new Lexer(source, this.filePath).tokenize()
568
+ const parser = new Parser(tokens, source, this.filePath)
569
+ const expr = parser.parseExpr()
570
+ if (!parser.check('eof')) {
571
+ parser.error(`Unexpected token '${parser.peek().kind}' in string interpolation`)
572
+ }
573
+ return expr
574
+ }
575
+
576
+ // -------------------------------------------------------------------------
577
+ // Struct / Array / BlockPos literals
578
+ // -------------------------------------------------------------------------
579
+
580
+ private parseStructLit(): Expr {
581
+ const braceToken = this.expect('{')
582
+ const fields: { name: string; value: Expr }[] = []
583
+
584
+ if (!this.check('}')) {
585
+ do {
586
+ const name = this.expect('ident').value
587
+ this.expect(':')
588
+ const value = this.parseExpr()
589
+ fields.push({ name, value })
590
+ } while (this.match(','))
591
+ }
592
+
593
+ this.expect('}')
594
+ return this.withLoc({ kind: 'struct_lit', fields }, braceToken)
595
+ }
596
+
597
+ private parseArrayLit(): Expr {
598
+ const bracketToken = this.expect('[')
599
+ const elements: Expr[] = []
600
+
601
+ if (!this.check(']')) {
602
+ do {
603
+ elements.push(this.parseExpr())
604
+ } while (this.match(','))
605
+ }
606
+
607
+ this.expect(']')
608
+ return this.withLoc({ kind: 'array_lit', elements }, bracketToken)
609
+ }
610
+
611
+ private isBlockPosLiteral(): boolean {
612
+ if (!this.check('(')) return false
613
+ let offset = 1
614
+ for (let i = 0; i < 3; i++) {
615
+ const consumed = this.coordComponentTokenLength(offset)
616
+ if (consumed === 0) return false
617
+ offset += consumed
618
+ if (i < 2) {
619
+ if (this.peek(offset).kind !== ',') return false
620
+ offset += 1
621
+ }
622
+ }
623
+ return this.peek(offset).kind === ')'
624
+ }
625
+
626
+ private coordComponentTokenLength(offset: number): number {
627
+ const token = this.peek(offset)
628
+ if (token.kind === 'int_lit') return 1
629
+ if (token.kind === '-') {
630
+ return this.peek(offset + 1).kind === 'int_lit' ? 2 : 0
631
+ }
632
+ if (token.kind === 'rel_coord' || token.kind === 'local_coord') return 1
633
+ return 0
634
+ }
635
+
636
+ private parseBlockPos(): BlockPosExpr {
637
+ const openParenToken = this.expect('(')
638
+ const x = this.parseCoordComponent()
639
+ this.expect(',')
640
+ const y = this.parseCoordComponent()
641
+ this.expect(',')
642
+ const z = this.parseCoordComponent()
643
+ this.expect(')')
644
+ return this.withLoc({ kind: 'blockpos', x, y, z }, openParenToken)
645
+ }
646
+
647
+ private parseCoordComponent(): CoordComponent {
648
+ const token = this.peek()
649
+ if (token.kind === 'rel_coord') {
650
+ this.advance()
651
+ return { kind: 'relative', offset: this.parseCoordOffsetFromValue(token.value.slice(1)) }
652
+ }
653
+ if (token.kind === 'local_coord') {
654
+ this.advance()
655
+ return { kind: 'local', offset: this.parseCoordOffsetFromValue(token.value.slice(1)) }
656
+ }
657
+ return { kind: 'absolute', value: this.parseSignedCoordOffset(true) }
658
+ }
659
+
660
+ private parseCoordOffsetFromValue(value: string): number {
661
+ if (value === '' || value === undefined) return 0
662
+ return parseFloat(value)
663
+ }
664
+
665
+ private parseSignedCoordOffset(requireValue = false): number {
666
+ let sign = 1
667
+ if (this.match('-')) sign = -1
668
+ if (this.check('int_lit')) return sign * parseInt(this.advance().value, 10)
669
+ if (requireValue) this.error('Expected integer coordinate component')
670
+ return 0
671
+ }
672
+
673
+ // -------------------------------------------------------------------------
674
+ // Selector parsing (also used by stmt-parser)
675
+ // -------------------------------------------------------------------------
676
+
677
+ parseSelector(): EntitySelector {
678
+ const token = this.expect('selector')
679
+ return this.parseSelectorValue(token.value)
680
+ }
681
+
682
+ parseSelectorOrVarSelector(): { selector?: EntitySelector, varName?: string, filters?: SelectorFilter } {
683
+ if (this.check('selector')) {
684
+ return { selector: this.parseSelector() }
685
+ }
686
+ const varToken = this.expect('ident')
687
+ const varName = varToken.value
688
+ if (this.check('[')) {
689
+ this.advance()
690
+ let filterStr = ''
691
+ let depth = 1
692
+ while (depth > 0 && !this.check('eof')) {
693
+ if (this.check('[')) depth++
694
+ else if (this.check(']')) depth--
695
+ if (depth > 0) {
696
+ filterStr += this.peek().value ?? this.peek().kind
697
+ this.advance()
698
+ }
699
+ }
700
+ this.expect(']')
701
+ const filters = this.parseSelectorFilters(filterStr)
702
+ return { varName, filters }
703
+ }
704
+ return { varName }
705
+ }
706
+
707
+ parseSelectorValue(value: string): EntitySelector {
708
+ const bracketIndex = value.indexOf('[')
709
+ if (bracketIndex === -1) {
710
+ return { kind: value as SelectorKind }
711
+ }
712
+ const kind = value.slice(0, bracketIndex) as SelectorKind
713
+ const paramsStr = value.slice(bracketIndex + 1, -1)
714
+ const filters = this.parseSelectorFilters(paramsStr)
715
+ return { kind, filters }
716
+ }
717
+
718
+ private parseSelectorFilters(paramsStr: string): SelectorFilter {
719
+ const filters: SelectorFilter = {}
720
+ const parts = this.splitSelectorParams(paramsStr)
721
+
722
+ for (const part of parts) {
723
+ const eqIndex = part.indexOf('=')
724
+ if (eqIndex === -1) continue
725
+ const key = part.slice(0, eqIndex).trim()
726
+ const val = part.slice(eqIndex + 1).trim()
727
+
728
+ switch (key) {
729
+ case 'type': filters.type = val; break
730
+ case 'distance': filters.distance = this.parseRangeValue(val); break
731
+ case 'tag':
732
+ if (val.startsWith('!')) {
733
+ filters.notTag = filters.notTag ?? []
734
+ filters.notTag.push(val.slice(1))
735
+ } else {
736
+ filters.tag = filters.tag ?? []
737
+ filters.tag.push(val)
738
+ }
739
+ break
740
+ case 'limit': filters.limit = parseInt(val, 10); break
741
+ case 'sort': filters.sort = val as SelectorFilter['sort']; break
742
+ case 'nbt': filters.nbt = val; break
743
+ case 'gamemode': filters.gamemode = val; break
744
+ case 'scores': filters.scores = this.parseScoresFilter(val); break
745
+ case 'x': filters.x = this.parseRangeValue(val); break
746
+ case 'y': filters.y = this.parseRangeValue(val); break
747
+ case 'z': filters.z = this.parseRangeValue(val); break
748
+ case 'x_rotation': filters.x_rotation = this.parseRangeValue(val); break
749
+ case 'y_rotation': filters.y_rotation = this.parseRangeValue(val); break
750
+ }
751
+ }
752
+
753
+ return filters
754
+ }
755
+
756
+ private splitSelectorParams(str: string): string[] {
757
+ const parts: string[] = []
758
+ let current = ''
759
+ let depth = 0
760
+ for (const char of str) {
761
+ if (char === '{' || char === '[') depth++
762
+ else if (char === '}' || char === ']') depth--
763
+ else if (char === ',' && depth === 0) {
764
+ parts.push(current.trim())
765
+ current = ''
766
+ continue
767
+ }
768
+ current += char
769
+ }
770
+ if (current.trim()) parts.push(current.trim())
771
+ return parts
772
+ }
773
+
774
+ private parseScoresFilter(val: string): Record<string, RangeExpr> {
775
+ const scores: Record<string, RangeExpr> = {}
776
+ const inner = val.slice(1, -1)
777
+ const parts = inner.split(',')
778
+ for (const part of parts) {
779
+ const [name, range] = part.split('=').map(s => s.trim())
780
+ scores[name] = this.parseRangeValue(range)
781
+ }
782
+ return scores
783
+ }
784
+
785
+ parseRangeValue(value: string): RangeExpr {
786
+ if (value.startsWith('..=')) {
787
+ const rest = value.slice(3)
788
+ if (!rest) return {}
789
+ return { max: parseInt(rest, 10) }
790
+ }
791
+ if (value.startsWith('..')) {
792
+ const rest = value.slice(2)
793
+ if (!rest) return {}
794
+ return { max: parseInt(rest, 10) }
795
+ }
796
+ const inclIdx = value.indexOf('..=')
797
+ if (inclIdx !== -1) {
798
+ const min = parseInt(value.slice(0, inclIdx), 10)
799
+ const rest = value.slice(inclIdx + 3)
800
+ if (!rest) return { min }
801
+ return { min, max: parseInt(rest, 10) }
802
+ }
803
+ const dotIndex = value.indexOf('..')
804
+ if (dotIndex !== -1) {
805
+ const min = parseInt(value.slice(0, dotIndex), 10)
806
+ const rest = value.slice(dotIndex + 2)
807
+ if (!rest) return { min }
808
+ return { min, max: parseInt(rest, 10) }
809
+ }
810
+ const val = parseInt(value, 10)
811
+ return { min: val, max: val }
812
+ }
813
+
814
+ // -------------------------------------------------------------------------
815
+ // Coord token (used by stmt-parser for execute subcommands)
816
+ // -------------------------------------------------------------------------
817
+
818
+ parseCoordToken(): string {
819
+ const token = this.peek()
820
+ if (token.kind === 'rel_coord' || token.kind === 'local_coord' ||
821
+ token.kind === 'int_lit' || token.kind === 'float_lit' ||
822
+ token.kind === '-' || token.kind === 'ident') {
823
+ return this.advance().value
824
+ }
825
+ return this.error(`Expected coordinate, got ${token.kind}`)
826
+ }
827
+
828
+ parseBlockId(): string {
829
+ let id = this.advance().value
830
+ if (this.match(':')) id += ':' + this.advance().value
831
+ if (this.check('[')) {
832
+ id += this.advance().value
833
+ while (!this.check(']') && !this.check('eof')) id += this.advance().value
834
+ id += this.advance().value
835
+ }
836
+ return id
837
+ }
838
+ }