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,276 @@
1
+ /**
2
+ * TypeParser — parses type annotations and generic type arguments.
3
+ * Extends ParserBase to gain token navigation.
4
+ */
5
+
6
+ import type { TypeNode, Param } from '../ast/types'
7
+ import { ParserBase } from './utils'
8
+
9
+ export class TypeParser extends ParserBase {
10
+ // -------------------------------------------------------------------------
11
+ // Type Parsing
12
+ // -------------------------------------------------------------------------
13
+
14
+ parseType(): TypeNode {
15
+ const token = this.peek()
16
+ let type: TypeNode
17
+
18
+ if (token.kind === '(') {
19
+ // Disambiguate: tuple type `(T, T)` vs function type `(T) -> R`
20
+ const saved = this.pos
21
+ this.advance() // consume '('
22
+ const elements: TypeNode[] = []
23
+ if (!this.check(')')) {
24
+ do {
25
+ elements.push(this.parseType())
26
+ } while (this.match(','))
27
+ }
28
+ this.expect(')')
29
+ if (this.check('->')) {
30
+ this.pos = saved
31
+ return this.parseFunctionType()
32
+ }
33
+ return { kind: 'tuple', elements }
34
+ }
35
+
36
+ if (token.kind === 'float') {
37
+ this.advance()
38
+ const filePart = this.filePath ? `${this.filePath}:` : ''
39
+ this.warnings.push(
40
+ `[DeprecatedType] ${filePart}line ${token.line}, col ${token.col}: 'float' is deprecated, use 'fixed' instead (×10000 fixed-point)`
41
+ )
42
+ type = { kind: 'named', name: 'float' }
43
+ } else if (token.kind === 'int' || token.kind === 'bool' ||
44
+ token.kind === 'fixed' || token.kind === 'string' || token.kind === 'void' ||
45
+ token.kind === 'BlockPos') {
46
+ this.advance()
47
+ type = { kind: 'named', name: token.kind }
48
+ } else if (token.kind === 'ident') {
49
+ this.advance()
50
+ if (token.value === 'selector' && this.check('<')) {
51
+ this.advance() // consume <
52
+ const entityType = this.expect('ident').value
53
+ this.expect('>')
54
+ type = { kind: 'selector', entityType }
55
+ } else if (token.value === 'selector') {
56
+ type = { kind: 'selector' }
57
+ } else if (token.value === 'Option' && this.check('<')) {
58
+ this.advance() // consume <
59
+ const inner = this.parseType()
60
+ this.expect('>')
61
+ type = { kind: 'option', inner }
62
+ } else if (token.value === 'double' || token.value === 'byte' ||
63
+ token.value === 'short' || token.value === 'long' ||
64
+ token.value === 'format_string') {
65
+ type = { kind: 'named', name: token.value as any }
66
+ } else {
67
+ type = { kind: 'struct', name: token.value }
68
+ }
69
+ } else {
70
+ this.error(`Expected type, got '${token.value || token.kind}'. Valid types: int, float, bool, string, void, or a struct/enum name`)
71
+ }
72
+
73
+ while (this.match('[')) {
74
+ this.expect(']')
75
+ type = { kind: 'array', elem: type }
76
+ }
77
+
78
+ return type
79
+ }
80
+
81
+ parseFunctionType(): TypeNode {
82
+ this.expect('(')
83
+ const params: TypeNode[] = []
84
+
85
+ if (!this.check(')')) {
86
+ do {
87
+ params.push(this.parseType())
88
+ } while (this.match(','))
89
+ }
90
+
91
+ this.expect(')')
92
+ this.expect('->')
93
+ const returnType = this.parseType()
94
+ return { kind: 'function_type', params, return: returnType }
95
+ }
96
+
97
+ /**
98
+ * Try to parse `<Type, ...>` as explicit generic type arguments.
99
+ * Returns the parsed type list if successful, null if this looks like a comparison.
100
+ * Does NOT consume any tokens if it returns null.
101
+ */
102
+ tryParseTypeArgs(): TypeNode[] | null {
103
+ const saved = this.pos
104
+ this.advance() // consume '<'
105
+ const typeArgs: TypeNode[] = []
106
+ try {
107
+ do {
108
+ typeArgs.push(this.parseType())
109
+ } while (this.match(','))
110
+ if (!this.check('>')) {
111
+ this.pos = saved
112
+ return null
113
+ }
114
+ this.advance() // consume '>'
115
+ return typeArgs
116
+ } catch {
117
+ this.pos = saved
118
+ return null
119
+ }
120
+ }
121
+
122
+ // -------------------------------------------------------------------------
123
+ // Lambda lookahead helpers (needed by expr-parser)
124
+ // -------------------------------------------------------------------------
125
+
126
+ isLambdaStart(): boolean {
127
+ if (!this.check('(')) return false
128
+
129
+ let offset = 1
130
+ if (this.peek(offset).kind !== ')') {
131
+ while (true) {
132
+ if (this.peek(offset).kind !== 'ident') {
133
+ return false
134
+ }
135
+ offset += 1
136
+
137
+ if (this.peek(offset).kind === ':') {
138
+ offset += 1
139
+ const consumed = this.typeTokenLength(offset)
140
+ if (consumed === 0) {
141
+ return false
142
+ }
143
+ offset += consumed
144
+ }
145
+
146
+ if (this.peek(offset).kind === ',') {
147
+ offset += 1
148
+ continue
149
+ }
150
+ break
151
+ }
152
+ }
153
+
154
+ if (this.peek(offset).kind !== ')') {
155
+ return false
156
+ }
157
+ offset += 1
158
+
159
+ if (this.peek(offset).kind === '=>') {
160
+ return true
161
+ }
162
+
163
+ if (this.peek(offset).kind === '->') {
164
+ offset += 1
165
+ const consumed = this.typeTokenLength(offset)
166
+ if (consumed === 0) {
167
+ return false
168
+ }
169
+ offset += consumed
170
+ return this.peek(offset).kind === '=>'
171
+ }
172
+
173
+ return false
174
+ }
175
+
176
+ typeTokenLength(offset: number): number {
177
+ const token = this.peek(offset)
178
+
179
+ if (token.kind === '(') {
180
+ let inner = offset + 1
181
+ if (this.peek(inner).kind !== ')') {
182
+ while (true) {
183
+ const consumed = this.typeTokenLength(inner)
184
+ if (consumed === 0) {
185
+ return 0
186
+ }
187
+ inner += consumed
188
+ if (this.peek(inner).kind === ',') {
189
+ inner += 1
190
+ continue
191
+ }
192
+ break
193
+ }
194
+ }
195
+
196
+ if (this.peek(inner).kind !== ')') {
197
+ return 0
198
+ }
199
+ inner += 1
200
+
201
+ if (this.peek(inner).kind !== '->') {
202
+ return 0
203
+ }
204
+ inner += 1
205
+ const returnLen = this.typeTokenLength(inner)
206
+ return returnLen === 0 ? 0 : inner + returnLen - offset
207
+ }
208
+
209
+ const isNamedType =
210
+ token.kind === 'int' ||
211
+ token.kind === 'bool' ||
212
+ token.kind === 'float' ||
213
+ token.kind === 'fixed' ||
214
+ token.kind === 'string' ||
215
+ token.kind === 'void' ||
216
+ token.kind === 'BlockPos' ||
217
+ token.kind === 'ident'
218
+ if (!isNamedType) {
219
+ return 0
220
+ }
221
+
222
+ let length = 1
223
+ while (this.peek(offset + length).kind === '[' && this.peek(offset + length + 1).kind === ']') {
224
+ length += 2
225
+ }
226
+ return length
227
+ }
228
+
229
+ // -------------------------------------------------------------------------
230
+ // Params parsing (used by decl-parser)
231
+ // -------------------------------------------------------------------------
232
+
233
+ parseParams(implTypeName?: string): Param[] {
234
+ const params: Param[] = []
235
+
236
+ if (!this.check(')')) {
237
+ do {
238
+ const paramToken = this.expect('ident')
239
+ const name = paramToken.value
240
+ let type: TypeNode
241
+ if (implTypeName && params.length === 0 && name === 'self' && !this.check(':')) {
242
+ type = { kind: 'struct', name: implTypeName }
243
+ } else {
244
+ this.expect(':')
245
+ type = this.parseType()
246
+ }
247
+ let defaultValue: import('../ast/types').Expr | undefined
248
+ if (this.match('=')) {
249
+ defaultValue = (this as any).parseExpr()
250
+ }
251
+ params.push(this.withLoc({ name, type, default: defaultValue }, paramToken))
252
+ } while (this.match(','))
253
+ }
254
+
255
+ return params
256
+ }
257
+
258
+ parseInterfaceParams(): Param[] {
259
+ const params: Param[] = []
260
+ if (!this.check(')')) {
261
+ do {
262
+ const paramToken = this.expect('ident')
263
+ const paramName = paramToken.value
264
+ let type: TypeNode
265
+ if (params.length === 0 && paramName === 'self' && !this.check(':')) {
266
+ type = { kind: 'named', name: 'void' }
267
+ } else {
268
+ this.expect(':')
269
+ type = this.parseType()
270
+ }
271
+ params.push(this.withLoc({ name: paramName, type }, paramToken))
272
+ } while (this.match(','))
273
+ }
274
+ return params
275
+ }
276
+ }
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Parser utilities — base class with token navigation, error handling,
3
+ * and shared constants used by all sub-parsers.
4
+ */
5
+
6
+ import { Lexer, type Token, type TokenKind } from '../lexer'
7
+ import type { Span } from '../ast/types'
8
+ import { DiagnosticError } from '../diagnostics'
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Operator Precedence (higher = binds tighter)
12
+ // ---------------------------------------------------------------------------
13
+
14
+ export const PRECEDENCE: Record<string, number> = {
15
+ '||': 1,
16
+ '&&': 2,
17
+ '==': 3, '!=': 3,
18
+ '<': 4, '<=': 4, '>': 4, '>=': 4, 'is': 4,
19
+ '+': 5, '-': 5,
20
+ '*': 6, '/': 6, '%': 6,
21
+ }
22
+
23
+ export const BINARY_OPS = new Set(['||', '&&', '==', '!=', '<', '<=', '>', '>=', 'is', '+', '-', '*', '/', '%'])
24
+
25
+ export type { Lexer }
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // ParserBase — token navigation, error reporting, span attachment
29
+ // ---------------------------------------------------------------------------
30
+
31
+ export class ParserBase {
32
+ protected tokens: Token[]
33
+ protected pos: number = 0
34
+ protected sourceLines: string[]
35
+ protected filePath?: string
36
+ /** Set to true once `module library;` is seen. */
37
+ protected inLibraryMode: boolean = false
38
+ /** Warnings accumulated during parsing (e.g. deprecated keyword usage). */
39
+ readonly warnings: string[] = []
40
+ /** Parse errors collected during error-recovery mode. */
41
+ readonly parseErrors: DiagnosticError[] = []
42
+
43
+ constructor(tokens: Token[], source?: string, filePath?: string) {
44
+ this.tokens = tokens
45
+ this.sourceLines = source?.split('\n') ?? []
46
+ this.filePath = filePath
47
+ }
48
+
49
+ // -------------------------------------------------------------------------
50
+ // Token navigation
51
+ // -------------------------------------------------------------------------
52
+
53
+ peek(offset = 0): Token {
54
+ const idx = this.pos + offset
55
+ if (idx >= this.tokens.length) {
56
+ return this.tokens[this.tokens.length - 1] // eof
57
+ }
58
+ return this.tokens[idx]
59
+ }
60
+
61
+ advance(): Token {
62
+ const token = this.tokens[this.pos]
63
+ if (token.kind !== 'eof') this.pos++
64
+ return token
65
+ }
66
+
67
+ check(kind: TokenKind): boolean {
68
+ return this.peek().kind === kind
69
+ }
70
+
71
+ match(...kinds: TokenKind[]): boolean {
72
+ for (const kind of kinds) {
73
+ if (this.check(kind)) {
74
+ this.advance()
75
+ return true
76
+ }
77
+ }
78
+ return false
79
+ }
80
+
81
+ expect(kind: TokenKind): Token {
82
+ const token = this.peek()
83
+ if (token.kind !== kind) {
84
+ throw new DiagnosticError(
85
+ 'ParseError',
86
+ `Expected '${kind}' but got '${token.kind}'`,
87
+ { file: this.filePath, line: token.line, col: token.col },
88
+ this.sourceLines
89
+ )
90
+ }
91
+ return this.advance()
92
+ }
93
+
94
+ error(message: string): never {
95
+ const token = this.peek()
96
+ throw new DiagnosticError(
97
+ 'ParseError',
98
+ message,
99
+ { file: this.filePath, line: token.line, col: token.col },
100
+ this.sourceLines
101
+ )
102
+ }
103
+
104
+ withLoc<T extends object>(node: T, token: Token): T {
105
+ const span: Span = { line: token.line, col: token.col }
106
+ Object.defineProperty(node, 'span', {
107
+ value: span,
108
+ enumerable: false,
109
+ configurable: true,
110
+ writable: true,
111
+ })
112
+ return node
113
+ }
114
+
115
+ getLocToken(node: object): Token | null {
116
+ const span = (node as { span?: Span }).span
117
+ if (!span) {
118
+ return null
119
+ }
120
+ return { kind: 'eof', value: '', line: span.line, col: span.col }
121
+ }
122
+
123
+ checkIdent(value: string): boolean {
124
+ return this.check('ident') && this.peek().value === value
125
+ }
126
+
127
+ // -------------------------------------------------------------------------
128
+ // Error Recovery
129
+ // -------------------------------------------------------------------------
130
+
131
+ syncToNextDecl(): void {
132
+ const TOP_LEVEL_KEYWORDS = new Set([
133
+ 'fn', 'struct', 'impl', 'enum', 'const', 'let', 'export', 'declare', 'import', 'namespace', 'module'
134
+ ])
135
+ while (!this.check('eof')) {
136
+ const kind = this.peek().kind
137
+ if (kind === '}') {
138
+ this.advance()
139
+ return
140
+ }
141
+ if (TOP_LEVEL_KEYWORDS.has(kind)) {
142
+ return
143
+ }
144
+ if (kind === 'ident' && this.peek().value === 'import') {
145
+ return
146
+ }
147
+ this.advance()
148
+ }
149
+ }
150
+
151
+ syncToNextStmt(): void {
152
+ while (!this.check('eof')) {
153
+ const kind = this.peek().kind
154
+ if (kind === ';') {
155
+ this.advance()
156
+ return
157
+ }
158
+ if (kind === '}') {
159
+ return
160
+ }
161
+ this.advance()
162
+ }
163
+ }
164
+
165
+ // -------------------------------------------------------------------------
166
+ // Sub-parser helper (used by string interpolation)
167
+ // -------------------------------------------------------------------------
168
+
169
+ protected makeSubParser(source: string): ParserBase {
170
+ const tokens = new Lexer(source, this.filePath).tokenize()
171
+ return new ParserBase(tokens, source, this.filePath)
172
+ }
173
+ }
@@ -11,6 +11,19 @@
11
11
 
