redscript-mc 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/bug_report.yml +72 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +57 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +17 -25
- package/CHANGELOG.md +58 -0
- package/CONTRIBUTING.md +140 -0
- package/README.md +28 -19
- package/README.zh.md +28 -19
- package/dist/__tests__/cli.test.js +10 -10
- package/dist/__tests__/codegen.test.js +1 -1
- package/dist/__tests__/diagnostics.test.js +5 -5
- package/dist/__tests__/e2e.test.js +146 -5
- package/dist/__tests__/formatter.test.d.ts +1 -0
- package/dist/__tests__/formatter.test.js +40 -0
- package/dist/__tests__/lowering.test.js +36 -3
- package/dist/__tests__/mc-integration.test.js +255 -10
- package/dist/__tests__/mc-syntax.test.js +3 -3
- package/dist/__tests__/nbt.test.js +2 -2
- package/dist/__tests__/optimizer-advanced.test.js +3 -3
- package/dist/__tests__/runtime.test.js +1 -1
- package/dist/ast/types.d.ts +21 -3
- package/dist/cli.js +25 -7
- package/dist/codegen/mcfunction/index.d.ts +1 -1
- package/dist/codegen/mcfunction/index.js +8 -2
- package/dist/codegen/structure/index.js +7 -1
- package/dist/formatter/index.d.ts +1 -0
- package/dist/formatter/index.js +26 -0
- package/dist/ir/builder.d.ts +2 -1
- package/dist/ir/types.d.ts +7 -2
- package/dist/ir/types.js +1 -1
- package/dist/lowering/index.d.ts +2 -0
- package/dist/lowering/index.js +183 -8
- package/dist/mc-test/runner.d.ts +2 -2
- package/dist/mc-test/runner.js +3 -3
- package/dist/mc-test/setup.js +2 -2
- package/dist/parser/index.d.ts +2 -0
- package/dist/parser/index.js +75 -7
- package/docs/COMPILATION_STATS.md +24 -24
- package/docs/IMPLEMENTATION_GUIDE.md +1 -1
- package/docs/STRUCTURE_TARGET.md +1 -1
- package/editors/vscode/.vscodeignore +1 -0
- package/editors/vscode/icons/mcrs.svg +7 -0
- package/editors/vscode/icons/redscript-icons.json +10 -0
- package/editors/vscode/out/extension.js +152 -9
- package/editors/vscode/package.json +10 -3
- package/editors/vscode/src/hover.ts +55 -2
- package/editors/vscode/src/symbols.ts +42 -0
- package/package.json +1 -1
- package/src/__tests__/cli.test.ts +10 -10
- package/src/__tests__/codegen.test.ts +1 -1
- package/src/__tests__/diagnostics.test.ts +5 -5
- package/src/__tests__/e2e.test.ts +134 -5
- package/src/__tests__/lowering.test.ts +48 -3
- package/src/__tests__/mc-integration.test.ts +285 -10
- package/src/__tests__/mc-syntax.test.ts +3 -3
- package/src/__tests__/nbt.test.ts +2 -2
- package/src/__tests__/optimizer-advanced.test.ts +3 -3
- package/src/__tests__/runtime.test.ts +1 -1
- package/src/ast/types.ts +20 -3
- package/src/cli.ts +10 -10
- package/src/codegen/mcfunction/index.ts +9 -2
- package/src/codegen/structure/index.ts +8 -1
- package/src/examples/capture_the_flag.mcrs +208 -0
- package/src/examples/{counter.rs → counter.mcrs} +1 -1
- package/src/examples/hunger_games.mcrs +301 -0
- package/src/examples/new_features_demo.mcrs +193 -0
- package/src/examples/parkour_race.mcrs +233 -0
- package/src/examples/rpg.mcrs +13 -0
- package/src/examples/{shop.rs → shop.mcrs} +1 -1
- package/src/examples/{showcase_game.rs → showcase_game.mcrs} +3 -3
- package/src/examples/{turret.rs → turret.mcrs} +1 -1
- package/src/examples/zombie_survival.mcrs +314 -0
- package/src/ir/builder.ts +3 -1
- package/src/ir/types.ts +8 -2
- package/src/lowering/index.ts +156 -8
- package/src/mc-test/runner.ts +3 -3
- package/src/mc-test/setup.ts +2 -2
- package/src/parser/index.ts +81 -8
- package/src/stdlib/README.md +155 -147
- package/src/stdlib/bossbar.mcrs +68 -0
- package/src/stdlib/{cooldown.rs → cooldown.mcrs} +1 -1
- package/src/stdlib/effects.mcrs +64 -0
- package/src/stdlib/interactions.mcrs +195 -0
- package/src/stdlib/inventory.mcrs +38 -0
- package/src/stdlib/mobs.mcrs +99 -0
- package/src/stdlib/particles.mcrs +52 -0
- package/src/stdlib/sets.mcrs +20 -0
- package/src/stdlib/spawn.mcrs +41 -0
- package/src/stdlib/teams.mcrs +68 -0
- package/src/stdlib/world.mcrs +92 -0
- package/src/examples/rpg.rs +0 -13
- package/src/stdlib/mobs.rs +0 -99
- /package/src/examples/{arena.rs → arena.mcrs} +0 -0
- /package/src/examples/{pvp_arena.rs → pvp_arena.mcrs} +0 -0
- /package/src/examples/{quiz.rs → quiz.mcrs} +0 -0
- /package/src/examples/{stdlib_demo.rs → stdlib_demo.mcrs} +0 -0
- /package/src/examples/{world_manager.rs → world_manager.mcrs} +0 -0
- /package/src/stdlib/{combat.rs → combat.mcrs} +0 -0
- /package/src/stdlib/{math.rs → math.mcrs} +0 -0
- /package/src/stdlib/{player.rs → player.mcrs} +0 -0
- /package/src/stdlib/{strings.rs → strings.mcrs} +0 -0
- /package/src/stdlib/{timer.rs → timer.mcrs} +0 -0
- /package/src/templates/{combat.rs → combat.mcrs} +0 -0
- /package/src/templates/{economy.rs → economy.mcrs} +0 -0
- /package/src/templates/{mini-game-framework.rs → mini-game-framework.mcrs} +0 -0
- /package/src/templates/{quest.rs → quest.mcrs} +0 -0
- /package/src/test_programs/{zombie_game.rs → zombie_game.mcrs} +0 -0
|
@@ -26,7 +26,7 @@ fn turret_tick() {
|
|
|
26
26
|
const result = (0, index_1.compile)(source, { namespace: 'test' });
|
|
27
27
|
const parent = getFileContent(result.files, 'data/test/function/turret_tick.mcfunction');
|
|
28
28
|
const loopBody = getFileContent(result.files, 'data/test/function/turret_tick/foreach_0.mcfunction');
|
|
29
|
-
const hoistedRead = 'execute store result score $
|
|
29
|
+
const hoistedRead = 'execute store result score $_0 rs run scoreboard players get config turret_range';
|
|
30
30
|
const executeCall = 'execute as @e[tag=turret] run function test:turret_tick/foreach_0';
|
|
31
31
|
expect(parent).toContain(hoistedRead);
|
|
32
32
|
expect(parent.indexOf(hoistedRead)).toBeLessThan(parent.indexOf(executeCall));
|
|
@@ -48,7 +48,7 @@ fn read_twice() {
|
|
|
48
48
|
const fn = getFileContent(result.files, 'data/test/function/read_twice.mcfunction');
|
|
49
49
|
const readMatches = fn.match(/scoreboard players get @s coins/g) ?? [];
|
|
50
50
|
expect(readMatches).toHaveLength(1);
|
|
51
|
-
expect(fn).toContain('scoreboard players operation $
|
|
51
|
+
expect(fn).toContain('scoreboard players operation $_1 rs = $_0 rs');
|
|
52
52
|
});
|
|
53
53
|
test('reuses duplicate arithmetic sequences', () => {
|
|
54
54
|
const source = `
|
|
@@ -65,7 +65,7 @@ fn math() {
|
|
|
65
65
|
const fn = getFileContent(result.files, 'data/test/function/math.mcfunction');
|
|
66
66
|
const addMatches = fn.match(/\+= \$const_2 rs/g) ?? [];
|
|
67
67
|
expect(addMatches).toHaveLength(1);
|
|
68
|
-
expect(fn).toContain('scoreboard players operation $
|
|
68
|
+
expect(fn).toContain('scoreboard players operation $_1 rs = $_0 rs');
|
|
69
69
|
});
|
|
70
70
|
});
|
|
71
71
|
describe('setblock batching', () => {
|
|
@@ -58,7 +58,7 @@ function loadExample(name) {
|
|
|
58
58
|
}
|
|
59
59
|
describe('MCRuntime behavioral integration', () => {
|
|
60
60
|
it('runs the counter example and increments the scoreboard across ticks', () => {
|
|
61
|
-
const runtime = loadCompiledProgram(loadExample('counter.
|
|
61
|
+
const runtime = loadCompiledProgram(loadExample('counter.mcrs'));
|
|
62
62
|
runtime.load();
|
|
63
63
|
runtime.ticks(5);
|
|
64
64
|
expect(runtime.getScore('counter', 'ticks')).toBe(5);
|
package/dist/ast/types.d.ts
CHANGED
|
@@ -54,6 +54,11 @@ export interface SelectorFilter {
|
|
|
54
54
|
sort?: 'nearest' | 'furthest' | 'random' | 'arbitrary';
|
|
55
55
|
nbt?: string;
|
|
56
56
|
gamemode?: string;
|
|
57
|
+
x?: RangeExpr;
|
|
58
|
+
y?: RangeExpr;
|
|
59
|
+
z?: RangeExpr;
|
|
60
|
+
x_rotation?: RangeExpr;
|
|
61
|
+
y_rotation?: RangeExpr;
|
|
57
62
|
}
|
|
58
63
|
export interface EntitySelector {
|
|
59
64
|
kind: SelectorKind;
|
|
@@ -213,10 +218,14 @@ export type ExecuteSubcommand = {
|
|
|
213
218
|
selector: EntitySelector;
|
|
214
219
|
} | {
|
|
215
220
|
kind: 'if_entity';
|
|
216
|
-
selector
|
|
221
|
+
selector?: EntitySelector;
|
|
222
|
+
varName?: string;
|
|
223
|
+
filters?: SelectorFilter;
|
|
217
224
|
} | {
|
|
218
225
|
kind: 'unless_entity';
|
|
219
|
-
selector
|
|
226
|
+
selector?: EntitySelector;
|
|
227
|
+
varName?: string;
|
|
228
|
+
filters?: SelectorFilter;
|
|
220
229
|
} | {
|
|
221
230
|
kind: 'in';
|
|
222
231
|
dimension: string;
|
|
@@ -302,7 +311,7 @@ export type Stmt = {
|
|
|
302
311
|
};
|
|
303
312
|
export type Block = Stmt[];
|
|
304
313
|
export interface Decorator {
|
|
305
|
-
name: 'tick' | 'on_trigger' | 'on_advancement' | 'on_craft' | 'on_death' | 'on_login' | 'on_join_team';
|
|
314
|
+
name: 'tick' | 'load' | 'on_trigger' | 'on_advancement' | 'on_craft' | 'on_death' | 'on_login' | 'on_join_team';
|
|
306
315
|
args?: {
|
|
307
316
|
rate?: number;
|
|
308
317
|
trigger?: string;
|
|
@@ -348,8 +357,17 @@ export interface ConstDecl {
|
|
|
348
357
|
value: LiteralExpr;
|
|
349
358
|
span?: Span;
|
|
350
359
|
}
|
|
360
|
+
export interface GlobalDecl {
|
|
361
|
+
kind: 'global';
|
|
362
|
+
name: string;
|
|
363
|
+
type: TypeNode;
|
|
364
|
+
init: Expr;
|
|
365
|
+
mutable: boolean;
|
|
366
|
+
span?: Span;
|
|
367
|
+
}
|
|
351
368
|
export interface Program {
|
|
352
369
|
namespace: string;
|
|
370
|
+
globals: GlobalDecl[];
|
|
353
371
|
declarations: FnDecl[];
|
|
354
372
|
structs: StructDecl[];
|
|
355
373
|
enums: EnumDecl[];
|
package/dist/cli.js
CHANGED
|
@@ -60,13 +60,15 @@ Usage:
|
|
|
60
60
|
redscript compile <file> [-o <out>] [--output-nbt <file>] [--namespace <ns>] [--target <target>]
|
|
61
61
|
redscript watch <dir> [-o <outdir>] [--namespace <ns>] [--hot-reload <url>]
|
|
62
62
|
redscript check <file>
|
|
63
|
+
redscript fmt <file.mcrs> [file2.mcrs ...]
|
|
63
64
|
redscript repl
|
|
64
65
|
redscript version
|
|
65
66
|
|
|
66
67
|
Commands:
|
|
67
68
|
compile Compile a RedScript file to a Minecraft datapack
|
|
68
|
-
watch Watch a directory for .
|
|
69
|
+
watch Watch a directory for .mcrs file changes, recompile, and hot reload
|
|
69
70
|
check Check a RedScript file for errors without generating output
|
|
71
|
+
fmt Auto-format RedScript source files
|
|
70
72
|
repl Start an interactive RedScript REPL
|
|
71
73
|
version Print the RedScript version
|
|
72
74
|
|
|
@@ -275,18 +277,18 @@ function watchCommand(dir, output, namespace, hotReloadUrl) {
|
|
|
275
277
|
console.error(`Error: ${dir} is not a directory`);
|
|
276
278
|
process.exit(1);
|
|
277
279
|
}
|
|
278
|
-
console.log(`👁 Watching ${dir} for .
|
|
280
|
+
console.log(`👁 Watching ${dir} for .mcrs file changes...`);
|
|
279
281
|
console.log(` Output: ${output}`);
|
|
280
282
|
if (hotReloadUrl)
|
|
281
283
|
console.log(` Hot reload: ${hotReloadUrl}`);
|
|
282
284
|
console.log(` Press Ctrl+C to stop\n`);
|
|
283
285
|
// Debounce timer
|
|
284
286
|
let debounceTimer = null;
|
|
285
|
-
// Compile all .
|
|
287
|
+
// Compile all .mcrs files in directory
|
|
286
288
|
async function compileAll() {
|
|
287
289
|
const files = findRsFiles(dir);
|
|
288
290
|
if (files.length === 0) {
|
|
289
|
-
console.log(`⚠ No .
|
|
291
|
+
console.log(`⚠ No .mcrs files found in ${dir}`);
|
|
290
292
|
return;
|
|
291
293
|
}
|
|
292
294
|
let hasErrors = false;
|
|
@@ -321,7 +323,7 @@ function watchCommand(dir, output, namespace, hotReloadUrl) {
|
|
|
321
323
|
console.log('');
|
|
322
324
|
}
|
|
323
325
|
}
|
|
324
|
-
// Find all .
|
|
326
|
+
// Find all .mcrs files recursively
|
|
325
327
|
function findRsFiles(directory) {
|
|
326
328
|
const results = [];
|
|
327
329
|
const entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
@@ -330,7 +332,7 @@ function watchCommand(dir, output, namespace, hotReloadUrl) {
|
|
|
330
332
|
if (entry.isDirectory()) {
|
|
331
333
|
results.push(...findRsFiles(fullPath));
|
|
332
334
|
}
|
|
333
|
-
else if (entry.isFile() && entry.name.endsWith('.
|
|
335
|
+
else if (entry.isFile() && entry.name.endsWith('.mcrs')) {
|
|
334
336
|
results.push(fullPath);
|
|
335
337
|
}
|
|
336
338
|
}
|
|
@@ -340,7 +342,7 @@ function watchCommand(dir, output, namespace, hotReloadUrl) {
|
|
|
340
342
|
void compileAll();
|
|
341
343
|
// Watch for changes
|
|
342
344
|
fs.watch(dir, { recursive: true }, (eventType, filename) => {
|
|
343
|
-
if (filename && filename.endsWith('.
|
|
345
|
+
if (filename && filename.endsWith('.mcrs')) {
|
|
344
346
|
// Debounce rapid changes
|
|
345
347
|
if (debounceTimer) {
|
|
346
348
|
clearTimeout(debounceTimer);
|
|
@@ -391,6 +393,22 @@ async function main() {
|
|
|
391
393
|
}
|
|
392
394
|
checkCommand(parsed.file);
|
|
393
395
|
break;
|
|
396
|
+
case 'fmt':
|
|
397
|
+
case 'format': {
|
|
398
|
+
const files = args.filter(a => a.endsWith('.mcrs'));
|
|
399
|
+
if (files.length === 0) {
|
|
400
|
+
console.error('Usage: redscript fmt <file.mcrs> [file2.mcrs ...]');
|
|
401
|
+
process.exit(1);
|
|
402
|
+
}
|
|
403
|
+
const { format } = require('./formatter');
|
|
404
|
+
for (const file of files) {
|
|
405
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
406
|
+
const formatted = format(content);
|
|
407
|
+
fs.writeFileSync(file, formatted);
|
|
408
|
+
console.log(`Formatted: ${file}`);
|
|
409
|
+
}
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
394
412
|
case 'repl':
|
|
395
413
|
await (0, repl_1.startRepl)(parsed.namespace ?? 'repl');
|
|
396
414
|
break;
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* Variable mapping:
|
|
13
13
|
* scoreboard objective: "rs"
|
|
14
14
|
* fake player: "$<varname>"
|
|
15
|
-
* temporaries: "$
|
|
15
|
+
* temporaries: "$_0", "$_1", ...
|
|
16
16
|
* return value: "$ret"
|
|
17
17
|
* parameters: "$p0", "$p1", ...
|
|
18
18
|
*/
|
|
@@ -258,7 +258,7 @@ function generateDatapackWithStats(module, options = {}) {
|
|
|
258
258
|
`scoreboard objectives add ${OBJ} dummy`,
|
|
259
259
|
];
|
|
260
260
|
for (const g of module.globals) {
|
|
261
|
-
loadLines.push(`scoreboard players set ${varRef(g)} ${OBJ}
|
|
261
|
+
loadLines.push(`scoreboard players set ${varRef(g.name)} ${OBJ} ${g.init}`);
|
|
262
262
|
}
|
|
263
263
|
// Add trigger objectives
|
|
264
264
|
for (const triggerName of triggerNames) {
|
|
@@ -310,6 +310,12 @@ function generateDatapackWithStats(module, options = {}) {
|
|
|
310
310
|
files.push({ path: filePath, content: lines.join('\n') });
|
|
311
311
|
}
|
|
312
312
|
}
|
|
313
|
+
// Call @load functions from __load
|
|
314
|
+
for (const fn of module.functions) {
|
|
315
|
+
if (fn.isLoadInit) {
|
|
316
|
+
loadLines.push(`function ${ns}:${fn.name}`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
313
319
|
// Write __load.mcfunction
|
|
314
320
|
files.push({
|
|
315
321
|
path: `data/${ns}/function/__load.mcfunction`,
|
|
@@ -64,13 +64,19 @@ function collectCommandEntriesFromModule(module) {
|
|
|
64
64
|
const triggerNames = new Set(triggerHandlers.map(fn => fn.triggerName));
|
|
65
65
|
const loadCommands = [
|
|
66
66
|
`scoreboard objectives add ${OBJ} dummy`,
|
|
67
|
-
...module.globals.map(
|
|
67
|
+
...module.globals.map(g => `scoreboard players set ${varRef(g.name)} ${OBJ} ${g.init}`),
|
|
68
68
|
...Array.from(triggerNames).flatMap(triggerName => [
|
|
69
69
|
`scoreboard objectives add ${triggerName} trigger`,
|
|
70
70
|
`scoreboard players enable @a ${triggerName}`,
|
|
71
71
|
]),
|
|
72
72
|
...Array.from(new Set(module.functions.flatMap(fn => Array.from(collectConsts(fn))))).map(constSetup),
|
|
73
73
|
];
|
|
74
|
+
// Call @load functions from __load
|
|
75
|
+
for (const fn of module.functions) {
|
|
76
|
+
if (fn.isLoadInit) {
|
|
77
|
+
loadCommands.push(`function ${module.namespace}:${fn.name}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
74
80
|
const sections = [];
|
|
75
81
|
if (loadCommands.length > 0) {
|
|
76
82
|
sections.push({
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function format(source: string): string;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.format = format;
|
|
4
|
+
function format(source) {
|
|
5
|
+
const lines = source.split("\n");
|
|
6
|
+
let indent = 0;
|
|
7
|
+
const result = [];
|
|
8
|
+
for (let line of lines) {
|
|
9
|
+
line = line.trim();
|
|
10
|
+
if (!line) {
|
|
11
|
+
result.push("");
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
// Decrease indent before }
|
|
15
|
+
if (line.startsWith("}"))
|
|
16
|
+
indent = Math.max(0, indent - 1);
|
|
17
|
+
// Add indentation
|
|
18
|
+
result.push(" ".repeat(indent) + line);
|
|
19
|
+
// Increase indent after {
|
|
20
|
+
if (line.endsWith("{"))
|
|
21
|
+
indent++;
|
|
22
|
+
}
|
|
23
|
+
// Ensure single newline at end
|
|
24
|
+
return result.join("\n").trimEnd() + "\n";
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=index.js.map
|
package/dist/ir/builder.d.ts
CHANGED
|
@@ -29,4 +29,5 @@ export declare class IRBuilder {
|
|
|
29
29
|
emitTickYield(continuation: string): void;
|
|
30
30
|
build(name: string, params: string[], isTickLoop?: boolean): IRFunction;
|
|
31
31
|
}
|
|
32
|
-
|
|
32
|
+
import type { GlobalVar } from './types';
|
|
33
|
+
export declare function buildModule(namespace: string, fns: IRFunction[], globals?: GlobalVar[]): IRModule;
|
package/dist/ir/types.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - Integer vars → scoreboard fake player ($name on objective "rs_vars")
|
|
9
9
|
* - Complex data → NBT storage (redscript:stack / redscript:heap)
|
|
10
10
|
* - Return value → fake player $ret
|
|
11
|
-
* - Temporaries → $
|
|
11
|
+
* - Temporaries → $_0, $_1, ...
|
|
12
12
|
*/
|
|
13
13
|
export type Operand = {
|
|
14
14
|
kind: 'var';
|
|
@@ -103,6 +103,7 @@ export interface IRFunction {
|
|
|
103
103
|
blocks: IRBlock[];
|
|
104
104
|
commands?: IRCommand[];
|
|
105
105
|
isTickLoop?: boolean;
|
|
106
|
+
isLoadInit?: boolean;
|
|
106
107
|
isTriggerHandler?: boolean;
|
|
107
108
|
triggerName?: string;
|
|
108
109
|
eventTrigger?: {
|
|
@@ -110,8 +111,12 @@ export interface IRFunction {
|
|
|
110
111
|
value?: string;
|
|
111
112
|
};
|
|
112
113
|
}
|
|
114
|
+
export interface GlobalVar {
|
|
115
|
+
name: string;
|
|
116
|
+
init: number;
|
|
117
|
+
}
|
|
113
118
|
export interface IRModule {
|
|
114
119
|
namespace: string;
|
|
115
120
|
functions: IRFunction[];
|
|
116
|
-
globals:
|
|
121
|
+
globals: GlobalVar[];
|
|
117
122
|
}
|
package/dist/ir/types.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* - Integer vars → scoreboard fake player ($name on objective "rs_vars")
|
|
10
10
|
* - Complex data → NBT storage (redscript:stack / redscript:heap)
|
|
11
11
|
* - Return value → fake player $ret
|
|
12
|
-
* - Temporaries → $
|
|
12
|
+
* - Temporaries → $_0, $_1, ...
|
|
13
13
|
*/
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
//# sourceMappingURL=types.js.map
|
package/dist/lowering/index.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export declare class Lowering {
|
|
|
16
16
|
private namespace;
|
|
17
17
|
private functions;
|
|
18
18
|
private globals;
|
|
19
|
+
private globalNames;
|
|
19
20
|
private fnDecls;
|
|
20
21
|
private specializedFunctions;
|
|
21
22
|
private currentFn;
|
|
@@ -80,6 +81,7 @@ export declare class Lowering {
|
|
|
80
81
|
private buildRichTextJson;
|
|
81
82
|
private appendRichTextExpr;
|
|
82
83
|
private exprToString;
|
|
84
|
+
private exprToSnbt;
|
|
83
85
|
private exprToTargetString;
|
|
84
86
|
private exprToLiteral;
|
|
85
87
|
private exprToQuotedString;
|
package/dist/lowering/index.js
CHANGED
|
@@ -20,9 +20,10 @@ const BUILTINS = {
|
|
|
20
20
|
subtitle: ([sel, msg]) => `title ${sel} subtitle {"text":"${msg}"}`,
|
|
21
21
|
title_times: ([sel, fadeIn, stay, fadeOut]) => `title ${sel} times ${fadeIn} ${stay} ${fadeOut}`,
|
|
22
22
|
announce: ([msg]) => `tellraw @a {"text":"${msg}"}`,
|
|
23
|
-
give: ([sel, item, count]) => `give ${sel} ${item} ${count ?? '1'}`,
|
|
23
|
+
give: ([sel, item, count, nbt]) => nbt ? `give ${sel} ${item}${nbt} ${count ?? '1'}` : `give ${sel} ${item} ${count ?? '1'}`,
|
|
24
24
|
kill: ([sel]) => `kill ${sel ?? '@s'}`,
|
|
25
25
|
effect: ([sel, eff, dur, amp]) => `effect give ${sel} ${eff} ${dur ?? '30'} ${amp ?? '0'}`,
|
|
26
|
+
effect_clear: ([sel, eff]) => eff ? `effect clear ${sel} ${eff}` : `effect clear ${sel}`,
|
|
26
27
|
summon: ([type, x, y, z, nbt]) => {
|
|
27
28
|
const pos = [x ?? '~', y ?? '~', z ?? '~'].join(' ');
|
|
28
29
|
return nbt ? `summon ${type} ${pos} ${nbt}` : `summon ${type} ${pos}`;
|
|
@@ -73,6 +74,12 @@ const BUILTINS = {
|
|
|
73
74
|
team_leave: () => null, // Special handling
|
|
74
75
|
team_option: () => null, // Special handling
|
|
75
76
|
data_get: () => null, // Special handling (returns value from NBT)
|
|
77
|
+
data_merge: () => null, // Special handling (merge NBT)
|
|
78
|
+
set_new: () => null, // Special handling (returns set ID)
|
|
79
|
+
set_add: () => null, // Special handling
|
|
80
|
+
set_contains: () => null, // Special handling (returns 1/0)
|
|
81
|
+
set_remove: () => null, // Special handling
|
|
82
|
+
set_clear: () => null, // Special handling
|
|
76
83
|
};
|
|
77
84
|
function getSpan(node) {
|
|
78
85
|
return node?.span;
|
|
@@ -118,6 +125,7 @@ class Lowering {
|
|
|
118
125
|
constructor(namespace) {
|
|
119
126
|
this.functions = [];
|
|
120
127
|
this.globals = [];
|
|
128
|
+
this.globalNames = new Map();
|
|
121
129
|
this.fnDecls = new Map();
|
|
122
130
|
this.specializedFunctions = new Map();
|
|
123
131
|
this.currentFn = '';
|
|
@@ -142,6 +150,7 @@ class Lowering {
|
|
|
142
150
|
// World object counter for unique tags
|
|
143
151
|
this.worldObjCounter = 0;
|
|
144
152
|
this.namespace = namespace;
|
|
153
|
+
LoweringBuilder.resetTempCounter();
|
|
145
154
|
}
|
|
146
155
|
lower(program) {
|
|
147
156
|
this.namespace = program.namespace;
|
|
@@ -164,6 +173,13 @@ class Lowering {
|
|
|
164
173
|
this.constValues.set(constDecl.name, constDecl.value);
|
|
165
174
|
this.varTypes.set(constDecl.name, this.normalizeType(constDecl.type));
|
|
166
175
|
}
|
|
176
|
+
// Process global variable declarations (top-level let)
|
|
177
|
+
for (const g of program.globals ?? []) {
|
|
178
|
+
this.globalNames.set(g.name, { mutable: g.mutable });
|
|
179
|
+
this.varTypes.set(g.name, this.normalizeType(g.type));
|
|
180
|
+
const initValue = g.init.kind === 'int_lit' ? g.init.value : 0;
|
|
181
|
+
this.globals.push({ name: `$${g.name}`, init: initValue });
|
|
182
|
+
}
|
|
167
183
|
for (const fn of program.declarations) {
|
|
168
184
|
this.fnDecls.set(fn.name, fn);
|
|
169
185
|
this.functionDefaults.set(fn.name, fn.params.map(param => param.default));
|
|
@@ -251,6 +267,10 @@ class Lowering {
|
|
|
251
267
|
break;
|
|
252
268
|
}
|
|
253
269
|
}
|
|
270
|
+
// Check for @load decorator
|
|
271
|
+
if (fn.decorators.some(d => d.name === 'load')) {
|
|
272
|
+
irFn.isLoadInit = true;
|
|
273
|
+
}
|
|
254
274
|
// Handle tick rate counter if needed
|
|
255
275
|
if (tickRate && tickRate > 1) {
|
|
256
276
|
this.wrapWithTickRate(irFn, tickRate);
|
|
@@ -264,7 +284,7 @@ class Lowering {
|
|
|
264
284
|
wrapWithTickRate(fn, rate) {
|
|
265
285
|
// Add tick counter logic to entry block
|
|
266
286
|
const counterVar = `$__tick_${fn.name}`;
|
|
267
|
-
this.globals.push(counterVar);
|
|
287
|
+
this.globals.push({ name: counterVar, init: 0 });
|
|
268
288
|
// Prepend counter logic to entry block
|
|
269
289
|
const entry = fn.blocks[0];
|
|
270
290
|
const originalInstrs = [...entry.instrs];
|
|
@@ -357,6 +377,10 @@ class Lowering {
|
|
|
357
377
|
}
|
|
358
378
|
}
|
|
359
379
|
lowerLetStmt(stmt) {
|
|
380
|
+
// Check for duplicate declaration of foreach binding
|
|
381
|
+
if (this.currentContext.binding === stmt.name) {
|
|
382
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', `Cannot redeclare foreach binding '${stmt.name}'`, stmt.span ?? { line: 0, col: 0 });
|
|
383
|
+
}
|
|
360
384
|
const varName = `$${stmt.name}`;
|
|
361
385
|
this.varMap.set(stmt.name, varName);
|
|
362
386
|
// Track variable type
|
|
@@ -406,6 +430,13 @@ class Lowering {
|
|
|
406
430
|
}
|
|
407
431
|
return;
|
|
408
432
|
}
|
|
433
|
+
// Handle set_new returning a set ID string
|
|
434
|
+
if (stmt.init.kind === 'call' && stmt.init.fn === 'set_new') {
|
|
435
|
+
const setId = `__set_${this.foreachCounter++}`;
|
|
436
|
+
this.builder.emitRaw(`data modify storage rs:sets ${setId} set value []`);
|
|
437
|
+
this.stringValues.set(stmt.name, setId);
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
409
440
|
// Handle spawn_object returning entity handle
|
|
410
441
|
if (stmt.init.kind === 'call' && stmt.init.fn === 'spawn_object') {
|
|
411
442
|
const value = this.lowerExpr(stmt.init);
|
|
@@ -760,10 +791,24 @@ class Lowering {
|
|
|
760
791
|
parts.push(`at ${this.selectorToString(sub.selector)}`);
|
|
761
792
|
break;
|
|
762
793
|
case 'if_entity':
|
|
763
|
-
|
|
794
|
+
if (sub.selector) {
|
|
795
|
+
parts.push(`if entity ${this.selectorToString(sub.selector)}`);
|
|
796
|
+
}
|
|
797
|
+
else if (sub.varName) {
|
|
798
|
+
// Variable with filters - substitute with @s and apply filters
|
|
799
|
+
const sel = { kind: '@s', filters: sub.filters };
|
|
800
|
+
parts.push(`if entity ${this.selectorToString(sel)}`);
|
|
801
|
+
}
|
|
764
802
|
break;
|
|
765
803
|
case 'unless_entity':
|
|
766
|
-
|
|
804
|
+
if (sub.selector) {
|
|
805
|
+
parts.push(`unless entity ${this.selectorToString(sub.selector)}`);
|
|
806
|
+
}
|
|
807
|
+
else if (sub.varName) {
|
|
808
|
+
// Variable with filters - substitute with @s and apply filters
|
|
809
|
+
const sel = { kind: '@s', filters: sub.filters };
|
|
810
|
+
parts.push(`unless entity ${this.selectorToString(sel)}`);
|
|
811
|
+
}
|
|
767
812
|
break;
|
|
768
813
|
case 'in':
|
|
769
814
|
parts.push(`in ${sub.dimension}`);
|
|
@@ -1051,6 +1096,14 @@ class Lowering {
|
|
|
1051
1096
|
return { kind: 'var', name: dst };
|
|
1052
1097
|
}
|
|
1053
1098
|
lowerAssignExpr(expr) {
|
|
1099
|
+
// Check for const reassignment (both compile-time consts and immutable globals)
|
|
1100
|
+
if (this.constValues.has(expr.target)) {
|
|
1101
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', `Cannot assign to constant '${expr.target}'`, getSpan(expr) ?? { line: 1, col: 1 });
|
|
1102
|
+
}
|
|
1103
|
+
const globalInfo = this.globalNames.get(expr.target);
|
|
1104
|
+
if (globalInfo && !globalInfo.mutable) {
|
|
1105
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', `Cannot assign to constant '${expr.target}'`, getSpan(expr) ?? { line: 1, col: 1 });
|
|
1106
|
+
}
|
|
1054
1107
|
const blockPosValue = this.resolveBlockPosExpr(expr.value);
|
|
1055
1108
|
if (blockPosValue) {
|
|
1056
1109
|
this.blockPosVars.set(expr.target, blockPosValue);
|
|
@@ -1473,6 +1526,62 @@ class Lowering {
|
|
|
1473
1526
|
this.builder.emitRaw(`execute store result score ${dst} rs run data get ${targetType} ${target} ${path} ${scale}`);
|
|
1474
1527
|
return { kind: 'var', name: dst };
|
|
1475
1528
|
}
|
|
1529
|
+
// data_merge(target, nbt) — merge NBT into entity/block/storage
|
|
1530
|
+
// data_merge(@s, { Invisible: 1b, Silent: 1b })
|
|
1531
|
+
if (name === 'data_merge') {
|
|
1532
|
+
const target = args[0];
|
|
1533
|
+
const nbt = args[1];
|
|
1534
|
+
const nbtStr = this.exprToSnbt ? this.exprToSnbt(nbt) : this.exprToString(nbt);
|
|
1535
|
+
// Check if target is a selector (entity) or string (block/storage)
|
|
1536
|
+
if (target.kind === 'selector') {
|
|
1537
|
+
const sel = this.exprToTargetString(target);
|
|
1538
|
+
this.builder.emitRaw(`data merge entity ${sel} ${nbtStr}`);
|
|
1539
|
+
}
|
|
1540
|
+
else {
|
|
1541
|
+
// Assume block position or storage
|
|
1542
|
+
const targetStr = this.exprToString(target);
|
|
1543
|
+
// If it looks like coordinates, use block; otherwise storage
|
|
1544
|
+
if (targetStr.match(/^~|^\d|^\^/)) {
|
|
1545
|
+
this.builder.emitRaw(`data merge block ${targetStr} ${nbtStr}`);
|
|
1546
|
+
}
|
|
1547
|
+
else {
|
|
1548
|
+
this.builder.emitRaw(`data merge storage ${targetStr} ${nbtStr}`);
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
return { kind: 'const', value: 0 };
|
|
1552
|
+
}
|
|
1553
|
+
// Set data structure operations — unique collections via NBT storage
|
|
1554
|
+
// set_new is primarily handled in lowerLetStmt for proper string tracking.
|
|
1555
|
+
// This fallback handles standalone set_new() calls without assignment.
|
|
1556
|
+
if (name === 'set_new') {
|
|
1557
|
+
const setId = `__set_${this.foreachCounter++}`;
|
|
1558
|
+
this.builder.emitRaw(`data modify storage rs:sets ${setId} set value []`);
|
|
1559
|
+
return { kind: 'const', value: 0 };
|
|
1560
|
+
}
|
|
1561
|
+
if (name === 'set_add') {
|
|
1562
|
+
const setId = this.exprToString(args[0]);
|
|
1563
|
+
const value = this.exprToString(args[1]);
|
|
1564
|
+
this.builder.emitRaw(`execute unless data storage rs:sets ${setId}[{value:${value}}] run data modify storage rs:sets ${setId} append value {value:${value}}`);
|
|
1565
|
+
return { kind: 'const', value: 0 };
|
|
1566
|
+
}
|
|
1567
|
+
if (name === 'set_contains') {
|
|
1568
|
+
const dst = this.builder.freshTemp();
|
|
1569
|
+
const setId = this.exprToString(args[0]);
|
|
1570
|
+
const value = this.exprToString(args[1]);
|
|
1571
|
+
this.builder.emitRaw(`execute store result score ${dst} rs if data storage rs:sets ${setId}[{value:${value}}]`);
|
|
1572
|
+
return { kind: 'var', name: dst };
|
|
1573
|
+
}
|
|
1574
|
+
if (name === 'set_remove') {
|
|
1575
|
+
const setId = this.exprToString(args[0]);
|
|
1576
|
+
const value = this.exprToString(args[1]);
|
|
1577
|
+
this.builder.emitRaw(`data remove storage rs:sets ${setId}[{value:${value}}]`);
|
|
1578
|
+
return { kind: 'const', value: 0 };
|
|
1579
|
+
}
|
|
1580
|
+
if (name === 'set_clear') {
|
|
1581
|
+
const setId = this.exprToString(args[0]);
|
|
1582
|
+
this.builder.emitRaw(`data modify storage rs:sets ${setId} set value []`);
|
|
1583
|
+
return { kind: 'const', value: 0 };
|
|
1584
|
+
}
|
|
1476
1585
|
const coordCommand = this.lowerCoordinateBuiltin(name, args);
|
|
1477
1586
|
if (coordCommand) {
|
|
1478
1587
|
this.builder.emitRaw(coordCommand);
|
|
@@ -1497,8 +1606,10 @@ class Lowering {
|
|
|
1497
1606
|
}
|
|
1498
1607
|
return { kind: 'const', value: 0 };
|
|
1499
1608
|
}
|
|
1500
|
-
// Convert args to strings for builtin
|
|
1501
|
-
const strArgs = args.map(arg =>
|
|
1609
|
+
// Convert args to strings for builtin (use SNBT for struct/array literals)
|
|
1610
|
+
const strArgs = args.map(arg => arg.kind === 'struct_lit' || arg.kind === 'array_lit'
|
|
1611
|
+
? this.exprToSnbt(arg)
|
|
1612
|
+
: this.exprToString(arg));
|
|
1502
1613
|
const cmd = BUILTINS[name](strArgs);
|
|
1503
1614
|
if (cmd) {
|
|
1504
1615
|
this.builder.emitRaw(cmd);
|
|
@@ -1647,12 +1758,53 @@ class Lowering {
|
|
|
1647
1758
|
}
|
|
1648
1759
|
case 'selector':
|
|
1649
1760
|
return this.selectorToString(expr.sel);
|
|
1761
|
+
case 'unary':
|
|
1762
|
+
// Handle unary minus on literals directly
|
|
1763
|
+
if (expr.op === '-' && expr.operand.kind === 'int_lit') {
|
|
1764
|
+
return (-expr.operand.value).toString();
|
|
1765
|
+
}
|
|
1766
|
+
if (expr.op === '-' && expr.operand.kind === 'float_lit') {
|
|
1767
|
+
return Math.trunc(-expr.operand.value).toString();
|
|
1768
|
+
}
|
|
1769
|
+
// Fall through to default for complex cases
|
|
1770
|
+
const unaryOp = this.lowerExpr(expr);
|
|
1771
|
+
return this.operandToVar(unaryOp);
|
|
1650
1772
|
default:
|
|
1651
1773
|
// Complex expression - lower and return var name
|
|
1652
1774
|
const op = this.lowerExpr(expr);
|
|
1653
1775
|
return this.operandToVar(op);
|
|
1654
1776
|
}
|
|
1655
1777
|
}
|
|
1778
|
+
exprToSnbt(expr) {
|
|
1779
|
+
switch (expr.kind) {
|
|
1780
|
+
case 'struct_lit': {
|
|
1781
|
+
const entries = expr.fields.map(f => `${f.name}:${this.exprToSnbt(f.value)}`);
|
|
1782
|
+
return `{${entries.join(',')}}`;
|
|
1783
|
+
}
|
|
1784
|
+
case 'array_lit': {
|
|
1785
|
+
const items = expr.elements.map(e => this.exprToSnbt(e));
|
|
1786
|
+
return `[${items.join(',')}]`;
|
|
1787
|
+
}
|
|
1788
|
+
case 'str_lit':
|
|
1789
|
+
return `"${expr.value}"`;
|
|
1790
|
+
case 'int_lit':
|
|
1791
|
+
return String(expr.value);
|
|
1792
|
+
case 'float_lit':
|
|
1793
|
+
return String(expr.value);
|
|
1794
|
+
case 'byte_lit':
|
|
1795
|
+
return `${expr.value}b`;
|
|
1796
|
+
case 'short_lit':
|
|
1797
|
+
return `${expr.value}s`;
|
|
1798
|
+
case 'long_lit':
|
|
1799
|
+
return `${expr.value}L`;
|
|
1800
|
+
case 'double_lit':
|
|
1801
|
+
return `${expr.value}d`;
|
|
1802
|
+
case 'bool_lit':
|
|
1803
|
+
return expr.value ? '1b' : '0b';
|
|
1804
|
+
default:
|
|
1805
|
+
return this.exprToString(expr);
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1656
1808
|
exprToTargetString(expr) {
|
|
1657
1809
|
if (expr.kind === 'selector') {
|
|
1658
1810
|
return this.selectorToString(expr.sel);
|
|
@@ -1712,6 +1864,13 @@ class Lowering {
|
|
|
1712
1864
|
}
|
|
1713
1865
|
return null;
|
|
1714
1866
|
}
|
|
1867
|
+
if (name === 'summon') {
|
|
1868
|
+
if (args.length >= 2 && pos1) {
|
|
1869
|
+
const nbt = args[2] ? ` ${this.exprToString(args[2])}` : '';
|
|
1870
|
+
return `summon ${this.exprToString(args[0])} ${emitBlockPos(pos1)}${nbt}`;
|
|
1871
|
+
}
|
|
1872
|
+
return null;
|
|
1873
|
+
}
|
|
1715
1874
|
return null;
|
|
1716
1875
|
}
|
|
1717
1876
|
lowerTpCommand(args) {
|
|
@@ -1941,6 +2100,18 @@ class Lowering {
|
|
|
1941
2100
|
parts.push(`nbt=${filters.nbt}`);
|
|
1942
2101
|
if (filters.gamemode)
|
|
1943
2102
|
parts.push(`gamemode=${filters.gamemode}`);
|
|
2103
|
+
// Position filters
|
|
2104
|
+
if (filters.x)
|
|
2105
|
+
parts.push(`x=${this.rangeToString(filters.x)}`);
|
|
2106
|
+
if (filters.y)
|
|
2107
|
+
parts.push(`y=${this.rangeToString(filters.y)}`);
|
|
2108
|
+
if (filters.z)
|
|
2109
|
+
parts.push(`z=${this.rangeToString(filters.z)}`);
|
|
2110
|
+
// Rotation filters
|
|
2111
|
+
if (filters.x_rotation)
|
|
2112
|
+
parts.push(`x_rotation=${this.rangeToString(filters.x_rotation)}`);
|
|
2113
|
+
if (filters.y_rotation)
|
|
2114
|
+
parts.push(`y_rotation=${this.rangeToString(filters.y_rotation)}`);
|
|
1944
2115
|
return this.finalizeSelector(parts.length ? `${kind}[${parts.join(',')}]` : kind);
|
|
1945
2116
|
}
|
|
1946
2117
|
finalizeSelector(selector) {
|
|
@@ -1965,14 +2136,17 @@ exports.Lowering = Lowering;
|
|
|
1965
2136
|
// ---------------------------------------------------------------------------
|
|
1966
2137
|
class LoweringBuilder {
|
|
1967
2138
|
constructor() {
|
|
1968
|
-
this.tempCount = 0;
|
|
1969
2139
|
this.labelCount = 0;
|
|
1970
2140
|
this.blocks = [];
|
|
1971
2141
|
this.currentBlock = null;
|
|
1972
2142
|
this.locals = new Set();
|
|
1973
2143
|
}
|
|
2144
|
+
/** Reset the global temp counter (call between compilations). */
|
|
2145
|
+
static resetTempCounter() {
|
|
2146
|
+
LoweringBuilder.globalTempId = 0;
|
|
2147
|
+
}
|
|
1974
2148
|
freshTemp() {
|
|
1975
|
-
const name = `$
|
|
2149
|
+
const name = `$_${LoweringBuilder.globalTempId++}`;
|
|
1976
2150
|
this.locals.add(name);
|
|
1977
2151
|
return name;
|
|
1978
2152
|
}
|
|
@@ -2038,4 +2212,5 @@ class LoweringBuilder {
|
|
|
2038
2212
|
};
|
|
2039
2213
|
}
|
|
2040
2214
|
}
|
|
2215
|
+
LoweringBuilder.globalTempId = 0;
|
|
2041
2216
|
//# sourceMappingURL=index.js.map
|