redscript-mc 1.2.21 → 1.2.25

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 (126) hide show
  1. package/.github/workflows/publish-extension-on-ci.yml +100 -0
  2. package/dist/__tests__/entity-types.test.d.ts +1 -0
  3. package/dist/__tests__/entity-types.test.js +203 -0
  4. package/dist/__tests__/var-allocator.test.d.ts +1 -0
  5. package/dist/__tests__/var-allocator.test.js +69 -0
  6. package/dist/ast/types.d.ts +2 -1
  7. package/dist/cli.js +17 -6
  8. package/dist/codegen/mcfunction/index.d.ts +2 -0
  9. package/dist/codegen/mcfunction/index.js +52 -43
  10. package/dist/codegen/structure/index.d.ts +4 -1
  11. package/dist/codegen/structure/index.js +8 -12
  12. package/dist/codegen/var-allocator.d.ts +28 -0
  13. package/dist/codegen/var-allocator.js +78 -0
  14. package/dist/index.d.ts +2 -0
  15. package/dist/index.js +8 -6
  16. package/dist/lowering/index.d.ts +2 -0
  17. package/dist/lowering/index.js +62 -3
  18. package/dist/parser/index.js +22 -1
  19. package/dist/typechecker/index.js +30 -0
  20. package/dist/types/entity-hierarchy.d.ts +29 -0
  21. package/dist/types/entity-hierarchy.js +107 -0
  22. package/editors/vscode/package-lock.json +6 -4
  23. package/editors/vscode/package.json +3 -3
  24. package/package.json +1 -1
  25. package/src/__tests__/entity-types.test.ts +236 -0
  26. package/src/__tests__/var-allocator.test.ts +75 -0
  27. package/src/ast/types.ts +8 -4
  28. package/src/cli.ts +20 -6
  29. package/src/codegen/mcfunction/index.ts +60 -48
  30. package/src/codegen/structure/index.ts +9 -14
  31. package/src/codegen/var-allocator.ts +75 -0
  32. package/src/examples/capture_the_flag.mcrs +34 -34
  33. package/src/examples/hunger_games.mcrs +59 -59
  34. package/src/examples/new_features_demo.mcrs +32 -32
  35. package/src/examples/parkour_race.mcrs +58 -58
  36. package/src/index.ts +10 -6
  37. package/src/lowering/index.ts +73 -8
  38. package/src/parser/index.ts +20 -1
  39. package/src/typechecker/index.ts +30 -0
  40. package/src/types/entity-hierarchy.ts +120 -0
  41. package/dist/data/arena/function/__load.mcfunction +0 -6
  42. package/dist/data/arena/function/__tick.mcfunction +0 -2
  43. package/dist/data/arena/function/announce_leaders/else_1.mcfunction +0 -3
  44. package/dist/data/arena/function/announce_leaders/foreach_0/merge_2.mcfunction +0 -1
  45. package/dist/data/arena/function/announce_leaders/foreach_0/then_0.mcfunction +0 -3
  46. package/dist/data/arena/function/announce_leaders/foreach_0.mcfunction +0 -7
  47. package/dist/data/arena/function/announce_leaders/foreach_1/merge_2.mcfunction +0 -1
  48. package/dist/data/arena/function/announce_leaders/foreach_1/then_0.mcfunction +0 -4
  49. package/dist/data/arena/function/announce_leaders/foreach_1.mcfunction +0 -6
  50. package/dist/data/arena/function/announce_leaders/merge_2.mcfunction +0 -1
  51. package/dist/data/arena/function/announce_leaders/then_0.mcfunction +0 -4
  52. package/dist/data/arena/function/announce_leaders.mcfunction +0 -6
  53. package/dist/data/arena/function/arena_tick/merge_2.mcfunction +0 -1
  54. package/dist/data/arena/function/arena_tick/then_0.mcfunction +0 -4
  55. package/dist/data/arena/function/arena_tick.mcfunction +0 -11
  56. package/dist/data/counter/function/__load.mcfunction +0 -5
  57. package/dist/data/counter/function/__tick.mcfunction +0 -2
  58. package/dist/data/counter/function/counter_tick/merge_2.mcfunction +0 -1
  59. package/dist/data/counter/function/counter_tick/then_0.mcfunction +0 -3
  60. package/dist/data/counter/function/counter_tick.mcfunction +0 -11
  61. package/dist/data/minecraft/tags/function/load.json +0 -5
  62. package/dist/data/minecraft/tags/function/tick.json +0 -5
  63. package/dist/data/quiz/function/__load.mcfunction +0 -16
  64. package/dist/data/quiz/function/__tick.mcfunction +0 -6
  65. package/dist/data/quiz/function/__trigger_quiz_a_dispatch.mcfunction +0 -4
  66. package/dist/data/quiz/function/__trigger_quiz_b_dispatch.mcfunction +0 -4
  67. package/dist/data/quiz/function/__trigger_quiz_c_dispatch.mcfunction +0 -4
  68. package/dist/data/quiz/function/__trigger_quiz_start_dispatch.mcfunction +0 -4
  69. package/dist/data/quiz/function/answer_a.mcfunction +0 -4
  70. package/dist/data/quiz/function/answer_b.mcfunction +0 -4
  71. package/dist/data/quiz/function/answer_c.mcfunction +0 -4
  72. package/dist/data/quiz/function/ask_question/else_1.mcfunction +0 -5
  73. package/dist/data/quiz/function/ask_question/else_4.mcfunction +0 -5
  74. package/dist/data/quiz/function/ask_question/else_7.mcfunction +0 -4
  75. package/dist/data/quiz/function/ask_question/merge_2.mcfunction +0 -1
  76. package/dist/data/quiz/function/ask_question/merge_5.mcfunction +0 -2
  77. package/dist/data/quiz/function/ask_question/merge_8.mcfunction +0 -2
  78. package/dist/data/quiz/function/ask_question/then_0.mcfunction +0 -4
  79. package/dist/data/quiz/function/ask_question/then_3.mcfunction +0 -4
  80. package/dist/data/quiz/function/ask_question/then_6.mcfunction +0 -4
  81. package/dist/data/quiz/function/ask_question.mcfunction +0 -7
  82. package/dist/data/quiz/function/finish_quiz.mcfunction +0 -6
  83. package/dist/data/quiz/function/handle_answer/else_1.mcfunction +0 -5
  84. package/dist/data/quiz/function/handle_answer/else_10.mcfunction +0 -3
  85. package/dist/data/quiz/function/handle_answer/else_16.mcfunction +0 -3
  86. package/dist/data/quiz/function/handle_answer/else_4.mcfunction +0 -3
  87. package/dist/data/quiz/function/handle_answer/else_7.mcfunction +0 -5
  88. package/dist/data/quiz/function/handle_answer/merge_11.mcfunction +0 -2
  89. package/dist/data/quiz/function/handle_answer/merge_14.mcfunction +0 -2
  90. package/dist/data/quiz/function/handle_answer/merge_17.mcfunction +0 -2
  91. package/dist/data/quiz/function/handle_answer/merge_2.mcfunction +0 -8
  92. package/dist/data/quiz/function/handle_answer/merge_5.mcfunction +0 -2
  93. package/dist/data/quiz/function/handle_answer/merge_8.mcfunction +0 -2
  94. package/dist/data/quiz/function/handle_answer/then_0.mcfunction +0 -5
  95. package/dist/data/quiz/function/handle_answer/then_12.mcfunction +0 -5
  96. package/dist/data/quiz/function/handle_answer/then_15.mcfunction +0 -6
  97. package/dist/data/quiz/function/handle_answer/then_3.mcfunction +0 -6
  98. package/dist/data/quiz/function/handle_answer/then_6.mcfunction +0 -5
  99. package/dist/data/quiz/function/handle_answer/then_9.mcfunction +0 -6
  100. package/dist/data/quiz/function/handle_answer.mcfunction +0 -11
  101. package/dist/data/quiz/function/start_quiz.mcfunction +0 -5
  102. package/dist/data/shop/function/__load.mcfunction +0 -7
  103. package/dist/data/shop/function/__tick.mcfunction +0 -3
  104. package/dist/data/shop/function/__trigger_shop_buy_dispatch.mcfunction +0 -4
  105. package/dist/data/shop/function/complete_purchase/else_1.mcfunction +0 -5
  106. package/dist/data/shop/function/complete_purchase/else_4.mcfunction +0 -5
  107. package/dist/data/shop/function/complete_purchase/else_7.mcfunction +0 -3
  108. package/dist/data/shop/function/complete_purchase/merge_2.mcfunction +0 -2
  109. package/dist/data/shop/function/complete_purchase/merge_5.mcfunction +0 -2
  110. package/dist/data/shop/function/complete_purchase/merge_8.mcfunction +0 -2
  111. package/dist/data/shop/function/complete_purchase/then_0.mcfunction +0 -4
  112. package/dist/data/shop/function/complete_purchase/then_3.mcfunction +0 -4
  113. package/dist/data/shop/function/complete_purchase/then_6.mcfunction +0 -4
  114. package/dist/data/shop/function/complete_purchase.mcfunction +0 -7
  115. package/dist/data/shop/function/handle_shop_trigger.mcfunction +0 -3
  116. package/dist/data/turret/function/__load.mcfunction +0 -5
  117. package/dist/data/turret/function/__tick.mcfunction +0 -4
  118. package/dist/data/turret/function/__trigger_deploy_turret_dispatch.mcfunction +0 -4
  119. package/dist/data/turret/function/deploy_turret.mcfunction +0 -8
  120. package/dist/data/turret/function/turret_tick/at_1.mcfunction +0 -2
  121. package/dist/data/turret/function/turret_tick/foreach_0.mcfunction +0 -2
  122. package/dist/data/turret/function/turret_tick/foreach_2.mcfunction +0 -2
  123. package/dist/data/turret/function/turret_tick/tick_body.mcfunction +0 -3
  124. package/dist/data/turret/function/turret_tick/tick_skip.mcfunction +0 -1
  125. package/dist/data/turret/function/turret_tick.mcfunction +0 -5
  126. package/dist/pack.mcmeta +0 -6
