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.
- package/.github/workflows/publish-extension-on-ci.yml +100 -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 +17 -6
- package/dist/codegen/mcfunction/index.d.ts +2 -0
- package/dist/codegen/mcfunction/index.js +52 -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 +78 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +8 -6
- package/dist/lowering/index.d.ts +2 -0
- package/dist/lowering/index.js +62 -3
- 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/package-lock.json +6 -4
- package/editors/vscode/package.json +3 -3
- package/package.json +1 -1
- 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 +20 -6
- package/src/codegen/mcfunction/index.ts +60 -48
- package/src/codegen/structure/index.ts +9 -14
- package/src/codegen/var-allocator.ts +75 -0
- package/src/examples/capture_the_flag.mcrs +34 -34
- package/src/examples/hunger_games.mcrs +59 -59
- package/src/examples/new_features_demo.mcrs +32 -32
- package/src/examples/parkour_race.mcrs +58 -58
- package/src/index.ts +10 -6
- package/src/lowering/index.ts +73 -8
- 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,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
|
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++;
|
|
@@ -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;
|