redscript-mc 1.2.10 → 1.2.12
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/dist/__tests__/dce.test.js +4 -3
- package/dist/__tests__/e2e.test.js +2 -2
- package/dist/__tests__/lowering.test.js +3 -3
- package/dist/__tests__/mc-integration.test.js +101 -16
- package/dist/ast/types.d.ts +78 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lowering/index.d.ts +1 -0
- package/dist/lowering/index.js +123 -18
- package/dist/optimizer/dce.js +5 -2
- package/dist/parser/index.d.ts +4 -0
- package/dist/parser/index.js +159 -18
- package/package.json +1 -1
- package/src/__tests__/dce.test.ts +4 -3
- package/src/__tests__/e2e.test.ts +2 -2
- package/src/__tests__/fixtures/array-test.mcrs +30 -0
- package/src/__tests__/fixtures/break-continue-test.mcrs +46 -0
- package/src/__tests__/fixtures/enum-test.mcrs +37 -0
- package/src/__tests__/fixtures/foreach-at-test.mcrs +33 -0
- package/src/__tests__/fixtures/is-check-test.mcrs +20 -13
- package/src/__tests__/fixtures/match-range-test.mcrs +45 -0
- package/src/__tests__/fixtures/struct-test.mcrs +34 -0
- package/src/__tests__/lowering.test.ts +3 -3
- package/src/__tests__/mc-integration.test.ts +114 -16
- package/src/ast/types.ts +22 -1
- package/src/index.ts +1 -1
- package/src/lowering/index.ts +123 -18
- package/src/optimizer/dce.ts +5 -2
- package/src/parser/index.ts +145 -18
|
@@ -46,15 +46,16 @@ function getFileContent(files, suffix) {
|
|
|
46
46
|
return file.content;
|
|
47
47
|
}
|
|
48
48
|
describe('AST dead code elimination', () => {
|
|
49
|
-
it('removes unused functions
|
|
49
|
+
it('removes private unused functions (prefixed with _)', () => {
|
|
50
50
|
const source = `
|
|
51
|
-
fn
|
|
51
|
+
fn _unused() { say("never called"); }
|
|
52
52
|
fn used() { say("called"); }
|
|
53
53
|
@tick fn main() { used(); }
|
|
54
54
|
`;
|
|
55
55
|
const result = (0, index_1.compile)(source, { namespace: 'test' });
|
|
56
|
+
// _unused is removed because it starts with _ (private) and is not called
|
|
56
57
|
expect(result.ast.declarations.map(fn => fn.name)).toEqual(['used', 'main']);
|
|
57
|
-
expect(result.ir.functions.some(fn => fn.name === '
|
|
58
|
+
expect(result.ir.functions.some(fn => fn.name === '_unused')).toBe(false);
|
|
58
59
|
});
|
|
59
60
|
it('removes unused local variables from the AST body', () => {
|
|
60
61
|
const source = `
|
|
@@ -171,8 +171,8 @@ fn main() {
|
|
|
171
171
|
const foreachFn = getSubFunction(files, 'main', 'foreach_0');
|
|
172
172
|
const thenFiles = files.filter(file => file.path.includes('/main/then_') && file.content.includes('kill @s'));
|
|
173
173
|
expect(mainFn).toContain('execute as @e run function test:main/foreach_0');
|
|
174
|
-
expect(foreachFn).toContain('execute if entity @s[type=player] run function test:main/then_');
|
|
175
|
-
expect(foreachFn).toContain('execute if entity @s[type=zombie] run function test:main/then_');
|
|
174
|
+
expect(foreachFn).toContain('execute if entity @s[type=minecraft:player] run function test:main/then_');
|
|
175
|
+
expect(foreachFn).toContain('execute if entity @s[type=minecraft:zombie] run function test:main/then_');
|
|
176
176
|
expect(thenFiles).toHaveLength(2);
|
|
177
177
|
});
|
|
178
178
|
});
|
|
@@ -251,7 +251,7 @@ fn scan() {
|
|
|
251
251
|
`);
|
|
252
252
|
const foreachFn = ir.functions.find(fn => fn.name.includes('scan/foreach'));
|
|
253
253
|
const rawCmds = getRawCommands(foreachFn);
|
|
254
|
-
const isCheckCmd = rawCmds.find(cmd => cmd.startsWith('execute if entity @s[type=player] run function test:scan/then_'));
|
|
254
|
+
const isCheckCmd = rawCmds.find(cmd => cmd.startsWith('execute if entity @s[type=minecraft:player] run function test:scan/then_'));
|
|
255
255
|
expect(isCheckCmd).toBeDefined();
|
|
256
256
|
const thenFn = ir.functions.find(fn => fn.name.startsWith('scan/then_'));
|
|
257
257
|
expect(getRawCommands(thenFn)).toContain('kill @s');
|
|
@@ -315,8 +315,8 @@ fn test() {
|
|
|
315
315
|
const [playerThenFn, zombieThenFn] = thenFns;
|
|
316
316
|
expect(getRawCommands(mainFn)).toContain('execute as @e run function test:test/foreach_0');
|
|
317
317
|
expect(thenFns).toHaveLength(2);
|
|
318
|
-
expect(rawCmds).toContain(`execute if entity @s[type=player] run function test:${playerThenFn.name}`);
|
|
319
|
-
expect(rawCmds).toContain(`execute if entity @s[type=zombie] run function test:${zombieThenFn.name}`);
|
|
318
|
+
expect(rawCmds).toContain(`execute if entity @s[type=minecraft:player] run function test:${playerThenFn.name}`);
|
|
319
|
+
expect(rawCmds).toContain(`execute if entity @s[type=minecraft:zombie] run function test:${zombieThenFn.name}`);
|
|
320
320
|
expect(getRawCommands(playerThenFn).some(cmd => cmd.includes('give @s diamond 1'))).toBe(true);
|
|
321
321
|
expect(getRawCommands(zombieThenFn)).toContain('kill @s');
|
|
322
322
|
});
|
|
@@ -694,28 +694,28 @@ describe('MC Integration - New Features', () => {
|
|
|
694
694
|
expect(count).toBeGreaterThanOrEqual(3);
|
|
695
695
|
expect(count).toBeLessThanOrEqual(3);
|
|
696
696
|
});
|
|
697
|
-
test('is-check-test.mcrs: foreach is-narrowing
|
|
697
|
+
test('is-check-test.mcrs: foreach is-narrowing correctly matches entity types', async () => {
|
|
698
698
|
if (!serverOnline)
|
|
699
699
|
return;
|
|
700
700
|
await mc.fullReset({ clearArea: false, killEntities: true, resetScoreboards: false });
|
|
701
|
-
await mc.command('/
|
|
702
|
-
await mc.command('/scoreboard
|
|
701
|
+
await mc.command('/forceload add 0 0').catch(() => { }); // Ensure chunk is loaded
|
|
702
|
+
await mc.command('/scoreboard objectives add armor_stands dummy').catch(() => { });
|
|
703
|
+
await mc.command('/scoreboard objectives add items dummy').catch(() => { });
|
|
704
|
+
await mc.command('/scoreboard players set #is_check armor_stands 0');
|
|
705
|
+
await mc.command('/scoreboard players set #is_check items 0');
|
|
703
706
|
await mc.command('/function is_check_test:__load').catch(() => { });
|
|
704
|
-
|
|
705
|
-
await mc.command('/
|
|
706
|
-
await mc.command('/summon minecraft:armor_stand 2 65 0');
|
|
707
|
-
await mc.command('/
|
|
707
|
+
// Spawn 2 armor_stands and 1 item (all persist without players)
|
|
708
|
+
await mc.command('/summon minecraft:armor_stand 0 65 0 {Tags:["is_check_target"],NoGravity:1b}');
|
|
709
|
+
await mc.command('/summon minecraft:armor_stand 2 65 0 {Tags:["is_check_target"],NoGravity:1b}');
|
|
710
|
+
await mc.command('/summon minecraft:item 4 65 0 {Tags:["is_check_target"],Item:{id:"minecraft:stone",count:1},Age:-32768}');
|
|
711
|
+
await mc.ticks(5);
|
|
708
712
|
await mc.command('/function is_check_test:check_types');
|
|
709
713
|
await mc.ticks(5);
|
|
710
|
-
const
|
|
711
|
-
const
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
expect(players).toBe(0);
|
|
716
|
-
expect(zombieEntities).toHaveLength(0);
|
|
717
|
-
expect(standEntities).toHaveLength(1);
|
|
718
|
-
await mc.command('/kill @e[tag=is_check_target]').catch(() => { });
|
|
714
|
+
const armorStands = await mc.scoreboard('#is_check', 'armor_stands');
|
|
715
|
+
const items = await mc.scoreboard('#is_check', 'items');
|
|
716
|
+
expect(armorStands).toBe(2); // 2 armor_stands matched
|
|
717
|
+
expect(items).toBe(1); // 1 item matched
|
|
718
|
+
await mc.command('/function is_check_test:cleanup').catch(() => { });
|
|
719
719
|
});
|
|
720
720
|
test('event-test.mcrs: @on(PlayerDeath) compiles and loads', async () => {
|
|
721
721
|
if (!serverOnline)
|
|
@@ -731,4 +731,89 @@ describe('MC Integration - New Features', () => {
|
|
|
731
731
|
expect(tickResult.ok).toBe(true);
|
|
732
732
|
});
|
|
733
733
|
});
|
|
734
|
+
describe('MC Integration - Extended Coverage', () => {
|
|
735
|
+
test('struct-test.mcrs: struct instantiation and field access', async () => {
|
|
736
|
+
if (!serverOnline)
|
|
737
|
+
return;
|
|
738
|
+
writeFixtureFile('struct-test.mcrs', 'struct_test');
|
|
739
|
+
await mc.reload();
|
|
740
|
+
await mc.command('/function struct_test:__load').catch(() => { });
|
|
741
|
+
await mc.command('/function struct_test:test_struct');
|
|
742
|
+
await mc.ticks(5);
|
|
743
|
+
expect(await mc.scoreboard('#struct_x', 'rs')).toBe(10);
|
|
744
|
+
expect(await mc.scoreboard('#struct_y', 'rs')).toBe(64);
|
|
745
|
+
expect(await mc.scoreboard('#struct_z', 'rs')).toBe(-5);
|
|
746
|
+
expect(await mc.scoreboard('#struct_x2', 'rs')).toBe(15); // 10+5
|
|
747
|
+
expect(await mc.scoreboard('#struct_z2', 'rs')).toBe(-10); // -5*2
|
|
748
|
+
expect(await mc.scoreboard('#struct_alive', 'rs')).toBe(1);
|
|
749
|
+
expect(await mc.scoreboard('#struct_score', 'rs')).toBe(100);
|
|
750
|
+
});
|
|
751
|
+
test('enum-test.mcrs: enum values and match', async () => {
|
|
752
|
+
if (!serverOnline)
|
|
753
|
+
return;
|
|
754
|
+
writeFixtureFile('enum-test.mcrs', 'enum_test');
|
|
755
|
+
await mc.reload();
|
|
756
|
+
await mc.command('/function enum_test:__load').catch(() => { });
|
|
757
|
+
await mc.command('/function enum_test:test_enum');
|
|
758
|
+
await mc.ticks(5);
|
|
759
|
+
expect(await mc.scoreboard('#enum_phase', 'rs')).toBe(2); // Playing=2
|
|
760
|
+
expect(await mc.scoreboard('#enum_match', 'rs')).toBe(2); // matched Playing
|
|
761
|
+
expect(await mc.scoreboard('#enum_rank', 'rs')).toBe(10); // Diamond=10
|
|
762
|
+
expect(await mc.scoreboard('#enum_high', 'rs')).toBe(1); // Diamond > Gold
|
|
763
|
+
});
|
|
764
|
+
test('array-test.mcrs: array operations', async () => {
|
|
765
|
+
if (!serverOnline)
|
|
766
|
+
return;
|
|
767
|
+
writeFixtureFile('array-test.mcrs', 'array_test');
|
|
768
|
+
await mc.reload();
|
|
769
|
+
await mc.command('/function array_test:__load').catch(() => { });
|
|
770
|
+
await mc.command('/function array_test:test_array');
|
|
771
|
+
await mc.ticks(5);
|
|
772
|
+
expect(await mc.scoreboard('#arr_0', 'rs')).toBe(10);
|
|
773
|
+
expect(await mc.scoreboard('#arr_2', 'rs')).toBe(30);
|
|
774
|
+
expect(await mc.scoreboard('#arr_4', 'rs')).toBe(50);
|
|
775
|
+
expect(await mc.scoreboard('#arr_len', 'rs')).toBe(5);
|
|
776
|
+
expect(await mc.scoreboard('#arr_sum', 'rs')).toBe(150); // 10+20+30+40+50
|
|
777
|
+
expect(await mc.scoreboard('#arr_push', 'rs')).toBe(4); // [1,2,3,4].len
|
|
778
|
+
expect(await mc.scoreboard('#arr_pop', 'rs')).toBe(4); // popped value
|
|
779
|
+
});
|
|
780
|
+
test('break-continue-test.mcrs: break and continue statements', async () => {
|
|
781
|
+
if (!serverOnline)
|
|
782
|
+
return;
|
|
783
|
+
writeFixtureFile('break-continue-test.mcrs', 'break_continue_test');
|
|
784
|
+
await mc.reload();
|
|
785
|
+
await mc.command('/function break_continue_test:__load').catch(() => { });
|
|
786
|
+
await mc.command('/function break_continue_test:test_break_continue');
|
|
787
|
+
await mc.ticks(10);
|
|
788
|
+
expect(await mc.scoreboard('#break_at', 'rs')).toBe(5);
|
|
789
|
+
expect(await mc.scoreboard('#sum_evens', 'rs')).toBe(20); // 0+2+4+6+8
|
|
790
|
+
expect(await mc.scoreboard('#while_break', 'rs')).toBe(7);
|
|
791
|
+
expect(await mc.scoreboard('#nested_break', 'rs')).toBe(3); // outer completes 3 times
|
|
792
|
+
});
|
|
793
|
+
test('match-range-test.mcrs: match with range patterns', async () => {
|
|
794
|
+
if (!serverOnline)
|
|
795
|
+
return;
|
|
796
|
+
writeFixtureFile('match-range-test.mcrs', 'match_range_test');
|
|
797
|
+
await mc.reload();
|
|
798
|
+
await mc.command('/function match_range_test:__load').catch(() => { });
|
|
799
|
+
await mc.command('/function match_range_test:test_match_range');
|
|
800
|
+
await mc.ticks(5);
|
|
801
|
+
expect(await mc.scoreboard('#grade', 'rs')).toBe(4); // score=85 → B
|
|
802
|
+
expect(await mc.scoreboard('#boundary_59', 'rs')).toBe(1); // 59 matches 0..59
|
|
803
|
+
expect(await mc.scoreboard('#boundary_60', 'rs')).toBe(2); // 60 matches 60..100
|
|
804
|
+
expect(await mc.scoreboard('#neg_range', 'rs')).toBe(1); // -5 matches ..0
|
|
805
|
+
});
|
|
806
|
+
test('foreach-at-test.mcrs: foreach with at @s context', async () => {
|
|
807
|
+
if (!serverOnline)
|
|
808
|
+
return;
|
|
809
|
+
writeFixtureFile('foreach-at-test.mcrs', 'foreach_at_test');
|
|
810
|
+
await mc.reload();
|
|
811
|
+
await mc.fullReset({ clearArea: false, killEntities: true, resetScoreboards: false });
|
|
812
|
+
await mc.command('/function foreach_at_test:setup').catch(() => { });
|
|
813
|
+
await mc.command('/function foreach_at_test:test_foreach_at');
|
|
814
|
+
await mc.ticks(10);
|
|
815
|
+
expect(await mc.scoreboard('#foreach_count', 'rs')).toBe(3);
|
|
816
|
+
expect(await mc.scoreboard('#foreach_at_count', 'rs')).toBe(3);
|
|
817
|
+
});
|
|
818
|
+
});
|
|
734
819
|
//# sourceMappingURL=mc-integration.test.js.map
|
package/dist/ast/types.d.ts
CHANGED
|
@@ -247,6 +247,45 @@ export type ExecuteSubcommand = {
|
|
|
247
247
|
} | {
|
|
248
248
|
kind: 'at';
|
|
249
249
|
selector: EntitySelector;
|
|
250
|
+
} | {
|
|
251
|
+
kind: 'positioned';
|
|
252
|
+
x: string;
|
|
253
|
+
y: string;
|
|
254
|
+
z: string;
|
|
255
|
+
} | {
|
|
256
|
+
kind: 'positioned_as';
|
|
257
|
+
selector: EntitySelector;
|
|
258
|
+
} | {
|
|
259
|
+
kind: 'rotated';
|
|
260
|
+
yaw: string;
|
|
261
|
+
pitch: string;
|
|
262
|
+
} | {
|
|
263
|
+
kind: 'rotated_as';
|
|
264
|
+
selector: EntitySelector;
|
|
265
|
+
} | {
|
|
266
|
+
kind: 'facing';
|
|
267
|
+
x: string;
|
|
268
|
+
y: string;
|
|
269
|
+
z: string;
|
|
270
|
+
} | {
|
|
271
|
+
kind: 'facing_entity';
|
|
272
|
+
selector: EntitySelector;
|
|
273
|
+
anchor: 'eyes' | 'feet';
|
|
274
|
+
} | {
|
|
275
|
+
kind: 'anchored';
|
|
276
|
+
anchor: 'eyes' | 'feet';
|
|
277
|
+
} | {
|
|
278
|
+
kind: 'align';
|
|
279
|
+
axes: string;
|
|
280
|
+
} | {
|
|
281
|
+
kind: 'in';
|
|
282
|
+
dimension: string;
|
|
283
|
+
} | {
|
|
284
|
+
kind: 'on';
|
|
285
|
+
relation: string;
|
|
286
|
+
} | {
|
|
287
|
+
kind: 'summon';
|
|
288
|
+
entity: string;
|
|
250
289
|
} | {
|
|
251
290
|
kind: 'if_entity';
|
|
252
291
|
selector?: EntitySelector;
|
|
@@ -258,8 +297,45 @@ export type ExecuteSubcommand = {
|
|
|
258
297
|
varName?: string;
|
|
259
298
|
filters?: SelectorFilter;
|
|
260
299
|
} | {
|
|
261
|
-
kind: '
|
|
262
|
-
|
|
300
|
+
kind: 'if_block';
|
|
301
|
+
pos: [string, string, string];
|
|
302
|
+
block: string;
|
|
303
|
+
} | {
|
|
304
|
+
kind: 'unless_block';
|
|
305
|
+
pos: [string, string, string];
|
|
306
|
+
block: string;
|
|
307
|
+
} | {
|
|
308
|
+
kind: 'if_score';
|
|
309
|
+
target: string;
|
|
310
|
+
targetObj: string;
|
|
311
|
+
op: string;
|
|
312
|
+
source: string;
|
|
313
|
+
sourceObj: string;
|
|
314
|
+
} | {
|
|
315
|
+
kind: 'unless_score';
|
|
316
|
+
target: string;
|
|
317
|
+
targetObj: string;
|
|
318
|
+
op: string;
|
|
319
|
+
source: string;
|
|
320
|
+
sourceObj: string;
|
|
321
|
+
} | {
|
|
322
|
+
kind: 'if_score_range';
|
|
323
|
+
target: string;
|
|
324
|
+
targetObj: string;
|
|
325
|
+
range: string;
|
|
326
|
+
} | {
|
|
327
|
+
kind: 'unless_score_range';
|
|
328
|
+
target: string;
|
|
329
|
+
targetObj: string;
|
|
330
|
+
range: string;
|
|
331
|
+
} | {
|
|
332
|
+
kind: 'store_result';
|
|
333
|
+
target: string;
|
|
334
|
+
targetObj: string;
|
|
335
|
+
} | {
|
|
336
|
+
kind: 'store_success';
|
|
337
|
+
target: string;
|
|
338
|
+
targetObj: string;
|
|
263
339
|
};
|
|
264
340
|
export type Stmt = {
|
|
265
341
|
kind: 'let';
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Main entry point for programmatic usage.
|
|
5
5
|
*/
|
|
6
|
-
export declare const version = "1.2.
|
|
6
|
+
export declare const version = "1.2.11";
|
|
7
7
|
import type { Warning } from './lowering';
|
|
8
8
|
import { DatapackFile } from './codegen/mcfunction';
|
|
9
9
|
import type { IRModule } from './ir/types';
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ exports.MCCommandValidator = exports.generateDatapack = exports.optimize = expor
|
|
|
9
9
|
exports.compile = compile;
|
|
10
10
|
exports.check = check;
|
|
11
11
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
12
|
-
exports.version = '1.2.
|
|
12
|
+
exports.version = '1.2.11';
|
|
13
13
|
const lexer_1 = require("./lexer");
|
|
14
14
|
const parser_1 = require("./parser");
|
|
15
15
|
const typechecker_1 = require("./typechecker");
|
package/dist/lowering/index.d.ts
CHANGED
package/dist/lowering/index.js
CHANGED
|
@@ -126,20 +126,20 @@ function getSpan(node) {
|
|
|
126
126
|
const NAMESPACED_ENTITY_TYPE_RE = /^[a-z0-9_.-]+:[a-z0-9_./-]+$/;
|
|
127
127
|
const BARE_ENTITY_TYPE_RE = /^[a-z0-9_./-]+$/;
|
|
128
128
|
const ENTITY_TO_MC_TYPE = {
|
|
129
|
-
Player: 'player',
|
|
130
|
-
Zombie: 'zombie',
|
|
131
|
-
Skeleton: 'skeleton',
|
|
132
|
-
Creeper: 'creeper',
|
|
133
|
-
Spider: 'spider',
|
|
134
|
-
Enderman: 'enderman',
|
|
135
|
-
Pig: 'pig',
|
|
136
|
-
Cow: 'cow',
|
|
137
|
-
Sheep: 'sheep',
|
|
138
|
-
Chicken: 'chicken',
|
|
139
|
-
Villager: 'villager',
|
|
140
|
-
ArmorStand: 'armor_stand',
|
|
141
|
-
Item: 'item',
|
|
142
|
-
Arrow: 'arrow',
|
|
129
|
+
Player: 'minecraft:player',
|
|
130
|
+
Zombie: 'minecraft:zombie',
|
|
131
|
+
Skeleton: 'minecraft:skeleton',
|
|
132
|
+
Creeper: 'minecraft:creeper',
|
|
133
|
+
Spider: 'minecraft:spider',
|
|
134
|
+
Enderman: 'minecraft:enderman',
|
|
135
|
+
Pig: 'minecraft:pig',
|
|
136
|
+
Cow: 'minecraft:cow',
|
|
137
|
+
Sheep: 'minecraft:sheep',
|
|
138
|
+
Chicken: 'minecraft:chicken',
|
|
139
|
+
Villager: 'minecraft:villager',
|
|
140
|
+
ArmorStand: 'minecraft:armor_stand',
|
|
141
|
+
Item: 'minecraft:item',
|
|
142
|
+
Arrow: 'minecraft:arrow',
|
|
143
143
|
};
|
|
144
144
|
function normalizeSelector(selector, warnings) {
|
|
145
145
|
return selector.replace(/type=([^,\]]+)/g, (match, entityType) => {
|
|
@@ -199,6 +199,8 @@ class Lowering {
|
|
|
199
199
|
this.blockPosVars = new Map();
|
|
200
200
|
// Struct definitions: name → { fieldName: TypeNode }
|
|
201
201
|
this.structDefs = new Map();
|
|
202
|
+
// Full struct declarations for field iteration
|
|
203
|
+
this.structDecls = new Map();
|
|
202
204
|
this.enumDefs = new Map();
|
|
203
205
|
this.functionDefaults = new Map();
|
|
204
206
|
this.constValues = new Map();
|
|
@@ -224,6 +226,7 @@ class Lowering {
|
|
|
224
226
|
fields.set(field.name, field.type);
|
|
225
227
|
}
|
|
226
228
|
this.structDefs.set(struct.name, fields);
|
|
229
|
+
this.structDecls.set(struct.name, struct);
|
|
227
230
|
}
|
|
228
231
|
for (const enumDecl of program.enums ?? []) {
|
|
229
232
|
const variants = new Map();
|
|
@@ -551,6 +554,22 @@ class Lowering {
|
|
|
551
554
|
}
|
|
552
555
|
return;
|
|
553
556
|
}
|
|
557
|
+
// Handle struct initialization from function call (copy from __ret_struct)
|
|
558
|
+
if ((stmt.init.kind === 'call' || stmt.init.kind === 'static_call') && stmt.type?.kind === 'struct') {
|
|
559
|
+
// First, execute the function call
|
|
560
|
+
this.lowerExpr(stmt.init);
|
|
561
|
+
// Then copy all fields from __ret_struct to the variable's storage
|
|
562
|
+
const structDecl = this.structDecls.get(stmt.type.name);
|
|
563
|
+
if (structDecl) {
|
|
564
|
+
const structName = stmt.type.name.toLowerCase();
|
|
565
|
+
for (const field of structDecl.fields) {
|
|
566
|
+
const srcPath = `rs:heap __ret_struct.${field.name}`;
|
|
567
|
+
const dstPath = `rs:heap ${structName}_${stmt.name}.${field.name}`;
|
|
568
|
+
this.builder.emitRaw(`data modify storage ${dstPath} set from storage ${srcPath}`);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
554
573
|
// Handle array literal initialization
|
|
555
574
|
if (stmt.init.kind === 'array_lit') {
|
|
556
575
|
// Initialize empty NBT array
|
|
@@ -600,6 +619,21 @@ class Lowering {
|
|
|
600
619
|
}
|
|
601
620
|
lowerReturnStmt(stmt) {
|
|
602
621
|
if (stmt.value) {
|
|
622
|
+
// Handle struct literal return: store fields to __ret_struct storage
|
|
623
|
+
if (stmt.value.kind === 'struct_lit') {
|
|
624
|
+
for (const field of stmt.value.fields) {
|
|
625
|
+
const path = `rs:heap __ret_struct.${field.name}`;
|
|
626
|
+
const fieldValue = this.lowerExpr(field.value);
|
|
627
|
+
if (fieldValue.kind === 'const') {
|
|
628
|
+
this.builder.emitRaw(`data modify storage ${path} set value ${fieldValue.value}`);
|
|
629
|
+
}
|
|
630
|
+
else if (fieldValue.kind === 'var') {
|
|
631
|
+
this.builder.emitRaw(`execute store result storage ${path} int 1 run scoreboard players get ${fieldValue.name} rs`);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
this.builder.emitReturn({ kind: 'const', value: 0 });
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
603
637
|
const value = this.lowerExpr(stmt.value);
|
|
604
638
|
this.builder.emitReturn(value);
|
|
605
639
|
}
|
|
@@ -1006,18 +1040,52 @@ class Lowering {
|
|
|
1006
1040
|
const parts = ['execute'];
|
|
1007
1041
|
for (const sub of stmt.subcommands) {
|
|
1008
1042
|
switch (sub.kind) {
|
|
1043
|
+
// Context modifiers
|
|
1009
1044
|
case 'as':
|
|
1010
1045
|
parts.push(`as ${this.selectorToString(sub.selector)}`);
|
|
1011
1046
|
break;
|
|
1012
1047
|
case 'at':
|
|
1013
1048
|
parts.push(`at ${this.selectorToString(sub.selector)}`);
|
|
1014
1049
|
break;
|
|
1050
|
+
case 'positioned':
|
|
1051
|
+
parts.push(`positioned ${sub.x} ${sub.y} ${sub.z}`);
|
|
1052
|
+
break;
|
|
1053
|
+
case 'positioned_as':
|
|
1054
|
+
parts.push(`positioned as ${this.selectorToString(sub.selector)}`);
|
|
1055
|
+
break;
|
|
1056
|
+
case 'rotated':
|
|
1057
|
+
parts.push(`rotated ${sub.yaw} ${sub.pitch}`);
|
|
1058
|
+
break;
|
|
1059
|
+
case 'rotated_as':
|
|
1060
|
+
parts.push(`rotated as ${this.selectorToString(sub.selector)}`);
|
|
1061
|
+
break;
|
|
1062
|
+
case 'facing':
|
|
1063
|
+
parts.push(`facing ${sub.x} ${sub.y} ${sub.z}`);
|
|
1064
|
+
break;
|
|
1065
|
+
case 'facing_entity':
|
|
1066
|
+
parts.push(`facing entity ${this.selectorToString(sub.selector)} ${sub.anchor}`);
|
|
1067
|
+
break;
|
|
1068
|
+
case 'anchored':
|
|
1069
|
+
parts.push(`anchored ${sub.anchor}`);
|
|
1070
|
+
break;
|
|
1071
|
+
case 'align':
|
|
1072
|
+
parts.push(`align ${sub.axes}`);
|
|
1073
|
+
break;
|
|
1074
|
+
case 'in':
|
|
1075
|
+
parts.push(`in ${sub.dimension}`);
|
|
1076
|
+
break;
|
|
1077
|
+
case 'on':
|
|
1078
|
+
parts.push(`on ${sub.relation}`);
|
|
1079
|
+
break;
|
|
1080
|
+
case 'summon':
|
|
1081
|
+
parts.push(`summon ${sub.entity}`);
|
|
1082
|
+
break;
|
|
1083
|
+
// Conditions
|
|
1015
1084
|
case 'if_entity':
|
|
1016
1085
|
if (sub.selector) {
|
|
1017
1086
|
parts.push(`if entity ${this.selectorToString(sub.selector)}`);
|
|
1018
1087
|
}
|
|
1019
1088
|
else if (sub.varName) {
|
|
1020
|
-
// Variable with filters - substitute with @s and apply filters
|
|
1021
1089
|
const sel = { kind: '@s', filters: sub.filters };
|
|
1022
1090
|
parts.push(`if entity ${this.selectorToString(sel)}`);
|
|
1023
1091
|
}
|
|
@@ -1027,13 +1095,34 @@ class Lowering {
|
|
|
1027
1095
|
parts.push(`unless entity ${this.selectorToString(sub.selector)}`);
|
|
1028
1096
|
}
|
|
1029
1097
|
else if (sub.varName) {
|
|
1030
|
-
// Variable with filters - substitute with @s and apply filters
|
|
1031
1098
|
const sel = { kind: '@s', filters: sub.filters };
|
|
1032
1099
|
parts.push(`unless entity ${this.selectorToString(sel)}`);
|
|
1033
1100
|
}
|
|
1034
1101
|
break;
|
|
1035
|
-
case '
|
|
1036
|
-
parts.push(`
|
|
1102
|
+
case 'if_block':
|
|
1103
|
+
parts.push(`if block ${sub.pos[0]} ${sub.pos[1]} ${sub.pos[2]} ${sub.block}`);
|
|
1104
|
+
break;
|
|
1105
|
+
case 'unless_block':
|
|
1106
|
+
parts.push(`unless block ${sub.pos[0]} ${sub.pos[1]} ${sub.pos[2]} ${sub.block}`);
|
|
1107
|
+
break;
|
|
1108
|
+
case 'if_score':
|
|
1109
|
+
parts.push(`if score ${sub.target} ${sub.targetObj} ${sub.op} ${sub.source} ${sub.sourceObj}`);
|
|
1110
|
+
break;
|
|
1111
|
+
case 'unless_score':
|
|
1112
|
+
parts.push(`unless score ${sub.target} ${sub.targetObj} ${sub.op} ${sub.source} ${sub.sourceObj}`);
|
|
1113
|
+
break;
|
|
1114
|
+
case 'if_score_range':
|
|
1115
|
+
parts.push(`if score ${sub.target} ${sub.targetObj} matches ${sub.range}`);
|
|
1116
|
+
break;
|
|
1117
|
+
case 'unless_score_range':
|
|
1118
|
+
parts.push(`unless score ${sub.target} ${sub.targetObj} matches ${sub.range}`);
|
|
1119
|
+
break;
|
|
1120
|
+
// Store
|
|
1121
|
+
case 'store_result':
|
|
1122
|
+
parts.push(`store result score ${sub.target} ${sub.targetObj}`);
|
|
1123
|
+
break;
|
|
1124
|
+
case 'store_success':
|
|
1125
|
+
parts.push(`store success score ${sub.target} ${sub.targetObj}`);
|
|
1037
1126
|
break;
|
|
1038
1127
|
}
|
|
1039
1128
|
}
|
|
@@ -1451,6 +1540,22 @@ class Lowering {
|
|
|
1451
1540
|
}
|
|
1452
1541
|
const implMethod = this.resolveInstanceMethod(expr);
|
|
1453
1542
|
if (implMethod) {
|
|
1543
|
+
// Copy struct fields from instance to 'self' storage before calling
|
|
1544
|
+
const receiver = expr.args[0];
|
|
1545
|
+
if (receiver?.kind === 'ident') {
|
|
1546
|
+
const receiverType = this.inferExprType(receiver);
|
|
1547
|
+
if (receiverType?.kind === 'struct') {
|
|
1548
|
+
const structDecl = this.structDecls.get(receiverType.name);
|
|
1549
|
+
const structName = receiverType.name.toLowerCase();
|
|
1550
|
+
if (structDecl) {
|
|
1551
|
+
for (const field of structDecl.fields) {
|
|
1552
|
+
const srcPath = `rs:heap ${structName}_${receiver.name}.${field.name}`;
|
|
1553
|
+
const dstPath = `rs:heap ${structName}_self.${field.name}`;
|
|
1554
|
+
this.builder.emitRaw(`data modify storage ${dstPath} set from storage ${srcPath}`);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1454
1559
|
return this.emitMethodCall(implMethod.loweredName, implMethod.fn, expr.args);
|
|
1455
1560
|
}
|
|
1456
1561
|
// Regular function call
|
package/dist/optimizer/dce.js
CHANGED
|
@@ -115,9 +115,12 @@ class DeadCodeEliminator {
|
|
|
115
115
|
findEntryPoints(program) {
|
|
116
116
|
const entries = new Set();
|
|
117
117
|
for (const fn of program.declarations) {
|
|
118
|
-
|
|
118
|
+
// All top-level functions are entry points (callable via /function)
|
|
119
|
+
// Exception: functions starting with _ are considered private/internal
|
|
120
|
+
if (!fn.name.startsWith('_')) {
|
|
119
121
|
entries.add(fn.name);
|
|
120
122
|
}
|
|
123
|
+
// Decorated functions are always entry points (even if prefixed with _)
|
|
121
124
|
if (fn.decorators.some(decorator => [
|
|
122
125
|
'tick',
|
|
123
126
|
'load',
|
|
@@ -128,7 +131,7 @@ class DeadCodeEliminator {
|
|
|
128
131
|
'on_death',
|
|
129
132
|
'on_login',
|
|
130
133
|
'on_join_team',
|
|
131
|
-
'keep',
|
|
134
|
+
'keep',
|
|
132
135
|
].includes(decorator.name))) {
|
|
133
136
|
entries.add(fn.name);
|
|
134
137
|
}
|
package/dist/parser/index.d.ts
CHANGED
|
@@ -45,6 +45,10 @@ export declare class Parser {
|
|
|
45
45
|
private parseAsStmt;
|
|
46
46
|
private parseAtStmt;
|
|
47
47
|
private parseExecuteStmt;
|
|
48
|
+
private parseExecuteCondition;
|
|
49
|
+
private parseCoordToken;
|
|
50
|
+
private parseBlockId;
|
|
51
|
+
private checkIdent;
|
|
48
52
|
private parseExprStmt;
|
|
49
53
|
private parseExpr;
|
|
50
54
|
private parseAssignment;
|