12
12
  module library;
13
13
 
14
+ // Placeholder conventions used in raw() strings:
15
+ // __OBJ__ → replaced with __<namespace> (per-namespace scoreboard objective)
16
+ // __NS__ → replaced with the current namespace
17
+ // __RS__ → replaced with "rs" (global RedScript scoreboard objective)
18
+
19
+ // ─── Initialisation ──────────────────────────────────────────────────────────
20
+
21
+ /// Ensure the shared "rs" scoreboard objective exists.
22
+ /// Called automatically on datapack load.
23
+ @load fn __queue_init() {
24
+ raw("scoreboard objectives add rs dummy");
25
+ }
26
+
14
27
  // ─── Internal macro helpers ──────────────────────────────────────────────────
15
28
 
16
29
  fn __queue_append_apply() {
@@ -18,11 +31,11 @@ fn __queue_append_apply() {
18
31
  }
19
32
 
20
33
  fn __queue_peek_apply() {
21
- raw("$execute store result score $ret __OBJ__ run data get storage rs:arrays Queue[$(idx)]");
34
+ raw("$execute store result score rs.peek_tmp __RS__ run data get storage rs:arrays Queue[$(idx)]");
22
35
  }
23
36
 
24
37
  fn __queue_size_raw_apply() {
25
- raw("$execute store result score $ret __OBJ__ run data get storage rs:arrays Queue");
38
+ raw("execute store result score $ret __OBJ__ run data get storage rs:arrays Queue");
26
39
  }
27
40
 
28
41
  // ─── Public API ──────────────────────────────────────────────────────────────
@@ -53,10 +66,10 @@ fn queue_pop(): int {
53
66
  let empty: int = queue_empty();
54
67
  if (empty == 1) { return -1; }
55
68
 
56
- // Read front element
57
- raw("scoreboard players set $ret __OBJ__ 0");
69
+ // Read front element via macro (idx → Queue[idx] → rs.peek_tmp)
58
70
  raw("execute store result storage rs:macro_args idx int 1 run scoreboard players get rs.q_head __RS__");
59
71
  raw("function __NS__:__queue_peek_apply with storage rs:macro_args");
72
+ raw("scoreboard players operation $ret __OBJ__ = rs.peek_tmp __RS__");
60
73
  // Advance head pointer
61
74
  raw("scoreboard players add rs.q_head __RS__ 1");
62
75
  raw("return 1");
@@ -75,9 +88,9 @@ fn queue_peek(): int {
75
88
  let empty: int = queue_empty();
76
89
  if (empty == 1) { return -1; }
77
90
 
78
- raw("scoreboard players set $ret __OBJ__ 0");
79
91
  raw("execute store result storage rs:macro_args idx int 1 run scoreboard players get rs.q_head __RS__");
80
92
  raw("function __NS__:__queue_peek_apply with storage rs:macro_args");
93
+ raw("scoreboard players operation $ret __OBJ__ = rs.peek_tmp __RS__");
81
94
  raw("return 1");
82
95
  return 0;
83
96
  }
@@ -92,7 +105,7 @@ fn queue_peek(): int {
92
105
  /// let n: int = queue_size()
93
106
  fn queue_size(): int {
94
107
  raw("scoreboard players set $ret __OBJ__ 0");
95
- raw("function __NS__:__queue_size_raw_apply with storage rs:macro_args");
108
+ raw("function __NS__:__queue_size_raw_apply");
96
109
  raw("scoreboard players operation $ret __OBJ__ -= rs.q_head __RS__");
97
110
  raw("return 1");
98
111
  return 0;