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
|
@@ -22,24 +22,18 @@ exports.generateDatapackWithStats = generateDatapackWithStats;
|
|
|
22
22
|
exports.generateDatapack = generateDatapack;
|
|
23
23
|
const commands_1 = require("../../optimizer/commands");
|
|
24
24
|
const types_1 = require("../../events/types");
|
|
25
|
+
const var_allocator_1 = require("../var-allocator");
|
|
25
26
|
// ---------------------------------------------------------------------------
|
|
26
27
|
// Utilities
|
|
27
28
|
// ---------------------------------------------------------------------------
|
|
28
29
|
const OBJ = 'rs'; // scoreboard objective name
|
|
29
|
-
function
|
|
30
|
-
// Ensure fake player prefix
|
|
31
|
-
return name.startsWith('$') ? name : `$${name}`;
|
|
32
|
-
}
|
|
33
|
-
function operandToScore(op) {
|
|
30
|
+
function operandToScore(op, alloc) {
|
|
34
31
|
if (op.kind === 'var')
|
|
35
|
-
return `${
|
|
32
|
+
return `${alloc.alloc(op.name)} ${OBJ}`;
|
|
36
33
|
if (op.kind === 'const')
|
|
37
|
-
return `$
|
|
34
|
+
return `${alloc.constant(op.value)} ${OBJ}`;
|
|
38
35
|
throw new Error(`Cannot convert storage operand to score: ${op.path}`);
|
|
39
36
|
}
|
|
40
|
-
function constSetup(value) {
|
|
41
|
-
return `scoreboard players set $const_${value} ${OBJ} ${value}`;
|
|
42
|
-
}
|
|
43
37
|
// Collect all constants used in a function for pre-setup
|
|
44
38
|
function collectConsts(fn) {
|
|
45
39
|
const consts = new Set();
|
|
@@ -73,17 +67,17 @@ const BOP_OP = {
|
|
|
73
67
|
// ---------------------------------------------------------------------------
|
|
74
68
|
// Instruction codegen
|
|
75
69
|
// ---------------------------------------------------------------------------
|
|
76
|
-
function emitInstr(instr, ns) {
|
|
70
|
+
function emitInstr(instr, ns, alloc) {
|
|
77
71
|
const lines = [];
|
|
78
72
|
switch (instr.op) {
|
|
79
73
|
case 'assign': {
|
|
80
|
-
const dst =
|
|
74
|
+
const dst = alloc.alloc(instr.dst);
|
|
81
75
|
const src = instr.src;
|
|
82
76
|
if (src.kind === 'const') {
|
|
83
77
|
lines.push(`scoreboard players set ${dst} ${OBJ} ${src.value}`);
|
|
84
78
|
}
|
|
85
79
|
else if (src.kind === 'var') {
|
|
86
|
-
lines.push(`scoreboard players operation ${dst} ${OBJ} = ${
|
|
80
|
+
lines.push(`scoreboard players operation ${dst} ${OBJ} = ${alloc.alloc(src.name)} ${OBJ}`);
|
|
87
81
|
}
|
|
88
82
|
else {
|
|
89
83
|
lines.push(`execute store result score ${dst} ${OBJ} run data get storage ${src.path}`);
|
|
@@ -91,18 +85,18 @@ function emitInstr(instr, ns) {
|
|
|
91
85
|
break;
|
|
92
86
|
}
|
|
93
87
|
case 'binop': {
|
|
94
|
-
const dst =
|
|
88
|
+
const dst = alloc.alloc(instr.dst);
|
|
95
89
|
const bop = BOP_OP[instr.bop] ?? '+=';
|
|
96
90
|
// Copy lhs → dst, then apply op with rhs
|
|
97
|
-
lines.push(...emitInstr({ op: 'assign', dst: instr.dst, src: instr.lhs }, ns));
|
|
98
|
-
lines.push(`scoreboard players operation ${dst} ${OBJ} ${bop} ${operandToScore(instr.rhs)}`);
|
|
91
|
+
lines.push(...emitInstr({ op: 'assign', dst: instr.dst, src: instr.lhs }, ns, alloc));
|
|
92
|
+
lines.push(`scoreboard players operation ${dst} ${OBJ} ${bop} ${operandToScore(instr.rhs, alloc)}`);
|
|
99
93
|
break;
|
|
100
94
|
}
|
|
101
95
|
case 'cmp': {
|
|
102
96
|
// MC doesn't have a direct compare-to-register; use execute store
|
|
103
|
-
const dst =
|
|
104
|
-
const lhsScore = operandToScore(instr.lhs);
|
|
105
|
-
const rhsScore = operandToScore(instr.rhs);
|
|
97
|
+
const dst = alloc.alloc(instr.dst);
|
|
98
|
+
const lhsScore = operandToScore(instr.lhs, alloc);
|
|
99
|
+
const rhsScore = operandToScore(instr.rhs, alloc);
|
|
106
100
|
lines.push(`scoreboard players set ${dst} ${OBJ} 0`);
|
|
107
101
|
switch (instr.cop) {
|
|
108
102
|
case '==':
|
|
@@ -127,13 +121,15 @@ function emitInstr(instr, ns) {
|
|
|
127
121
|
break;
|
|
128
122
|
}
|
|
129
123
|
case 'call': {
|
|
130
|
-
// Push args as fake players
|
|
124
|
+
// Push args as param fake players
|
|
131
125
|
for (let i = 0; i < instr.args.length; i++) {
|
|
132
|
-
|
|
126
|
+
const paramName = alloc.internal(`p${i}`);
|
|
127
|
+
lines.push(...emitInstr({ op: 'assign', dst: paramName, src: instr.args[i] }, ns, alloc));
|
|
133
128
|
}
|
|
134
129
|
lines.push(`function ${ns}:${instr.fn}`);
|
|
135
130
|
if (instr.dst) {
|
|
136
|
-
|
|
131
|
+
const retName = alloc.internal('ret');
|
|
132
|
+
lines.push(`scoreboard players operation ${alloc.alloc(instr.dst)} ${OBJ} = ${retName} ${OBJ}`);
|
|
137
133
|
}
|
|
138
134
|
break;
|
|
139
135
|
}
|
|
@@ -146,32 +142,34 @@ function emitInstr(instr, ns) {
|
|
|
146
142
|
// ---------------------------------------------------------------------------
|
|
147
143
|
// Terminator codegen
|
|
148
144
|
// ---------------------------------------------------------------------------
|
|
149
|
-
function emitTerm(term, ns, fnName) {
|
|
145
|
+
function emitTerm(term, ns, fnName, alloc) {
|
|
150
146
|
const lines = [];
|
|
151
147
|
switch (term.op) {
|
|
152
148
|
case 'jump':
|
|
153
149
|
lines.push(`function ${ns}:${fnName}/${term.target}`);
|
|
154
150
|
break;
|
|
155
151
|
case 'jump_if':
|
|
156
|
-
lines.push(`execute if score ${
|
|
157
|
-
lines.push(`execute if score ${
|
|
152
|
+
lines.push(`execute if score ${alloc.alloc(term.cond)} ${OBJ} matches 1.. run function ${ns}:${fnName}/${term.then}`);
|
|
153
|
+
lines.push(`execute if score ${alloc.alloc(term.cond)} ${OBJ} matches ..0 run function ${ns}:${fnName}/${term.else_}`);
|
|
158
154
|
break;
|
|
159
155
|
case 'jump_unless':
|
|
160
|
-
lines.push(`execute if score ${
|
|
161
|
-
lines.push(`execute if score ${
|
|
156
|
+
lines.push(`execute if score ${alloc.alloc(term.cond)} ${OBJ} matches ..0 run function ${ns}:${fnName}/${term.then}`);
|
|
157
|
+
lines.push(`execute if score ${alloc.alloc(term.cond)} ${OBJ} matches 1.. run function ${ns}:${fnName}/${term.else_}`);
|
|
162
158
|
break;
|
|
163
|
-
case 'return':
|
|
159
|
+
case 'return': {
|
|
160
|
+
const retName = alloc.internal('ret');
|
|
164
161
|
if (term.value) {
|
|
165
|
-
lines.push(...emitInstr({ op: 'assign', dst:
|
|
162
|
+
lines.push(...emitInstr({ op: 'assign', dst: retName, src: term.value }, ns, alloc));
|
|
166
163
|
}
|
|
167
164
|
// In MC 1.20+, use `return` command
|
|
168
165
|
if (term.value?.kind === 'const') {
|
|
169
166
|
lines.push(`return ${term.value.value}`);
|
|
170
167
|
}
|
|
171
168
|
else if (term.value?.kind === 'var') {
|
|
172
|
-
lines.push(`return run scoreboard players get ${
|
|
169
|
+
lines.push(`return run scoreboard players get ${alloc.alloc(term.value.name)} ${OBJ}`);
|
|
173
170
|
}
|
|
174
171
|
break;
|
|
172
|
+
}
|
|
175
173
|
case 'tick_yield':
|
|
176
174
|
lines.push(`schedule function ${ns}:${fnName}/${term.continuation} 1t replace`);
|
|
177
175
|
break;
|
|
@@ -240,7 +238,8 @@ function countMcfunctionCommands(files) {
|
|
|
240
238
|
}, 0);
|
|
241
239
|
}
|
|
242
240
|
function generateDatapackWithStats(module, options = {}) {
|
|
243
|
-
const { optimizeCommands = true } = options;
|
|
241
|
+
const { optimizeCommands = true, mangle = false } = options;
|
|
242
|
+
const alloc = new var_allocator_1.VarAllocator(mangle);
|
|
244
243
|
const files = [];
|
|
245
244
|
const advancements = [];
|
|
246
245
|
const ns = module.namespace;
|
|
@@ -269,7 +268,7 @@ function generateDatapackWithStats(module, options = {}) {
|
|
|
269
268
|
`scoreboard objectives add ${OBJ} dummy`,
|
|
270
269
|
];
|
|
271
270
|
for (const g of module.globals) {
|
|
272
|
-
loadLines.push(`scoreboard players set ${
|
|
271
|
+
loadLines.push(`scoreboard players set ${alloc.alloc(g.name)} ${OBJ} ${g.init}`);
|
|
273
272
|
}
|
|
274
273
|
// Add trigger objectives
|
|
275
274
|
for (const triggerName of triggerNames) {
|
|
@@ -308,13 +307,17 @@ function generateDatapackWithStats(module, options = {}) {
|
|
|
308
307
|
content: dispatchLines.join('\n'),
|
|
309
308
|
});
|
|
310
309
|
}
|
|
311
|
-
//
|
|
310
|
+
// Collect all constants across all functions first (deduplicated)
|
|
311
|
+
const allConsts = new Set();
|
|
312
|
+
for (const fn of module.functions) {
|
|
313
|
+
for (const c of collectConsts(fn))
|
|
314
|
+
allConsts.add(c);
|
|
315
|
+
}
|
|
316
|
+
if (allConsts.size > 0) {
|
|
317
|
+
loadLines.push(...Array.from(allConsts).sort((a, b) => a - b).map(value => `scoreboard players set ${alloc.constant(value)} ${OBJ} ${value}`));
|
|
318
|
+
}
|
|
319
|
+
// Generate each function
|
|
312
320
|
for (const fn of module.functions) {
|
|
313
|
-
// Constant setup — place constants in __load.mcfunction
|
|
314
|
-
const consts = collectConsts(fn);
|
|
315
|
-
if (consts.size > 0) {
|
|
316
|
-
loadLines.push(...Array.from(consts).map(constSetup));
|
|
317
|
-
}
|
|
318
321
|
// Entry block → <fn_name>.mcfunction
|
|
319
322
|
// Continuation blocks → <fn_name>/<label>.mcfunction
|
|
320
323
|
for (let i = 0; i < fn.blocks.length; i++) {
|
|
@@ -323,16 +326,21 @@ function generateDatapackWithStats(module, options = {}) {
|
|
|
323
326
|
// Param setup in entry block
|
|
324
327
|
if (i === 0) {
|
|
325
328
|
for (let j = 0; j < fn.params.length; j++) {
|
|
326
|
-
lines.push(`scoreboard players operation ${
|
|
329
|
+
lines.push(`scoreboard players operation ${alloc.alloc(fn.params[j])} ${OBJ} = ${alloc.internal(`p${j}`)} ${OBJ}`);
|
|
327
330
|
}
|
|
328
331
|
}
|
|
329
332
|
for (const instr of block.instrs) {
|
|
330
|
-
lines.push(...emitInstr(instr, ns));
|
|
333
|
+
lines.push(...emitInstr(instr, ns, alloc));
|
|
331
334
|
}
|
|
332
|
-
lines.push(...emitTerm(block.term, ns, fn.name));
|
|
335
|
+
lines.push(...emitTerm(block.term, ns, fn.name, alloc));
|
|
333
336
|
const filePath = i === 0
|
|
334
337
|
? `data/${ns}/function/${fn.name}.mcfunction`
|
|
335
338
|
: `data/${ns}/function/${fn.name}/${block.label}.mcfunction`;
|
|
339
|
+
// Skip empty continuation blocks (only contain the block comment, no real commands)
|
|
340
|
+
// Entry block (i === 0) is always emitted so the function file exists
|
|
341
|
+
const hasRealContent = lines.some(l => !l.startsWith('#') && l.trim() !== '');
|
|
342
|
+
if (i !== 0 && !hasRealContent)
|
|
343
|
+
continue;
|
|
336
344
|
files.push({ path: filePath, content: lines.join('\n') });
|
|
337
345
|
}
|
|
338
346
|
}
|
|
@@ -442,12 +450,13 @@ function generateDatapackWithStats(module, options = {}) {
|
|
|
442
450
|
});
|
|
443
451
|
}
|
|
444
452
|
const stats = (0, commands_1.createEmptyOptimizationStats)();
|
|
453
|
+
const sourceMap = mangle ? alloc.toSourceMap() : undefined;
|
|
445
454
|
if (!optimizeCommands) {
|
|
446
|
-
return { files, advancements, stats };
|
|
455
|
+
return { files, advancements, stats, sourceMap };
|
|
447
456
|
}
|
|
448
457
|
const optimized = applyFunctionOptimization(files);
|
|
449
458
|
(0, commands_1.mergeOptimizationStats)(stats, optimized.stats);
|
|
450
|
-
return { files: optimized.files, advancements, stats };
|
|
459
|
+
return { files: optimized.files, advancements, stats, sourceMap };
|
|
451
460
|
}
|
|
452
461
|
function generateDatapack(module) {
|
|
453
462
|
const generated = generateDatapackWithStats(module);
|
|
@@ -16,6 +16,9 @@ export interface StructureCompileResult {
|
|
|
16
16
|
}
|
|
17
17
|
export interface StructureCompileOptions {
|
|
18
18
|
dce?: boolean;
|
|
19
|
+
mangle?: boolean;
|
|
19
20
|
}
|
|
20
|
-
export declare function generateStructure(input: IRModule | DatapackFile[]
|
|
21
|
+
export declare function generateStructure(input: IRModule | DatapackFile[], options?: {
|
|
22
|
+
mangle?: boolean;
|
|
23
|
+
}): StructureCompileResult;
|
|
21
24
|
export declare function compileToStructure(source: string, namespace: string, filePath?: string, options?: StructureCompileOptions): StructureCompileResult;
|
|
@@ -12,6 +12,7 @@ const structure_1 = require("../../optimizer/structure");
|
|
|
12
12
|
const dce_1 = require("../../optimizer/dce");
|
|
13
13
|
const compile_1 = require("../../compile");
|
|
14
14
|
const types_1 = require("../../events/types");
|
|
15
|
+
const var_allocator_1 = require("../var-allocator");
|
|
15
16
|
const DATA_VERSION = 3953;
|
|
16
17
|
const MAX_WIDTH = 16;
|
|
17
18
|
const OBJ = 'rs';
|
|
@@ -29,9 +30,6 @@ const palette = [
|
|
|
29
30
|
function escapeJsonString(value) {
|
|
30
31
|
return JSON.stringify(value).slice(1, -1);
|
|
31
32
|
}
|
|
32
|
-
function varRef(name) {
|
|
33
|
-
return name.startsWith('$') ? name : `$${name}`;
|
|
34
|
-
}
|
|
35
33
|
function collectConsts(fn) {
|
|
36
34
|
const consts = new Set();
|
|
37
35
|
for (const block of fn.blocks) {
|
|
@@ -57,10 +55,8 @@ function collectConsts(fn) {
|
|
|
57
55
|
}
|
|
58
56
|
return consts;
|
|
59
57
|
}
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
function collectCommandEntriesFromModule(module) {
|
|
58
|
+
function collectCommandEntriesFromModule(module, mangle = false) {
|
|
59
|
+
const alloc = new var_allocator_1.VarAllocator(mangle);
|
|
64
60
|
const entries = [];
|
|
65
61
|
const triggerHandlers = module.functions.filter(fn => fn.isTriggerHandler && fn.triggerName);
|
|
66
62
|
const triggerNames = new Set(triggerHandlers.map(fn => fn.triggerName));
|
|
@@ -68,12 +64,12 @@ function collectCommandEntriesFromModule(module) {
|
|
|
68
64
|
const eventTypes = new Set(eventHandlers.map(fn => fn.eventHandler.eventType));
|
|
69
65
|
const loadCommands = [
|
|
70
66
|
`scoreboard objectives add ${OBJ} dummy`,
|
|
71
|
-
...module.globals.map(g => `scoreboard players set ${
|
|
67
|
+
...module.globals.map(g => `scoreboard players set ${alloc.alloc(g.name)} ${OBJ} ${g.init}`),
|
|
72
68
|
...Array.from(triggerNames).flatMap(triggerName => [
|
|
73
69
|
`scoreboard objectives add ${triggerName} trigger`,
|
|
74
70
|
`scoreboard players enable @a ${triggerName}`,
|
|
75
71
|
]),
|
|
76
|
-
...Array.from(new Set(module.functions.flatMap(fn => Array.from(collectConsts(fn))))).map(
|
|
72
|
+
...Array.from(new Set(module.functions.flatMap(fn => Array.from(collectConsts(fn))))).map(value => `scoreboard players set ${alloc.constant(value)} ${OBJ} ${value}`),
|
|
77
73
|
];
|
|
78
74
|
for (const eventType of eventTypes) {
|
|
79
75
|
if (eventType === 'PlayerDeath') {
|
|
@@ -229,10 +225,10 @@ function createBlockTag(entry, index) {
|
|
|
229
225
|
nbt: createBlockEntityTag(entry),
|
|
230
226
|
});
|
|
231
227
|
}
|
|
232
|
-
function generateStructure(input) {
|
|
228
|
+
function generateStructure(input, options) {
|
|
233
229
|
const entries = Array.isArray(input)
|
|
234
230
|
? collectCommandEntriesFromFiles(input)
|
|
235
|
-
: collectCommandEntriesFromModule(input);
|
|
231
|
+
: collectCommandEntriesFromModule(input, options?.mangle);
|
|
236
232
|
const blockTags = entries.map(createBlockTag);
|
|
237
233
|
const sizeX = Math.max(1, Math.min(MAX_WIDTH, entries.length || 1));
|
|
238
234
|
const sizeZ = Math.max(1, Math.min(MAX_WIDTH, Math.ceil(entries.length / MAX_WIDTH) || 1));
|
|
@@ -276,7 +272,7 @@ function compileToStructure(source, namespace, filePath, options = {}) {
|
|
|
276
272
|
functions: structureOptimized.functions,
|
|
277
273
|
};
|
|
278
274
|
return {
|
|
279
|
-
...generateStructure(optimizedModule),
|
|
275
|
+
...generateStructure(optimizedModule, { mangle: options.mangle }),
|
|
280
276
|
stats,
|
|
281
277
|
};
|
|
282
278
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VarAllocator — assigns scoreboard fake-player names to variables.
|
|
3
|
+
*
|
|
4
|
+
* mangle=true: sequential short names ($a, $b, ..., $z, $aa, $ab, ...)
|
|
5
|
+
* mangle=false: legacy names ($<name> for vars, $const_<v> for consts, $p0/$ret for internals)
|
|
6
|
+
*/
|
|
7
|
+
export declare class VarAllocator {
|
|
8
|
+
private readonly mangle;
|
|
9
|
+
private seq;
|
|
10
|
+
private readonly varCache;
|
|
11
|
+
private readonly constCache;
|
|
12
|
+
private readonly internalCache;
|
|
13
|
+
constructor(mangle?: boolean);
|
|
14
|
+
/** Allocate a name for a user variable. Strips leading '$' if present. */
|
|
15
|
+
alloc(originalName: string): string;
|
|
16
|
+
/** Allocate a name for a constant value (content-addressed). */
|
|
17
|
+
constant(value: number): string;
|
|
18
|
+
/** Allocate a name for a compiler internal (e.g. "ret", "p0"). */
|
|
19
|
+
internal(suffix: string): string;
|
|
20
|
+
/** Generate the next sequential name: a, b, ..., z, aa, ab, ..., az, ba, ... */
|
|
21
|
+
private nextSeqName;
|
|
22
|
+
/**
|
|
23
|
+
* Returns a sourcemap object mapping allocated name → original name.
|
|
24
|
+
* Useful for debugging: write to <output>.map.json alongside the datapack.
|
|
25
|
+
* Only meaningful when mangle=true.
|
|
26
|
+
*/
|
|
27
|
+
toSourceMap(): Record<string, string>;
|
|
28
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* VarAllocator — assigns scoreboard fake-player names to variables.
|
|
4
|
+
*
|
|
5
|
+
* mangle=true: sequential short names ($a, $b, ..., $z, $aa, $ab, ...)
|
|
6
|
+
* mangle=false: legacy names ($<name> for vars, $const_<v> for consts, $p0/$ret for internals)
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.VarAllocator = void 0;
|
|
10
|
+
class VarAllocator {
|
|
11
|
+
constructor(mangle = true) {
|
|
12
|
+
this.seq = 0;
|
|
13
|
+
this.varCache = new Map();
|
|
14
|
+
this.constCache = new Map();
|
|
15
|
+
this.internalCache = new Map();
|
|
16
|
+
this.mangle = mangle;
|
|
17
|
+
}
|
|
18
|
+
/** Allocate a name for a user variable. Strips leading '$' if present. */
|
|
19
|
+
alloc(originalName) {
|
|
20
|
+
const clean = originalName.startsWith('$') ? originalName.slice(1) : originalName;
|
|
21
|
+
const cached = this.varCache.get(clean);
|
|
22
|
+
if (cached)
|
|
23
|
+
return cached;
|
|
24
|
+
const name = this.mangle ? `$${this.nextSeqName()}` : `$${clean}`;
|
|
25
|
+
this.varCache.set(clean, name);
|
|
26
|
+
return name;
|
|
27
|
+
}
|
|
28
|
+
/** Allocate a name for a constant value (content-addressed). */
|
|
29
|
+
constant(value) {
|
|
30
|
+
const cached = this.constCache.get(value);
|
|
31
|
+
if (cached)
|
|
32
|
+
return cached;
|
|
33
|
+
const name = this.mangle ? `$${this.nextSeqName()}` : `$const_${value}`;
|
|
34
|
+
this.constCache.set(value, name);
|
|
35
|
+
return name;
|
|
36
|
+
}
|
|
37
|
+
/** Allocate a name for a compiler internal (e.g. "ret", "p0"). */
|
|
38
|
+
internal(suffix) {
|
|
39
|
+
const cached = this.internalCache.get(suffix);
|
|
40
|
+
if (cached)
|
|
41
|
+
return cached;
|
|
42
|
+
const name = this.mangle ? `$${this.nextSeqName()}` : `$${suffix}`;
|
|
43
|
+
this.internalCache.set(suffix, name);
|
|
44
|
+
return name;
|
|
45
|
+
}
|
|
46
|
+
/** Generate the next sequential name: a, b, ..., z, aa, ab, ..., az, ba, ... */
|
|
47
|
+
nextSeqName() {
|
|
48
|
+
const n = this.seq++;
|
|
49
|
+
let result = '';
|
|
50
|
+
let remaining = n;
|
|
51
|
+
do {
|
|
52
|
+
result = String.fromCharCode(97 + (remaining % 26)) + result;
|
|
53
|
+
remaining = Math.floor(remaining / 26) - 1;
|
|
54
|
+
} while (remaining >= 0);
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Returns a sourcemap object mapping allocated name → original name.
|
|
59
|
+
* Useful for debugging: write to <output>.map.json alongside the datapack.
|
|
60
|
+
* Only meaningful when mangle=true.
|
|
61
|
+
*/
|
|
62
|
+
toSourceMap() {
|
|
63
|
+
const map = {};
|
|
64
|
+
for (const [orig, alloc] of this.varCache) {
|
|
65
|
+
// Skip compiler-generated temporaries (start with _ followed by digits)
|
|
66
|
+
if (/^_\d+$/.test(orig))
|
|
67
|
+
continue;
|
|
68
|
+
map[alloc] = orig;
|
|
69
|
+
}
|
|
70
|
+
for (const [val, alloc] of this.constCache)
|
|
71
|
+
map[alloc] = `const:${val}`;
|
|
72
|
+
for (const [suf, alloc] of this.internalCache)
|
|
73
|
+
map[alloc] = `internal:${suf}`;
|
|
74
|
+
return map;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
exports.VarAllocator = VarAllocator;
|
|
78
|
+
//# sourceMappingURL=var-allocator.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export interface CompileOptions {
|
|
|
16
16
|
typeCheck?: boolean;
|
|
17
17
|
filePath?: string;
|
|
18
18
|
dce?: boolean;
|
|
19
|
+
mangle?: boolean;
|
|
19
20
|
}
|
|
20
21
|
export interface CompileResult {
|
|
21
22
|
files: DatapackFile[];
|
|
@@ -25,6 +26,7 @@ export interface CompileResult {
|
|
|
25
26
|
typeErrors?: DiagnosticError[];
|
|
26
27
|
warnings?: Warning[];
|
|
27
28
|
stats?: OptimizationStats;
|
|
29
|
+
sourceMap?: Record<string, string>;
|
|
28
30
|
}
|
|
29
31
|
/**
|
|
30
32
|
* Compile RedScript source code to a Minecraft datapack.
|
package/dist/index.js
CHANGED
|
@@ -31,6 +31,7 @@ function compile(source, options = {}) {
|
|
|
31
31
|
const shouldOptimize = options.optimize ?? true;
|
|
32
32
|
const shouldTypeCheck = options.typeCheck ?? true;
|
|
33
33
|
const shouldRunDce = options.dce ?? shouldOptimize;
|
|
34
|
+
const mangle = options.mangle ?? false;
|
|
34
35
|
const filePath = options.filePath;
|
|
35
36
|
const preprocessed = (0, compile_1.preprocessSourceWithMetadata)(source, { filePath });
|
|
36
37
|
const preprocessedSource = preprocessed.source;
|
|
@@ -50,7 +51,7 @@ function compile(source, options = {}) {
|
|
|
50
51
|
const lowering = new lowering_1.Lowering(namespace, preprocessed.ranges);
|
|
51
52
|
const ir = lowering.lower(ast);
|
|
52
53
|
let optimizedIR = ir;
|
|
53
|
-
let generated = (0, mcfunction_1.generateDatapackWithStats)(ir, { optimizeCommands: shouldOptimize });
|
|
54
|
+
let generated = (0, mcfunction_1.generateDatapackWithStats)(ir, { optimizeCommands: shouldOptimize, mangle });
|
|
54
55
|
let optimizationStats;
|
|
55
56
|
if (shouldOptimize) {
|
|
56
57
|
const stats = (0, commands_1.createEmptyOptimizationStats)();
|
|
@@ -66,10 +67,10 @@ function compile(source, options = {}) {
|
|
|
66
67
|
}
|
|
67
68
|
const copyPropagatedIR = { ...ir, functions: copyPropagatedFunctions };
|
|
68
69
|
optimizedIR = { ...ir, functions: deadCodeEliminatedFunctions };
|
|
69
|
-
const baselineGenerated = (0, mcfunction_1.generateDatapackWithStats)(ir, { optimizeCommands: false });
|
|
70
|
-
const beforeDceGenerated = (0, mcfunction_1.generateDatapackWithStats)(copyPropagatedIR, { optimizeCommands: false });
|
|
71
|
-
const afterDceGenerated = (0, mcfunction_1.generateDatapackWithStats)(optimizedIR, { optimizeCommands: false });
|
|
72
|
-
generated = (0, mcfunction_1.generateDatapackWithStats)(optimizedIR, { optimizeCommands: true });
|
|
70
|
+
const baselineGenerated = (0, mcfunction_1.generateDatapackWithStats)(ir, { optimizeCommands: false, mangle });
|
|
71
|
+
const beforeDceGenerated = (0, mcfunction_1.generateDatapackWithStats)(copyPropagatedIR, { optimizeCommands: false, mangle });
|
|
72
|
+
const afterDceGenerated = (0, mcfunction_1.generateDatapackWithStats)(optimizedIR, { optimizeCommands: false, mangle });
|
|
73
|
+
generated = (0, mcfunction_1.generateDatapackWithStats)(optimizedIR, { optimizeCommands: true, mangle });
|
|
73
74
|
stats.deadCodeRemoved =
|
|
74
75
|
(0, mcfunction_1.countMcfunctionCommands)(beforeDceGenerated.files) - (0, mcfunction_1.countMcfunctionCommands)(afterDceGenerated.files);
|
|
75
76
|
stats.licmHoists = generated.stats.licmHoists;
|
|
@@ -85,7 +86,7 @@ function compile(source, options = {}) {
|
|
|
85
86
|
}
|
|
86
87
|
else {
|
|
87
88
|
optimizedIR = ir;
|
|
88
|
-
generated = (0, mcfunction_1.generateDatapackWithStats)(ir, { optimizeCommands: false });
|
|
89
|
+
generated = (0, mcfunction_1.generateDatapackWithStats)(ir, { optimizeCommands: false, mangle });
|
|
89
90
|
}
|
|
90
91
|
return {
|
|
91
92
|
files: [...generated.files, ...generated.advancements],
|
|
@@ -95,6 +96,7 @@ function compile(source, options = {}) {
|
|
|
95
96
|
typeErrors,
|
|
96
97
|
warnings: [...dceResult.warnings, ...lowering.warnings],
|
|
97
98
|
stats: optimizationStats,
|
|
99
|
+
sourceMap: generated.sourceMap,
|
|
98
100
|
};
|
|
99
101
|
}
|
|
100
102
|
/**
|
package/dist/lowering/index.d.ts
CHANGED
package/dist/lowering/index.js
CHANGED
|
@@ -44,6 +44,7 @@ const builder_1 = require("../ir/builder");
|
|
|
44
44
|
const diagnostics_1 = require("../diagnostics");
|
|
45
45
|
const path = __importStar(require("path"));
|
|
46
46
|
const types_1 = require("../events/types");
|
|
47
|
+
const entity_hierarchy_1 = require("../types/entity-hierarchy");
|
|
47
48
|
// ---------------------------------------------------------------------------
|
|
48
49
|
// Macro-aware builtins (MC 1.20.2+)
|
|
49
50
|
// These builtins generate commands where parameter variables cannot appear
|
|
@@ -140,11 +141,21 @@ const ENTITY_TO_MC_TYPE = {
|
|
|
140
141
|
Creeper: 'minecraft:creeper',
|
|
141
142
|
Spider: 'minecraft:spider',
|
|
142
143
|
Enderman: 'minecraft:enderman',
|
|
144
|
+
Blaze: 'minecraft:blaze',
|
|
145
|
+
Witch: 'minecraft:witch',
|
|
146
|
+
Slime: 'minecraft:slime',
|
|
147
|
+
ZombieVillager: 'minecraft:zombie_villager',
|
|
148
|
+
Husk: 'minecraft:husk',
|
|
149
|
+
Drowned: 'minecraft:drowned',
|
|
150
|
+
Stray: 'minecraft:stray',
|
|
151
|
+
WitherSkeleton: 'minecraft:wither_skeleton',
|
|
152
|
+
CaveSpider: 'minecraft:cave_spider',
|
|
143
153
|
Pig: 'minecraft:pig',
|
|
144
154
|
Cow: 'minecraft:cow',
|
|
145
155
|
Sheep: 'minecraft:sheep',
|
|
146
156
|
Chicken: 'minecraft:chicken',
|
|
147
157
|
Villager: 'minecraft:villager',
|
|
158
|
+
WanderingTrader: 'minecraft:wandering_trader',
|
|
148
159
|
ArmorStand: 'minecraft:armor_stand',
|
|
149
160
|
Item: 'minecraft:item',
|
|
150
161
|
Arrow: 'minecraft:arrow',
|
|
@@ -185,6 +196,11 @@ function emitBlockPos(pos) {
|
|
|
185
196
|
// Lowering Class
|
|
186
197
|
// ---------------------------------------------------------------------------
|
|
187
198
|
class Lowering {
|
|
199
|
+
currentEntityContext() {
|
|
200
|
+
return this.entityContextStack.length > 0
|
|
201
|
+
? this.entityContextStack[this.entityContextStack.length - 1]
|
|
202
|
+
: 'Entity';
|
|
203
|
+
}
|
|
188
204
|
constructor(namespace, sourceRanges = []) {
|
|
189
205
|
this.functions = [];
|
|
190
206
|
this.globals = [];
|
|
@@ -198,6 +214,8 @@ class Lowering {
|
|
|
198
214
|
this.timeoutCounter = 0;
|
|
199
215
|
this.intervalCounter = 0;
|
|
200
216
|
this.warnings = [];
|
|
217
|
+
// Entity type context stack for W_IMPOSSIBLE_AS warnings
|
|
218
|
+
this.entityContextStack = [];
|
|
201
219
|
this.varMap = new Map();
|
|
202
220
|
this.lambdaBindings = new Map();
|
|
203
221
|
this.intervalBindings = new Map();
|
|
@@ -938,11 +956,26 @@ class Lowering {
|
|
|
938
956
|
throw new diagnostics_1.DiagnosticError('LoweringError', "'is' checks require an entity selector or entity binding", cond.span ?? stmt.span ?? { line: 0, col: 0 });
|
|
939
957
|
}
|
|
940
958
|
const mcType = ENTITY_TO_MC_TYPE[cond.entityType];
|
|
959
|
+
const thenFnName = `${this.currentFn}/then_${this.foreachCounter++}`;
|
|
941
960
|
if (!mcType) {
|
|
942
|
-
|
|
961
|
+
// Abstract type — check all concrete subtypes
|
|
962
|
+
const subtypes = (0, entity_hierarchy_1.getConcreteSubtypes)(cond.entityType);
|
|
963
|
+
if (subtypes.length === 0) {
|
|
964
|
+
throw new diagnostics_1.DiagnosticError('LoweringError', `Cannot lower entity type check for '${cond.entityType}'`, cond.span ?? stmt.span ?? { line: 0, col: 0 });
|
|
965
|
+
}
|
|
966
|
+
// Use a temp scoreboard variable to OR multiple type checks
|
|
967
|
+
this.builder.emitRaw(`scoreboard players set __is_result rs:temp 0`);
|
|
968
|
+
for (const subtype of subtypes) {
|
|
969
|
+
if (subtype.mcId) {
|
|
970
|
+
this.builder.emitRaw(`execute if entity ${this.appendTypeFilter(selector, subtype.mcId)} run scoreboard players set __is_result rs:temp 1`);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
this.builder.emitRaw(`execute if score __is_result rs:temp matches 1 run function ${this.namespace}:${thenFnName}`);
|
|
974
|
+
}
|
|
975
|
+
else {
|
|
976
|
+
// Concrete type — single check
|
|
977
|
+
this.builder.emitRaw(`execute if entity ${this.appendTypeFilter(selector, mcType)} run function ${this.namespace}:${thenFnName}`);
|
|
943
978
|
}
|
|
944
|
-
const thenFnName = `${this.currentFn}/then_${this.foreachCounter++}`;
|
|
945
|
-
this.builder.emitRaw(`execute if entity ${this.appendTypeFilter(selector, mcType)} run function ${this.namespace}:${thenFnName}`);
|
|
946
979
|
const savedBuilder = this.builder;
|
|
947
980
|
const savedVarMap = new Map(this.varMap);
|
|
948
981
|
const savedBlockPosVars = new Map(this.blockPosVars);
|
|
@@ -1080,11 +1113,19 @@ class Lowering {
|
|
|
1080
1113
|
this.blockPosVars = new Map(savedBlockPosVars);
|
|
1081
1114
|
// In foreach body, the binding maps to @s
|
|
1082
1115
|
this.varMap.set(stmt.binding, '@s');
|
|
1116
|
+
// Track entity context for type narrowing
|
|
1117
|
+
const selectorEntityType = (0, entity_hierarchy_1.getBaseSelectorType)(selector);
|
|
1118
|
+
if (selectorEntityType) {
|
|
1119
|
+
this.entityContextStack.push(selectorEntityType);
|
|
1120
|
+
}
|
|
1083
1121
|
this.builder.startBlock('entry');
|
|
1084
1122
|
this.lowerBlock(stmt.body);
|
|
1085
1123
|
if (!this.builder.isBlockSealed()) {
|
|
1086
1124
|
this.builder.emitReturn();
|
|
1087
1125
|
}
|
|
1126
|
+
if (selectorEntityType) {
|
|
1127
|
+
this.entityContextStack.pop();
|
|
1128
|
+
}
|
|
1088
1129
|
const subFn = this.builder.build(subFnName, [], false);
|
|
1089
1130
|
this.functions.push(subFn);
|
|
1090
1131
|
// Restore
|
|
@@ -1213,6 +1254,17 @@ class Lowering {
|
|
|
1213
1254
|
lowerAsBlockStmt(stmt) {
|
|
1214
1255
|
const selector = this.selectorToString(stmt.selector);
|
|
1215
1256
|
const subFnName = `${this.currentFn}/as_${this.foreachCounter++}`;
|
|
1257
|
+
// Check for impossible type assertions (W_IMPOSSIBLE_AS)
|
|
1258
|
+
const innerType = (0, entity_hierarchy_1.getBaseSelectorType)(selector);
|
|
1259
|
+
const outerType = this.currentEntityContext();
|
|
1260
|
+
if (innerType && outerType !== 'Entity' && innerType !== 'Entity' && !(0, entity_hierarchy_1.areCompatibleTypes)(outerType, innerType)) {
|
|
1261
|
+
this.warnings.push({
|
|
1262
|
+
message: `Impossible type assertion: @s is ${outerType} but as-block targets ${innerType}`,
|
|
1263
|
+
code: 'W_IMPOSSIBLE_AS',
|
|
1264
|
+
line: stmt.span?.line,
|
|
1265
|
+
col: stmt.span?.col,
|
|
1266
|
+
});
|
|
1267
|
+
}
|
|
1216
1268
|
this.builder.emitRaw(`execute as ${selector} run function ${this.namespace}:${subFnName}`);
|
|
1217
1269
|
// Create sub-function
|
|
1218
1270
|
const savedBuilder = this.builder;
|
|
@@ -1221,11 +1273,18 @@ class Lowering {
|
|
|
1221
1273
|
this.builder = new LoweringBuilder();
|
|
1222
1274
|
this.varMap = new Map(savedVarMap);
|
|
1223
1275
|
this.blockPosVars = new Map(savedBlockPosVars);
|
|
1276
|
+
// Track entity context inside as-block
|
|
1277
|
+
if (innerType) {
|
|
1278
|
+
this.entityContextStack.push(innerType);
|
|
1279
|
+
}
|
|
1224
1280
|
this.builder.startBlock('entry');
|
|
1225
1281
|
this.lowerBlock(stmt.body);
|
|
1226
1282
|
if (!this.builder.isBlockSealed()) {
|
|
1227
1283
|
this.builder.emitReturn();
|
|
1228
1284
|
}
|
|
1285
|
+
if (innerType) {
|
|
1286
|
+
this.entityContextStack.pop();
|
|
1287
|
+
}
|
|
1229
1288
|
const subFn = this.builder.build(subFnName, [], false);
|
|
1230
1289
|
this.functions.push(subFn);
|
|
1231
1290
|
this.builder = savedBuilder;
|
package/dist/parser/index.js
CHANGED
|
@@ -32,11 +32,21 @@ const ENTITY_TYPE_NAMES = new Set([
|
|
|
32
32
|
'Creeper',
|
|
33
33
|
'Spider',
|
|
34
34
|
'Enderman',
|
|
35
|
+
'Blaze',
|
|
36
|
+
'Witch',
|
|
37
|
+
'Slime',
|
|
38
|
+
'ZombieVillager',
|
|
39
|
+
'Husk',
|
|
40
|
+
'Drowned',
|
|
41
|
+
'Stray',
|
|
42
|
+
'WitherSkeleton',
|
|
43
|
+
'CaveSpider',
|
|
35
44
|
'Pig',
|
|
36
45
|
'Cow',
|
|
37
46
|
'Sheep',
|
|
38
47
|
'Chicken',
|
|
39
48
|
'Villager',
|
|
49
|
+
'WanderingTrader',
|
|
40
50
|
'ArmorStand',
|
|
41
51
|
'Item',
|
|
42
52
|
'Arrow',
|
|
@@ -384,7 +394,18 @@ class Parser {
|
|
|
384
394
|
}
|
|
385
395
|
else if (token.kind === 'ident') {
|
|
386
396
|
this.advance();
|
|
387
|
-
|
|
397
|
+
if (token.value === 'selector' && this.check('<')) {
|
|
398
|
+
this.advance(); // consume <
|
|
399
|
+
const entityType = this.expect('ident').value;
|
|
400
|
+
this.expect('>');
|
|
401
|
+
type = { kind: 'selector', entityType };
|
|
402
|
+
}
|
|
403
|
+
else if (token.value === 'selector') {
|
|
404
|
+
type = { kind: 'selector' };
|
|
405
|
+
}
|
|
406
|
+
else {
|
|
407
|
+
type = { kind: 'struct', name: token.value };
|
|
408
|
+
}
|
|
388
409
|
}
|
|
389
410
|
else {
|
|
390
411
|
this.error(`Expected type, got '${token.kind}'`);
|