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.
- package/.github/workflows/ci.yml +1 -0
- package/README.md +119 -313
- package/README.zh.md +118 -314
- package/ROADMAP.md +5 -5
- package/dist/data/impl_test/function/counter/get.mcfunction +5 -0
- package/dist/data/impl_test/function/counter/inc.mcfunction +7 -0
- package/dist/data/impl_test/function/counter/new.mcfunction +4 -0
- package/dist/data/impl_test/function/load.mcfunction +1 -0
- package/dist/data/impl_test/function/test_impl.mcfunction +10 -0
- package/dist/data/minecraft/tags/function/load.json +5 -0
- package/dist/data/playground/function/load.mcfunction +1 -0
- package/dist/data/playground/function/start.mcfunction +4 -0
- package/dist/data/playground/function/start__say_macro_t1.mcfunction +1 -0
- package/dist/data/playground/function/stop.mcfunction +5 -0
- package/dist/data/playground/function/stop__say_macro_t0.mcfunction +1 -0
- package/dist/data/stdlib_queue8_test/function/__queue_append_apply.mcfunction +4 -0
- package/dist/data/stdlib_queue8_test/function/__queue_peek_apply.mcfunction +4 -0
- package/dist/data/stdlib_queue8_test/function/__queue_size_raw_apply.mcfunction +4 -0
- package/dist/data/stdlib_queue8_test/function/load.mcfunction +1 -0
- package/dist/data/stdlib_queue8_test/function/queue_clear.mcfunction +6 -0
- package/dist/data/stdlib_queue8_test/function/queue_empty__merge_1.mcfunction +5 -0
- package/dist/data/stdlib_queue8_test/function/queue_empty__then_0.mcfunction +5 -0
- package/dist/data/stdlib_queue8_test/function/queue_peek__merge_1.mcfunction +13 -0
- package/dist/data/stdlib_queue8_test/function/queue_peek__then_0.mcfunction +5 -0
- package/dist/data/stdlib_queue8_test/function/queue_pop__merge_1.mcfunction +15 -0
- package/dist/data/stdlib_queue8_test/function/queue_pop__then_0.mcfunction +5 -0
- package/dist/data/stdlib_queue8_test/function/queue_push__const_11.mcfunction +6 -0
- package/dist/data/stdlib_queue8_test/function/queue_push__const_22.mcfunction +6 -0
- package/dist/data/stdlib_queue8_test/function/queue_size.mcfunction +13 -0
- package/dist/data/stdlib_queue8_test/function/test_queue_push_and_size.mcfunction +13 -0
- package/dist/data/test/function/load.mcfunction +1 -0
- package/dist/data/test/function/say_at.mcfunction +6 -0
- package/dist/data/test/function/test.mcfunction +4 -0
- package/dist/pack.mcmeta +6 -0
- package/dist/package.json +1 -1
- package/dist/src/__tests__/formatter-extra.test.d.ts +7 -0
- package/dist/src/__tests__/formatter-extra.test.js +123 -0
- package/dist/src/__tests__/global-vars.test.d.ts +13 -0
- package/dist/src/__tests__/global-vars.test.js +156 -0
- package/dist/src/__tests__/lint/new-rules.test.d.ts +9 -0
- package/dist/src/__tests__/lint/new-rules.test.js +402 -0
- package/dist/src/__tests__/lsp-rename.test.d.ts +8 -0
- package/dist/src/__tests__/lsp-rename.test.js +157 -0
- package/dist/src/__tests__/mc-integration/say-fstring.test.d.ts +11 -0
- package/dist/src/__tests__/mc-integration/say-fstring.test.js +220 -0
- package/dist/src/__tests__/mc-integration/stdlib-coverage-2.test.js +1 -1
- package/dist/src/__tests__/mc-integration/stdlib-coverage-3.test.js +1 -1
- package/dist/src/__tests__/mc-integration/stdlib-coverage-4.test.js +1 -1
- package/dist/src/__tests__/mc-integration/stdlib-coverage-5.test.js +1 -1
- package/dist/src/__tests__/mc-integration/stdlib-coverage-6.test.js +1 -1
- package/dist/src/__tests__/mc-integration/stdlib-coverage-7.test.js +1 -1
- package/dist/src/__tests__/mc-integration/stdlib-coverage-8.test.js +1 -1
- package/dist/src/__tests__/mc-syntax.test.js +4 -1
- package/dist/src/__tests__/monomorphize-coverage.test.d.ts +9 -0
- package/dist/src/__tests__/monomorphize-coverage.test.js +204 -0
- package/dist/src/__tests__/optimizer-cse.test.d.ts +7 -0
- package/dist/src/__tests__/optimizer-cse.test.js +226 -0
- package/dist/src/__tests__/parser.test.js +4 -13
- package/dist/src/__tests__/repl-server-extra.test.js +6 -7
- package/dist/src/__tests__/repl-server.test.js +5 -7
- package/dist/src/__tests__/stdlib/queue.test.js +6 -6
- package/dist/src/cli.js +0 -0
- package/dist/src/lexer/index.js +2 -1
- package/dist/src/lint/index.d.ts +12 -5
- package/dist/src/lint/index.js +730 -5
- package/dist/src/lsp/main.js +0 -0
- package/dist/src/mc-test/client.d.ts +21 -0
- package/dist/src/mc-test/client.js +34 -0
- package/dist/src/mir/lower.js +108 -6
- package/dist/src/optimizer/interprocedural.js +37 -2
- package/dist/src/parser/decl-parser.d.ts +19 -0
- package/dist/src/parser/decl-parser.js +323 -0
- package/dist/src/parser/expr-parser.d.ts +46 -0
- package/dist/src/parser/expr-parser.js +759 -0
- package/dist/src/parser/index.d.ts +8 -129
- package/dist/src/parser/index.js +13 -2262
- package/dist/src/parser/stmt-parser.d.ts +28 -0
- package/dist/src/parser/stmt-parser.js +577 -0
- package/dist/src/parser/type-parser.d.ts +20 -0
- package/dist/src/parser/type-parser.js +257 -0
- package/dist/src/parser/utils.d.ts +34 -0
- package/dist/src/parser/utils.js +141 -0
- package/docs/dev/README-mc-integration-tests.md +141 -0
- package/docs/lint-rules.md +162 -0
- package/docs/stdlib/bigint.md +2 -0
- package/editors/vscode/README.md +63 -41
- package/editors/vscode/out/extension.js +1881 -1776
- package/editors/vscode/out/lsp-server.js +4257 -3651
- package/editors/vscode/package-lock.json +3 -3
- package/editors/vscode/package.json +1 -1
- package/examples/loops-demo.mcrs +87 -0
- package/package.json +1 -1
- package/redscript-docs/docs/en/stdlib/advanced.md +629 -0
- package/redscript-docs/docs/en/stdlib/bigint.md +316 -0
- package/redscript-docs/docs/en/stdlib/bits.md +292 -0
- package/redscript-docs/docs/en/stdlib/bossbar.md +177 -0
- package/redscript-docs/docs/en/stdlib/calculus.md +289 -0
- package/redscript-docs/docs/en/stdlib/color.md +353 -0
- package/redscript-docs/docs/en/stdlib/combat.md +88 -0
- package/redscript-docs/docs/en/stdlib/cooldown.md +82 -0
- package/redscript-docs/docs/en/stdlib/dialog.md +155 -0
- package/redscript-docs/docs/en/stdlib/easing.md +558 -0
- package/redscript-docs/docs/en/stdlib/ecs.md +475 -0
- package/redscript-docs/docs/en/stdlib/effects.md +324 -0
- package/redscript-docs/docs/en/stdlib/events.md +3 -0
- package/redscript-docs/docs/en/stdlib/expr.md +45 -0
- package/redscript-docs/docs/en/stdlib/fft.md +141 -0
- package/redscript-docs/docs/en/stdlib/geometry.md +430 -0
- package/redscript-docs/docs/en/stdlib/graph.md +259 -0
- package/redscript-docs/docs/en/stdlib/heap.md +185 -0
- package/redscript-docs/docs/en/stdlib/interactions.md +179 -0
- package/redscript-docs/docs/en/stdlib/inventory.md +97 -0
- package/redscript-docs/docs/en/stdlib/linalg.md +557 -0
- package/redscript-docs/docs/en/stdlib/list.md +559 -0
- package/redscript-docs/docs/en/stdlib/map.md +140 -0
- package/redscript-docs/docs/en/stdlib/math.md +193 -0
- package/redscript-docs/docs/en/stdlib/math_hp.md +149 -0
- package/redscript-docs/docs/en/stdlib/matrix.md +403 -0
- package/redscript-docs/docs/en/stdlib/mobs.md +965 -0
- package/redscript-docs/docs/en/stdlib/noise.md +244 -0
- package/redscript-docs/docs/en/stdlib/ode.md +253 -0
- package/redscript-docs/docs/en/stdlib/parabola.md +342 -0
- package/redscript-docs/docs/en/stdlib/particles.md +311 -0
- package/redscript-docs/docs/en/stdlib/pathfind.md +255 -0
- package/redscript-docs/docs/en/stdlib/physics.md +493 -0
- package/redscript-docs/docs/en/stdlib/player.md +78 -0
- package/redscript-docs/docs/en/stdlib/quaternion.md +673 -0
- package/redscript-docs/docs/en/stdlib/queue.md +134 -0
- package/redscript-docs/docs/en/stdlib/random.md +223 -0
- package/redscript-docs/docs/en/stdlib/result.md +143 -0
- package/redscript-docs/docs/en/stdlib/scheduler.md +183 -0
- package/redscript-docs/docs/en/stdlib/set_int.md +190 -0
- package/redscript-docs/docs/en/stdlib/sets.md +101 -0
- package/redscript-docs/docs/en/stdlib/signal.md +400 -0
- package/redscript-docs/docs/en/stdlib/sort.md +104 -0
- package/redscript-docs/docs/en/stdlib/spawn.md +147 -0
- package/redscript-docs/docs/en/stdlib/state.md +142 -0
- package/redscript-docs/docs/en/stdlib/strings.md +154 -0
- package/redscript-docs/docs/en/stdlib/tags.md +3451 -0
- package/redscript-docs/docs/en/stdlib/teams.md +153 -0
- package/redscript-docs/docs/en/stdlib/timer.md +246 -0
- package/redscript-docs/docs/en/stdlib/vec.md +158 -0
- package/redscript-docs/docs/en/stdlib/world.md +298 -0
- package/redscript-docs/docs/zh/stdlib/advanced.md +615 -0
- package/redscript-docs/docs/zh/stdlib/bigint.md +316 -0
- package/redscript-docs/docs/zh/stdlib/bits.md +292 -0
- package/redscript-docs/docs/zh/stdlib/bossbar.md +170 -0
- package/redscript-docs/docs/zh/stdlib/calculus.md +287 -0
- package/redscript-docs/docs/zh/stdlib/color.md +353 -0
- package/redscript-docs/docs/zh/stdlib/combat.md +88 -0
- package/redscript-docs/docs/zh/stdlib/cooldown.md +84 -0
- package/redscript-docs/docs/zh/stdlib/dialog.md +152 -0
- package/redscript-docs/docs/zh/stdlib/easing.md +558 -0
- package/redscript-docs/docs/zh/stdlib/ecs.md +472 -0
- package/redscript-docs/docs/zh/stdlib/effects.md +324 -0
- package/redscript-docs/docs/zh/stdlib/events.md +3 -0
- package/redscript-docs/docs/zh/stdlib/expr.md +37 -0
- package/redscript-docs/docs/zh/stdlib/fft.md +128 -0
- package/redscript-docs/docs/zh/stdlib/geometry.md +430 -0
- package/redscript-docs/docs/zh/stdlib/graph.md +259 -0
- package/redscript-docs/docs/zh/stdlib/heap.md +185 -0
- package/redscript-docs/docs/zh/stdlib/interactions.md +160 -0
- package/redscript-docs/docs/zh/stdlib/inventory.md +94 -0
- package/redscript-docs/docs/zh/stdlib/linalg.md +543 -0
- package/redscript-docs/docs/zh/stdlib/list.md +561 -0
- package/redscript-docs/docs/zh/stdlib/map.md +132 -0
- package/redscript-docs/docs/zh/stdlib/math.md +193 -0
- package/redscript-docs/docs/zh/stdlib/math_hp.md +143 -0
- package/redscript-docs/docs/zh/stdlib/matrix.md +396 -0
- package/redscript-docs/docs/zh/stdlib/mobs.md +965 -0
- package/redscript-docs/docs/zh/stdlib/noise.md +244 -0
- package/redscript-docs/docs/zh/stdlib/ode.md +243 -0
- package/redscript-docs/docs/zh/stdlib/parabola.md +337 -0
- package/redscript-docs/docs/zh/stdlib/particles.md +307 -0
- package/redscript-docs/docs/zh/stdlib/pathfind.md +255 -0
- package/redscript-docs/docs/zh/stdlib/physics.md +493 -0
- package/redscript-docs/docs/zh/stdlib/player.md +78 -0
- package/redscript-docs/docs/zh/stdlib/quaternion.md +669 -0
- package/redscript-docs/docs/zh/stdlib/queue.md +124 -0
- package/redscript-docs/docs/zh/stdlib/random.md +222 -0
- package/redscript-docs/docs/zh/stdlib/result.md +147 -0
- package/redscript-docs/docs/zh/stdlib/scheduler.md +173 -0
- package/redscript-docs/docs/zh/stdlib/set_int.md +180 -0
- package/redscript-docs/docs/zh/stdlib/sets.md +107 -0
- package/redscript-docs/docs/zh/stdlib/signal.md +373 -0
- package/redscript-docs/docs/zh/stdlib/sort.md +104 -0
- package/redscript-docs/docs/zh/stdlib/spawn.md +142 -0
- package/redscript-docs/docs/zh/stdlib/state.md +134 -0
- package/redscript-docs/docs/zh/stdlib/strings.md +107 -0
- package/redscript-docs/docs/zh/stdlib/tags.md +3451 -0
- package/redscript-docs/docs/zh/stdlib/teams.md +150 -0
- package/redscript-docs/docs/zh/stdlib/timer.md +254 -0
- package/redscript-docs/docs/zh/stdlib/vec.md +158 -0
- package/redscript-docs/docs/zh/stdlib/world.md +289 -0
- package/src/__tests__/formatter-extra.test.ts +139 -0
- package/src/__tests__/global-vars.test.ts +171 -0
- package/src/__tests__/lint/new-rules.test.ts +437 -0
- package/src/__tests__/lsp-rename.test.ts +171 -0
- package/src/__tests__/mc-integration/say-fstring.test.ts +211 -0
- package/src/__tests__/mc-integration/stdlib-coverage-2.test.ts +1 -1
- package/src/__tests__/mc-integration/stdlib-coverage-3.test.ts +1 -1
- package/src/__tests__/mc-integration/stdlib-coverage-4.test.ts +1 -1
- package/src/__tests__/mc-integration/stdlib-coverage-5.test.ts +1 -1
- package/src/__tests__/mc-integration/stdlib-coverage-6.test.ts +1 -1
- package/src/__tests__/mc-integration/stdlib-coverage-7.test.ts +1 -1
- package/src/__tests__/mc-integration/stdlib-coverage-8.test.ts +1 -1
- package/src/__tests__/mc-syntax.test.ts +3 -0
- package/src/__tests__/monomorphize-coverage.test.ts +220 -0
- package/src/__tests__/optimizer-cse.test.ts +250 -0
- package/src/__tests__/parser.test.ts +4 -13
- package/src/__tests__/repl-server-extra.test.ts +6 -6
- package/src/__tests__/repl-server.test.ts +5 -6
- package/src/__tests__/stdlib/queue.test.ts +6 -6
- package/src/lexer/index.ts +2 -1
- package/src/lint/index.ts +713 -5
- package/src/mc-test/client.ts +40 -0
- package/src/mir/lower.ts +111 -2
- package/src/optimizer/interprocedural.ts +40 -2
- package/src/parser/decl-parser.ts +349 -0
- package/src/parser/expr-parser.ts +838 -0
- package/src/parser/index.ts +17 -2558
- package/src/parser/stmt-parser.ts +585 -0
- package/src/parser/type-parser.ts +276 -0
- package/src/parser/utils.ts +173 -0
- package/src/stdlib/queue.mcrs +19 -6
package/src/mc-test/client.ts
CHANGED
|
@@ -266,6 +266,46 @@ export class MCTestClient {
|
|
|
266
266
|
}
|
|
267
267
|
}
|
|
268
268
|
|
|
269
|
+
/**
|
|
270
|
+
* Dump all scoreboard entries for a namespace's objective (__<ns>).
|
|
271
|
+
* Returns { "$p0": 0, "$val": 11, "#result": 42, ... }
|
|
272
|
+
*/
|
|
273
|
+
async dumpScores(ns: string): Promise<Record<string, number>> {
|
|
274
|
+
return this.get('/scoreboard/dump', { ns })
|
|
275
|
+
.then((r: any) => r.entries ?? {})
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Dump all scoreboard entries for a specific objective.
|
|
280
|
+
*/
|
|
281
|
+
async dumpScoresByObj(obj: string): Promise<Record<string, number>> {
|
|
282
|
+
return this.get('/scoreboard/dump', { obj })
|
|
283
|
+
.then((r: any) => r.entries ?? {})
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Dump the raw NBT of an entire storage namespace.
|
|
288
|
+
*/
|
|
289
|
+
async dumpStorage(storage: string): Promise<{ raw: string; ok: boolean }> {
|
|
290
|
+
return this.get('/storage/dump', { storage })
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Assert multiple scoreboard values at once.
|
|
295
|
+
* Usage: await mc.assertScoreMap('ns', { '$p0': 11, '$ret': 2 })
|
|
296
|
+
*/
|
|
297
|
+
async assertScoreMap(ns: string, expected: Record<string, number>): Promise<void> {
|
|
298
|
+
const actual = await this.dumpScores(ns)
|
|
299
|
+
for (const [key, val] of Object.entries(expected)) {
|
|
300
|
+
if (actual[key] !== val) {
|
|
301
|
+
throw new Error(
|
|
302
|
+
`assertScoreMap[${ns}] ${key}: expected ${val}, got ${actual[key] ?? '(unset)'}\n` +
|
|
303
|
+
`Full dump: ${JSON.stringify(actual)}`
|
|
304
|
+
)
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
269
309
|
/**
|
|
270
310
|
* Wait until a scoreboard value equals expected, up to timeout ms.
|
|
271
311
|
*/
|
package/src/mir/lower.ts
CHANGED
|
@@ -136,9 +136,12 @@ export function lowerToMIR(hir: HIRModule, sourceFile?: string): MIRModule {
|
|
|
136
136
|
else if (c.value.kind === 'float_lit') constValues.set(c.name, Math.round(c.value.value * 10000))
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
// Collect module-level global variable names (mutable lets at top level)
|
|
140
|
+
const globalVarNames = new Set<string>(hir.globals.map(g => g.name))
|
|
141
|
+
|
|
139
142
|
const allFunctions: MIRFunction[] = []
|
|
140
143
|
for (const f of hir.functions) {
|
|
141
|
-
const { fn, helpers } = lowerFunction(f, hir.namespace, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, sourceFile, timerCounter, undefined, hirFnMap, specializedFnsRegistry, undefined, enumPayloads, constValues, singletonStructs, displayImpls)
|
|
144
|
+
const { fn, helpers } = lowerFunction(f, hir.namespace, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, sourceFile, timerCounter, undefined, hirFnMap, specializedFnsRegistry, undefined, enumPayloads, constValues, singletonStructs, displayImpls, globalVarNames)
|
|
142
145
|
allFunctions.push(fn, ...helpers)
|
|
143
146
|
}
|
|
144
147
|
|
|
@@ -146,7 +149,7 @@ export function lowerToMIR(hir: HIRModule, sourceFile?: string): MIRModule {
|
|
|
146
149
|
for (const ib of hir.implBlocks) {
|
|
147
150
|
if (ib.traitName === 'Display') continue // Display impls are inlined, not emitted as functions
|
|
148
151
|
for (const m of ib.methods) {
|
|
149
|
-
const { fn, helpers } = lowerImplMethod(m, ib.typeName, hir.namespace, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, sourceFile, timerCounter, enumPayloads, constValues)
|
|
152
|
+
const { fn, helpers } = lowerImplMethod(m, ib.typeName, hir.namespace, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, sourceFile, timerCounter, enumPayloads, constValues, globalVarNames)
|
|
150
153
|
allFunctions.push(fn, ...helpers)
|
|
151
154
|
}
|
|
152
155
|
}
|
|
@@ -226,6 +229,8 @@ class FnContext {
|
|
|
226
229
|
singletonStructs: Set<string> = new Set()
|
|
227
230
|
/** Display trait impls: typeName → f-string parts from to_string body (inlined at call sites) */
|
|
228
231
|
displayImpls: Map<string, HIRFStringPart[]> = new Map()
|
|
232
|
+
/** Module-level global variable names — reads/writes must go through scoreboard */
|
|
233
|
+
globalVarNames: Set<string> = new Set()
|
|
229
234
|
|
|
230
235
|
constructor(
|
|
231
236
|
namespace: string,
|
|
@@ -366,6 +371,7 @@ function lowerFunction(
|
|
|
366
371
|
constValues: Map<string, number> = new Map(),
|
|
367
372
|
singletonStructs: Set<string> = new Set(),
|
|
368
373
|
displayImpls: Map<string, HIRFStringPart[]> = new Map(),
|
|
374
|
+
globalVarNames: Set<string> = new Set(),
|
|
369
375
|
): { fn: MIRFunction; helpers: MIRFunction[] } {
|
|
370
376
|
const mirFnName = overrideName ?? fn.name
|
|
371
377
|
const ctx = new FnContext(namespace, mirFnName, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, timerCounter, enumPayloads)
|
|
@@ -373,6 +379,7 @@ function lowerFunction(
|
|
|
373
379
|
ctx.constValues = constValues
|
|
374
380
|
ctx.singletonStructs = singletonStructs
|
|
375
381
|
ctx.displayImpls = displayImpls
|
|
382
|
+
ctx.globalVarNames = globalVarNames
|
|
376
383
|
if (hirFnMap) ctx.hirFunctions = hirFnMap
|
|
377
384
|
if (specializedFnsRegistry) ctx.specializedFnsRegistry = specializedFnsRegistry
|
|
378
385
|
const fnMacroInfo = macroInfo.get(fn.name)
|
|
@@ -452,11 +459,13 @@ function lowerImplMethod(
|
|
|
452
459
|
timerCounter: { count: number; timerId: number } = { count: 0, timerId: 0 },
|
|
453
460
|
enumPayloads: Map<string, Map<string, { name: string; type: TypeNode }[]>> = new Map(),
|
|
454
461
|
constValues: Map<string, number> = new Map(),
|
|
462
|
+
globalVarNames: Set<string> = new Set(),
|
|
455
463
|
): { fn: MIRFunction; helpers: MIRFunction[] } {
|
|
456
464
|
const fnName = `${typeName}::${method.name}`
|
|
457
465
|
const ctx = new FnContext(namespace, fnName, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, timerCounter, enumPayloads)
|
|
458
466
|
ctx.sourceFile = method.sourceFile ?? sourceFile
|
|
459
467
|
ctx.constValues = constValues
|
|
468
|
+
ctx.globalVarNames = globalVarNames
|
|
460
469
|
const fields = structDefs.get(typeName) ?? []
|
|
461
470
|
const hasSelf = method.params.length > 0 && method.params[0].name === 'self'
|
|
462
471
|
|
|
@@ -1318,6 +1327,7 @@ function lowerStmt(
|
|
|
1318
1327
|
const rawCmd = stmt.cmd
|
|
1319
1328
|
.replace(/__NS__/g, ns)
|
|
1320
1329
|
.replace(/__OBJ__/g, `__${ns}`)
|
|
1330
|
+
.replace(/__RS__/g, 'rs')
|
|
1321
1331
|
ctx.emit({ kind: 'call', dst: null, fn: `__raw:${rawCmd}`, args: [] })
|
|
1322
1332
|
break
|
|
1323
1333
|
}
|
|
@@ -1520,6 +1530,13 @@ function lowerExpr(
|
|
|
1520
1530
|
if (ctx.constValues.has(expr.name)) {
|
|
1521
1531
|
return { kind: 'const', value: ctx.constValues.get(expr.name)! }
|
|
1522
1532
|
}
|
|
1533
|
+
// Module-level global variable: read from scoreboard slot
|
|
1534
|
+
if (ctx.globalVarNames.has(expr.name)) {
|
|
1535
|
+
const t = ctx.freshTemp()
|
|
1536
|
+
ctx.emit({ kind: 'score_read', dst: t, player: expr.name, obj: `__${ctx.getNamespace()}` })
|
|
1537
|
+
scope.set(expr.name, t)
|
|
1538
|
+
return { kind: 'temp', name: t }
|
|
1539
|
+
}
|
|
1523
1540
|
// Unresolved ident — could be a global or external reference
|
|
1524
1541
|
const t = ctx.freshTemp()
|
|
1525
1542
|
ctx.emit({ kind: 'copy', dst: t, src: { kind: 'const', value: 0 } })
|
|
@@ -1620,6 +1637,31 @@ function lowerExpr(
|
|
|
1620
1637
|
|
|
1621
1638
|
case 'assign': {
|
|
1622
1639
|
const val = lowerExpr(expr.value, ctx, scope)
|
|
1640
|
+
// Check if the target is a struct variable — if so, update its field temps
|
|
1641
|
+
// from the __rf_<field> return slots that the callee populated.
|
|
1642
|
+
const sv = ctx.structVars.get(expr.target)
|
|
1643
|
+
if (sv) {
|
|
1644
|
+
const fields = ctx.structDefs.get(sv.typeName) ?? []
|
|
1645
|
+
for (const fieldName of fields) {
|
|
1646
|
+
const existingFieldTemp = sv.fields.get(fieldName)
|
|
1647
|
+
// Reuse the existing field temp if it exists (so scoreboard slot stays stable),
|
|
1648
|
+
// otherwise allocate a fresh one.
|
|
1649
|
+
const fieldTemp = existingFieldTemp ?? ctx.freshTemp()
|
|
1650
|
+
ctx.emit({ kind: 'copy', dst: fieldTemp, src: { kind: 'temp', name: `__rf_${fieldName}` } })
|
|
1651
|
+
sv.fields.set(fieldName, fieldTemp)
|
|
1652
|
+
}
|
|
1653
|
+
return val
|
|
1654
|
+
}
|
|
1655
|
+
// Global variable assignment: write to scoreboard (score_write has side effects, DCE-safe)
|
|
1656
|
+
if (ctx.globalVarNames.has(expr.target)) {
|
|
1657
|
+
const globalObj = `__${ctx.getNamespace()}`
|
|
1658
|
+
ctx.emit({ kind: 'score_write', player: expr.target, obj: globalObj, src: val })
|
|
1659
|
+
// Also update scope temp so subsequent reads in this function see the new value
|
|
1660
|
+
const t = ctx.freshTemp()
|
|
1661
|
+
ctx.emit({ kind: 'score_read', dst: t, player: expr.target, obj: globalObj })
|
|
1662
|
+
scope.set(expr.target, t)
|
|
1663
|
+
return val
|
|
1664
|
+
}
|
|
1623
1665
|
// Reuse the existing temp for this variable so that updates inside
|
|
1624
1666
|
// if/while bodies are visible to outer code (we target mutable
|
|
1625
1667
|
// scoreboard slots, not true SSA registers).
|
|
@@ -2074,6 +2116,72 @@ function lowerExpr(
|
|
|
2074
2116
|
|
|
2075
2117
|
// Handle builtin calls → raw MC commands
|
|
2076
2118
|
if (BUILTIN_SET.has(expr.fn)) {
|
|
2119
|
+
// Special case: say() with f_string → MC macro function ($say template)
|
|
2120
|
+
// MC `say` is plain text and cannot reference scoreboards directly;
|
|
2121
|
+
// use function macros (MC 1.20.2+) to interpolate variables.
|
|
2122
|
+
if (expr.fn === 'say' && expr.args[0]?.kind === 'f_string') {
|
|
2123
|
+
const fstr = precomputeFStringParts(expr.args[0], ctx, scope)
|
|
2124
|
+
if (fstr.kind === 'f_string') {
|
|
2125
|
+
const ns = ctx.getNamespace()
|
|
2126
|
+
const obj = `__${ns}`
|
|
2127
|
+
const helperName = `${ctx.getFnName()}__say_macro_${ctx.freshTemp()}`
|
|
2128
|
+
|
|
2129
|
+
// Build macro template: "text $(var) more text"
|
|
2130
|
+
let template = 'say '
|
|
2131
|
+
const macroVarNames: string[] = []
|
|
2132
|
+
for (const part of fstr.parts) {
|
|
2133
|
+
if (part.kind === 'text') {
|
|
2134
|
+
template += part.value
|
|
2135
|
+
} else {
|
|
2136
|
+
const inner = part.expr as HIRExpr
|
|
2137
|
+
if (inner.kind === 'ident') {
|
|
2138
|
+
// Strip leading $ from temp names for macro param names
|
|
2139
|
+
const varName = inner.name.startsWith('$') ? inner.name.slice(1) : inner.name
|
|
2140
|
+
template += `$(${varName})`
|
|
2141
|
+
macroVarNames.push(inner.name)
|
|
2142
|
+
} else if (inner.kind === 'int_lit') {
|
|
2143
|
+
template += String(inner.value)
|
|
2144
|
+
} else {
|
|
2145
|
+
template += '?'
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
// Emit: copy each scoreboard var to rs:macro_args storage
|
|
2151
|
+
for (const varName of macroVarNames) {
|
|
2152
|
+
const cleanName = varName.startsWith('$') ? varName.slice(1) : varName
|
|
2153
|
+
ctx.emit({
|
|
2154
|
+
kind: 'call', dst: null,
|
|
2155
|
+
fn: `__raw:execute store result storage rs:macro_args ${cleanName} int 1 run scoreboard players get ${varName} ${obj}`,
|
|
2156
|
+
args: [],
|
|
2157
|
+
})
|
|
2158
|
+
}
|
|
2159
|
+
|
|
2160
|
+
// Build helper MIR function with isMacro: true
|
|
2161
|
+
const helperCtx = new FnContext(ns, helperName, ctx.structDefs, ctx.implMethods)
|
|
2162
|
+
helperCtx.emit({ kind: 'call', dst: null, fn: `__raw:$${template}`, args: [] })
|
|
2163
|
+
helperCtx.terminate({ kind: 'return', value: null })
|
|
2164
|
+
const helperReachable = computeReachable(helperCtx.blocks, 'entry')
|
|
2165
|
+
const helperBlocks = helperCtx.blocks.filter(b => helperReachable.has(b.id))
|
|
2166
|
+
computePreds(helperBlocks)
|
|
2167
|
+
ctx.helperFunctions.push({
|
|
2168
|
+
name: helperName,
|
|
2169
|
+
params: [],
|
|
2170
|
+
blocks: helperBlocks,
|
|
2171
|
+
entry: 'entry',
|
|
2172
|
+
isMacro: true,
|
|
2173
|
+
sourceSnippet: 'say macro helper',
|
|
2174
|
+
})
|
|
2175
|
+
|
|
2176
|
+
// Emit: function <helper> with storage rs:macro_args
|
|
2177
|
+
ctx.emit({ kind: 'call', dst: null, fn: `__raw:function ${ns}:${helperName} with storage rs:macro_args`, args: [] })
|
|
2178
|
+
|
|
2179
|
+
const t = ctx.freshTemp()
|
|
2180
|
+
ctx.emit({ kind: 'const', dst: t, value: 0 })
|
|
2181
|
+
return { kind: 'temp', name: t }
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
|
|
2077
2185
|
// For text builtins with f-string args, precompute complex expressions to temp vars
|
|
2078
2186
|
const TEXT_BUILTINS_SET = new Set(['tell', 'tellraw', 'title', 'subtitle', 'actionbar', 'announce'])
|
|
2079
2187
|
let resolvedArgs = expr.args
|
|
@@ -2249,6 +2357,7 @@ function lowerExpr(
|
|
|
2249
2357
|
ctx.constValues,
|
|
2250
2358
|
ctx.singletonStructs,
|
|
2251
2359
|
ctx.displayImpls,
|
|
2360
|
+
ctx.globalVarNames,
|
|
2252
2361
|
)
|
|
2253
2362
|
ctx.specializedFnsRegistry.set(specializedName, [specFn, ...specHelpers])
|
|
2254
2363
|
}
|
|
@@ -58,7 +58,7 @@ function runOnePass(mod: MIRModule): MIRModule {
|
|
|
58
58
|
if (fnMap.has(mangledName) || added.has(mangledName)) continue
|
|
59
59
|
|
|
60
60
|
// Create specialized clone
|
|
61
|
-
const specialized = specialize(callee, constArgs.map(a => a.value), mangledName)
|
|
61
|
+
const specialized = specialize(callee, constArgs.map(a => a.value), mangledName, mod.objective)
|
|
62
62
|
newFunctions.push(specialized)
|
|
63
63
|
added.add(mangledName)
|
|
64
64
|
fnMap.set(mangledName, specialized)
|
|
@@ -89,7 +89,27 @@ function mangleName(name: string, args: number[]): string {
|
|
|
89
89
|
return `${name}__const_${args.map(v => v < 0 ? `n${Math.abs(v)}` : String(v)).join('_')}`
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
|
|
92
|
+
/**
|
|
93
|
+
* Returns true if the function has any __raw: calls that directly reference
|
|
94
|
+
* scoreboard param slots ($p0, $p1, ...) by name in the raw command string.
|
|
95
|
+
* Such functions use the raw() pattern to read params via scoreboard, so the
|
|
96
|
+
* specialized clone must pre-set those slots before executing the body.
|
|
97
|
+
*/
|
|
98
|
+
function hasRawParamRefs(fn: MIRFunction, paramCount: number): boolean {
|
|
99
|
+
for (let i = 0; i < paramCount; i++) {
|
|
100
|
+
const pattern = `$p${i}`
|
|
101
|
+
for (const block of fn.blocks) {
|
|
102
|
+
for (const instr of block.instrs) {
|
|
103
|
+
if (instr.kind === 'call' && instr.fn.startsWith('__raw:') && instr.fn.includes(pattern)) {
|
|
104
|
+
return true
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return false
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function specialize(fn: MIRFunction, args: number[], newName: string, objective: string): MIRFunction {
|
|
93
113
|
// Build substitution map: param.name → const operand
|
|
94
114
|
const sub = new Map<Temp, Operand>()
|
|
95
115
|
for (let i = 0; i < fn.params.length; i++) {
|
|
@@ -97,6 +117,24 @@ function specialize(fn: MIRFunction, args: number[], newName: string): MIRFuncti
|
|
|
97
117
|
}
|
|
98
118
|
|
|
99
119
|
const newBlocks = fn.blocks.map(block => substituteBlock(block, sub))
|
|
120
|
+
|
|
121
|
+
// If the function uses raw() commands that read from $p<i> scoreboard slots
|
|
122
|
+
// directly, we must pre-set those slots in the entry block so the raw commands
|
|
123
|
+
// see the correct values (the normal call convention sets $p<i> at the call
|
|
124
|
+
// site, but the specialized function is called with no args).
|
|
125
|
+
if (hasRawParamRefs(fn, args.length)) {
|
|
126
|
+
const entryBlock = newBlocks.find(b => b.id === fn.entry)
|
|
127
|
+
if (entryBlock) {
|
|
128
|
+
const scoreWrites: MIRInstr[] = args.map((value, i) => ({
|
|
129
|
+
kind: 'score_write' as const,
|
|
130
|
+
player: `$p${i}`,
|
|
131
|
+
obj: objective,
|
|
132
|
+
src: { kind: 'const' as const, value },
|
|
133
|
+
}))
|
|
134
|
+
entryBlock.instrs = [...scoreWrites, ...entryBlock.instrs]
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
100
138
|
const specialized: MIRFunction = {
|
|
101
139
|
...fn,
|
|
102
140
|
name: newName,
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DeclParser — declaration parsing (fn/struct/enum/impl/interface/const/global/import).
|
|
3
|
+
* Extends StmtParser so declaration methods can call block/statement methods.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
FnDecl, StructDecl, StructField, EnumDecl, EnumVariant, ImplBlock,
|
|
8
|
+
InterfaceDecl, InterfaceMethod, ConstDecl, GlobalDecl, Decorator,
|
|
9
|
+
TypeNode, Expr,
|
|
10
|
+
} from '../ast/types'
|
|
11
|
+
import { StmtParser } from './stmt-parser'
|
|
12
|
+
|
|
13
|
+
export class DeclParser extends StmtParser {
|
|
14
|
+
// -------------------------------------------------------------------------
|
|
15
|
+
// Struct
|
|
16
|
+
// -------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
parseStructDecl(): StructDecl {
|
|
19
|
+
const structToken = this.expect('struct')
|
|
20
|
+
const name = this.expect('ident').value
|
|
21
|
+
const extendsName = this.match('extends') ? this.expect('ident').value : undefined
|
|
22
|
+
this.expect('{')
|
|
23
|
+
|
|
24
|
+
const fields: StructField[] = []
|
|
25
|
+
while (!this.check('}') && !this.check('eof')) {
|
|
26
|
+
const fieldName = this.expect('ident').value
|
|
27
|
+
this.expect(':')
|
|
28
|
+
const fieldType = this.parseType()
|
|
29
|
+
fields.push({ name: fieldName, type: fieldType })
|
|
30
|
+
this.match(',')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
this.expect('}')
|
|
34
|
+
return this.withLoc({ name, extends: extendsName, fields }, structToken)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// -------------------------------------------------------------------------
|
|
38
|
+
// Enum
|
|
39
|
+
// -------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
parseEnumDecl(): EnumDecl {
|
|
42
|
+
const enumToken = this.expect('enum')
|
|
43
|
+
const name = this.expect('ident').value
|
|
44
|
+
this.expect('{')
|
|
45
|
+
|
|
46
|
+
const variants: EnumVariant[] = []
|
|
47
|
+
let nextValue = 0
|
|
48
|
+
|
|
49
|
+
while (!this.check('}') && !this.check('eof')) {
|
|
50
|
+
const variantToken = this.expect('ident')
|
|
51
|
+
const variant: EnumVariant = { name: variantToken.value }
|
|
52
|
+
|
|
53
|
+
if (this.check('(')) {
|
|
54
|
+
this.advance()
|
|
55
|
+
const fields: { name: string; type: TypeNode }[] = []
|
|
56
|
+
while (!this.check(')') && !this.check('eof')) {
|
|
57
|
+
const fieldName = this.expect('ident').value
|
|
58
|
+
this.expect(':')
|
|
59
|
+
const fieldType = this.parseType()
|
|
60
|
+
fields.push({ name: fieldName, type: fieldType })
|
|
61
|
+
if (!this.match(',')) break
|
|
62
|
+
}
|
|
63
|
+
this.expect(')')
|
|
64
|
+
variant.fields = fields
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (this.match('=')) {
|
|
68
|
+
const valueToken = this.expect('int_lit')
|
|
69
|
+
variant.value = parseInt(valueToken.value, 10)
|
|
70
|
+
nextValue = variant.value + 1
|
|
71
|
+
} else {
|
|
72
|
+
variant.value = nextValue++
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
variants.push(variant)
|
|
76
|
+
if (!this.match(',')) break
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
this.expect('}')
|
|
80
|
+
return this.withLoc({ name, variants }, enumToken)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// -------------------------------------------------------------------------
|
|
84
|
+
// Impl Block
|
|
85
|
+
// -------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
parseImplBlock(): ImplBlock {
|
|
88
|
+
const implToken = this.expect('impl')
|
|
89
|
+
let traitName: string | undefined
|
|
90
|
+
let typeName: string
|
|
91
|
+
const firstName = this.expect('ident').value
|
|
92
|
+
if (this.match('for')) {
|
|
93
|
+
traitName = firstName
|
|
94
|
+
typeName = this.expect('ident').value
|
|
95
|
+
} else {
|
|
96
|
+
typeName = firstName
|
|
97
|
+
}
|
|
98
|
+
this.expect('{')
|
|
99
|
+
|
|
100
|
+
const methods: FnDecl[] = []
|
|
101
|
+
while (!this.check('}') && !this.check('eof')) {
|
|
102
|
+
methods.push(this.parseFnDecl(typeName))
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this.expect('}')
|
|
106
|
+
return this.withLoc({ kind: 'impl_block', traitName, typeName, methods }, implToken)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// -------------------------------------------------------------------------
|
|
110
|
+
// Interface
|
|
111
|
+
// -------------------------------------------------------------------------
|
|
112
|
+
|
|
113
|
+
parseInterfaceDecl(): InterfaceDecl {
|
|
114
|
+
const ifaceToken = this.expect('interface')
|
|
115
|
+
const name = this.expect('ident').value
|
|
116
|
+
this.expect('{')
|
|
117
|
+
|
|
118
|
+
const methods: InterfaceMethod[] = []
|
|
119
|
+
while (!this.check('}') && !this.check('eof')) {
|
|
120
|
+
const fnToken = this.expect('fn')
|
|
121
|
+
const methodName = this.expect('ident').value
|
|
122
|
+
this.expect('(')
|
|
123
|
+
const params = this.parseInterfaceParams()
|
|
124
|
+
this.expect(')')
|
|
125
|
+
let returnType: TypeNode | undefined
|
|
126
|
+
if (this.match(':')) returnType = this.parseType()
|
|
127
|
+
methods.push(this.withLoc({ name: methodName, params, returnType }, fnToken) as InterfaceMethod)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
this.expect('}')
|
|
131
|
+
return this.withLoc({ name, methods }, ifaceToken) as InterfaceDecl
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// -------------------------------------------------------------------------
|
|
135
|
+
// Const / Global
|
|
136
|
+
// -------------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
parseConstDecl(): ConstDecl {
|
|
139
|
+
const constToken = this.expect('const')
|
|
140
|
+
const name = this.expect('ident').value
|
|
141
|
+
let type: TypeNode | undefined
|
|
142
|
+
if (this.match(':')) type = this.parseType()
|
|
143
|
+
this.expect('=')
|
|
144
|
+
const value = this.parseLiteralExpr()
|
|
145
|
+
this.match(';')
|
|
146
|
+
const inferredType: TypeNode = type ?? (
|
|
147
|
+
value.kind === 'str_lit' ? { kind: 'named', name: 'string' } :
|
|
148
|
+
value.kind === 'bool_lit' ? { kind: 'named', name: 'bool' } :
|
|
149
|
+
value.kind === 'float_lit' ? { kind: 'named', name: 'fixed' } :
|
|
150
|
+
{ kind: 'named', name: 'int' }
|
|
151
|
+
)
|
|
152
|
+
return this.withLoc({ name, type: inferredType, value }, constToken)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
parseGlobalDecl(mutable: boolean): GlobalDecl {
|
|
156
|
+
const token = this.advance() // consume 'let'
|
|
157
|
+
const name = this.expect('ident').value
|
|
158
|
+
this.expect(':')
|
|
159
|
+
const type = this.parseType()
|
|
160
|
+
let init: Expr
|
|
161
|
+
if (this.match('=')) {
|
|
162
|
+
init = this.parseExpr()
|
|
163
|
+
} else {
|
|
164
|
+
init = { kind: 'int_lit', value: 0 }
|
|
165
|
+
}
|
|
166
|
+
this.match(';')
|
|
167
|
+
return this.withLoc({ kind: 'global', name, type, init, mutable }, token)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// -------------------------------------------------------------------------
|
|
171
|
+
// Function
|
|
172
|
+
// -------------------------------------------------------------------------
|
|
173
|
+
|
|
174
|
+
parseExportedFnDecl(): FnDecl {
|
|
175
|
+
this.expect('export')
|
|
176
|
+
const fn = this.parseFnDecl()
|
|
177
|
+
fn.isExported = true
|
|
178
|
+
return fn
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
parseFnDecl(implTypeName?: string): FnDecl {
|
|
182
|
+
const decorators = this.parseDecorators()
|
|
183
|
+
const watchObjective = decorators.find(decorator => decorator.name === 'watch')?.args?.objective
|
|
184
|
+
|
|
185
|
+
let isExported: boolean | undefined
|
|
186
|
+
const filteredDecorators = decorators.filter(d => {
|
|
187
|
+
if (d.name === 'keep') {
|
|
188
|
+
isExported = true
|
|
189
|
+
return false
|
|
190
|
+
}
|
|
191
|
+
return true
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
const fnToken = this.expect('fn')
|
|
195
|
+
const name = this.expect('ident').value
|
|
196
|
+
|
|
197
|
+
let typeParams: string[] | undefined
|
|
198
|
+
if (this.check('<')) {
|
|
199
|
+
this.advance()
|
|
200
|
+
typeParams = []
|
|
201
|
+
do {
|
|
202
|
+
typeParams.push(this.expect('ident').value)
|
|
203
|
+
} while (this.match(','))
|
|
204
|
+
this.expect('>')
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
this.expect('(')
|
|
208
|
+
const params = this.parseParams(implTypeName)
|
|
209
|
+
this.expect(')')
|
|
210
|
+
|
|
211
|
+
let returnType: TypeNode = { kind: 'named', name: 'void' }
|
|
212
|
+
if (this.match('->') || this.match(':')) {
|
|
213
|
+
returnType = this.parseType()
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const body = this.parseBlock()
|
|
217
|
+
const closingBraceLine = this.tokens[this.pos - 1]?.line
|
|
218
|
+
|
|
219
|
+
const fn: FnDecl = this.withLoc(
|
|
220
|
+
{ name, typeParams, params, returnType, decorators: filteredDecorators, body,
|
|
221
|
+
isLibraryFn: this.inLibraryMode || undefined, isExported, watchObjective },
|
|
222
|
+
fnToken,
|
|
223
|
+
)
|
|
224
|
+
if (fn.span && closingBraceLine) fn.span.endLine = closingBraceLine
|
|
225
|
+
return fn
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
parseDeclareStub(): void {
|
|
229
|
+
this.expect('fn')
|
|
230
|
+
this.expect('ident')
|
|
231
|
+
this.expect('(')
|
|
232
|
+
let depth = 1
|
|
233
|
+
while (!this.check('eof') && depth > 0) {
|
|
234
|
+
const t = this.advance()
|
|
235
|
+
if (t.kind === '(') depth++
|
|
236
|
+
else if (t.kind === ')') depth--
|
|
237
|
+
}
|
|
238
|
+
if (this.match(':') || this.match('->')) {
|
|
239
|
+
this.parseType()
|
|
240
|
+
}
|
|
241
|
+
this.match(';')
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// -------------------------------------------------------------------------
|
|
245
|
+
// Decorators
|
|
246
|
+
// -------------------------------------------------------------------------
|
|
247
|
+
|
|
248
|
+
private parseDecorators(): Decorator[] {
|
|
249
|
+
const decorators: Decorator[] = []
|
|
250
|
+
while (this.check('decorator')) {
|
|
251
|
+
const token = this.advance()
|
|
252
|
+
const decorator = this.parseDecoratorValue(token.value)
|
|
253
|
+
decorators.push(decorator)
|
|
254
|
+
}
|
|
255
|
+
return decorators
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
parseDecoratorValue(value: string): Decorator {
|
|
259
|
+
const match = value.match(/^@([A-Za-z_][A-Za-z0-9_-]*)(?:\((.*)\))?$/s)
|
|
260
|
+
if (!match) {
|
|
261
|
+
this.error(`Invalid decorator: ${value}`)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const name = match[1] as Decorator['name']
|
|
265
|
+
const argsStr = match[2]
|
|
266
|
+
|
|
267
|
+
if (!argsStr) return { name }
|
|
268
|
+
|
|
269
|
+
if (name === 'profile' || name === 'benchmark' || name === 'memoize') {
|
|
270
|
+
this.error(`@${name} decorator does not accept arguments`)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const args: Decorator['args'] = {}
|
|
274
|
+
|
|
275
|
+
if (name === 'on') {
|
|
276
|
+
const eventTypeMatch = argsStr.match(/^([A-Za-z_][A-Za-z0-9_]*)$/)
|
|
277
|
+
if (eventTypeMatch) {
|
|
278
|
+
args.eventType = eventTypeMatch[1]
|
|
279
|
+
return { name, args }
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (name === 'watch' || name === 'on_trigger' || name === 'on_advancement' || name === 'on_craft' || name === 'on_join_team') {
|
|
284
|
+
const strMatch = argsStr.match(/^"([^"]*)"$/)
|
|
285
|
+
if (strMatch) {
|
|
286
|
+
if (name === 'watch') args.objective = strMatch[1]
|
|
287
|
+
else if (name === 'on_trigger') args.trigger = strMatch[1]
|
|
288
|
+
else if (name === 'on_advancement') args.advancement = strMatch[1]
|
|
289
|
+
else if (name === 'on_craft') args.item = strMatch[1]
|
|
290
|
+
else if (name === 'on_join_team') args.team = strMatch[1]
|
|
291
|
+
return { name, args }
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (name === 'config') {
|
|
296
|
+
const configMatch = argsStr.match(/^"([^"]+)"\s*,\s*default\s*:\s*(-?\d+(?:\.\d+)?)$/)
|
|
297
|
+
if (configMatch) {
|
|
298
|
+
return { name, args: { configKey: configMatch[1], configDefault: parseFloat(configMatch[2]) } }
|
|
299
|
+
}
|
|
300
|
+
const keyOnlyMatch = argsStr.match(/^"([^"]+)"$/)
|
|
301
|
+
if (keyOnlyMatch) {
|
|
302
|
+
return { name, args: { configKey: keyOnlyMatch[1] } }
|
|
303
|
+
}
|
|
304
|
+
this.error(`Invalid @config syntax. Expected: @config("key", default: value) or @config("key")`)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (name === 'deprecated') {
|
|
308
|
+
const strMatch = argsStr.match(/^"([^"]*)"$/)
|
|
309
|
+
if (strMatch) return { name, args: { message: strMatch[1] } }
|
|
310
|
+
return { name, args: {} }
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (name === 'test') {
|
|
314
|
+
const strMatch = argsStr.match(/^"([^"]*)"$/)
|
|
315
|
+
if (strMatch) return { name, args: { testLabel: strMatch[1] } }
|
|
316
|
+
return { name, args: { testLabel: '' } }
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (name === 'require_on_load') {
|
|
320
|
+
const rawArgs: NonNullable<Decorator['rawArgs']> = []
|
|
321
|
+
for (const part of argsStr.split(',')) {
|
|
322
|
+
const trimmed = part.trim()
|
|
323
|
+
const identMatch = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)$/)
|
|
324
|
+
if (identMatch) {
|
|
325
|
+
rawArgs.push({ kind: 'string', value: identMatch[1] })
|
|
326
|
+
} else {
|
|
327
|
+
const strMatch = trimmed.match(/^"([^"]*)"$/)
|
|
328
|
+
if (strMatch) rawArgs.push({ kind: 'string', value: strMatch[1] })
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return { name, rawArgs }
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
for (const part of argsStr.split(',')) {
|
|
335
|
+
const [key, val] = part.split('=').map(s => s.trim())
|
|
336
|
+
if (key === 'rate') args.rate = parseInt(val, 10)
|
|
337
|
+
else if (key === 'ticks') args.ticks = parseInt(val, 10)
|
|
338
|
+
else if (key === 'batch') args.batch = parseInt(val, 10)
|
|
339
|
+
else if (key === 'onDone') args.onDone = val.replace(/^["']|["']$/g, '')
|
|
340
|
+
else if (key === 'trigger') args.trigger = val
|
|
341
|
+
else if (key === 'advancement') args.advancement = val
|
|
342
|
+
else if (key === 'item') args.item = val
|
|
343
|
+
else if (key === 'team') args.team = val
|
|
344
|
+
else if (key === 'max') args.max = parseInt(val, 10)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return { name, args }
|
|
348
|
+
}
|
|
349
|
+
}
|