@@ -0,0 +1,100 @@
1
+ name: Publish VSCode Extension (Auto)
2
+
3
+ # Triggers when the CI workflow completes successfully on main
4
+ on:
5
+ workflow_run:
6
+ workflows: ["CI"]
7
+ types: [completed]
8
+ branches: [main]
9
+
10
+ jobs:
11
+ publish:
12
+ # Only run if CI passed
13
+ if: ${{ github.event.workflow_run.conclusion == 'success' }}
14
+ runs-on: ubuntu-latest
15
+ permissions:
16
+ contents: write
17
+
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+ with:
21
+ # Need full history to detect changes and push version bump
22
+ fetch-depth: 0
23
+ token: ${{ secrets.IDE_DISPATCH_TOKEN }}
24
+
25
+ - uses: actions/setup-node@v4
26
+ with:
27
+ node-version: 20
28
+ cache: npm
29
+
30
+ - name: Install root dependencies
31
+ run: npm install
32
+
33
+ - name: Build compiler
34
+ run: npm run build
35
+
36
+ - name: Install extension dependencies
37
+ working-directory: editors/vscode
38
+ run: npm install
39
+
40
+ - name: Check if extension needs update
41
+ id: check
42
+ run: |
43
+ # Get last commit that changed the extension publish workflow or core compiler
44
+ LAST_EXT_PUBLISH=$(git log --oneline --follow -- .github/workflows/publish-extension-on-ci.yml editors/vscode/ | head -1 | awk '{print $1}')
45
+ CORE_CHANGED=$(git diff --name-only HEAD~1 HEAD -- src/ package.json | head -1)
46
+ EXT_CHANGED=$(git diff --name-only HEAD~1 HEAD -- editors/vscode/ | head -1)
47
+ echo "core_changed=${CORE_CHANGED}" >> $GITHUB_OUTPUT
48
+ echo "ext_changed=${EXT_CHANGED}" >> $GITHUB_OUTPUT
49
+ if [ -n "$CORE_CHANGED" ] || [ -n "$EXT_CHANGED" ]; then
50
+ echo "should_publish=true" >> $GITHUB_OUTPUT
51
+ else
52
+ echo "should_publish=false" >> $GITHUB_OUTPUT
53
+ fi
54
+
55
+ - name: Bump extension version and commit first
56
+ if: steps.check.outputs.should_publish == 'true'
57
+ run: |
58
+ cd editors/vscode
59
+ npm version patch --no-git-tag-version
60
+ NEW_VER=$(node -p "require('./package.json').version")
61
+ echo "new_version=${NEW_VER}" >> $GITHUB_OUTPUT
62
+ cd ../..
63
+ git config user.name "github-actions[bot]"
64
+ git config user.email "github-actions[bot]@users.noreply.github.com"
65
+ git add editors/vscode/package.json editors/vscode/package-lock.json
66
+ git commit -m "chore: auto-bump vscode extension to ${NEW_VER} [skip ci]" || echo "Nothing to commit"
67
+ git pull --rebase origin main || true
68
+ git push
69
+ id: bump
70
+
71
+ - name: Publish npm package (redscript-mc)
72
+ if: steps.check.outputs.should_publish == 'true'
73
+ env:
74
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
75
+ run: |
76
+ # Check if this version is already published
77
+ PKG_VER=$(node -p "require('./package.json').version")
78
+ if npm view "redscript-mc@${PKG_VER}" version &>/dev/null 2>&1; then
79
+ echo "⏭️ redscript-mc@${PKG_VER} already on npm, skipping"
80
+ else
81
+ echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" >> ~/.npmrc
82
+ npm publish --access public
83
+ echo "✅ Published redscript-mc@${PKG_VER}"
84
+ fi
85
+
86
+ - name: Build and publish extension
87
+ if: steps.check.outputs.should_publish == 'true'
88
+ working-directory: editors/vscode
89
+ env:
90
+ VSCE_PAT: ${{ secrets.VSCE_PAT }}
91
+ run: npm run publish -- -p "$VSCE_PAT"
92
+
93
+ - name: Trigger IDE rebuild
94
+ if: steps.check.outputs.should_publish == 'true'
95
+ env:
96
+ GH_TOKEN: ${{ secrets.IDE_DISPATCH_TOKEN }}
97
+ run: |
98
+ gh api repos/bkmashiro/redscript-ide/dispatches \
99
+ -f event_type=redscript-updated \
100
+ -f client_payload[version]="${{ github.sha }}"
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,203 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const entity_hierarchy_1 = require("../types/entity-hierarchy");
4
+ const lexer_1 = require("../lexer");
5
+ const parser_1 = require("../parser");
6
+ const lowering_1 = require("../lowering");
7
+ function compileWithWarnings(source, namespace = 'test') {
8
+ const tokens = new lexer_1.Lexer(source).tokenize();
9
+ const ast = new parser_1.Parser(tokens).parse(namespace);
10
+ const lowering = new lowering_1.Lowering(namespace);
11
+ return { ir: lowering.lower(ast), warnings: lowering.warnings };
12
+ }
13
+ // ---------------------------------------------------------------------------
14
+ // Entity hierarchy utilities
15
+ // ---------------------------------------------------------------------------
16
+ describe('Entity type hierarchy', () => {
17
+ test('isSubtype: Zombie is a subtype of Mob', () => {
18
+ expect((0, entity_hierarchy_1.isSubtype)('Zombie', 'Mob')).toBe(true);
19
+ });
20
+ test('isSubtype: Player is NOT a subtype of Mob', () => {
21
+ expect((0, entity_hierarchy_1.isSubtype)('Player', 'Mob')).toBe(false);
22
+ });
23
+ test('isSubtype: Zombie is a subtype of Entity (transitive)', () => {
24
+ expect((0, entity_hierarchy_1.isSubtype)('Zombie', 'Entity')).toBe(true);
25
+ });
26
+ test('isSubtype: identity — Zombie is subtype of Zombie', () => {
27
+ expect((0, entity_hierarchy_1.isSubtype)('Zombie', 'Zombie')).toBe(true);
28
+ });
29
+ test('areCompatibleTypes: Player and Zombie are NOT compatible', () => {
30
+ expect((0, entity_hierarchy_1.areCompatibleTypes)('Player', 'Zombie')).toBe(false);
31
+ });
32
+ test('areCompatibleTypes: Zombie and Mob are compatible', () => {
33
+ expect((0, entity_hierarchy_1.areCompatibleTypes)('Zombie', 'Mob')).toBe(true);
34
+ });
35
+ test('areCompatibleTypes: Mob and Zombie are compatible (reverse)', () => {
36
+ expect((0, entity_hierarchy_1.areCompatibleTypes)('Mob', 'Zombie')).toBe(true);
37
+ });
38
+ test('getConcreteSubtypes: HostileMob includes Zombie, Skeleton, Creeper', () => {
39
+ const subtypes = (0, entity_hierarchy_1.getConcreteSubtypes)('HostileMob');
40
+ const names = subtypes.map(n => n.name);
41
+ expect(names).toContain('Zombie');
42
+ expect(names).toContain('Skeleton');
43
+ expect(names).toContain('Creeper');
44
+ expect(names).toContain('Blaze');
45
+ expect(names).toContain('CaveSpider');
46
+ // Should NOT include passive mobs
47
+ expect(names).not.toContain('Pig');
48
+ expect(names).not.toContain('Player');
49
+ });
50
+ test('getConcreteSubtypes: PassiveMob includes Pig, Cow, Villager', () => {
51
+ const subtypes = (0, entity_hierarchy_1.getConcreteSubtypes)('PassiveMob');
52
+ const names = subtypes.map(n => n.name);
53
+ expect(names).toContain('Pig');
54
+ expect(names).toContain('Cow');
55
+ expect(names).toContain('Villager');
56
+ expect(names).toContain('WanderingTrader');
57
+ expect(names).not.toContain('Zombie');
58
+ });
59
+ test('getSelectorEntityType: parses type=zombie', () => {
60
+ expect((0, entity_hierarchy_1.getSelectorEntityType)('@e[type=zombie]')).toBe('Zombie');
61
+ });
62
+ test('getSelectorEntityType: parses type=minecraft:skeleton', () => {
63
+ expect((0, entity_hierarchy_1.getSelectorEntityType)('@e[type=minecraft:skeleton]')).toBe('Skeleton');
64
+ });
65
+ test('getBaseSelectorType: @a → Player', () => {
66
+ expect((0, entity_hierarchy_1.getBaseSelectorType)('@a')).toBe('Player');
67
+ });
68
+ test('getBaseSelectorType: @e → Entity', () => {
69
+ expect((0, entity_hierarchy_1.getBaseSelectorType)('@e')).toBe('Entity');
70
+ });
71
+ test('getBaseSelectorType: @e[type=zombie] → Zombie', () => {
72
+ expect((0, entity_hierarchy_1.getBaseSelectorType)('@e[type=zombie]')).toBe('Zombie');
73
+ });
74
+ test('getBaseSelectorType: @s → null', () => {
75
+ expect((0, entity_hierarchy_1.getBaseSelectorType)('@s')).toBeNull();
76
+ });
77
+ });
78
+ // ---------------------------------------------------------------------------
79
+ // W_IMPOSSIBLE_AS warning
80
+ // ---------------------------------------------------------------------------
81
+ describe('W_IMPOSSIBLE_AS warning', () => {
82
+ test('foreach @a with as @e[type=zombie] produces warning', () => {
83
+ const source = `
84
+ fn main() {
85
+ foreach (p in @a) {
86
+ as @e[type=minecraft:zombie] {
87
+ kill(@s);
88
+ }
89
+ }
90
+ }
91
+ `;
92
+ const { warnings } = compileWithWarnings(source);
93
+ const impossible = warnings.filter(w => w.code === 'W_IMPOSSIBLE_AS');
94
+ expect(impossible.length).toBe(1);
95
+ expect(impossible[0].message).toContain('Player');
96
+ expect(impossible[0].message).toContain('Zombie');
97
+ });
98
+ test('foreach @e[type=zombie] with as @e[type=zombie] produces NO warning', () => {
99
+ const source = `
100
+ fn main() {
101
+ foreach (z in @e[type=minecraft:zombie]) {
102
+ as @e[type=minecraft:zombie] {
103
+ kill(@s);
104
+ }
105
+ }
106
+ }
107
+ `;
108
+ const { warnings } = compileWithWarnings(source);
109
+ const impossible = warnings.filter(w => w.code === 'W_IMPOSSIBLE_AS');
110
+ expect(impossible.length).toBe(0);
111
+ });
112
+ test('foreach @e (generic) with as @e[type=zombie] produces NO warning', () => {
113
+ const source = `
114
+ fn main() {
115
+ foreach (e in @e) {
116
+ as @e[type=minecraft:zombie] {
117
+ kill(@s);
118
+ }
119
+ }
120
+ }
121
+ `;
122
+ const { warnings } = compileWithWarnings(source);
123
+ const impossible = warnings.filter(w => w.code === 'W_IMPOSSIBLE_AS');
124
+ expect(impossible.length).toBe(0);
125
+ });
126
+ });
127
+ // ---------------------------------------------------------------------------
128
+ // is_check with abstract types
129
+ // ---------------------------------------------------------------------------
130
+ describe('is_check compilation', () => {
131
+ test('concrete is_check emits single execute if entity', () => {
132
+ const source = `
133
+ fn main() {
134
+ foreach (e in @e) {
135
+ if (e is Zombie) {
136
+ kill(@s);
137
+ }
138
+ }
139
+ }
140
+ `;
141
+ const { ir } = compileWithWarnings(source);
142
+ const thenFn = ir.functions.find(f => f.name.includes('then_'));
143
+ expect(thenFn).toBeDefined();
144
+ // The parent foreach function should contain the execute if entity command
145
+ const foreachFn = ir.functions.find(f => f.name.includes('foreach_'));
146
+ expect(foreachFn).toBeDefined();
147
+ const rawCmds = foreachFn.blocks.flatMap(b => b.instrs)
148
+ .filter((i) => i.op === 'raw')
149
+ .map(i => i.cmd);
150
+ expect(rawCmds.some(c => c.includes('type=minecraft:zombie'))).toBe(true);
151
+ });
152
+ test('abstract is_check (HostileMob) emits multiple type checks', () => {
153
+ const source = `
154
+ fn main() {
155
+ foreach (e in @e) {
156
+ if (e is HostileMob) {
157
+ kill(@s);
158
+ }
159
+ }
160
+ }
161
+ `;
162
+ const { ir } = compileWithWarnings(source);
163
+ const foreachFn = ir.functions.find(f => f.name.includes('foreach_'));
164
+ expect(foreachFn).toBeDefined();
165
+ const rawCmds = foreachFn.blocks.flatMap(b => b.instrs)
166
+ .filter((i) => i.op === 'raw')
167
+ .map(i => i.cmd);
168
+ // Should have scoreboard setup and multiple type checks
169
+ expect(rawCmds.some(c => c.includes('scoreboard players set __is_result rs:temp 0'))).toBe(true);
170
+ expect(rawCmds.some(c => c.includes('type=minecraft:zombie'))).toBe(true);
171
+ expect(rawCmds.some(c => c.includes('type=minecraft:skeleton'))).toBe(true);
172
+ expect(rawCmds.some(c => c.includes('type=minecraft:creeper'))).toBe(true);
173
+ expect(rawCmds.some(c => c.includes('if score __is_result rs:temp matches 1'))).toBe(true);
174
+ });
175
+ });
176
+ // ---------------------------------------------------------------------------
177
+ // selector<T> type annotation
178
+ // ---------------------------------------------------------------------------
179
+ describe('selector<T> type annotation', () => {
180
+ test('parses selector<Player> type', () => {
181
+ const source = `
182
+ fn greet(target: selector<Player>) {
183
+ say("hello");
184
+ }
185
+ `;
186
+ const tokens = new lexer_1.Lexer(source).tokenize();
187
+ const ast = new parser_1.Parser(tokens).parse('test');
188
+ const fn = ast.declarations[0];
189
+ expect(fn.params[0].type).toEqual({ kind: 'selector', entityType: 'Player' });
190
+ });
191
+ test('parses plain selector type', () => {
192
+ const source = `
193
+ fn greet(target: selector) {
194
+ say("hello");
195
+ }
196
+ `;
197
+ const tokens = new lexer_1.Lexer(source).tokenize();
198
+ const ast = new parser_1.Parser(tokens).parse('test');
199
+ const fn = ast.declarations[0];
200
+ expect(fn.params[0].type).toEqual({ kind: 'selector' });
201
+ });
202
+ });
203
+ //# sourceMappingURL=entity-types.test.js.map
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const var_allocator_1 = require("../codegen/var-allocator");
4
+ describe('VarAllocator', () => {
5
+ describe('mangle mode (default)', () => {
6
+ it('generates sequential names: a, b, ..., z, aa, ab', () => {
7
+ const alloc = new var_allocator_1.VarAllocator(true);
8
+ const names = [];
9
+ for (let i = 0; i < 28; i++) {
10
+ names.push(alloc.alloc(`var${i}`));
11
+ }
12
+ expect(names[0]).toBe('$a');
13
+ expect(names[1]).toBe('$b');
14
+ expect(names[25]).toBe('$z');
15
+ expect(names[26]).toBe('$aa');
16
+ expect(names[27]).toBe('$ab');
17
+ });
18
+ it('caches: same name returns same result', () => {
19
+ const alloc = new var_allocator_1.VarAllocator(true);
20
+ const first = alloc.alloc('x');
21
+ const second = alloc.alloc('x');
22
+ expect(first).toBe(second);
23
+ });
24
+ it('constant() is content-addressed: same value returns same result', () => {
25
+ const alloc = new var_allocator_1.VarAllocator(true);
26
+ const first = alloc.constant(42);
27
+ const second = alloc.constant(42);
28
+ expect(first).toBe(second);
29
+ });
30
+ it('different variables get different names', () => {
31
+ const alloc = new var_allocator_1.VarAllocator(true);
32
+ const a = alloc.alloc('foo');
33
+ const b = alloc.alloc('bar');
34
+ expect(a).not.toBe(b);
35
+ });
36
+ it('alloc, constant, and internal share the same sequential pool', () => {
37
+ const alloc = new var_allocator_1.VarAllocator(true);
38
+ const v = alloc.alloc('x'); // $a
39
+ const c = alloc.constant(1); // $b
40
+ const i = alloc.internal('ret'); // $c
41
+ expect(v).toBe('$a');
42
+ expect(c).toBe('$b');
43
+ expect(i).toBe('$c');
44
+ });
45
+ it('strips $ prefix from variable names', () => {
46
+ const alloc = new var_allocator_1.VarAllocator(true);
47
+ const a = alloc.alloc('$foo');
48
+ const b = alloc.alloc('foo');
49
+ expect(a).toBe(b); // same underlying name
50
+ });
51
+ });
52
+ describe('no-mangle mode', () => {
53
+ it('uses $<name> for user vars', () => {
54
+ const alloc = new var_allocator_1.VarAllocator(false);
55
+ expect(alloc.alloc('counter')).toBe('$counter');
56
+ });
57
+ it('uses $const_<value> for constants', () => {
58
+ const alloc = new var_allocator_1.VarAllocator(false);
59
+ expect(alloc.constant(10)).toBe('$const_10');
60
+ expect(alloc.constant(-3)).toBe('$const_-3');
61
+ });
62
+ it('uses $<suffix> for internals', () => {
63
+ const alloc = new var_allocator_1.VarAllocator(false);
64
+ expect(alloc.internal('ret')).toBe('$ret');
65
+ expect(alloc.internal('p0')).toBe('$p0');
66
+ });
67
+ });
68
+ });
69
+ //# sourceMappingURL=var-allocator.test.js.map
@@ -12,7 +12,7 @@ export interface Span {
12
12
  endCol?: number;
13
13
  }
