redscript-mc 1.2.20 → 1.2.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish-extension-on-ci.yml +99 -0
- package/dist/__tests__/compile-all.test.js +5 -0
- package/dist/__tests__/entity-types.test.d.ts +1 -0
- package/dist/__tests__/entity-types.test.js +203 -0
- package/dist/__tests__/var-allocator.test.d.ts +1 -0
- package/dist/__tests__/var-allocator.test.js +69 -0
- package/dist/ast/types.d.ts +2 -1
- package/dist/cli.js +24 -7
- package/dist/codegen/mcfunction/index.d.ts +2 -0
- package/dist/codegen/mcfunction/index.js +47 -43
- package/dist/codegen/structure/index.d.ts +4 -1
- package/dist/codegen/structure/index.js +8 -12
- package/dist/codegen/var-allocator.d.ts +28 -0
- package/dist/codegen/var-allocator.js +74 -0
- package/dist/compile.d.ts +8 -0
- package/dist/compile.js +14 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +9 -7
- package/dist/lowering/index.d.ts +2 -0
- package/dist/lowering/index.js +62 -3
- package/dist/optimizer/dce.d.ts +2 -1
- package/dist/optimizer/dce.js +13 -2
- package/dist/parser/index.js +22 -1
- package/dist/typechecker/index.js +30 -0
- package/dist/types/entity-hierarchy.d.ts +29 -0
- package/dist/types/entity-hierarchy.js +107 -0
- package/editors/vscode/out/extension.js +29 -4
- package/editors/vscode/package-lock.json +6 -4
- package/editors/vscode/package.json +3 -3
- package/package.json +1 -1
- package/src/__tests__/compile-all.test.ts +6 -0
- package/src/__tests__/entity-types.test.ts +236 -0
- package/src/__tests__/var-allocator.test.ts +75 -0
- package/src/ast/types.ts +8 -4
- package/src/cli.ts +28 -8
- package/src/codegen/mcfunction/index.ts +55 -48
- package/src/codegen/structure/index.ts +9 -14
- package/src/codegen/var-allocator.ts +71 -0
- package/src/compile.ts +18 -0
- package/src/examples/capture_the_flag.mcrs +34 -34
- package/src/examples/hunger_games.mcrs +60 -60
- package/src/examples/new_features_demo.mcrs +32 -32
- package/src/examples/parkour_race.mcrs +58 -58
- package/src/examples/zombie_survival.mcrs +73 -73
- package/src/index.ts +11 -7
- package/src/lowering/index.ts +73 -8
- package/src/optimizer/dce.ts +18 -2
- package/src/parser/index.ts +20 -1
- package/src/typechecker/index.ts +30 -0
- package/src/types/entity-hierarchy.ts +120 -0
- package/dist/data/arena/function/__load.mcfunction +0 -6
- package/dist/data/arena/function/__tick.mcfunction +0 -2
- package/dist/data/arena/function/announce_leaders/else_1.mcfunction +0 -3
- package/dist/data/arena/function/announce_leaders/foreach_0/merge_2.mcfunction +0 -1
- package/dist/data/arena/function/announce_leaders/foreach_0/then_0.mcfunction +0 -3
- package/dist/data/arena/function/announce_leaders/foreach_0.mcfunction +0 -7
- package/dist/data/arena/function/announce_leaders/foreach_1/merge_2.mcfunction +0 -1
- package/dist/data/arena/function/announce_leaders/foreach_1/then_0.mcfunction +0 -4
- package/dist/data/arena/function/announce_leaders/foreach_1.mcfunction +0 -6
- package/dist/data/arena/function/announce_leaders/merge_2.mcfunction +0 -1
- package/dist/data/arena/function/announce_leaders/then_0.mcfunction +0 -4
- package/dist/data/arena/function/announce_leaders.mcfunction +0 -6
- package/dist/data/arena/function/arena_tick/merge_2.mcfunction +0 -1
- package/dist/data/arena/function/arena_tick/then_0.mcfunction +0 -4
- package/dist/data/arena/function/arena_tick.mcfunction +0 -11
- package/dist/data/counter/function/__load.mcfunction +0 -5
- package/dist/data/counter/function/__tick.mcfunction +0 -2
- package/dist/data/counter/function/counter_tick/merge_2.mcfunction +0 -1
- package/dist/data/counter/function/counter_tick/then_0.mcfunction +0 -3
- package/dist/data/counter/function/counter_tick.mcfunction +0 -11
- package/dist/data/minecraft/tags/function/load.json +0 -5
- package/dist/data/minecraft/tags/function/tick.json +0 -5
- package/dist/data/quiz/function/__load.mcfunction +0 -16
- package/dist/data/quiz/function/__tick.mcfunction +0 -6
- package/dist/data/quiz/function/__trigger_quiz_a_dispatch.mcfunction +0 -4
- package/dist/data/quiz/function/__trigger_quiz_b_dispatch.mcfunction +0 -4
- package/dist/data/quiz/function/__trigger_quiz_c_dispatch.mcfunction +0 -4
- package/dist/data/quiz/function/__trigger_quiz_start_dispatch.mcfunction +0 -4
- package/dist/data/quiz/function/answer_a.mcfunction +0 -4
- package/dist/data/quiz/function/answer_b.mcfunction +0 -4
- package/dist/data/quiz/function/answer_c.mcfunction +0 -4
- package/dist/data/quiz/function/ask_question/else_1.mcfunction +0 -5
- package/dist/data/quiz/function/ask_question/else_4.mcfunction +0 -5
- package/dist/data/quiz/function/ask_question/else_7.mcfunction +0 -4
- package/dist/data/quiz/function/ask_question/merge_2.mcfunction +0 -1
- package/dist/data/quiz/function/ask_question/merge_5.mcfunction +0 -2
- package/dist/data/quiz/function/ask_question/merge_8.mcfunction +0 -2
- package/dist/data/quiz/function/ask_question/then_0.mcfunction +0 -4
- package/dist/data/quiz/function/ask_question/then_3.mcfunction +0 -4
- package/dist/data/quiz/function/ask_question/then_6.mcfunction +0 -4
- package/dist/data/quiz/function/ask_question.mcfunction +0 -7
- package/dist/data/quiz/function/finish_quiz.mcfunction +0 -6
- package/dist/data/quiz/function/handle_answer/else_1.mcfunction +0 -5
- package/dist/data/quiz/function/handle_answer/else_10.mcfunction +0 -3
- package/dist/data/quiz/function/handle_answer/else_16.mcfunction +0 -3
- package/dist/data/quiz/function/handle_answer/else_4.mcfunction +0 -3
- package/dist/data/quiz/function/handle_answer/else_7.mcfunction +0 -5
- package/dist/data/quiz/function/handle_answer/merge_11.mcfunction +0 -2
- package/dist/data/quiz/function/handle_answer/merge_14.mcfunction +0 -2
- package/dist/data/quiz/function/handle_answer/merge_17.mcfunction +0 -2
- package/dist/data/quiz/function/handle_answer/merge_2.mcfunction +0 -8
- package/dist/data/quiz/function/handle_answer/merge_5.mcfunction +0 -2
- package/dist/data/quiz/function/handle_answer/merge_8.mcfunction +0 -2
- package/dist/data/quiz/function/handle_answer/then_0.mcfunction +0 -5
- package/dist/data/quiz/function/handle_answer/then_12.mcfunction +0 -5
- package/dist/data/quiz/function/handle_answer/then_15.mcfunction +0 -6
- package/dist/data/quiz/function/handle_answer/then_3.mcfunction +0 -6
- package/dist/data/quiz/function/handle_answer/then_6.mcfunction +0 -5
- package/dist/data/quiz/function/handle_answer/then_9.mcfunction +0 -6
- package/dist/data/quiz/function/handle_answer.mcfunction +0 -11
- package/dist/data/quiz/function/start_quiz.mcfunction +0 -5
- package/dist/data/shop/function/__load.mcfunction +0 -7
- package/dist/data/shop/function/__tick.mcfunction +0 -3
- package/dist/data/shop/function/__trigger_shop_buy_dispatch.mcfunction +0 -4
- package/dist/data/shop/function/complete_purchase/else_1.mcfunction +0 -5
- package/dist/data/shop/function/complete_purchase/else_4.mcfunction +0 -5
- package/dist/data/shop/function/complete_purchase/else_7.mcfunction +0 -3
- package/dist/data/shop/function/complete_purchase/merge_2.mcfunction +0 -2
- package/dist/data/shop/function/complete_purchase/merge_5.mcfunction +0 -2
- package/dist/data/shop/function/complete_purchase/merge_8.mcfunction +0 -2
- package/dist/data/shop/function/complete_purchase/then_0.mcfunction +0 -4
- package/dist/data/shop/function/complete_purchase/then_3.mcfunction +0 -4
- package/dist/data/shop/function/complete_purchase/then_6.mcfunction +0 -4
- package/dist/data/shop/function/complete_purchase.mcfunction +0 -7
- package/dist/data/shop/function/handle_shop_trigger.mcfunction +0 -3
- package/dist/data/turret/function/__load.mcfunction +0 -5
- package/dist/data/turret/function/__tick.mcfunction +0 -4
- package/dist/data/turret/function/__trigger_deploy_turret_dispatch.mcfunction +0 -4
- package/dist/data/turret/function/deploy_turret.mcfunction +0 -8
- package/dist/data/turret/function/turret_tick/at_1.mcfunction +0 -2
- package/dist/data/turret/function/turret_tick/foreach_0.mcfunction +0 -2
- package/dist/data/turret/function/turret_tick/foreach_2.mcfunction +0 -2
- package/dist/data/turret/function/turret_tick/tick_body.mcfunction +0 -3
- package/dist/data/turret/function/turret_tick/tick_skip.mcfunction +0 -1
- package/dist/data/turret/function/turret_tick.mcfunction +0 -5
- package/dist/pack.mcmeta +0 -6
|
@@ -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
|
package/dist/ast/types.d.ts
CHANGED
|
@@ -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
|
-
|
|
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;
|