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
@@ -0,0 +1,99 @@
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 push
68
+ id: bump
69
+
70
+ - name: Publish npm package (redscript-mc)
71
+ if: steps.check.outputs.should_publish == 'true'
72
+ env:
73
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
74
+ run: |
75
+ # Check if this version is already published
76
+ PKG_VER=$(node -p "require('./package.json').version")
77
+ if npm view "redscript-mc@${PKG_VER}" version &>/dev/null 2>&1; then
78
+ echo "⏭️ redscript-mc@${PKG_VER} already on npm, skipping"
79
+ else
80
+ echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" >> ~/.npmrc
81
+ npm publish --access public
82
+ echo "✅ Published redscript-mc@${PKG_VER}"
83
+ fi
84
+
85
+ - name: Build and publish extension
86
+ if: steps.check.outputs.should_publish == 'true'
87
+ working-directory: editors/vscode
88
+ env:
89
+ VSCE_PAT: ${{ secrets.VSCE_PAT }}
90
+ run: npm run publish -- -p "$VSCE_PAT"
91
+
92
+ - name: Trigger IDE rebuild
93
+ if: steps.check.outputs.should_publish == 'true'
94
+ env:
95
+ GH_TOKEN: ${{ secrets.IDE_DISPATCH_TOKEN }}
96
+ run: |
97
+ gh api repos/bkmashiro/redscript-ide/dispatches \
98
+ -f event_type=redscript-updated \
99
+ -f client_payload[version]="${{ github.sha }}"
@@ -48,6 +48,11 @@ const child_process_1 = require("child_process");
48
48
  const os = __importStar(require("os"));
49
49
  const REPO_ROOT = path.resolve(__dirname, '../../');
50
50
  const CLI = path.join(REPO_ROOT, 'dist', 'cli.js');
51
+ // Ensure dist/cli.js exists — build first if not (e.g. in CI)
52
+ if (!fs.existsSync(CLI)) {
53
+ console.log('[compile-all] dist/cli.js not found, running npm run build...');
54
+ (0, child_process_1.execSync)('npm run build', { cwd: REPO_ROOT, stdio: 'inherit' });
55
+ }
51
56
  /** Patterns to skip */
52
57
  const SKIP_GLOBS = [
53
58
  'node_modules',
@@ -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++;
@@ -241,7 +246,13 @@ function printWarnings(warnings) {
241
246
  return;
242
247
  }
243
248
  for (const warning of warnings) {
244
- console.error(`Warning [${warning.code}]: ${warning.message}`);
249
+ const loc = warning.filePath
250
+ ? `${warning.filePath}:${warning.line ?? '?'}`
251
+ : warning.line != null
252
+ ? `line ${warning.line}`
253
+ : null;
254
+ const locStr = loc ? ` (${loc})` : '';
255
+ console.error(`Warning [${warning.code}]: ${warning.message}${locStr}`);
245
256
  }
246
257
  }
247
258
  function formatReduction(before, after) {
@@ -260,7 +271,7 @@ function printOptimizationStats(stats) {
260
271
  console.log(` constant folding: ${stats.constantFolds} constants folded`);
261
272
  console.log(` Total mcfunction commands: ${stats.totalCommandsBefore} -> ${stats.totalCommandsAfter} (${formatReduction(stats.totalCommandsBefore, stats.totalCommandsAfter)} reduction)`);
262
273
  }
263
- 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) {
264
275
  // Read source file
265
276
  if (!fs.existsSync(file)) {
266
277
  console.error(`Error: File not found: ${file}`);
@@ -269,7 +280,7 @@ function compileCommand(file, output, namespace, target = 'datapack', showStats
269
280
  const source = fs.readFileSync(file, 'utf-8');
270
281
  try {
271
282
  if (target === 'cmdblock') {
272
- const result = (0, index_1.compile)(source, { namespace, filePath: file, dce });
283
+ const result = (0, index_1.compile)(source, { namespace, filePath: file, dce, mangle });
273
284
  printWarnings(result.warnings);
274
285
  // Generate command block JSON
275
286
  const hasTick = result.files.some(f => f.path.includes('__tick.mcfunction'));
@@ -287,7 +298,7 @@ function compileCommand(file, output, namespace, target = 'datapack', showStats
287
298
  }
288
299
  }
289
300
  else if (target === 'structure') {
290
- const structure = (0, structure_1.compileToStructure)(source, namespace, file, { dce });
301
+ const structure = (0, structure_1.compileToStructure)(source, namespace, file, { dce, mangle });
291
302
  fs.mkdirSync(path.dirname(output), { recursive: true });
292
303
  fs.writeFileSync(output, structure.buffer);
293
304
  console.log(`✓ Generated structure for ${file}`);
@@ -298,7 +309,7 @@ function compileCommand(file, output, namespace, target = 'datapack', showStats
298
309
  }
299
310
  }
300
311
  else {
301
- const result = (0, index_1.compile)(source, { namespace, filePath: file, dce });
312
+ const result = (0, index_1.compile)(source, { namespace, filePath: file, dce, mangle });
302
313
  printWarnings(result.warnings);
303
314
  // Default: generate datapack
304
315
  // Create output directory
@@ -310,6 +321,12 @@ function compileCommand(file, output, namespace, target = 'datapack', showStats
310
321
  fs.mkdirSync(dir, { recursive: true });
311
322
  fs.writeFileSync(filePath, dataFile.content);
312
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
+ }
313
330
  console.log(`✓ Compiled ${file} to ${output}/`);
314
331
  console.log(` Namespace: ${namespace}`);
315
332
  console.log(` Functions: ${result.ir.functions.length}`);
@@ -466,7 +483,7 @@ async function main() {
466
483
  const output = target === 'structure'
467
484
  ? (parsed.outputNbt ?? parsed.output ?? `./${namespace}.nbt`)
468
485
  : (parsed.output ?? './dist');
469
- compileCommand(parsed.file, output, namespace, target, parsed.stats, parsed.dce);
486
+ compileCommand(parsed.file, output, namespace, target, parsed.stats, parsed.dce, parsed.mangle);
470
487
  }
471
488
  break;
472
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;