14
14
  export type PrimitiveType = 'int' | 'bool' | 'float' | 'string' | 'void' | 'BlockPos' | 'byte' | 'short' | 'long' | 'double' | 'format_string';
15
- export type EntityTypeName = 'entity' | 'Player' | 'Mob' | 'HostileMob' | 'PassiveMob' | 'Zombie' | 'Skeleton' | 'Creeper' | 'Spider' | 'Enderman' | 'Pig' | 'Cow' | 'Sheep' | 'Chicken' | 'Villager' | 'ArmorStand' | 'Item' | 'Arrow';
15
+ export type EntityTypeName = 'entity' | 'Player' | 'Mob' | 'HostileMob' | 'PassiveMob' | 'Zombie' | 'Skeleton' | 'Creeper' | 'Spider' | 'Enderman' | 'Blaze' | 'Witch' | 'Slime' | 'ZombieVillager' | 'Husk' | 'Drowned' | 'Stray' | 'WitherSkeleton' | 'CaveSpider' | 'Pig' | 'Cow' | 'Sheep' | 'Chicken' | 'Villager' | 'WanderingTrader' | 'ArmorStand' | 'Item' | 'Arrow';
16
16
  export type TypeNode = {
17
17
  kind: 'named';
18
18
  name: PrimitiveType;
@@ -34,6 +34,7 @@ export type TypeNode = {
34
34
  entityType: EntityTypeName;
35
35
  } | {
36
36
  kind: 'selector';
37
+ entityType?: string;
37
38
  };
38
39
  export interface LambdaParam {
39
40
  name: string;
package/dist/cli.js CHANGED
@@ -84,6 +84,7 @@ Options:
84
84
  --namespace <ns> Datapack namespace (default: derived from filename)
85
85
  --target <target> Output target: datapack (default), cmdblock, or structure
86
86
  --no-dce Disable AST dead code elimination
87
+ --no-mangle Disable variable name mangling (use readable names)
87
88
  --stats Print optimizer statistics
88
89
  --hot-reload <url> After each successful compile, POST to <url>/reload
89
90
  (use with redscript-testharness; e.g. http://localhost:25561)
@@ -181,7 +182,7 @@ function upgradeCommand() {
181
182
  });
182
183
  }
183
184
  function parseArgs(args) {
184
- const result = { dce: true };
185
+ const result = { dce: true, mangle: true };
185
186
  let i = 0;
186
187
  while (i < args.length) {
187
188
  const arg = args[i];
@@ -213,6 +214,10 @@ function parseArgs(args) {
213
214
  result.dce = false;
214
215
  i++;
215
216
  }
217
+ else if (arg === '--no-mangle') {
218
+ result.mangle = false;
219
+ i++;
220
+ }
216
221
  else if (arg === '--hot-reload') {
217
222
  result.hotReload = args[++i];
218
223
  i++;
@@ -266,7 +271,7 @@ function printOptimizationStats(stats) {
266
271
  console.log(` constant folding: ${stats.constantFolds} constants folded`);
267
272
  console.log(` Total mcfunction commands: ${stats.totalCommandsBefore} -> ${stats.totalCommandsAfter} (${formatReduction(stats.totalCommandsBefore, stats.totalCommandsAfter)} reduction)`);
268
273
  }
269
- function compileCommand(file, output, namespace, target = 'datapack', showStats = false, dce = true) {
274
+ function compileCommand(file, output, namespace, target = 'datapack', showStats = false, dce = true, mangle = true) {
270
275
  // Read source file
271
276
  if (!fs.existsSync(file)) {
272
277
  console.error(`Error: File not found: ${file}`);
@@ -275,7 +280,7 @@ function compileCommand(file, output, namespace, target = 'datapack', showStats
275
280
  const source = fs.readFileSync(file, 'utf-8');
276
281
  try {
277
282
  if (target === 'cmdblock') {
278
- const result = (0, index_1.compile)(source, { namespace, filePath: file, dce });
283
+ const result = (0, index_1.compile)(source, { namespace, filePath: file, dce, mangle });
279
284
  printWarnings(result.warnings);
280
285
  // Generate command block JSON
281
286
  const hasTick = result.files.some(f => f.path.includes('__tick.mcfunction'));
@@ -293,7 +298,7 @@ function compileCommand(file, output, namespace, target = 'datapack', showStats
293
298
  }
294
299
  }
295
300
  else if (target === 'structure') {
296
- const structure = (0, structure_1.compileToStructure)(source, namespace, file, { dce });
301
+ const structure = (0, structure_1.compileToStructure)(source, namespace, file, { dce, mangle });
297
302
  fs.mkdirSync(path.dirname(output), { recursive: true });
298
303
  fs.writeFileSync(output, structure.buffer);
299
304
  console.log(`✓ Generated structure for ${file}`);
@@ -304,7 +309,7 @@ function compileCommand(file, output, namespace, target = 'datapack', showStats
304
309
  }
305
310
  }
306
311
  else {
307
- const result = (0, index_1.compile)(source, { namespace, filePath: file, dce });
312
+ const result = (0, index_1.compile)(source, { namespace, filePath: file, dce, mangle });
308
313
  printWarnings(result.warnings);
309
314
  // Default: generate datapack
310
315
  // Create output directory
@@ -316,6 +321,12 @@ function compileCommand(file, output, namespace, target = 'datapack', showStats
316
321
  fs.mkdirSync(dir, { recursive: true });
317
322
  fs.writeFileSync(filePath, dataFile.content);
318
323
  }
324
+ // Write sourcemap alongside datapack when mangle mode is active
325
+ if (mangle && result.sourceMap && Object.keys(result.sourceMap).length > 0) {
326
+ const mapPath = path.join(output, `${namespace}.map.json`);
327
+ fs.writeFileSync(mapPath, JSON.stringify(result.sourceMap, null, 2));
328
+ console.log(` Sourcemap: ${mapPath}`);
329
+ }
319
330
  console.log(`✓ Compiled ${file} to ${output}/`);
320
331
  console.log(` Namespace: ${namespace}`);
321
332
  console.log(` Functions: ${result.ir.functions.length}`);
@@ -472,7 +483,7 @@ async function main() {
472
483
  const output = target === 'structure'
473
484
  ? (parsed.outputNbt ?? parsed.output ?? `./${namespace}.nbt`)
474
485
  : (parsed.output ?? './dist');
475
- compileCommand(parsed.file, output, namespace, target, parsed.stats, parsed.dce);
486
+ compileCommand(parsed.file, output, namespace, target, parsed.stats, parsed.dce, parsed.mangle);
476
487
  }
477
488
  break;
478
489
  case 'watch':
@@ -25,9 +25,11 @@ export interface DatapackGenerationResult {
25
25
  files: DatapackFile[];
26
26
  advancements: DatapackFile[];
27
27
  stats: OptimizationStats;
28
+ sourceMap?: Record<string, string>;
28
29
  }
29
30
  export interface DatapackGenerationOptions {
30
31
  optimizeCommands?: boolean;
32
+ mangle?: boolean;
31
33
  }
32
34
  export declare function countMcfunctionCommands(files: DatapackFile[]): number;
33
35
  export declare function generateDatapackWithStats(module: IRModule, options?: DatapackGenerationOptions): DatapackGenerationResult;