redscript-mc 1.2.20 → 1.2.24
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/publish-extension-on-ci.yml +99 -0
- package/dist/__tests__/compile-all.test.js +5 -0
- package/dist/__tests__/entity-types.test.d.ts +1 -0
- package/dist/__tests__/entity-types.test.js +203 -0
- package/dist/__tests__/var-allocator.test.d.ts +1 -0
- package/dist/__tests__/var-allocator.test.js +69 -0
- package/dist/ast/types.d.ts +2 -1
- package/dist/cli.js +24 -7
- package/dist/codegen/mcfunction/index.d.ts +2 -0
- package/dist/codegen/mcfunction/index.js +47 -43
- package/dist/codegen/structure/index.d.ts +4 -1
- package/dist/codegen/structure/index.js +8 -12
- package/dist/codegen/var-allocator.d.ts +28 -0
- package/dist/codegen/var-allocator.js +74 -0
- package/dist/compile.d.ts +8 -0
- package/dist/compile.js +14 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +9 -7
- package/dist/lowering/index.d.ts +2 -0
- package/dist/lowering/index.js +62 -3
- package/dist/optimizer/dce.d.ts +2 -1
- package/dist/optimizer/dce.js +13 -2
- package/dist/parser/index.js +22 -1
- package/dist/typechecker/index.js +30 -0
- package/dist/types/entity-hierarchy.d.ts +29 -0
- package/dist/types/entity-hierarchy.js +107 -0
- package/editors/vscode/out/extension.js +29 -4
- package/editors/vscode/package-lock.json +6 -4
- package/editors/vscode/package.json +3 -3
- package/package.json +1 -1
- package/src/__tests__/compile-all.test.ts +6 -0
- package/src/__tests__/entity-types.test.ts +236 -0
- package/src/__tests__/var-allocator.test.ts +75 -0
- package/src/ast/types.ts +8 -4
- package/src/cli.ts +28 -8
- package/src/codegen/mcfunction/index.ts +55 -48
- package/src/codegen/structure/index.ts +9 -14
- package/src/codegen/var-allocator.ts +71 -0
- package/src/compile.ts +18 -0
- package/src/examples/capture_the_flag.mcrs +34 -34
- package/src/examples/hunger_games.mcrs +60 -60
- package/src/examples/new_features_demo.mcrs +32 -32
- package/src/examples/parkour_race.mcrs +58 -58
- package/src/examples/zombie_survival.mcrs +73 -73
- package/src/index.ts +11 -7
- package/src/lowering/index.ts +73 -8
- package/src/optimizer/dce.ts +18 -2
- package/src/parser/index.ts +20 -1
- package/src/typechecker/index.ts +30 -0
- package/src/types/entity-hierarchy.ts +120 -0
- package/dist/data/arena/function/__load.mcfunction +0 -6
- package/dist/data/arena/function/__tick.mcfunction +0 -2
- package/dist/data/arena/function/announce_leaders/else_1.mcfunction +0 -3
- package/dist/data/arena/function/announce_leaders/foreach_0/merge_2.mcfunction +0 -1
- package/dist/data/arena/function/announce_leaders/foreach_0/then_0.mcfunction +0 -3
- package/dist/data/arena/function/announce_leaders/foreach_0.mcfunction +0 -7
- package/dist/data/arena/function/announce_leaders/foreach_1/merge_2.mcfunction +0 -1
- package/dist/data/arena/function/announce_leaders/foreach_1/then_0.mcfunction +0 -4
- package/dist/data/arena/function/announce_leaders/foreach_1.mcfunction +0 -6
- package/dist/data/arena/function/announce_leaders/merge_2.mcfunction +0 -1
- package/dist/data/arena/function/announce_leaders/then_0.mcfunction +0 -4
- package/dist/data/arena/function/announce_leaders.mcfunction +0 -6
- package/dist/data/arena/function/arena_tick/merge_2.mcfunction +0 -1
- package/dist/data/arena/function/arena_tick/then_0.mcfunction +0 -4
- package/dist/data/arena/function/arena_tick.mcfunction +0 -11
- package/dist/data/counter/function/__load.mcfunction +0 -5
- package/dist/data/counter/function/__tick.mcfunction +0 -2
- package/dist/data/counter/function/counter_tick/merge_2.mcfunction +0 -1
- package/dist/data/counter/function/counter_tick/then_0.mcfunction +0 -3
- package/dist/data/counter/function/counter_tick.mcfunction +0 -11
- package/dist/data/minecraft/tags/function/load.json +0 -5
- package/dist/data/minecraft/tags/function/tick.json +0 -5
- package/dist/data/quiz/function/__load.mcfunction +0 -16
- package/dist/data/quiz/function/__tick.mcfunction +0 -6
- package/dist/data/quiz/function/__trigger_quiz_a_dispatch.mcfunction +0 -4
- package/dist/data/quiz/function/__trigger_quiz_b_dispatch.mcfunction +0 -4
- package/dist/data/quiz/function/__trigger_quiz_c_dispatch.mcfunction +0 -4
- package/dist/data/quiz/function/__trigger_quiz_start_dispatch.mcfunction +0 -4
- package/dist/data/quiz/function/answer_a.mcfunction +0 -4
- package/dist/data/quiz/function/answer_b.mcfunction +0 -4
- package/dist/data/quiz/function/answer_c.mcfunction +0 -4
- package/dist/data/quiz/function/ask_question/else_1.mcfunction +0 -5
- package/dist/data/quiz/function/ask_question/else_4.mcfunction +0 -5
- package/dist/data/quiz/function/ask_question/else_7.mcfunction +0 -4
- package/dist/data/quiz/function/ask_question/merge_2.mcfunction +0 -1
- package/dist/data/quiz/function/ask_question/merge_5.mcfunction +0 -2
- package/dist/data/quiz/function/ask_question/merge_8.mcfunction +0 -2
- package/dist/data/quiz/function/ask_question/then_0.mcfunction +0 -4
- package/dist/data/quiz/function/ask_question/then_3.mcfunction +0 -4
- package/dist/data/quiz/function/ask_question/then_6.mcfunction +0 -4
- package/dist/data/quiz/function/ask_question.mcfunction +0 -7
- package/dist/data/quiz/function/finish_quiz.mcfunction +0 -6
- package/dist/data/quiz/function/handle_answer/else_1.mcfunction +0 -5
- package/dist/data/quiz/function/handle_answer/else_10.mcfunction +0 -3
- package/dist/data/quiz/function/handle_answer/else_16.mcfunction +0 -3
- package/dist/data/quiz/function/handle_answer/else_4.mcfunction +0 -3
- package/dist/data/quiz/function/handle_answer/else_7.mcfunction +0 -5
- package/dist/data/quiz/function/handle_answer/merge_11.mcfunction +0 -2
- package/dist/data/quiz/function/handle_answer/merge_14.mcfunction +0 -2
- package/dist/data/quiz/function/handle_answer/merge_17.mcfunction +0 -2
- package/dist/data/quiz/function/handle_answer/merge_2.mcfunction +0 -8
- package/dist/data/quiz/function/handle_answer/merge_5.mcfunction +0 -2
- package/dist/data/quiz/function/handle_answer/merge_8.mcfunction +0 -2
- package/dist/data/quiz/function/handle_answer/then_0.mcfunction +0 -5
- package/dist/data/quiz/function/handle_answer/then_12.mcfunction +0 -5
- package/dist/data/quiz/function/handle_answer/then_15.mcfunction +0 -6
- package/dist/data/quiz/function/handle_answer/then_3.mcfunction +0 -6
- package/dist/data/quiz/function/handle_answer/then_6.mcfunction +0 -5
- package/dist/data/quiz/function/handle_answer/then_9.mcfunction +0 -6
- package/dist/data/quiz/function/handle_answer.mcfunction +0 -11
- package/dist/data/quiz/function/start_quiz.mcfunction +0 -5
- package/dist/data/shop/function/__load.mcfunction +0 -7
- package/dist/data/shop/function/__tick.mcfunction +0 -3
- package/dist/data/shop/function/__trigger_shop_buy_dispatch.mcfunction +0 -4
- package/dist/data/shop/function/complete_purchase/else_1.mcfunction +0 -5
- package/dist/data/shop/function/complete_purchase/else_4.mcfunction +0 -5
- package/dist/data/shop/function/complete_purchase/else_7.mcfunction +0 -3
- package/dist/data/shop/function/complete_purchase/merge_2.mcfunction +0 -2
- package/dist/data/shop/function/complete_purchase/merge_5.mcfunction +0 -2
- package/dist/data/shop/function/complete_purchase/merge_8.mcfunction +0 -2
- package/dist/data/shop/function/complete_purchase/then_0.mcfunction +0 -4
- package/dist/data/shop/function/complete_purchase/then_3.mcfunction +0 -4
- package/dist/data/shop/function/complete_purchase/then_6.mcfunction +0 -4
- package/dist/data/shop/function/complete_purchase.mcfunction +0 -7
- package/dist/data/shop/function/handle_shop_trigger.mcfunction +0 -3
- package/dist/data/turret/function/__load.mcfunction +0 -5
- package/dist/data/turret/function/__tick.mcfunction +0 -4
- package/dist/data/turret/function/__trigger_deploy_turret_dispatch.mcfunction +0 -4
- package/dist/data/turret/function/deploy_turret.mcfunction +0 -8
- package/dist/data/turret/function/turret_tick/at_1.mcfunction +0 -2
- package/dist/data/turret/function/turret_tick/foreach_0.mcfunction +0 -2
- package/dist/data/turret/function/turret_tick/foreach_2.mcfunction +0 -2
- package/dist/data/turret/function/turret_tick/tick_body.mcfunction +0 -3
- package/dist/data/turret/function/turret_tick/tick_skip.mcfunction +0 -1
- package/dist/data/turret/function/turret_tick.mcfunction +0 -5
- package/dist/pack.mcmeta +0 -6
package/dist/optimizer/dce.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export interface DCEWarning {
|
|
|
4
4
|
code: string;
|
|
5
5
|
line?: number;
|
|
6
6
|
col?: number;
|
|
7
|
+
filePath?: string;
|
|
7
8
|
}
|
|
8
9
|
export declare class DeadCodeEliminator {
|
|
9
10
|
private readonly functionMap;
|
|
@@ -27,7 +28,7 @@ export declare class DeadCodeEliminator {
|
|
|
27
28
|
private transformStmt;
|
|
28
29
|
private transformExpr;
|
|
29
30
|
}
|
|
30
|
-
export declare function eliminateDeadCode(program: Program): {
|
|
31
|
+
export declare function eliminateDeadCode(program: Program, sourceRanges?: import('../compile').SourceRange[]): {
|
|
31
32
|
program: Program;
|
|
32
33
|
warnings: DCEWarning[];
|
|
33
34
|
};
|
package/dist/optimizer/dce.js
CHANGED
|
@@ -604,9 +604,20 @@ class DeadCodeEliminator {
|
|
|
604
604
|
}
|
|
605
605
|
}
|
|
606
606
|
exports.DeadCodeEliminator = DeadCodeEliminator;
|
|
607
|
-
function eliminateDeadCode(program) {
|
|
607
|
+
function eliminateDeadCode(program, sourceRanges) {
|
|
608
608
|
const eliminator = new DeadCodeEliminator();
|
|
609
609
|
const result = eliminator.eliminate(program);
|
|
610
|
-
|
|
610
|
+
let warnings = eliminator.warnings;
|
|
611
|
+
// Resolve combined-source line numbers back to original file + line
|
|
612
|
+
if (sourceRanges && sourceRanges.length > 0) {
|
|
613
|
+
const { resolveSourceLine } = require('../compile');
|
|
614
|
+
warnings = warnings.map(w => {
|
|
615
|
+
if (w.line == null)
|
|
616
|
+
return w;
|
|
617
|
+
const resolved = resolveSourceLine(w.line, sourceRanges);
|
|
618
|
+
return { ...w, line: resolved.line, filePath: resolved.filePath ?? w.filePath };
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
return { program: result, warnings };
|
|
611
622
|
}
|
|
612
623
|
//# sourceMappingURL=dce.js.map
|
package/dist/parser/index.js
CHANGED
|
@@ -32,11 +32,21 @@ const ENTITY_TYPE_NAMES = new Set([
|
|
|
32
32
|
'Creeper',
|
|
33
33
|
'Spider',
|
|
34
34
|
'Enderman',
|
|
35
|
+
'Blaze',
|
|
36
|
+
'Witch',
|
|
37
|
+
'Slime',
|
|
38
|
+
'ZombieVillager',
|
|
39
|
+
'Husk',
|
|
40
|
+
'Drowned',
|
|
41
|
+
'Stray',
|
|
42
|
+
'WitherSkeleton',
|
|
43
|
+
'CaveSpider',
|
|
35
44
|
'Pig',
|
|
36
45
|
'Cow',
|
|
37
46
|
'Sheep',
|
|
38
47
|
'Chicken',
|
|
39
48
|
'Villager',
|
|
49
|
+
'WanderingTrader',
|
|
40
50
|
'ArmorStand',
|
|
41
51
|
'Item',
|
|
42
52
|
'Arrow',
|
|
@@ -384,7 +394,18 @@ class Parser {
|
|
|
384
394
|
}
|
|
385
395
|
else if (token.kind === 'ident') {
|
|
386
396
|
this.advance();
|
|
387
|
-
|
|
397
|
+
if (token.value === 'selector' && this.check('<')) {
|
|
398
|
+
this.advance(); // consume <
|
|
399
|
+
const entityType = this.expect('ident').value;
|
|
400
|
+
this.expect('>');
|
|
401
|
+
type = { kind: 'selector', entityType };
|
|
402
|
+
}
|
|
403
|
+
else if (token.value === 'selector') {
|
|
404
|
+
type = { kind: 'selector' };
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
type = { kind: 'struct', name: token.value };
|
|
408
|
+
}
|
|
388
409
|
}
|
|
389
410
|
else {
|
|
390
411
|
this.error(`Expected type, got '${token.kind}'`);
|
|
@@ -21,11 +21,21 @@ const ENTITY_HIERARCHY = {
|
|
|
21
21
|
'Creeper': 'HostileMob',
|
|
22
22
|
'Spider': 'HostileMob',
|
|
23
23
|
'Enderman': 'HostileMob',
|
|
24
|
+
'Blaze': 'HostileMob',
|
|
25
|
+
'Witch': 'HostileMob',
|
|
26
|
+
'Slime': 'HostileMob',
|
|
27
|
+
'ZombieVillager': 'HostileMob',
|
|
28
|
+
'Husk': 'HostileMob',
|
|
29
|
+
'Drowned': 'HostileMob',
|
|
30
|
+
'Stray': 'HostileMob',
|
|
31
|
+
'WitherSkeleton': 'HostileMob',
|
|
32
|
+
'CaveSpider': 'HostileMob',
|
|
24
33
|
'Pig': 'PassiveMob',
|
|
25
34
|
'Cow': 'PassiveMob',
|
|
26
35
|
'Sheep': 'PassiveMob',
|
|
27
36
|
'Chicken': 'PassiveMob',
|
|
28
37
|
'Villager': 'PassiveMob',
|
|
38
|
+
'WanderingTrader': 'PassiveMob',
|
|
29
39
|
'ArmorStand': 'entity',
|
|
30
40
|
'Item': 'entity',
|
|
31
41
|
'Arrow': 'entity',
|
|
@@ -42,6 +52,24 @@ const MC_TYPE_TO_ENTITY = {
|
|
|
42
52
|
'minecraft:spider': 'Spider',
|
|
43
53
|
'enderman': 'Enderman',
|
|
44
54
|
'minecraft:enderman': 'Enderman',
|
|
55
|
+
'blaze': 'Blaze',
|
|
56
|
+
'minecraft:blaze': 'Blaze',
|
|
57
|
+
'witch': 'Witch',
|
|
58
|
+
'minecraft:witch': 'Witch',
|
|
59
|
+
'slime': 'Slime',
|
|
60
|
+
'minecraft:slime': 'Slime',
|
|
61
|
+
'zombie_villager': 'ZombieVillager',
|
|
62
|
+
'minecraft:zombie_villager': 'ZombieVillager',
|
|
63
|
+
'husk': 'Husk',
|
|
64
|
+
'minecraft:husk': 'Husk',
|
|
65
|
+
'drowned': 'Drowned',
|
|
66
|
+
'minecraft:drowned': 'Drowned',
|
|
67
|
+
'stray': 'Stray',
|
|
68
|
+
'minecraft:stray': 'Stray',
|
|
69
|
+
'wither_skeleton': 'WitherSkeleton',
|
|
70
|
+
'minecraft:wither_skeleton': 'WitherSkeleton',
|
|
71
|
+
'cave_spider': 'CaveSpider',
|
|
72
|
+
'minecraft:cave_spider': 'CaveSpider',
|
|
45
73
|
'pig': 'Pig',
|
|
46
74
|
'minecraft:pig': 'Pig',
|
|
47
75
|
'cow': 'Cow',
|
|
@@ -52,6 +80,8 @@ const MC_TYPE_TO_ENTITY = {
|
|
|
52
80
|
'minecraft:chicken': 'Chicken',
|
|
53
81
|
'villager': 'Villager',
|
|
54
82
|
'minecraft:villager': 'Villager',
|
|
83
|
+
'wandering_trader': 'WanderingTrader',
|
|
84
|
+
'minecraft:wandering_trader': 'WanderingTrader',
|
|
55
85
|
'armor_stand': 'ArmorStand',
|
|
56
86
|
'minecraft:armor_stand': 'ArmorStand',
|
|
57
87
|
'item': 'Item',
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entity Type Hierarchy
|
|
3
|
+
*
|
|
4
|
+
* Closed inheritance tree mapping to Minecraft's entity registry.
|
|
5
|
+
* Used for type narrowing (is checks), selector<T> annotations,
|
|
6
|
+
* and W_IMPOSSIBLE_AS warnings.
|
|
7
|
+
*/
|
|
8
|
+
export interface EntityTypeNode {
|
|
9
|
+
name: string;
|
|
10
|
+
mcId: string | null;
|
|
11
|
+
abstract: boolean;
|
|
12
|
+
parent: string | null;
|
|
13
|
+
}
|
|
14
|
+
export declare const ENTITY_TYPES: EntityTypeNode[];
|
|
15
|
+
/** Map from lowercase name → EntityTypeNode */
|
|
16
|
+
export declare const ENTITY_TYPE_MAP: Map<string, EntityTypeNode>;
|
|
17
|
+
/** Map from mcId (without namespace) → EntityTypeNode */
|
|
18
|
+
export declare const ENTITY_TYPE_BY_MCID: Map<string, EntityTypeNode>;
|
|
19
|
+
/** Check if typeA is a subtype of typeB (recursive ancestry) */
|
|
20
|
+
export declare function isSubtype(typeA: string, typeB: string): boolean;
|
|
21
|
+
/** True if one type is a subtype of the other (in either direction) */
|
|
22
|
+
export declare function areCompatibleTypes(outerType: string, innerType: string): boolean;
|
|
23
|
+
/** Get all non-abstract leaf types under a given node */
|
|
24
|
+
export declare function getConcreteSubtypes(typeName: string): EntityTypeNode[];
|
|
25
|
+
/** Parse "type=zombie" from a selector string, return the entity type name or null */
|
|
26
|
+
export declare function getSelectorEntityType(selector: string): string | null;
|
|
27
|
+
/** Determine the base entity type from a selector kind:
|
|
28
|
+
* @a/@p/@r → "Player", @e → "Entity" or from type filter, @s → null */
|
|
29
|
+
export declare function getBaseSelectorType(selector: string): string | null;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Entity Type Hierarchy
|
|
4
|
+
*
|
|
5
|
+
* Closed inheritance tree mapping to Minecraft's entity registry.
|
|
6
|
+
* Used for type narrowing (is checks), selector<T> annotations,
|
|
7
|
+
* and W_IMPOSSIBLE_AS warnings.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.ENTITY_TYPE_BY_MCID = exports.ENTITY_TYPE_MAP = exports.ENTITY_TYPES = void 0;
|
|
11
|
+
exports.isSubtype = isSubtype;
|
|
12
|
+
exports.areCompatibleTypes = areCompatibleTypes;
|
|
13
|
+
exports.getConcreteSubtypes = getConcreteSubtypes;
|
|
14
|
+
exports.getSelectorEntityType = getSelectorEntityType;
|
|
15
|
+
exports.getBaseSelectorType = getBaseSelectorType;
|
|
16
|
+
exports.ENTITY_TYPES = [
|
|
17
|
+
// Root
|
|
18
|
+
{ name: 'Entity', mcId: null, abstract: true, parent: null },
|
|
19
|
+
// Direct children of Entity
|
|
20
|
+
{ name: 'Player', mcId: 'minecraft:player', abstract: false, parent: 'Entity' },
|
|
21
|
+
{ name: 'ArmorStand', mcId: 'minecraft:armor_stand', abstract: false, parent: 'Entity' },
|
|
22
|
+
{ name: 'Item', mcId: 'minecraft:item', abstract: false, parent: 'Entity' },
|
|
23
|
+
{ name: 'Arrow', mcId: 'minecraft:arrow', abstract: false, parent: 'Entity' },
|
|
24
|
+
// Mob hierarchy
|
|
25
|
+
{ name: 'Mob', mcId: null, abstract: true, parent: 'Entity' },
|
|
26
|
+
// Hostile mobs
|
|
27
|
+
{ name: 'HostileMob', mcId: null, abstract: true, parent: 'Mob' },
|
|
28
|
+
{ name: 'Zombie', mcId: 'minecraft:zombie', abstract: false, parent: 'HostileMob' },
|
|
29
|
+
{ name: 'Skeleton', mcId: 'minecraft:skeleton', abstract: false, parent: 'HostileMob' },
|
|
30
|
+
{ name: 'Creeper', mcId: 'minecraft:creeper', abstract: false, parent: 'HostileMob' },
|
|
31
|
+
{ name: 'Spider', mcId: 'minecraft:spider', abstract: false, parent: 'HostileMob' },
|
|
32
|
+
{ name: 'Enderman', mcId: 'minecraft:enderman', abstract: false, parent: 'HostileMob' },
|
|
33
|
+
{ name: 'Blaze', mcId: 'minecraft:blaze', abstract: false, parent: 'HostileMob' },
|
|
34
|
+
{ name: 'Witch', mcId: 'minecraft:witch', abstract: false, parent: 'HostileMob' },
|
|
35
|
+
{ name: 'Slime', mcId: 'minecraft:slime', abstract: false, parent: 'HostileMob' },
|
|
36
|
+
{ name: 'ZombieVillager', mcId: 'minecraft:zombie_villager', abstract: false, parent: 'HostileMob' },
|
|
37
|
+
{ name: 'Husk', mcId: 'minecraft:husk', abstract: false, parent: 'HostileMob' },
|
|
38
|
+
{ name: 'Drowned', mcId: 'minecraft:drowned', abstract: false, parent: 'HostileMob' },
|
|
39
|
+
{ name: 'Stray', mcId: 'minecraft:stray', abstract: false, parent: 'HostileMob' },
|
|
40
|
+
{ name: 'WitherSkeleton', mcId: 'minecraft:wither_skeleton', abstract: false, parent: 'HostileMob' },
|
|
41
|
+
{ name: 'CaveSpider', mcId: 'minecraft:cave_spider', abstract: false, parent: 'HostileMob' },
|
|
42
|
+
// Passive mobs
|
|
43
|
+
{ name: 'PassiveMob', mcId: null, abstract: true, parent: 'Mob' },
|
|
44
|
+
{ name: 'Pig', mcId: 'minecraft:pig', abstract: false, parent: 'PassiveMob' },
|
|
45
|
+
{ name: 'Cow', mcId: 'minecraft:cow', abstract: false, parent: 'PassiveMob' },
|
|
46
|
+
{ name: 'Sheep', mcId: 'minecraft:sheep', abstract: false, parent: 'PassiveMob' },
|
|
47
|
+
{ name: 'Chicken', mcId: 'minecraft:chicken', abstract: false, parent: 'PassiveMob' },
|
|
48
|
+
{ name: 'Villager', mcId: 'minecraft:villager', abstract: false, parent: 'PassiveMob' },
|
|
49
|
+
{ name: 'WanderingTrader', mcId: 'minecraft:wandering_trader', abstract: false, parent: 'PassiveMob' },
|
|
50
|
+
];
|
|
51
|
+
/** Map from lowercase name → EntityTypeNode */
|
|
52
|
+
exports.ENTITY_TYPE_MAP = new Map(exports.ENTITY_TYPES.map(t => [t.name.toLowerCase(), t]));
|
|
53
|
+
/** Map from mcId (without namespace) → EntityTypeNode */
|
|
54
|
+
exports.ENTITY_TYPE_BY_MCID = new Map(exports.ENTITY_TYPES
|
|
55
|
+
.filter(t => t.mcId !== null)
|
|
56
|
+
.map(t => [t.mcId.replace('minecraft:', ''), t]));
|
|
57
|
+
/** Check if typeA is a subtype of typeB (recursive ancestry) */
|
|
58
|
+
function isSubtype(typeA, typeB) {
|
|
59
|
+
if (typeA === typeB)
|
|
60
|
+
return true;
|
|
61
|
+
const node = exports.ENTITY_TYPE_MAP.get(typeA.toLowerCase());
|
|
62
|
+
if (!node || !node.parent)
|
|
63
|
+
return false;
|
|
64
|
+
return isSubtype(node.parent, typeB);
|
|
65
|
+
}
|
|
66
|
+
/** True if one type is a subtype of the other (in either direction) */
|
|
67
|
+
function areCompatibleTypes(outerType, innerType) {
|
|
68
|
+
return isSubtype(outerType, innerType) || isSubtype(innerType, outerType);
|
|
69
|
+
}
|
|
70
|
+
/** Get all non-abstract leaf types under a given node */
|
|
71
|
+
function getConcreteSubtypes(typeName) {
|
|
72
|
+
const results = [];
|
|
73
|
+
for (const node of exports.ENTITY_TYPES) {
|
|
74
|
+
if (!node.abstract && isSubtype(node.name, typeName)) {
|
|
75
|
+
results.push(node);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return results;
|
|
79
|
+
}
|
|
80
|
+
/** Parse "type=zombie" from a selector string, return the entity type name or null */
|
|
81
|
+
function getSelectorEntityType(selector) {
|
|
82
|
+
const match = selector.match(/type=(?:minecraft:)?([a-z_]+)/);
|
|
83
|
+
if (!match)
|
|
84
|
+
return null;
|
|
85
|
+
const mcId = match[1];
|
|
86
|
+
const node = exports.ENTITY_TYPE_BY_MCID.get(mcId);
|
|
87
|
+
return node ? node.name : null;
|
|
88
|
+
}
|
|
89
|
+
/** Determine the base entity type from a selector kind:
|
|
90
|
+
* @a/@p/@r → "Player", @e → "Entity" or from type filter, @s → null */
|
|
91
|
+
function getBaseSelectorType(selector) {
|
|
92
|
+
const trimmed = selector.trim();
|
|
93
|
+
// Check for type filter first (works for any selector)
|
|
94
|
+
const typeFromFilter = getSelectorEntityType(trimmed);
|
|
95
|
+
if (trimmed.startsWith('@a') || trimmed.startsWith('@p') || trimmed.startsWith('@r')) {
|
|
96
|
+
return typeFromFilter ?? 'Player';
|
|
97
|
+
}
|
|
98
|
+
if (trimmed.startsWith('@e')) {
|
|
99
|
+
return typeFromFilter ?? 'Entity';
|
|
100
|
+
}
|
|
101
|
+
// @s — context-dependent, we don't know unless there's a type filter
|
|
102
|
+
if (trimmed.startsWith('@s')) {
|
|
103
|
+
return typeFromFilter ?? null;
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=entity-hierarchy.js.map
|
|
@@ -1193,10 +1193,22 @@ var require_parser = __commonJS({
|
|
|
1193
1193
|
parseForRangeStmt(forToken) {
|
|
1194
1194
|
const varName = this.expect("ident").value;
|
|
1195
1195
|
this.expect("in");
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1196
|
+
let start;
|
|
1197
|
+
let end;
|
|
1198
|
+
if (this.check("range_lit")) {
|
|
1199
|
+
const rangeToken = this.advance();
|
|
1200
|
+
const range = this.parseRangeValue(rangeToken.value);
|
|
1201
|
+
start = this.withLoc({ kind: "int_lit", value: range.min ?? 0 }, rangeToken);
|
|
1202
|
+
if (range.max !== null && range.max !== void 0) {
|
|
1203
|
+
end = this.withLoc({ kind: "int_lit", value: range.max }, rangeToken);
|
|
1204
|
+
} else {
|
|
1205
|
+
end = this.parseUnaryExpr();
|
|
1206
|
+
}
|
|
1207
|
+
} else {
|
|
1208
|
+
start = this.withLoc({ kind: "int_lit", value: 0 }, this.peek());
|
|
1209
|
+
end = this.withLoc({ kind: "int_lit", value: 0 }, this.peek());
|
|
1210
|
+
this.error("Dynamic range start requires a literal integer (e.g. 0..count)");
|
|
1211
|
+
}
|
|
1200
1212
|
const body = this.parseBlock();
|
|
1201
1213
|
return this.withLoc({ kind: "for_range", varName, start, end, body }, forToken);
|
|
1202
1214
|
}
|
|
@@ -1656,6 +1668,19 @@ var require_parser = __commonJS({
|
|
|
1656
1668
|
this.error(`Unexpected token '${token.kind}'`);
|
|
1657
1669
|
}
|
|
1658
1670
|
parseLiteralExpr() {
|
|
1671
|
+
if (this.check("-")) {
|
|
1672
|
+
this.advance();
|
|
1673
|
+
const token = this.peek();
|
|
1674
|
+
if (token.kind === "int_lit") {
|
|
1675
|
+
this.advance();
|
|
1676
|
+
return this.withLoc({ kind: "int_lit", value: -Number(token.value) }, token);
|
|
1677
|
+
}
|
|
1678
|
+
if (token.kind === "float_lit") {
|
|
1679
|
+
this.advance();
|
|
1680
|
+
return this.withLoc({ kind: "float_lit", value: -Number(token.value) }, token);
|
|
1681
|
+
}
|
|
1682
|
+
this.error("Expected number after unary -");
|
|
1683
|
+
}
|
|
1659
1684
|
const expr = this.parsePrimaryExpr();
|
|
1660
1685
|
if (expr.kind === "int_lit" || expr.kind === "float_lit" || expr.kind === "bool_lit" || expr.kind === "str_lit") {
|
|
1661
1686
|
return expr;
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "redscript-vscode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.13",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "redscript-vscode",
|
|
9
|
-
"version": "1.0.
|
|
9
|
+
"version": "1.0.13",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"redscript": "file:../../"
|
|
@@ -22,10 +22,12 @@
|
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
"../..": {
|
|
25
|
-
"
|
|
25
|
+
"name": "redscript-mc",
|
|
26
|
+
"version": "1.2.24",
|
|
26
27
|
"license": "MIT",
|
|
27
28
|
"bin": {
|
|
28
|
-
"redscript": "dist/cli.js"
|
|
29
|
+
"redscript": "dist/cli.js",
|
|
30
|
+
"rsc": "dist/cli.js"
|
|
29
31
|
},
|
|
30
32
|
"devDependencies": {
|
|
31
33
|
"@types/jest": "^29.5.0",
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "redscript-vscode",
|
|
3
3
|
"displayName": "RedScript for Minecraft",
|
|
4
4
|
"description": "Syntax highlighting, error diagnostics, and language support for RedScript — a compiler targeting Minecraft Java Edition",
|
|
5
|
-
"version": "1.0.
|
|
5
|
+
"version": "1.0.13",
|
|
6
6
|
"publisher": "bkmashiro",
|
|
7
7
|
"icon": "icon.png",
|
|
8
8
|
"license": "MIT",
|
|
@@ -129,8 +129,8 @@
|
|
|
129
129
|
"compile": "node build.mjs",
|
|
130
130
|
"watch": "node build.mjs --watch",
|
|
131
131
|
"build": "npm run compile",
|
|
132
|
-
"package": "npm run build && vsce package --no-dependencies",
|
|
133
|
-
"publish": "npm run build && vsce publish --no-dependencies"
|
|
132
|
+
"package": "npm run build && npx @vscode/vsce package --no-dependencies",
|
|
133
|
+
"publish": "npm run build && npx @vscode/vsce publish --no-dependencies"
|
|
134
134
|
},
|
|
135
135
|
"devDependencies": {
|
|
136
136
|
"@types/node": "^20.0.0",
|
package/package.json
CHANGED
|
@@ -16,6 +16,12 @@ import * as os from 'os'
|
|
|
16
16
|
const REPO_ROOT = path.resolve(__dirname, '../../')
|
|
17
17
|
const CLI = path.join(REPO_ROOT, 'dist', 'cli.js')
|
|
18
18
|
|
|
19
|
+
// Ensure dist/cli.js exists — build first if not (e.g. in CI)
|
|
20
|
+
if (!fs.existsSync(CLI)) {
|
|
21
|
+
console.log('[compile-all] dist/cli.js not found, running npm run build...')
|
|
22
|
+
execSync('npm run build', { cwd: REPO_ROOT, stdio: 'inherit' })
|
|
23
|
+
}
|
|
24
|
+
|
|
19
25
|
/** Patterns to skip */
|
|
20
26
|
const SKIP_GLOBS = [
|
|
21
27
|
'node_modules',
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isSubtype,
|
|
3
|
+
areCompatibleTypes,
|
|
4
|
+
getConcreteSubtypes,
|
|
5
|
+
getSelectorEntityType,
|
|
6
|
+
getBaseSelectorType,
|
|
7
|
+
} from '../types/entity-hierarchy'
|
|
8
|
+
import { Lexer } from '../lexer'
|
|
9
|
+
import { Parser } from '../parser'
|
|
10
|
+
import { Lowering } from '../lowering'
|
|
11
|
+
import type { IRModule } from '../ir/types'
|
|
12
|
+
|
|
13
|
+
function compileWithWarnings(source: string, namespace = 'test'): { ir: IRModule; warnings: Lowering['warnings'] } {
|
|
14
|
+
const tokens = new Lexer(source).tokenize()
|
|
15
|
+
const ast = new Parser(tokens).parse(namespace)
|
|
16
|
+
const lowering = new Lowering(namespace)
|
|
17
|
+
return { ir: lowering.lower(ast), warnings: lowering.warnings }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Entity hierarchy utilities
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
describe('Entity type hierarchy', () => {
|
|
25
|
+
test('isSubtype: Zombie is a subtype of Mob', () => {
|
|
26
|
+
expect(isSubtype('Zombie', 'Mob')).toBe(true)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('isSubtype: Player is NOT a subtype of Mob', () => {
|
|
30
|
+
expect(isSubtype('Player', 'Mob')).toBe(false)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('isSubtype: Zombie is a subtype of Entity (transitive)', () => {
|
|
34
|
+
expect(isSubtype('Zombie', 'Entity')).toBe(true)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('isSubtype: identity — Zombie is subtype of Zombie', () => {
|
|
38
|
+
expect(isSubtype('Zombie', 'Zombie')).toBe(true)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('areCompatibleTypes: Player and Zombie are NOT compatible', () => {
|
|
42
|
+
expect(areCompatibleTypes('Player', 'Zombie')).toBe(false)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test('areCompatibleTypes: Zombie and Mob are compatible', () => {
|
|
46
|
+
expect(areCompatibleTypes('Zombie', 'Mob')).toBe(true)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('areCompatibleTypes: Mob and Zombie are compatible (reverse)', () => {
|
|
50
|
+
expect(areCompatibleTypes('Mob', 'Zombie')).toBe(true)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test('getConcreteSubtypes: HostileMob includes Zombie, Skeleton, Creeper', () => {
|
|
54
|
+
const subtypes = getConcreteSubtypes('HostileMob')
|
|
55
|
+
const names = subtypes.map(n => n.name)
|
|
56
|
+
expect(names).toContain('Zombie')
|
|
57
|
+
expect(names).toContain('Skeleton')
|
|
58
|
+
expect(names).toContain('Creeper')
|
|
59
|
+
expect(names).toContain('Blaze')
|
|
60
|
+
expect(names).toContain('CaveSpider')
|
|
61
|
+
// Should NOT include passive mobs
|
|
62
|
+
expect(names).not.toContain('Pig')
|
|
63
|
+
expect(names).not.toContain('Player')
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('getConcreteSubtypes: PassiveMob includes Pig, Cow, Villager', () => {
|
|
67
|
+
const subtypes = getConcreteSubtypes('PassiveMob')
|
|
68
|
+
const names = subtypes.map(n => n.name)
|
|
69
|
+
expect(names).toContain('Pig')
|
|
70
|
+
expect(names).toContain('Cow')
|
|
71
|
+
expect(names).toContain('Villager')
|
|
72
|
+
expect(names).toContain('WanderingTrader')
|
|
73
|
+
expect(names).not.toContain('Zombie')
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
test('getSelectorEntityType: parses type=zombie', () => {
|
|
77
|
+
expect(getSelectorEntityType('@e[type=zombie]')).toBe('Zombie')
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test('getSelectorEntityType: parses type=minecraft:skeleton', () => {
|
|
81
|
+
expect(getSelectorEntityType('@e[type=minecraft:skeleton]')).toBe('Skeleton')
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test('getBaseSelectorType: @a → Player', () => {
|
|
85
|
+
expect(getBaseSelectorType('@a')).toBe('Player')
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('getBaseSelectorType: @e → Entity', () => {
|
|
89
|
+
expect(getBaseSelectorType('@e')).toBe('Entity')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('getBaseSelectorType: @e[type=zombie] → Zombie', () => {
|
|
93
|
+
expect(getBaseSelectorType('@e[type=zombie]')).toBe('Zombie')
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
test('getBaseSelectorType: @s → null', () => {
|
|
97
|
+
expect(getBaseSelectorType('@s')).toBeNull()
|
|
98
|
+
})
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
// W_IMPOSSIBLE_AS warning
|
|
103
|
+
// ---------------------------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
describe('W_IMPOSSIBLE_AS warning', () => {
|
|
106
|
+
test('foreach @a with as @e[type=zombie] produces warning', () => {
|
|
107
|
+
const source = `
|
|
108
|
+
fn main() {
|
|
109
|
+
foreach (p in @a) {
|
|
110
|
+
as @e[type=minecraft:zombie] {
|
|
111
|
+
kill(@s);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
`
|
|
116
|
+
const { warnings } = compileWithWarnings(source)
|
|
117
|
+
const impossible = warnings.filter(w => w.code === 'W_IMPOSSIBLE_AS')
|
|
118
|
+
expect(impossible.length).toBe(1)
|
|
119
|
+
expect(impossible[0].message).toContain('Player')
|
|
120
|
+
expect(impossible[0].message).toContain('Zombie')
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
test('foreach @e[type=zombie] with as @e[type=zombie] produces NO warning', () => {
|
|
124
|
+
const source = `
|
|
125
|
+
fn main() {
|
|
126
|
+
foreach (z in @e[type=minecraft:zombie]) {
|
|
127
|
+
as @e[type=minecraft:zombie] {
|
|
128
|
+
kill(@s);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
`
|
|
133
|
+
const { warnings } = compileWithWarnings(source)
|
|
134
|
+
const impossible = warnings.filter(w => w.code === 'W_IMPOSSIBLE_AS')
|
|
135
|
+
expect(impossible.length).toBe(0)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
test('foreach @e (generic) with as @e[type=zombie] produces NO warning', () => {
|
|
139
|
+
const source = `
|
|
140
|
+
fn main() {
|
|
141
|
+
foreach (e in @e) {
|
|
142
|
+
as @e[type=minecraft:zombie] {
|
|
143
|
+
kill(@s);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
`
|
|
148
|
+
const { warnings } = compileWithWarnings(source)
|
|
149
|
+
const impossible = warnings.filter(w => w.code === 'W_IMPOSSIBLE_AS')
|
|
150
|
+
expect(impossible.length).toBe(0)
|
|
151
|
+
})
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
// is_check with abstract types
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
|
|
158
|
+
describe('is_check compilation', () => {
|
|
159
|
+
test('concrete is_check emits single execute if entity', () => {
|
|
160
|
+
const source = `
|
|
161
|
+
fn main() {
|
|
162
|
+
foreach (e in @e) {
|
|
163
|
+
if (e is Zombie) {
|
|
164
|
+
kill(@s);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
`
|
|
169
|
+
const { ir } = compileWithWarnings(source)
|
|
170
|
+
const thenFn = ir.functions.find(f => f.name.includes('then_'))
|
|
171
|
+
expect(thenFn).toBeDefined()
|
|
172
|
+
|
|
173
|
+
// The parent foreach function should contain the execute if entity command
|
|
174
|
+
const foreachFn = ir.functions.find(f => f.name.includes('foreach_'))
|
|
175
|
+
expect(foreachFn).toBeDefined()
|
|
176
|
+
const rawCmds = foreachFn!.blocks.flatMap(b => b.instrs)
|
|
177
|
+
.filter((i): i is any => i.op === 'raw')
|
|
178
|
+
.map(i => i.cmd)
|
|
179
|
+
expect(rawCmds.some(c => c.includes('type=minecraft:zombie'))).toBe(true)
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
test('abstract is_check (HostileMob) emits multiple type checks', () => {
|
|
183
|
+
const source = `
|
|
184
|
+
fn main() {
|
|
185
|
+
foreach (e in @e) {
|
|
186
|
+
if (e is HostileMob) {
|
|
187
|
+
kill(@s);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
`
|
|
192
|
+
const { ir } = compileWithWarnings(source)
|
|
193
|
+
const foreachFn = ir.functions.find(f => f.name.includes('foreach_'))
|
|
194
|
+
expect(foreachFn).toBeDefined()
|
|
195
|
+
const rawCmds = foreachFn!.blocks.flatMap(b => b.instrs)
|
|
196
|
+
.filter((i): i is any => i.op === 'raw')
|
|
197
|
+
.map(i => i.cmd)
|
|
198
|
+
|
|
199
|
+
// Should have scoreboard setup and multiple type checks
|
|
200
|
+
expect(rawCmds.some(c => c.includes('scoreboard players set __is_result rs:temp 0'))).toBe(true)
|
|
201
|
+
expect(rawCmds.some(c => c.includes('type=minecraft:zombie'))).toBe(true)
|
|
202
|
+
expect(rawCmds.some(c => c.includes('type=minecraft:skeleton'))).toBe(true)
|
|
203
|
+
expect(rawCmds.some(c => c.includes('type=minecraft:creeper'))).toBe(true)
|
|
204
|
+
expect(rawCmds.some(c => c.includes('if score __is_result rs:temp matches 1'))).toBe(true)
|
|
205
|
+
})
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
// selector<T> type annotation
|
|
210
|
+
// ---------------------------------------------------------------------------
|
|
211
|
+
|
|
212
|
+
describe('selector<T> type annotation', () => {
|
|
213
|
+
test('parses selector<Player> type', () => {
|
|
214
|
+
const source = `
|
|
215
|
+
fn greet(target: selector<Player>) {
|
|
216
|
+
say("hello");
|
|
217
|
+
}
|
|
218
|
+
`
|
|
219
|
+
const tokens = new Lexer(source).tokenize()
|
|
220
|
+
const ast = new Parser(tokens).parse('test')
|
|
221
|
+
const fn = ast.declarations[0]
|
|
222
|
+
expect(fn.params[0].type).toEqual({ kind: 'selector', entityType: 'Player' })
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
test('parses plain selector type', () => {
|
|
226
|
+
const source = `
|
|
227
|
+
fn greet(target: selector) {
|
|
228
|
+
say("hello");
|
|
229
|
+
}
|
|
230
|
+
`
|
|
231
|
+
const tokens = new Lexer(source).tokenize()
|
|
232
|
+
const ast = new Parser(tokens).parse('test')
|
|
233
|
+
const fn = ast.declarations[0]
|
|
234
|
+
expect(fn.params[0].type).toEqual({ kind: 'selector' })
|
|
235
|
+
})
|
|
236
|
+
})
|