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.
Files changed (136) hide show
  1. package/.github/workflows/publish-extension-on-ci.yml +99 -0
  2. package/dist/__tests__/compile-all.test.js +5 -0
  3. package/dist/__tests__/entity-types.test.d.ts +1 -0
  4. package/dist/__tests__/entity-types.test.js +203 -0
  5. package/dist/__tests__/var-allocator.test.d.ts +1 -0
  6. package/dist/__tests__/var-allocator.test.js +69 -0
  7. package/dist/ast/types.d.ts +2 -1
  8. package/dist/cli.js +24 -7
  9. package/dist/codegen/mcfunction/index.d.ts +2 -0
  10. package/dist/codegen/mcfunction/index.js +47 -43
  11. package/dist/codegen/structure/index.d.ts +4 -1
  12. package/dist/codegen/structure/index.js +8 -12
  13. package/dist/codegen/var-allocator.d.ts +28 -0
  14. package/dist/codegen/var-allocator.js +74 -0
  15. package/dist/compile.d.ts +8 -0
  16. package/dist/compile.js +14 -0
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.js +9 -7
  19. package/dist/lowering/index.d.ts +2 -0
  20. package/dist/lowering/index.js +62 -3
  21. package/dist/optimizer/dce.d.ts +2 -1
  22. package/dist/optimizer/dce.js +13 -2
  23. package/dist/parser/index.js +22 -1
  24. package/dist/typechecker/index.js +30 -0
  25. package/dist/types/entity-hierarchy.d.ts +29 -0
  26. package/dist/types/entity-hierarchy.js +107 -0
  27. package/editors/vscode/out/extension.js +29 -4
  28. package/editors/vscode/package-lock.json +6 -4
  29. package/editors/vscode/package.json +3 -3
  30. package/package.json +1 -1
  31. package/src/__tests__/compile-all.test.ts +6 -0
  32. package/src/__tests__/entity-types.test.ts +236 -0
  33. package/src/__tests__/var-allocator.test.ts +75 -0
  34. package/src/ast/types.ts +8 -4
  35. package/src/cli.ts +28 -8
  36. package/src/codegen/mcfunction/index.ts +55 -48
  37. package/src/codegen/structure/index.ts +9 -14
  38. package/src/codegen/var-allocator.ts +71 -0
  39. package/src/compile.ts +18 -0
  40. package/src/examples/capture_the_flag.mcrs +34 -34
  41. package/src/examples/hunger_games.mcrs +60 -60
  42. package/src/examples/new_features_demo.mcrs +32 -32
  43. package/src/examples/parkour_race.mcrs +58 -58
  44. package/src/examples/zombie_survival.mcrs +73 -73
  45. package/src/index.ts +11 -7
  46. package/src/lowering/index.ts +73 -8
  47. package/src/optimizer/dce.ts +18 -2
  48. package/src/parser/index.ts +20 -1
  49. package/src/typechecker/index.ts +30 -0
  50. package/src/types/entity-hierarchy.ts +120 -0
  51. package/dist/data/arena/function/__load.mcfunction +0 -6
  52. package/dist/data/arena/function/__tick.mcfunction +0 -2
  53. package/dist/data/arena/function/announce_leaders/else_1.mcfunction +0 -3
  54. package/dist/data/arena/function/announce_leaders/foreach_0/merge_2.mcfunction +0 -1
  55. package/dist/data/arena/function/announce_leaders/foreach_0/then_0.mcfunction +0 -3
  56. package/dist/data/arena/function/announce_leaders/foreach_0.mcfunction +0 -7
  57. package/dist/data/arena/function/announce_leaders/foreach_1/merge_2.mcfunction +0 -1
  58. package/dist/data/arena/function/announce_leaders/foreach_1/then_0.mcfunction +0 -4
  59. package/dist/data/arena/function/announce_leaders/foreach_1.mcfunction +0 -6
  60. package/dist/data/arena/function/announce_leaders/merge_2.mcfunction +0 -1
  61. package/dist/data/arena/function/announce_leaders/then_0.mcfunction +0 -4
  62. package/dist/data/arena/function/announce_leaders.mcfunction +0 -6
  63. package/dist/data/arena/function/arena_tick/merge_2.mcfunction +0 -1
  64. package/dist/data/arena/function/arena_tick/then_0.mcfunction +0 -4
  65. package/dist/data/arena/function/arena_tick.mcfunction +0 -11
  66. package/dist/data/counter/function/__load.mcfunction +0 -5
  67. package/dist/data/counter/function/__tick.mcfunction +0 -2
  68. package/dist/data/counter/function/counter_tick/merge_2.mcfunction +0 -1
  69. package/dist/data/counter/function/counter_tick/then_0.mcfunction +0 -3
  70. package/dist/data/counter/function/counter_tick.mcfunction +0 -11
  71. package/dist/data/minecraft/tags/function/load.json +0 -5
  72. package/dist/data/minecraft/tags/function/tick.json +0 -5
  73. package/dist/data/quiz/function/__load.mcfunction +0 -16
  74. package/dist/data/quiz/function/__tick.mcfunction +0 -6
  75. package/dist/data/quiz/function/__trigger_quiz_a_dispatch.mcfunction +0 -4
  76. package/dist/data/quiz/function/__trigger_quiz_b_dispatch.mcfunction +0 -4
  77. package/dist/data/quiz/function/__trigger_quiz_c_dispatch.mcfunction +0 -4
  78. package/dist/data/quiz/function/__trigger_quiz_start_dispatch.mcfunction +0 -4
  79. package/dist/data/quiz/function/answer_a.mcfunction +0 -4
  80. package/dist/data/quiz/function/answer_b.mcfunction +0 -4
  81. package/dist/data/quiz/function/answer_c.mcfunction +0 -4
  82. package/dist/data/quiz/function/ask_question/else_1.mcfunction +0 -5
  83. package/dist/data/quiz/function/ask_question/else_4.mcfunction +0 -5
  84. package/dist/data/quiz/function/ask_question/else_7.mcfunction +0 -4
  85. package/dist/data/quiz/function/ask_question/merge_2.mcfunction +0 -1
  86. package/dist/data/quiz/function/ask_question/merge_5.mcfunction +0 -2
  87. package/dist/data/quiz/function/ask_question/merge_8.mcfunction +0 -2
  88. package/dist/data/quiz/function/ask_question/then_0.mcfunction +0 -4
  89. package/dist/data/quiz/function/ask_question/then_3.mcfunction +0 -4
  90. package/dist/data/quiz/function/ask_question/then_6.mcfunction +0 -4
  91. package/dist/data/quiz/function/ask_question.mcfunction +0 -7
  92. package/dist/data/quiz/function/finish_quiz.mcfunction +0 -6
  93. package/dist/data/quiz/function/handle_answer/else_1.mcfunction +0 -5
  94. package/dist/data/quiz/function/handle_answer/else_10.mcfunction +0 -3
  95. package/dist/data/quiz/function/handle_answer/else_16.mcfunction +0 -3
  96. package/dist/data/quiz/function/handle_answer/else_4.mcfunction +0 -3
  97. package/dist/data/quiz/function/handle_answer/else_7.mcfunction +0 -5
  98. package/dist/data/quiz/function/handle_answer/merge_11.mcfunction +0 -2
  99. package/dist/data/quiz/function/handle_answer/merge_14.mcfunction +0 -2
  100. package/dist/data/quiz/function/handle_answer/merge_17.mcfunction +0 -2
  101. package/dist/data/quiz/function/handle_answer/merge_2.mcfunction +0 -8
  102. package/dist/data/quiz/function/handle_answer/merge_5.mcfunction +0 -2
  103. package/dist/data/quiz/function/handle_answer/merge_8.mcfunction +0 -2
  104. package/dist/data/quiz/function/handle_answer/then_0.mcfunction +0 -5
  105. package/dist/data/quiz/function/handle_answer/then_12.mcfunction +0 -5
  106. package/dist/data/quiz/function/handle_answer/then_15.mcfunction +0 -6
  107. package/dist/data/quiz/function/handle_answer/then_3.mcfunction +0 -6
  108. package/dist/data/quiz/function/handle_answer/then_6.mcfunction +0 -5
  109. package/dist/data/quiz/function/handle_answer/then_9.mcfunction +0 -6
  110. package/dist/data/quiz/function/handle_answer.mcfunction +0 -11
  111. package/dist/data/quiz/function/start_quiz.mcfunction +0 -5
  112. package/dist/data/shop/function/__load.mcfunction +0 -7
  113. package/dist/data/shop/function/__tick.mcfunction +0 -3
  114. package/dist/data/shop/function/__trigger_shop_buy_dispatch.mcfunction +0 -4
  115. package/dist/data/shop/function/complete_purchase/else_1.mcfunction +0 -5
  116. package/dist/data/shop/function/complete_purchase/else_4.mcfunction +0 -5
  117. package/dist/data/shop/function/complete_purchase/else_7.mcfunction +0 -3
  118. package/dist/data/shop/function/complete_purchase/merge_2.mcfunction +0 -2
  119. package/dist/data/shop/function/complete_purchase/merge_5.mcfunction +0 -2
  120. package/dist/data/shop/function/complete_purchase/merge_8.mcfunction +0 -2
  121. package/dist/data/shop/function/complete_purchase/then_0.mcfunction +0 -4
  122. package/dist/data/shop/function/complete_purchase/then_3.mcfunction +0 -4
  123. package/dist/data/shop/function/complete_purchase/then_6.mcfunction +0 -4
  124. package/dist/data/shop/function/complete_purchase.mcfunction +0 -7
  125. package/dist/data/shop/function/handle_shop_trigger.mcfunction +0 -3
  126. package/dist/data/turret/function/__load.mcfunction +0 -5
  127. package/dist/data/turret/function/__tick.mcfunction +0 -4
  128. package/dist/data/turret/function/__trigger_deploy_turret_dispatch.mcfunction +0 -4
  129. package/dist/data/turret/function/deploy_turret.mcfunction +0 -8
  130. package/dist/data/turret/function/turret_tick/at_1.mcfunction +0 -2
  131. package/dist/data/turret/function/turret_tick/foreach_0.mcfunction +0 -2
  132. package/dist/data/turret/function/turret_tick/foreach_2.mcfunction +0 -2
  133. package/dist/data/turret/function/turret_tick/tick_body.mcfunction +0 -3
  134. package/dist/data/turret/function/turret_tick/tick_skip.mcfunction +0 -1
  135. package/dist/data/turret/function/turret_tick.mcfunction +0 -5
  136. package/dist/pack.mcmeta +0 -6
@@ -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
  };
@@ -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
- return { program: result, warnings: eliminator.warnings };
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
@@ -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
- type = { kind: 'struct', name: token.value };
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
- const rangeToken = this.expect("range_lit");
1197
- const range = this.parseRangeValue(rangeToken.value);
1198
- const start = this.withLoc({ kind: "int_lit", value: range.min ?? 0 }, rangeToken);
1199
- const end = this.withLoc({ kind: "int_lit", value: range.max ?? 0 }, rangeToken);
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.11",
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.11",
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
- "version": "0.1.0",
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.11",
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "redscript-mc",
3
- "version": "1.2.20",
3
+ "version": "1.2.24",
4
4
  "description": "A high-level programming language that compiles to Minecraft datapacks",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -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
+ })