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/dist/src/lsp/main.js
CHANGED
|
File without changes
|
|
@@ -133,6 +133,27 @@ export declare class MCTestClient {
|
|
|
133
133
|
* Assert chat log contains a message matching substring.
|
|
134
134
|
*/
|
|
135
135
|
assertChatContains(substring: string, since?: number): Promise<void>;
|
|
136
|
+
/**
|
|
137
|
+
* Dump all scoreboard entries for a namespace's objective (__<ns>).
|
|
138
|
+
* Returns { "$p0": 0, "$val": 11, "#result": 42, ... }
|
|
139
|
+
*/
|
|
140
|
+
dumpScores(ns: string): Promise<Record<string, number>>;
|
|
141
|
+
/**
|
|
142
|
+
* Dump all scoreboard entries for a specific objective.
|
|
143
|
+
*/
|
|
144
|
+
dumpScoresByObj(obj: string): Promise<Record<string, number>>;
|
|
145
|
+
/**
|
|
146
|
+
* Dump the raw NBT of an entire storage namespace.
|
|
147
|
+
*/
|
|
148
|
+
dumpStorage(storage: string): Promise<{
|
|
149
|
+
raw: string;
|
|
150
|
+
ok: boolean;
|
|
151
|
+
}>;
|
|
152
|
+
/**
|
|
153
|
+
* Assert multiple scoreboard values at once.
|
|
154
|
+
* Usage: await mc.assertScoreMap('ns', { '$p0': 11, '$ret': 2 })
|
|
155
|
+
*/
|
|
156
|
+
assertScoreMap(ns: string, expected: Record<string, number>): Promise<void>;
|
|
136
157
|
/**
|
|
137
158
|
* Wait until a scoreboard value equals expected, up to timeout ms.
|
|
138
159
|
*/
|
|
@@ -180,6 +180,40 @@ class MCTestClient {
|
|
|
180
180
|
throw new Error(`assertChatContains: "${substring}" not found in chat. Recent: [${recent}]`);
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* Dump all scoreboard entries for a namespace's objective (__<ns>).
|
|
185
|
+
* Returns { "$p0": 0, "$val": 11, "#result": 42, ... }
|
|
186
|
+
*/
|
|
187
|
+
async dumpScores(ns) {
|
|
188
|
+
return this.get('/scoreboard/dump', { ns })
|
|
189
|
+
.then((r) => r.entries ?? {});
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Dump all scoreboard entries for a specific objective.
|
|
193
|
+
*/
|
|
194
|
+
async dumpScoresByObj(obj) {
|
|
195
|
+
return this.get('/scoreboard/dump', { obj })
|
|
196
|
+
.then((r) => r.entries ?? {});
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Dump the raw NBT of an entire storage namespace.
|
|
200
|
+
*/
|
|
201
|
+
async dumpStorage(storage) {
|
|
202
|
+
return this.get('/storage/dump', { storage });
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Assert multiple scoreboard values at once.
|
|
206
|
+
* Usage: await mc.assertScoreMap('ns', { '$p0': 11, '$ret': 2 })
|
|
207
|
+
*/
|
|
208
|
+
async assertScoreMap(ns, expected) {
|
|
209
|
+
const actual = await this.dumpScores(ns);
|
|
210
|
+
for (const [key, val] of Object.entries(expected)) {
|
|
211
|
+
if (actual[key] !== val) {
|
|
212
|
+
throw new Error(`assertScoreMap[${ns}] ${key}: expected ${val}, got ${actual[key] ?? '(unset)'}\n` +
|
|
213
|
+
`Full dump: ${JSON.stringify(actual)}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
183
217
|
/**
|
|
184
218
|
* Wait until a scoreboard value equals expected, up to timeout ms.
|
|
185
219
|
*/
|
package/dist/src/mir/lower.js
CHANGED
|
@@ -121,9 +121,11 @@ function lowerToMIR(hir, sourceFile) {
|
|
|
121
121
|
else if (c.value.kind === 'float_lit')
|
|
122
122
|
constValues.set(c.name, Math.round(c.value.value * 10000));
|
|
123
123
|
}
|
|
124
|
+
// Collect module-level global variable names (mutable lets at top level)
|
|
125
|
+
const globalVarNames = new Set(hir.globals.map(g => g.name));
|
|
124
126
|
const allFunctions = [];
|
|
125
127
|
for (const f of hir.functions) {
|
|
126
|
-
const { fn, helpers } = lowerFunction(f, hir.namespace, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, sourceFile, timerCounter, undefined, hirFnMap, specializedFnsRegistry, undefined, enumPayloads, constValues, singletonStructs, displayImpls);
|
|
128
|
+
const { fn, helpers } = lowerFunction(f, hir.namespace, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, sourceFile, timerCounter, undefined, hirFnMap, specializedFnsRegistry, undefined, enumPayloads, constValues, singletonStructs, displayImpls, globalVarNames);
|
|
127
129
|
allFunctions.push(fn, ...helpers);
|
|
128
130
|
}
|
|
129
131
|
// Lower impl block methods (skip Display::to_string — inlined at call sites instead)
|
|
@@ -131,7 +133,7 @@ function lowerToMIR(hir, sourceFile) {
|
|
|
131
133
|
if (ib.traitName === 'Display')
|
|
132
134
|
continue; // Display impls are inlined, not emitted as functions
|
|
133
135
|
for (const m of ib.methods) {
|
|
134
|
-
const { fn, helpers } = lowerImplMethod(m, ib.typeName, hir.namespace, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, sourceFile, timerCounter, enumPayloads, constValues);
|
|
136
|
+
const { fn, helpers } = lowerImplMethod(m, ib.typeName, hir.namespace, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, sourceFile, timerCounter, enumPayloads, constValues, globalVarNames);
|
|
135
137
|
allFunctions.push(fn, ...helpers);
|
|
136
138
|
}
|
|
137
139
|
}
|
|
@@ -189,6 +191,8 @@ class FnContext {
|
|
|
189
191
|
this.singletonStructs = new Set();
|
|
190
192
|
/** Display trait impls: typeName → f-string parts from to_string body (inlined at call sites) */
|
|
191
193
|
this.displayImpls = new Map();
|
|
194
|
+
/** Module-level global variable names — reads/writes must go through scoreboard */
|
|
195
|
+
this.globalVarNames = new Set();
|
|
192
196
|
this.namespace = namespace;
|
|
193
197
|
this.fnName = fnName;
|
|
194
198
|
this.structDefs = structDefs;
|
|
@@ -286,13 +290,14 @@ hirFnMap,
|
|
|
286
290
|
/** Shared registry of already-generated specialized MIR functions */
|
|
287
291
|
specializedFnsRegistry,
|
|
288
292
|
/** Override the MIR function name (used when generating specialized versions) */
|
|
289
|
-
overrideName, enumPayloads = new Map(), constValues = new Map(), singletonStructs = new Set(), displayImpls = new Map()) {
|
|
293
|
+
overrideName, enumPayloads = new Map(), constValues = new Map(), singletonStructs = new Set(), displayImpls = new Map(), globalVarNames = new Set()) {
|
|
290
294
|
const mirFnName = overrideName ?? fn.name;
|
|
291
295
|
const ctx = new FnContext(namespace, mirFnName, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, timerCounter, enumPayloads);
|
|
292
296
|
ctx.sourceFile = fn.sourceFile ?? sourceFile;
|
|
293
297
|
ctx.constValues = constValues;
|
|
294
298
|
ctx.singletonStructs = singletonStructs;
|
|
295
299
|
ctx.displayImpls = displayImpls;
|
|
300
|
+
ctx.globalVarNames = globalVarNames;
|
|
296
301
|
if (hirFnMap)
|
|
297
302
|
ctx.hirFunctions = hirFnMap;
|
|
298
303
|
if (specializedFnsRegistry)
|
|
@@ -352,11 +357,12 @@ overrideName, enumPayloads = new Map(), constValues = new Map(), singletonStruct
|
|
|
352
357
|
};
|
|
353
358
|
return { fn: result, helpers: ctx.helperFunctions };
|
|
354
359
|
}
|
|
355
|
-
function lowerImplMethod(method, typeName, namespace, structDefs, implMethods, macroInfo = new Map(), fnParamInfo = new Map(), enumDefs = new Map(), sourceFile, timerCounter = { count: 0, timerId: 0 }, enumPayloads = new Map(), constValues = new Map()) {
|
|
360
|
+
function lowerImplMethod(method, typeName, namespace, structDefs, implMethods, macroInfo = new Map(), fnParamInfo = new Map(), enumDefs = new Map(), sourceFile, timerCounter = { count: 0, timerId: 0 }, enumPayloads = new Map(), constValues = new Map(), globalVarNames = new Set()) {
|
|
356
361
|
const fnName = `${typeName}::${method.name}`;
|
|
357
362
|
const ctx = new FnContext(namespace, fnName, structDefs, implMethods, macroInfo, fnParamInfo, enumDefs, timerCounter, enumPayloads);
|
|
358
363
|
ctx.sourceFile = method.sourceFile ?? sourceFile;
|
|
359
364
|
ctx.constValues = constValues;
|
|
365
|
+
ctx.globalVarNames = globalVarNames;
|
|
360
366
|
const fields = structDefs.get(typeName) ?? [];
|
|
361
367
|
const hasSelf = method.params.length > 0 && method.params[0].name === 'self';
|
|
362
368
|
const params = [];
|
|
@@ -1188,7 +1194,8 @@ function lowerStmt(stmt, ctx, scope) {
|
|
|
1188
1194
|
const ns = ctx.getNamespace();
|
|
1189
1195
|
const rawCmd = stmt.cmd
|
|
1190
1196
|
.replace(/__NS__/g, ns)
|
|
1191
|
-
.replace(/__OBJ__/g, `__${ns}`)
|
|
1197
|
+
.replace(/__OBJ__/g, `__${ns}`)
|
|
1198
|
+
.replace(/__RS__/g, 'rs');
|
|
1192
1199
|
ctx.emit({ kind: 'call', dst: null, fn: `__raw:${rawCmd}`, args: [] });
|
|
1193
1200
|
break;
|
|
1194
1201
|
}
|
|
@@ -1367,6 +1374,13 @@ function lowerExpr(expr, ctx, scope) {
|
|
|
1367
1374
|
if (ctx.constValues.has(expr.name)) {
|
|
1368
1375
|
return { kind: 'const', value: ctx.constValues.get(expr.name) };
|
|
1369
1376
|
}
|
|
1377
|
+
// Module-level global variable: read from scoreboard slot
|
|
1378
|
+
if (ctx.globalVarNames.has(expr.name)) {
|
|
1379
|
+
const t = ctx.freshTemp();
|
|
1380
|
+
ctx.emit({ kind: 'score_read', dst: t, player: expr.name, obj: `__${ctx.getNamespace()}` });
|
|
1381
|
+
scope.set(expr.name, t);
|
|
1382
|
+
return { kind: 'temp', name: t };
|
|
1383
|
+
}
|
|
1370
1384
|
// Unresolved ident — could be a global or external reference
|
|
1371
1385
|
const t = ctx.freshTemp();
|
|
1372
1386
|
ctx.emit({ kind: 'copy', dst: t, src: { kind: 'const', value: 0 } });
|
|
@@ -1465,6 +1479,31 @@ function lowerExpr(expr, ctx, scope) {
|
|
|
1465
1479
|
}
|
|
1466
1480
|
case 'assign': {
|
|
1467
1481
|
const val = lowerExpr(expr.value, ctx, scope);
|
|
1482
|
+
// Check if the target is a struct variable — if so, update its field temps
|
|
1483
|
+
// from the __rf_<field> return slots that the callee populated.
|
|
1484
|
+
const sv = ctx.structVars.get(expr.target);
|
|
1485
|
+
if (sv) {
|
|
1486
|
+
const fields = ctx.structDefs.get(sv.typeName) ?? [];
|
|
1487
|
+
for (const fieldName of fields) {
|
|
1488
|
+
const existingFieldTemp = sv.fields.get(fieldName);
|
|
1489
|
+
// Reuse the existing field temp if it exists (so scoreboard slot stays stable),
|
|
1490
|
+
// otherwise allocate a fresh one.
|
|
1491
|
+
const fieldTemp = existingFieldTemp ?? ctx.freshTemp();
|
|
1492
|
+
ctx.emit({ kind: 'copy', dst: fieldTemp, src: { kind: 'temp', name: `__rf_${fieldName}` } });
|
|
1493
|
+
sv.fields.set(fieldName, fieldTemp);
|
|
1494
|
+
}
|
|
1495
|
+
return val;
|
|
1496
|
+
}
|
|
1497
|
+
// Global variable assignment: write to scoreboard (score_write has side effects, DCE-safe)
|
|
1498
|
+
if (ctx.globalVarNames.has(expr.target)) {
|
|
1499
|
+
const globalObj = `__${ctx.getNamespace()}`;
|
|
1500
|
+
ctx.emit({ kind: 'score_write', player: expr.target, obj: globalObj, src: val });
|
|
1501
|
+
// Also update scope temp so subsequent reads in this function see the new value
|
|
1502
|
+
const t = ctx.freshTemp();
|
|
1503
|
+
ctx.emit({ kind: 'score_read', dst: t, player: expr.target, obj: globalObj });
|
|
1504
|
+
scope.set(expr.target, t);
|
|
1505
|
+
return val;
|
|
1506
|
+
}
|
|
1468
1507
|
// Reuse the existing temp for this variable so that updates inside
|
|
1469
1508
|
// if/while bodies are visible to outer code (we target mutable
|
|
1470
1509
|
// scoreboard slots, not true SSA registers).
|
|
@@ -1887,6 +1926,69 @@ function lowerExpr(expr, ctx, scope) {
|
|
|
1887
1926
|
}
|
|
1888
1927
|
// Handle builtin calls → raw MC commands
|
|
1889
1928
|
if (macro_1.BUILTIN_SET.has(expr.fn)) {
|
|
1929
|
+
// Special case: say() with f_string → MC macro function ($say template)
|
|
1930
|
+
// MC `say` is plain text and cannot reference scoreboards directly;
|
|
1931
|
+
// use function macros (MC 1.20.2+) to interpolate variables.
|
|
1932
|
+
if (expr.fn === 'say' && expr.args[0]?.kind === 'f_string') {
|
|
1933
|
+
const fstr = precomputeFStringParts(expr.args[0], ctx, scope);
|
|
1934
|
+
if (fstr.kind === 'f_string') {
|
|
1935
|
+
const ns = ctx.getNamespace();
|
|
1936
|
+
const obj = `__${ns}`;
|
|
1937
|
+
const helperName = `${ctx.getFnName()}__say_macro_${ctx.freshTemp()}`;
|
|
1938
|
+
// Build macro template: "text $(var) more text"
|
|
1939
|
+
let template = 'say ';
|
|
1940
|
+
const macroVarNames = [];
|
|
1941
|
+
for (const part of fstr.parts) {
|
|
1942
|
+
if (part.kind === 'text') {
|
|
1943
|
+
template += part.value;
|
|
1944
|
+
}
|
|
1945
|
+
else {
|
|
1946
|
+
const inner = part.expr;
|
|
1947
|
+
if (inner.kind === 'ident') {
|
|
1948
|
+
// Strip leading $ from temp names for macro param names
|
|
1949
|
+
const varName = inner.name.startsWith('$') ? inner.name.slice(1) : inner.name;
|
|
1950
|
+
template += `$(${varName})`;
|
|
1951
|
+
macroVarNames.push(inner.name);
|
|
1952
|
+
}
|
|
1953
|
+
else if (inner.kind === 'int_lit') {
|
|
1954
|
+
template += String(inner.value);
|
|
1955
|
+
}
|
|
1956
|
+
else {
|
|
1957
|
+
template += '?';
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
// Emit: copy each scoreboard var to rs:macro_args storage
|
|
1962
|
+
for (const varName of macroVarNames) {
|
|
1963
|
+
const cleanName = varName.startsWith('$') ? varName.slice(1) : varName;
|
|
1964
|
+
ctx.emit({
|
|
1965
|
+
kind: 'call', dst: null,
|
|
1966
|
+
fn: `__raw:execute store result storage rs:macro_args ${cleanName} int 1 run scoreboard players get ${varName} ${obj}`,
|
|
1967
|
+
args: [],
|
|
1968
|
+
});
|
|
1969
|
+
}
|
|
1970
|
+
// Build helper MIR function with isMacro: true
|
|
1971
|
+
const helperCtx = new FnContext(ns, helperName, ctx.structDefs, ctx.implMethods);
|
|
1972
|
+
helperCtx.emit({ kind: 'call', dst: null, fn: `__raw:$${template}`, args: [] });
|
|
1973
|
+
helperCtx.terminate({ kind: 'return', value: null });
|
|
1974
|
+
const helperReachable = computeReachable(helperCtx.blocks, 'entry');
|
|
1975
|
+
const helperBlocks = helperCtx.blocks.filter(b => helperReachable.has(b.id));
|
|
1976
|
+
computePreds(helperBlocks);
|
|
1977
|
+
ctx.helperFunctions.push({
|
|
1978
|
+
name: helperName,
|
|
1979
|
+
params: [],
|
|
1980
|
+
blocks: helperBlocks,
|
|
1981
|
+
entry: 'entry',
|
|
1982
|
+
isMacro: true,
|
|
1983
|
+
sourceSnippet: 'say macro helper',
|
|
1984
|
+
});
|
|
1985
|
+
// Emit: function <helper> with storage rs:macro_args
|
|
1986
|
+
ctx.emit({ kind: 'call', dst: null, fn: `__raw:function ${ns}:${helperName} with storage rs:macro_args`, args: [] });
|
|
1987
|
+
const t = ctx.freshTemp();
|
|
1988
|
+
ctx.emit({ kind: 'const', dst: t, value: 0 });
|
|
1989
|
+
return { kind: 'temp', name: t };
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1890
1992
|
// For text builtins with f-string args, precompute complex expressions to temp vars
|
|
1891
1993
|
const TEXT_BUILTINS_SET = new Set(['tell', 'tellraw', 'title', 'subtitle', 'actionbar', 'announce']);
|
|
1892
1994
|
let resolvedArgs = expr.args;
|
|
@@ -2040,7 +2142,7 @@ function lowerExpr(expr, ctx, scope) {
|
|
|
2040
2142
|
if (!ctx.specializedFnsRegistry.has(specializedName)) {
|
|
2041
2143
|
// Placeholder to prevent re-entry
|
|
2042
2144
|
ctx.specializedFnsRegistry.set(specializedName, []);
|
|
2043
|
-
const { fn: specFn, helpers: specHelpers } = lowerFunction(targetHirFn, ctx.getNamespace(), ctx.structDefs, ctx.implMethods, ctx.macroInfo, ctx.fnParamInfo, ctx.enumDefs, ctx.sourceFile, ctx.timerCounter, arrayArgBindings, ctx.hirFunctions, ctx.specializedFnsRegistry, specializedName, ctx.enumPayloads, ctx.constValues, ctx.singletonStructs, ctx.displayImpls);
|
|
2145
|
+
const { fn: specFn, helpers: specHelpers } = lowerFunction(targetHirFn, ctx.getNamespace(), ctx.structDefs, ctx.implMethods, ctx.macroInfo, ctx.fnParamInfo, ctx.enumDefs, ctx.sourceFile, ctx.timerCounter, arrayArgBindings, ctx.hirFunctions, ctx.specializedFnsRegistry, specializedName, ctx.enumPayloads, ctx.constValues, ctx.singletonStructs, ctx.displayImpls, ctx.globalVarNames);
|
|
2044
2146
|
ctx.specializedFnsRegistry.set(specializedName, [specFn, ...specHelpers]);
|
|
2045
2147
|
}
|
|
2046
2148
|
// Emit call to the specialized function, passing only non-array args
|
|
@@ -62,7 +62,7 @@ function runOnePass(mod) {
|
|
|
62
62
|
if (fnMap.has(mangledName) || added.has(mangledName))
|
|
63
63
|
continue;
|
|
64
64
|
// Create specialized clone
|
|
65
|
-
const specialized = specialize(callee, constArgs.map(a => a.value), mangledName);
|
|
65
|
+
const specialized = specialize(callee, constArgs.map(a => a.value), mangledName, mod.objective);
|
|
66
66
|
newFunctions.push(specialized);
|
|
67
67
|
added.add(mangledName);
|
|
68
68
|
fnMap.set(mangledName, specialized);
|
|
@@ -90,13 +90,48 @@ function isSelfRecursive(fn) {
|
|
|
90
90
|
function mangleName(name, args) {
|
|
91
91
|
return `${name}__const_${args.map(v => v < 0 ? `n${Math.abs(v)}` : String(v)).join('_')}`;
|
|
92
92
|
}
|
|
93
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Returns true if the function has any __raw: calls that directly reference
|
|
95
|
+
* scoreboard param slots ($p0, $p1, ...) by name in the raw command string.
|
|
96
|
+
* Such functions use the raw() pattern to read params via scoreboard, so the
|
|
97
|
+
* specialized clone must pre-set those slots before executing the body.
|
|
98
|
+
*/
|
|
99
|
+
function hasRawParamRefs(fn, paramCount) {
|
|
100
|
+
for (let i = 0; i < paramCount; i++) {
|
|
101
|
+
const pattern = `$p${i}`;
|
|
102
|
+
for (const block of fn.blocks) {
|
|
103
|
+
for (const instr of block.instrs) {
|
|
104
|
+
if (instr.kind === 'call' && instr.fn.startsWith('__raw:') && instr.fn.includes(pattern)) {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
function specialize(fn, args, newName, objective) {
|
|
94
113
|
// Build substitution map: param.name → const operand
|
|
95
114
|
const sub = new Map();
|
|
96
115
|
for (let i = 0; i < fn.params.length; i++) {
|
|
97
116
|
sub.set(fn.params[i].name, { kind: 'const', value: args[i] });
|
|
98
117
|
}
|
|
99
118
|
const newBlocks = fn.blocks.map(block => substituteBlock(block, sub));
|
|
119
|
+
// If the function uses raw() commands that read from $p<i> scoreboard slots
|
|
120
|
+
// directly, we must pre-set those slots in the entry block so the raw commands
|
|
121
|
+
// see the correct values (the normal call convention sets $p<i> at the call
|
|
122
|
+
// site, but the specialized function is called with no args).
|
|
123
|
+
if (hasRawParamRefs(fn, args.length)) {
|
|
124
|
+
const entryBlock = newBlocks.find(b => b.id === fn.entry);
|
|
125
|
+
if (entryBlock) {
|
|
126
|
+
const scoreWrites = args.map((value, i) => ({
|
|
127
|
+
kind: 'score_write',
|
|
128
|
+
player: `$p${i}`,
|
|
129
|
+
obj: objective,
|
|
130
|
+
src: { kind: 'const', value },
|
|
131
|
+
}));
|
|
132
|
+
entryBlock.instrs = [...scoreWrites, ...entryBlock.instrs];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
100
135
|
const specialized = {
|
|
101
136
|
...fn,
|
|
102
137
|
name: newName,
|
|
@@ -0,0 +1,19 @@
|
|
|
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
|
+
import type { FnDecl, StructDecl, EnumDecl, ImplBlock, InterfaceDecl, ConstDecl, GlobalDecl, Decorator } from '../ast/types';
|
|
6
|
+
import { StmtParser } from './stmt-parser';
|
|
7
|
+
export declare class DeclParser extends StmtParser {
|
|
8
|
+
parseStructDecl(): StructDecl;
|
|
9
|
+
parseEnumDecl(): EnumDecl;
|
|
10
|
+
parseImplBlock(): ImplBlock;
|
|
11
|
+
parseInterfaceDecl(): InterfaceDecl;
|
|
12
|
+
parseConstDecl(): ConstDecl;
|
|
13
|
+
parseGlobalDecl(mutable: boolean): GlobalDecl;
|
|
14
|
+
parseExportedFnDecl(): FnDecl;
|
|
15
|
+
parseFnDecl(implTypeName?: string): FnDecl;
|
|
16
|
+
parseDeclareStub(): void;
|
|
17
|
+
private parseDecorators;
|
|
18
|
+
parseDecoratorValue(value: string): Decorator;
|
|
19
|
+
}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* DeclParser — declaration parsing (fn/struct/enum/impl/interface/const/global/import).
|
|
4
|
+
* Extends StmtParser so declaration methods can call block/statement methods.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.DeclParser = void 0;
|
|
8
|
+
const stmt_parser_1 = require("./stmt-parser");
|
|
9
|
+
class DeclParser extends stmt_parser_1.StmtParser {
|
|
10
|
+
// -------------------------------------------------------------------------
|
|
11
|
+
// Struct
|
|
12
|
+
// -------------------------------------------------------------------------
|
|
13
|
+
parseStructDecl() {
|
|
14
|
+
const structToken = this.expect('struct');
|
|
15
|
+
const name = this.expect('ident').value;
|
|
16
|
+
const extendsName = this.match('extends') ? this.expect('ident').value : undefined;
|
|
17
|
+
this.expect('{');
|
|
18
|
+
const fields = [];
|
|
19
|
+
while (!this.check('}') && !this.check('eof')) {
|
|
20
|
+
const fieldName = this.expect('ident').value;
|
|
21
|
+
this.expect(':');
|
|
22
|
+
const fieldType = this.parseType();
|
|
23
|
+
fields.push({ name: fieldName, type: fieldType });
|
|
24
|
+
this.match(',');
|
|
25
|
+
}
|
|
26
|
+
this.expect('}');
|
|
27
|
+
return this.withLoc({ name, extends: extendsName, fields }, structToken);
|
|
28
|
+
}
|
|
29
|
+
// -------------------------------------------------------------------------
|
|
30
|
+
// Enum
|
|
31
|
+
// -------------------------------------------------------------------------
|
|
32
|
+
parseEnumDecl() {
|
|
33
|
+
const enumToken = this.expect('enum');
|
|
34
|
+
const name = this.expect('ident').value;
|
|
35
|
+
this.expect('{');
|
|
36
|
+
const variants = [];
|
|
37
|
+
let nextValue = 0;
|
|
38
|
+
while (!this.check('}') && !this.check('eof')) {
|
|
39
|
+
const variantToken = this.expect('ident');
|
|
40
|
+
const variant = { name: variantToken.value };
|
|
41
|
+
if (this.check('(')) {
|
|
42
|
+
this.advance();
|
|
43
|
+
const fields = [];
|
|
44
|
+
while (!this.check(')') && !this.check('eof')) {
|
|
45
|
+
const fieldName = this.expect('ident').value;
|
|
46
|
+
this.expect(':');
|
|
47
|
+
const fieldType = this.parseType();
|
|
48
|
+
fields.push({ name: fieldName, type: fieldType });
|
|
49
|
+
if (!this.match(','))
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
this.expect(')');
|
|
53
|
+
variant.fields = fields;
|
|
54
|
+
}
|
|
55
|
+
if (this.match('=')) {
|
|
56
|
+
const valueToken = this.expect('int_lit');
|
|
57
|
+
variant.value = parseInt(valueToken.value, 10);
|
|
58
|
+
nextValue = variant.value + 1;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
variant.value = nextValue++;
|
|
62
|
+
}
|
|
63
|
+
variants.push(variant);
|
|
64
|
+
if (!this.match(','))
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
this.expect('}');
|
|
68
|
+
return this.withLoc({ name, variants }, enumToken);
|
|
69
|
+
}
|
|
70
|
+
// -------------------------------------------------------------------------
|
|
71
|
+
// Impl Block
|
|
72
|
+
// -------------------------------------------------------------------------
|
|
73
|
+
parseImplBlock() {
|
|
74
|
+
const implToken = this.expect('impl');
|
|
75
|
+
let traitName;
|
|
76
|
+
let typeName;
|
|
77
|
+
const firstName = this.expect('ident').value;
|
|
78
|
+
if (this.match('for')) {
|
|
79
|
+
traitName = firstName;
|
|
80
|
+
typeName = this.expect('ident').value;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
typeName = firstName;
|
|
84
|
+
}
|
|
85
|
+
this.expect('{');
|
|
86
|
+
const methods = [];
|
|
87
|
+
while (!this.check('}') && !this.check('eof')) {
|
|
88
|
+
methods.push(this.parseFnDecl(typeName));
|
|
89
|
+
}
|
|
90
|
+
this.expect('}');
|
|
91
|
+
return this.withLoc({ kind: 'impl_block', traitName, typeName, methods }, implToken);
|
|
92
|
+
}
|
|
93
|
+
// -------------------------------------------------------------------------
|
|
94
|
+
// Interface
|
|
95
|
+
// -------------------------------------------------------------------------
|
|
96
|
+
parseInterfaceDecl() {
|
|
97
|
+
const ifaceToken = this.expect('interface');
|
|
98
|
+
const name = this.expect('ident').value;
|
|
99
|
+
this.expect('{');
|
|
100
|
+
const methods = [];
|
|
101
|
+
while (!this.check('}') && !this.check('eof')) {
|
|
102
|
+
const fnToken = this.expect('fn');
|
|
103
|
+
const methodName = this.expect('ident').value;
|
|
104
|
+
this.expect('(');
|
|
105
|
+
const params = this.parseInterfaceParams();
|
|
106
|
+
this.expect(')');
|
|
107
|
+
let returnType;
|
|
108
|
+
if (this.match(':'))
|
|
109
|
+
returnType = this.parseType();
|
|
110
|
+
methods.push(this.withLoc({ name: methodName, params, returnType }, fnToken));
|
|
111
|
+
}
|
|
112
|
+
this.expect('}');
|
|
113
|
+
return this.withLoc({ name, methods }, ifaceToken);
|
|
114
|
+
}
|
|
115
|
+
// -------------------------------------------------------------------------
|
|
116
|
+
// Const / Global
|
|
117
|
+
// -------------------------------------------------------------------------
|
|
118
|
+
parseConstDecl() {
|
|
119
|
+
const constToken = this.expect('const');
|
|
120
|
+
const name = this.expect('ident').value;
|
|
121
|
+
let type;
|
|
122
|
+
if (this.match(':'))
|
|
123
|
+
type = this.parseType();
|
|
124
|
+
this.expect('=');
|
|
125
|
+
const value = this.parseLiteralExpr();
|
|
126
|
+
this.match(';');
|
|
127
|
+
const inferredType = type ?? (value.kind === 'str_lit' ? { kind: 'named', name: 'string' } :
|
|
128
|
+
value.kind === 'bool_lit' ? { kind: 'named', name: 'bool' } :
|
|
129
|
+
value.kind === 'float_lit' ? { kind: 'named', name: 'fixed' } :
|
|
130
|
+
{ kind: 'named', name: 'int' });
|
|
131
|
+
return this.withLoc({ name, type: inferredType, value }, constToken);
|
|
132
|
+
}
|
|
133
|
+
parseGlobalDecl(mutable) {
|
|
134
|
+
const token = this.advance(); // consume 'let'
|
|
135
|
+
const name = this.expect('ident').value;
|
|
136
|
+
this.expect(':');
|
|
137
|
+
const type = this.parseType();
|
|
138
|
+
let init;
|
|
139
|
+
if (this.match('=')) {
|
|
140
|
+
init = this.parseExpr();
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
init = { kind: 'int_lit', value: 0 };
|
|
144
|
+
}
|
|
145
|
+
this.match(';');
|
|
146
|
+
return this.withLoc({ kind: 'global', name, type, init, mutable }, token);
|
|
147
|
+
}
|
|
148
|
+
// -------------------------------------------------------------------------
|
|
149
|
+
// Function
|
|
150
|
+
// -------------------------------------------------------------------------
|
|
151
|
+
parseExportedFnDecl() {
|
|
152
|
+
this.expect('export');
|
|
153
|
+
const fn = this.parseFnDecl();
|
|
154
|
+
fn.isExported = true;
|
|
155
|
+
return fn;
|
|
156
|
+
}
|
|
157
|
+
parseFnDecl(implTypeName) {
|
|
158
|
+
const decorators = this.parseDecorators();
|
|
159
|
+
const watchObjective = decorators.find(decorator => decorator.name === 'watch')?.args?.objective;
|
|
160
|
+
let isExported;
|
|
161
|
+
const filteredDecorators = decorators.filter(d => {
|
|
162
|
+
if (d.name === 'keep') {
|
|
163
|
+
isExported = true;
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
return true;
|
|
167
|
+
});
|
|
168
|
+
const fnToken = this.expect('fn');
|
|
169
|
+
const name = this.expect('ident').value;
|
|
170
|
+
let typeParams;
|
|
171
|
+
if (this.check('<')) {
|
|
172
|
+
this.advance();
|
|
173
|
+
typeParams = [];
|
|
174
|
+
do {
|
|
175
|
+
typeParams.push(this.expect('ident').value);
|
|
176
|
+
} while (this.match(','));
|
|
177
|
+
this.expect('>');
|
|
178
|
+
}
|
|
179
|
+
this.expect('(');
|
|
180
|
+
const params = this.parseParams(implTypeName);
|
|
181
|
+
this.expect(')');
|
|
182
|
+
let returnType = { kind: 'named', name: 'void' };
|
|
183
|
+
if (this.match('->') || this.match(':')) {
|
|
184
|
+
returnType = this.parseType();
|
|
185
|
+
}
|
|
186
|
+
const body = this.parseBlock();
|
|
187
|
+
const closingBraceLine = this.tokens[this.pos - 1]?.line;
|
|
188
|
+
const fn = this.withLoc({ name, typeParams, params, returnType, decorators: filteredDecorators, body,
|
|
189
|
+
isLibraryFn: this.inLibraryMode || undefined, isExported, watchObjective }, fnToken);
|
|
190
|
+
if (fn.span && closingBraceLine)
|
|
191
|
+
fn.span.endLine = closingBraceLine;
|
|
192
|
+
return fn;
|
|
193
|
+
}
|
|
194
|
+
parseDeclareStub() {
|
|
195
|
+
this.expect('fn');
|
|
196
|
+
this.expect('ident');
|
|
197
|
+
this.expect('(');
|
|
198
|
+
let depth = 1;
|
|
199
|
+
while (!this.check('eof') && depth > 0) {
|
|
200
|
+
const t = this.advance();
|
|
201
|
+
if (t.kind === '(')
|
|
202
|
+
depth++;
|
|
203
|
+
else if (t.kind === ')')
|
|
204
|
+
depth--;
|
|
205
|
+
}
|
|
206
|
+
if (this.match(':') || this.match('->')) {
|
|
207
|
+
this.parseType();
|
|
208
|
+
}
|
|
209
|
+
this.match(';');
|
|
210
|
+
}
|
|
211
|
+
// -------------------------------------------------------------------------
|
|
212
|
+
// Decorators
|
|
213
|
+
// -------------------------------------------------------------------------
|
|
214
|
+
parseDecorators() {
|
|
215
|
+
const decorators = [];
|
|
216
|
+
while (this.check('decorator')) {
|
|
217
|
+
const token = this.advance();
|
|
218
|
+
const decorator = this.parseDecoratorValue(token.value);
|
|
219
|
+
decorators.push(decorator);
|
|
220
|
+
}
|
|
221
|
+
return decorators;
|
|
222
|
+
}
|
|
223
|
+
parseDecoratorValue(value) {
|
|
224
|
+
const match = value.match(/^@([A-Za-z_][A-Za-z0-9_-]*)(?:\((.*)\))?$/s);
|
|
225
|
+
if (!match) {
|
|
226
|
+
this.error(`Invalid decorator: ${value}`);
|
|
227
|
+
}
|
|
228
|
+
const name = match[1];
|
|
229
|
+
const argsStr = match[2];
|
|
230
|
+
if (!argsStr)
|
|
231
|
+
return { name };
|
|
232
|
+
if (name === 'profile' || name === 'benchmark' || name === 'memoize') {
|
|
233
|
+
this.error(`@${name} decorator does not accept arguments`);
|
|
234
|
+
}
|
|
235
|
+
const args = {};
|
|
236
|
+
if (name === 'on') {
|
|
237
|
+
const eventTypeMatch = argsStr.match(/^([A-Za-z_][A-Za-z0-9_]*)$/);
|
|
238
|
+
if (eventTypeMatch) {
|
|
239
|
+
args.eventType = eventTypeMatch[1];
|
|
240
|
+
return { name, args };
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (name === 'watch' || name === 'on_trigger' || name === 'on_advancement' || name === 'on_craft' || name === 'on_join_team') {
|
|
244
|
+
const strMatch = argsStr.match(/^"([^"]*)"$/);
|
|
245
|
+
if (strMatch) {
|
|
246
|
+
if (name === 'watch')
|
|
247
|
+
args.objective = strMatch[1];
|
|
248
|
+
else if (name === 'on_trigger')
|
|
249
|
+
args.trigger = strMatch[1];
|
|
250
|
+
else if (name === 'on_advancement')
|
|
251
|
+
args.advancement = strMatch[1];
|
|
252
|
+
else if (name === 'on_craft')
|
|
253
|
+
args.item = strMatch[1];
|
|
254
|
+
else if (name === 'on_join_team')
|
|
255
|
+
args.team = strMatch[1];
|
|
256
|
+
return { name, args };
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (name === 'config') {
|
|
260
|
+
const configMatch = argsStr.match(/^"([^"]+)"\s*,\s*default\s*:\s*(-?\d+(?:\.\d+)?)$/);
|
|
261
|
+
if (configMatch) {
|
|
262
|
+
return { name, args: { configKey: configMatch[1], configDefault: parseFloat(configMatch[2]) } };
|
|
263
|
+
}
|
|
264
|
+
const keyOnlyMatch = argsStr.match(/^"([^"]+)"$/);
|
|
265
|
+
if (keyOnlyMatch) {
|
|
266
|
+
return { name, args: { configKey: keyOnlyMatch[1] } };
|
|
267
|
+
}
|
|
268
|
+
this.error(`Invalid @config syntax. Expected: @config("key", default: value) or @config("key")`);
|
|
269
|
+
}
|
|
270
|
+
if (name === 'deprecated') {
|
|
271
|
+
const strMatch = argsStr.match(/^"([^"]*)"$/);
|
|
272
|
+
if (strMatch)
|
|
273
|
+
return { name, args: { message: strMatch[1] } };
|
|
274
|
+
return { name, args: {} };
|
|
275
|
+
}
|
|
276
|
+
if (name === 'test') {
|
|
277
|
+
const strMatch = argsStr.match(/^"([^"]*)"$/);
|
|
278
|
+
if (strMatch)
|
|
279
|
+
return { name, args: { testLabel: strMatch[1] } };
|
|
280
|
+
return { name, args: { testLabel: '' } };
|
|
281
|
+
}
|
|
282
|
+
if (name === 'require_on_load') {
|
|
283
|
+
const rawArgs = [];
|
|
284
|
+
for (const part of argsStr.split(',')) {
|
|
285
|
+
const trimmed = part.trim();
|
|
286
|
+
const identMatch = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)$/);
|
|
287
|
+
if (identMatch) {
|
|
288
|
+
rawArgs.push({ kind: 'string', value: identMatch[1] });
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
const strMatch = trimmed.match(/^"([^"]*)"$/);
|
|
292
|
+
if (strMatch)
|
|
293
|
+
rawArgs.push({ kind: 'string', value: strMatch[1] });
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return { name, rawArgs };
|
|
297
|
+
}
|
|
298
|
+
for (const part of argsStr.split(',')) {
|
|
299
|
+
const [key, val] = part.split('=').map(s => s.trim());
|
|
300
|
+
if (key === 'rate')
|
|
301
|
+
args.rate = parseInt(val, 10);
|
|
302
|
+
else if (key === 'ticks')
|
|
303
|
+
args.ticks = parseInt(val, 10);
|
|
304
|
+
else if (key === 'batch')
|
|
305
|
+
args.batch = parseInt(val, 10);
|
|
306
|
+
else if (key === 'onDone')
|
|
307
|
+
args.onDone = val.replace(/^["']|["']$/g, '');
|
|
308
|
+
else if (key === 'trigger')
|
|
309
|
+
args.trigger = val;
|
|
310
|
+
else if (key === 'advancement')
|
|
311
|
+
args.advancement = val;
|
|
312
|
+
else if (key === 'item')
|
|
313
|
+
args.item = val;
|
|
314
|
+
else if (key === 'team')
|
|
315
|
+
args.team = val;
|
|
316
|
+
else if (key === 'max')
|
|
317
|
+
args.max = parseInt(val, 10);
|
|
318
|
+
}
|
|
319
|
+
return { name, args };
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
exports.DeclParser = DeclParser;
|
|
323
|
+
//# sourceMappingURL=decl-parser.js.map
|