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
package/src/index.ts
CHANGED
|
@@ -35,6 +35,7 @@ export interface CompileOptions {
|
|
|
35
35
|
typeCheck?: boolean
|
|
36
36
|
filePath?: string
|
|
37
37
|
dce?: boolean
|
|
38
|
+
mangle?: boolean
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
export interface CompileResult {
|
|
@@ -45,6 +46,7 @@ export interface CompileResult {
|
|
|
45
46
|
typeErrors?: DiagnosticError[]
|
|
46
47
|
warnings?: Warning[]
|
|
47
48
|
stats?: OptimizationStats
|
|
49
|
+
sourceMap?: Record<string, string>
|
|
48
50
|
}
|
|
49
51
|
|
|
50
52
|
/**
|
|
@@ -59,6 +61,7 @@ export function compile(source: string, options: CompileOptions = {}): CompileRe
|
|
|
59
61
|
const shouldOptimize = options.optimize ?? true
|
|
60
62
|
const shouldTypeCheck = options.typeCheck ?? true
|
|
61
63
|
const shouldRunDce = options.dce ?? shouldOptimize
|
|
64
|
+
const mangle = options.mangle ?? false
|
|
62
65
|
const filePath = options.filePath
|
|
63
66
|
const preprocessed = preprocessSourceWithMetadata(source, { filePath })
|
|
64
67
|
const preprocessedSource = preprocessed.source
|
|
@@ -68,7 +71,7 @@ export function compile(source: string, options: CompileOptions = {}): CompileRe
|
|
|
68
71
|
|
|
69
72
|
// Parsing
|
|
70
73
|
const parsedAst = new Parser(tokens, preprocessedSource, filePath).parse(namespace)
|
|
71
|
-
const dceResult = shouldRunDce ? eliminateDeadCode(parsedAst) : { program: parsedAst, warnings: [] }
|
|
74
|
+
const dceResult = shouldRunDce ? eliminateDeadCode(parsedAst, preprocessed.ranges) : { program: parsedAst, warnings: [] }
|
|
72
75
|
const ast = dceResult.program
|
|
73
76
|
|
|
74
77
|
// Type checking (warn mode - collect errors but don't block)
|
|
@@ -83,7 +86,7 @@ export function compile(source: string, options: CompileOptions = {}): CompileRe
|
|
|
83
86
|
const ir = lowering.lower(ast)
|
|
84
87
|
|
|
85
88
|
let optimizedIR: IRModule = ir
|
|
86
|
-
let generated = generateDatapackWithStats(ir, { optimizeCommands: shouldOptimize })
|
|
89
|
+
let generated = generateDatapackWithStats(ir, { optimizeCommands: shouldOptimize, mangle })
|
|
87
90
|
let optimizationStats: OptimizationStats | undefined
|
|
88
91
|
|
|
89
92
|
if (shouldOptimize) {
|
|
@@ -105,10 +108,10 @@ export function compile(source: string, options: CompileOptions = {}): CompileRe
|
|
|
105
108
|
const copyPropagatedIR: IRModule = { ...ir, functions: copyPropagatedFunctions }
|
|
106
109
|
optimizedIR = { ...ir, functions: deadCodeEliminatedFunctions }
|
|
107
110
|
|
|
108
|
-
const baselineGenerated = generateDatapackWithStats(ir, { optimizeCommands: false })
|
|
109
|
-
const beforeDceGenerated = generateDatapackWithStats(copyPropagatedIR, { optimizeCommands: false })
|
|
110
|
-
const afterDceGenerated = generateDatapackWithStats(optimizedIR, { optimizeCommands: false })
|
|
111
|
-
generated = generateDatapackWithStats(optimizedIR, { optimizeCommands: true })
|
|
111
|
+
const baselineGenerated = generateDatapackWithStats(ir, { optimizeCommands: false, mangle })
|
|
112
|
+
const beforeDceGenerated = generateDatapackWithStats(copyPropagatedIR, { optimizeCommands: false, mangle })
|
|
113
|
+
const afterDceGenerated = generateDatapackWithStats(optimizedIR, { optimizeCommands: false, mangle })
|
|
114
|
+
generated = generateDatapackWithStats(optimizedIR, { optimizeCommands: true, mangle })
|
|
112
115
|
|
|
113
116
|
stats.deadCodeRemoved =
|
|
114
117
|
countMcfunctionCommands(beforeDceGenerated.files) - countMcfunctionCommands(afterDceGenerated.files)
|
|
@@ -124,7 +127,7 @@ export function compile(source: string, options: CompileOptions = {}): CompileRe
|
|
|
124
127
|
optimizationStats = stats
|
|
125
128
|
} else {
|
|
126
129
|
optimizedIR = ir
|
|
127
|
-
generated = generateDatapackWithStats(ir, { optimizeCommands: false })
|
|
130
|
+
generated = generateDatapackWithStats(ir, { optimizeCommands: false, mangle })
|
|
128
131
|
}
|
|
129
132
|
|
|
130
133
|
return {
|
|
@@ -135,6 +138,7 @@ export function compile(source: string, options: CompileOptions = {}): CompileRe
|
|
|
135
138
|
typeErrors,
|
|
136
139
|
warnings: [...dceResult.warnings, ...lowering.warnings],
|
|
137
140
|
stats: optimizationStats,
|
|
141
|
+
sourceMap: generated.sourceMap,
|
|
138
142
|
}
|
|
139
143
|
}
|
|
140
144
|
|
package/src/lowering/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ import type {
|
|
|
17
17
|
import type { GlobalVar } from '../ir/types'
|
|
18
18
|
import * as path from 'path'
|
|
19
19
|
import { EVENT_TYPES, getEventParamSpecs, isEventTypeName } from '../events/types'
|
|
20
|
+
import { getBaseSelectorType, areCompatibleTypes, getConcreteSubtypes } from '../types/entity-hierarchy'
|
|
20
21
|
|
|
21
22
|
// ---------------------------------------------------------------------------
|
|
22
23
|
// Macro-aware builtins (MC 1.20.2+)
|
|
@@ -134,11 +135,21 @@ const ENTITY_TO_MC_TYPE: Partial<Record<EntityTypeName, string>> = {
|
|
|
134
135
|
Creeper: 'minecraft:creeper',
|
|
135
136
|
Spider: 'minecraft:spider',
|
|
136
137
|
Enderman: 'minecraft:enderman',
|
|
138
|
+
Blaze: 'minecraft:blaze',
|
|
139
|
+
Witch: 'minecraft:witch',
|
|
140
|
+
Slime: 'minecraft:slime',
|
|
141
|
+
ZombieVillager: 'minecraft:zombie_villager',
|
|
142
|
+
Husk: 'minecraft:husk',
|
|
143
|
+
Drowned: 'minecraft:drowned',
|
|
144
|
+
Stray: 'minecraft:stray',
|
|
145
|
+
WitherSkeleton: 'minecraft:wither_skeleton',
|
|
146
|
+
CaveSpider: 'minecraft:cave_spider',
|
|
137
147
|
Pig: 'minecraft:pig',
|
|
138
148
|
Cow: 'minecraft:cow',
|
|
139
149
|
Sheep: 'minecraft:sheep',
|
|
140
150
|
Chicken: 'minecraft:chicken',
|
|
141
151
|
Villager: 'minecraft:villager',
|
|
152
|
+
WanderingTrader: 'minecraft:wandering_trader',
|
|
142
153
|
ArmorStand: 'minecraft:armor_stand',
|
|
143
154
|
Item: 'minecraft:item',
|
|
144
155
|
Arrow: 'minecraft:arrow',
|
|
@@ -211,6 +222,15 @@ export class Lowering {
|
|
|
211
222
|
private intervalCounter: number = 0
|
|
212
223
|
readonly warnings: Warning[] = []
|
|
213
224
|
|
|
225
|
+
// Entity type context stack for W_IMPOSSIBLE_AS warnings
|
|
226
|
+
private entityContextStack: string[] = []
|
|
227
|
+
|
|
228
|
+
private currentEntityContext(): string {
|
|
229
|
+
return this.entityContextStack.length > 0
|
|
230
|
+
? this.entityContextStack[this.entityContextStack.length - 1]
|
|
231
|
+
: 'Entity'
|
|
232
|
+
}
|
|
233
|
+
|
|
214
234
|
// Builder state for current function
|
|
215
235
|
private builder!: LoweringBuilder
|
|
216
236
|
private varMap: Map<string, string> = new Map()
|
|
@@ -1056,17 +1076,31 @@ export class Lowering {
|
|
|
1056
1076
|
}
|
|
1057
1077
|
|
|
1058
1078
|
const mcType = ENTITY_TO_MC_TYPE[cond.entityType]
|
|
1079
|
+
const thenFnName = `${this.currentFn}/then_${this.foreachCounter++}`
|
|
1080
|
+
|
|
1059
1081
|
if (!mcType) {
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1082
|
+
// Abstract type — check all concrete subtypes
|
|
1083
|
+
const subtypes = getConcreteSubtypes(cond.entityType)
|
|
1084
|
+
if (subtypes.length === 0) {
|
|
1085
|
+
throw new DiagnosticError(
|
|
1086
|
+
'LoweringError',
|
|
1087
|
+
`Cannot lower entity type check for '${cond.entityType}'`,
|
|
1088
|
+
cond.span ?? stmt.span ?? { line: 0, col: 0 }
|
|
1089
|
+
)
|
|
1090
|
+
}
|
|
1091
|
+
// Use a temp scoreboard variable to OR multiple type checks
|
|
1092
|
+
this.builder.emitRaw(`scoreboard players set __is_result rs:temp 0`)
|
|
1093
|
+
for (const subtype of subtypes) {
|
|
1094
|
+
if (subtype.mcId) {
|
|
1095
|
+
this.builder.emitRaw(`execute if entity ${this.appendTypeFilter(selector, subtype.mcId)} run scoreboard players set __is_result rs:temp 1`)
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
this.builder.emitRaw(`execute if score __is_result rs:temp matches 1 run function ${this.namespace}:${thenFnName}`)
|
|
1099
|
+
} else {
|
|
1100
|
+
// Concrete type — single check
|
|
1101
|
+
this.builder.emitRaw(`execute if entity ${this.appendTypeFilter(selector, mcType)} run function ${this.namespace}:${thenFnName}`)
|
|
1065
1102
|
}
|
|
1066
1103
|
|
|
1067
|
-
const thenFnName = `${this.currentFn}/then_${this.foreachCounter++}`
|
|
1068
|
-
this.builder.emitRaw(`execute if entity ${this.appendTypeFilter(selector, mcType)} run function ${this.namespace}:${thenFnName}`)
|
|
1069
|
-
|
|
1070
1104
|
const savedBuilder = this.builder
|
|
1071
1105
|
const savedVarMap = new Map(this.varMap)
|
|
1072
1106
|
const savedBlockPosVars = new Map(this.blockPosVars)
|
|
@@ -1243,12 +1277,22 @@ export class Lowering {
|
|
|
1243
1277
|
// In foreach body, the binding maps to @s
|
|
1244
1278
|
this.varMap.set(stmt.binding, '@s')
|
|
1245
1279
|
|
|
1280
|
+
// Track entity context for type narrowing
|
|
1281
|
+
const selectorEntityType = getBaseSelectorType(selector)
|
|
1282
|
+
if (selectorEntityType) {
|
|
1283
|
+
this.entityContextStack.push(selectorEntityType)
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1246
1286
|
this.builder.startBlock('entry')
|
|
1247
1287
|
this.lowerBlock(stmt.body)
|
|
1248
1288
|
if (!this.builder.isBlockSealed()) {
|
|
1249
1289
|
this.builder.emitReturn()
|
|
1250
1290
|
}
|
|
1251
1291
|
|
|
1292
|
+
if (selectorEntityType) {
|
|
1293
|
+
this.entityContextStack.pop()
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1252
1296
|
const subFn = this.builder.build(subFnName, [], false)
|
|
1253
1297
|
this.functions.push(subFn)
|
|
1254
1298
|
|
|
@@ -1397,6 +1441,18 @@ export class Lowering {
|
|
|
1397
1441
|
const selector = this.selectorToString(stmt.selector)
|
|
1398
1442
|
const subFnName = `${this.currentFn}/as_${this.foreachCounter++}`
|
|
1399
1443
|
|
|
1444
|
+
// Check for impossible type assertions (W_IMPOSSIBLE_AS)
|
|
1445
|
+
const innerType = getBaseSelectorType(selector)
|
|
1446
|
+
const outerType = this.currentEntityContext()
|
|
1447
|
+
if (innerType && outerType !== 'Entity' && innerType !== 'Entity' && !areCompatibleTypes(outerType, innerType)) {
|
|
1448
|
+
this.warnings.push({
|
|
1449
|
+
message: `Impossible type assertion: @s is ${outerType} but as-block targets ${innerType}`,
|
|
1450
|
+
code: 'W_IMPOSSIBLE_AS',
|
|
1451
|
+
line: stmt.span?.line,
|
|
1452
|
+
col: stmt.span?.col,
|
|
1453
|
+
})
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1400
1456
|
this.builder.emitRaw(`execute as ${selector} run function ${this.namespace}:${subFnName}`)
|
|
1401
1457
|
|
|
1402
1458
|
// Create sub-function
|
|
@@ -1408,12 +1464,21 @@ export class Lowering {
|
|
|
1408
1464
|
this.varMap = new Map(savedVarMap)
|
|
1409
1465
|
this.blockPosVars = new Map(savedBlockPosVars)
|
|
1410
1466
|
|
|
1467
|
+
// Track entity context inside as-block
|
|
1468
|
+
if (innerType) {
|
|
1469
|
+
this.entityContextStack.push(innerType)
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1411
1472
|
this.builder.startBlock('entry')
|
|
1412
1473
|
this.lowerBlock(stmt.body)
|
|
1413
1474
|
if (!this.builder.isBlockSealed()) {
|
|
1414
1475
|
this.builder.emitReturn()
|
|
1415
1476
|
}
|
|
1416
1477
|
|
|
1478
|
+
if (innerType) {
|
|
1479
|
+
this.entityContextStack.pop()
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1417
1482
|
const subFn = this.builder.build(subFnName, [], false)
|
|
1418
1483
|
this.functions.push(subFn)
|
|
1419
1484
|
|
package/src/optimizer/dce.ts
CHANGED
|
@@ -72,6 +72,7 @@ export interface DCEWarning {
|
|
|
72
72
|
code: string
|
|
73
73
|
line?: number
|
|
74
74
|
col?: number
|
|
75
|
+
filePath?: string
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
export class DeadCodeEliminator {
|
|
@@ -639,8 +640,23 @@ export class DeadCodeEliminator {
|
|
|
639
640
|
}
|
|
640
641
|
}
|
|
641
642
|
|
|
642
|
-
export function eliminateDeadCode(
|
|
643
|
+
export function eliminateDeadCode(
|
|
644
|
+
program: Program,
|
|
645
|
+
sourceRanges?: import('../compile').SourceRange[]
|
|
646
|
+
): { program: Program; warnings: DCEWarning[] } {
|
|
643
647
|
const eliminator = new DeadCodeEliminator()
|
|
644
648
|
const result = eliminator.eliminate(program)
|
|
645
|
-
|
|
649
|
+
let warnings = eliminator.warnings
|
|
650
|
+
|
|
651
|
+
// Resolve combined-source line numbers back to original file + line
|
|
652
|
+
if (sourceRanges && sourceRanges.length > 0) {
|
|
653
|
+
const { resolveSourceLine } = require('../compile') as typeof import('../compile')
|
|
654
|
+
warnings = warnings.map(w => {
|
|
655
|
+
if (w.line == null) return w
|
|
656
|
+
const resolved = resolveSourceLine(w.line, sourceRanges)
|
|
657
|
+
return { ...w, line: resolved.line, filePath: resolved.filePath ?? w.filePath }
|
|
658
|
+
})
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
return { program: result, warnings }
|
|
646
662
|
}
|
package/src/parser/index.ts
CHANGED
|
@@ -41,11 +41,21 @@ const ENTITY_TYPE_NAMES = new Set<EntityTypeName>([
|
|
|
41
41
|
'Creeper',
|
|
42
42
|
'Spider',
|
|
43
43
|
'Enderman',
|
|
44
|
+
'Blaze',
|
|
45
|
+
'Witch',
|
|
46
|
+
'Slime',
|
|
47
|
+
'ZombieVillager',
|
|
48
|
+
'Husk',
|
|
49
|
+
'Drowned',
|
|
50
|
+
'Stray',
|
|
51
|
+
'WitherSkeleton',
|
|
52
|
+
'CaveSpider',
|
|
44
53
|
'Pig',
|
|
45
54
|
'Cow',
|
|
46
55
|
'Sheep',
|
|
47
56
|
'Chicken',
|
|
48
57
|
'Villager',
|
|
58
|
+
'WanderingTrader',
|
|
49
59
|
'ArmorStand',
|
|
50
60
|
'Item',
|
|
51
61
|
'Arrow',
|
|
@@ -446,7 +456,16 @@ export class Parser {
|
|
|
446
456
|
type = { kind: 'named', name: token.kind }
|
|
447
457
|
} else if (token.kind === 'ident') {
|
|
448
458
|
this.advance()
|
|
449
|
-
|
|
459
|
+
if (token.value === 'selector' && this.check('<')) {
|
|
460
|
+
this.advance() // consume <
|
|
461
|
+
const entityType = this.expect('ident').value
|
|
462
|
+
this.expect('>')
|
|
463
|
+
type = { kind: 'selector', entityType }
|
|
464
|
+
} else if (token.value === 'selector') {
|
|
465
|
+
type = { kind: 'selector' }
|
|
466
|
+
} else {
|
|
467
|
+
type = { kind: 'struct', name: token.value }
|
|
468
|
+
}
|
|
450
469
|
} else {
|
|
451
470
|
this.error(`Expected type, got '${token.kind}'`)
|
|
452
471
|
}
|
package/src/typechecker/index.ts
CHANGED
|
@@ -31,11 +31,21 @@ const ENTITY_HIERARCHY: Record<EntityTypeName, EntityTypeName | null> = {
|
|
|
31
31
|
'Creeper': 'HostileMob',
|
|
32
32
|
'Spider': 'HostileMob',
|
|
33
33
|
'Enderman': 'HostileMob',
|
|
34
|
+
'Blaze': 'HostileMob',
|
|
35
|
+
'Witch': 'HostileMob',
|
|
36
|
+
'Slime': 'HostileMob',
|
|
37
|
+
'ZombieVillager': 'HostileMob',
|
|
38
|
+
'Husk': 'HostileMob',
|
|
39
|
+
'Drowned': 'HostileMob',
|
|
40
|
+
'Stray': 'HostileMob',
|
|
41
|
+
'WitherSkeleton': 'HostileMob',
|
|
42
|
+
'CaveSpider': 'HostileMob',
|
|
34
43
|
'Pig': 'PassiveMob',
|
|
35
44
|
'Cow': 'PassiveMob',
|
|
36
45
|
'Sheep': 'PassiveMob',
|
|
37
46
|
'Chicken': 'PassiveMob',
|
|
38
47
|
'Villager': 'PassiveMob',
|
|
48
|
+
'WanderingTrader': 'PassiveMob',
|
|
39
49
|
'ArmorStand': 'entity',
|
|
40
50
|
'Item': 'entity',
|
|
41
51
|
'Arrow': 'entity',
|
|
@@ -53,6 +63,24 @@ const MC_TYPE_TO_ENTITY: Record<string, EntityTypeName> = {
|
|
|
53
63
|
'minecraft:spider': 'Spider',
|
|
54
64
|
'enderman': 'Enderman',
|
|
55
65
|
'minecraft:enderman': 'Enderman',
|
|
66
|
+
'blaze': 'Blaze',
|
|
67
|
+
'minecraft:blaze': 'Blaze',
|
|
68
|
+
'witch': 'Witch',
|
|
69
|
+
'minecraft:witch': 'Witch',
|
|
70
|
+
'slime': 'Slime',
|
|
71
|
+
'minecraft:slime': 'Slime',
|
|
72
|
+
'zombie_villager': 'ZombieVillager',
|
|
73
|
+
'minecraft:zombie_villager': 'ZombieVillager',
|
|
74
|
+
'husk': 'Husk',
|
|
75
|
+
'minecraft:husk': 'Husk',
|
|
76
|
+
'drowned': 'Drowned',
|
|
77
|
+
'minecraft:drowned': 'Drowned',
|
|
78
|
+
'stray': 'Stray',
|
|
79
|
+
'minecraft:stray': 'Stray',
|
|
80
|
+
'wither_skeleton': 'WitherSkeleton',
|
|
81
|
+
'minecraft:wither_skeleton': 'WitherSkeleton',
|
|
82
|
+
'cave_spider': 'CaveSpider',
|
|
83
|
+
'minecraft:cave_spider': 'CaveSpider',
|
|
56
84
|
'pig': 'Pig',
|
|
57
85
|
'minecraft:pig': 'Pig',
|
|
58
86
|
'cow': 'Cow',
|
|
@@ -63,6 +91,8 @@ const MC_TYPE_TO_ENTITY: Record<string, EntityTypeName> = {
|
|
|
63
91
|
'minecraft:chicken': 'Chicken',
|
|
64
92
|
'villager': 'Villager',
|
|
65
93
|
'minecraft:villager': 'Villager',
|
|
94
|
+
'wandering_trader': 'WanderingTrader',
|
|
95
|
+
'minecraft:wandering_trader': 'WanderingTrader',
|
|
66
96
|
'armor_stand': 'ArmorStand',
|
|
67
97
|
'minecraft:armor_stand': 'ArmorStand',
|
|
68
98
|
'item': 'Item',
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entity Type Hierarchy
|
|
3
|
+
*
|
|
4
|
+
* Closed inheritance tree mapping to Minecraft's entity registry.
|
|
5
|
+
* Used for type narrowing (is checks), selector<T> annotations,
|
|
6
|
+
* and W_IMPOSSIBLE_AS warnings.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export interface EntityTypeNode {
|
|
10
|
+
name: string
|
|
11
|
+
mcId: string | null // null for abstract types
|
|
12
|
+
abstract: boolean
|
|
13
|
+
parent: string | null // null for root "Entity"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const ENTITY_TYPES: EntityTypeNode[] = [
|
|
17
|
+
// Root
|
|
18
|
+
{ name: 'Entity', mcId: null, abstract: true, parent: null },
|
|
19
|
+
|
|
20
|
+
// Direct children of Entity
|
|
21
|
+
{ name: 'Player', mcId: 'minecraft:player', abstract: false, parent: 'Entity' },
|
|
22
|
+
{ name: 'ArmorStand', mcId: 'minecraft:armor_stand', abstract: false, parent: 'Entity' },
|
|
23
|
+
{ name: 'Item', mcId: 'minecraft:item', abstract: false, parent: 'Entity' },
|
|
24
|
+
{ name: 'Arrow', mcId: 'minecraft:arrow', abstract: false, parent: 'Entity' },
|
|
25
|
+
|
|
26
|
+
// Mob hierarchy
|
|
27
|
+
{ name: 'Mob', mcId: null, abstract: true, parent: 'Entity' },
|
|
28
|
+
|
|
29
|
+
// Hostile mobs
|
|
30
|
+
{ name: 'HostileMob', mcId: null, abstract: true, parent: 'Mob' },
|
|
31
|
+
{ name: 'Zombie', mcId: 'minecraft:zombie', abstract: false, parent: 'HostileMob' },
|
|
32
|
+
{ name: 'Skeleton', mcId: 'minecraft:skeleton', abstract: false, parent: 'HostileMob' },
|
|
33
|
+
{ name: 'Creeper', mcId: 'minecraft:creeper', abstract: false, parent: 'HostileMob' },
|
|
34
|
+
{ name: 'Spider', mcId: 'minecraft:spider', abstract: false, parent: 'HostileMob' },
|
|
35
|
+
{ name: 'Enderman', mcId: 'minecraft:enderman', abstract: false, parent: 'HostileMob' },
|
|
36
|
+
{ name: 'Blaze', mcId: 'minecraft:blaze', abstract: false, parent: 'HostileMob' },
|
|
37
|
+
{ name: 'Witch', mcId: 'minecraft:witch', abstract: false, parent: 'HostileMob' },
|
|
38
|
+
{ name: 'Slime', mcId: 'minecraft:slime', abstract: false, parent: 'HostileMob' },
|
|
39
|
+
{ name: 'ZombieVillager', mcId: 'minecraft:zombie_villager', abstract: false, parent: 'HostileMob' },
|
|
40
|
+
{ name: 'Husk', mcId: 'minecraft:husk', abstract: false, parent: 'HostileMob' },
|
|
41
|
+
{ name: 'Drowned', mcId: 'minecraft:drowned', abstract: false, parent: 'HostileMob' },
|
|
42
|
+
{ name: 'Stray', mcId: 'minecraft:stray', abstract: false, parent: 'HostileMob' },
|
|
43
|
+
{ name: 'WitherSkeleton', mcId: 'minecraft:wither_skeleton', abstract: false, parent: 'HostileMob' },
|
|
44
|
+
{ name: 'CaveSpider', mcId: 'minecraft:cave_spider', abstract: false, parent: 'HostileMob' },
|
|
45
|
+
|
|
46
|
+
// Passive mobs
|
|
47
|
+
{ name: 'PassiveMob', mcId: null, abstract: true, parent: 'Mob' },
|
|
48
|
+
{ name: 'Pig', mcId: 'minecraft:pig', abstract: false, parent: 'PassiveMob' },
|
|
49
|
+
{ name: 'Cow', mcId: 'minecraft:cow', abstract: false, parent: 'PassiveMob' },
|
|
50
|
+
{ name: 'Sheep', mcId: 'minecraft:sheep', abstract: false, parent: 'PassiveMob' },
|
|
51
|
+
{ name: 'Chicken', mcId: 'minecraft:chicken', abstract: false, parent: 'PassiveMob' },
|
|
52
|
+
{ name: 'Villager', mcId: 'minecraft:villager', abstract: false, parent: 'PassiveMob' },
|
|
53
|
+
{ name: 'WanderingTrader', mcId: 'minecraft:wandering_trader', abstract: false, parent: 'PassiveMob' },
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
/** Map from lowercase name → EntityTypeNode */
|
|
57
|
+
export const ENTITY_TYPE_MAP: Map<string, EntityTypeNode> = new Map(
|
|
58
|
+
ENTITY_TYPES.map(t => [t.name.toLowerCase(), t])
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
/** Map from mcId (without namespace) → EntityTypeNode */
|
|
62
|
+
export const ENTITY_TYPE_BY_MCID: Map<string, EntityTypeNode> = new Map(
|
|
63
|
+
ENTITY_TYPES
|
|
64
|
+
.filter(t => t.mcId !== null)
|
|
65
|
+
.map(t => [t.mcId!.replace('minecraft:', ''), t])
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
/** Check if typeA is a subtype of typeB (recursive ancestry) */
|
|
69
|
+
export function isSubtype(typeA: string, typeB: string): boolean {
|
|
70
|
+
if (typeA === typeB) return true
|
|
71
|
+
const node = ENTITY_TYPE_MAP.get(typeA.toLowerCase())
|
|
72
|
+
if (!node || !node.parent) return false
|
|
73
|
+
return isSubtype(node.parent, typeB)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** True if one type is a subtype of the other (in either direction) */
|
|
77
|
+
export function areCompatibleTypes(outerType: string, innerType: string): boolean {
|
|
78
|
+
return isSubtype(outerType, innerType) || isSubtype(innerType, outerType)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Get all non-abstract leaf types under a given node */
|
|
82
|
+
export function getConcreteSubtypes(typeName: string): EntityTypeNode[] {
|
|
83
|
+
const results: EntityTypeNode[] = []
|
|
84
|
+
for (const node of ENTITY_TYPES) {
|
|
85
|
+
if (!node.abstract && isSubtype(node.name, typeName)) {
|
|
86
|
+
results.push(node)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return results
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Parse "type=zombie" from a selector string, return the entity type name or null */
|
|
93
|
+
export function getSelectorEntityType(selector: string): string | null {
|
|
94
|
+
const match = selector.match(/type=(?:minecraft:)?([a-z_]+)/)
|
|
95
|
+
if (!match) return null
|
|
96
|
+
const mcId = match[1]
|
|
97
|
+
const node = ENTITY_TYPE_BY_MCID.get(mcId)
|
|
98
|
+
return node ? node.name : null
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Determine the base entity type from a selector kind:
|
|
102
|
+
* @a/@p/@r → "Player", @e → "Entity" or from type filter, @s → null */
|
|
103
|
+
export function getBaseSelectorType(selector: string): string | null {
|
|
104
|
+
const trimmed = selector.trim()
|
|
105
|
+
|
|
106
|
+
// Check for type filter first (works for any selector)
|
|
107
|
+
const typeFromFilter = getSelectorEntityType(trimmed)
|
|
108
|
+
|
|
109
|
+
if (trimmed.startsWith('@a') || trimmed.startsWith('@p') || trimmed.startsWith('@r')) {
|
|
110
|
+
return typeFromFilter ?? 'Player'
|
|
111
|
+
}
|
|
112
|
+
if (trimmed.startsWith('@e')) {
|
|
113
|
+
return typeFromFilter ?? 'Entity'
|
|
114
|
+
}
|
|
115
|
+
// @s — context-dependent, we don't know unless there's a type filter
|
|
116
|
+
if (trimmed.startsWith('@s')) {
|
|
117
|
+
return typeFromFilter ?? null
|
|
118
|
+
}
|
|
119
|
+
return null
|
|
120
|
+
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
# block: merge_2
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
# block: entry
|
|
2
|
-
execute store result score $t0 rs run scoreboard players get @s kills
|
|
3
|
-
scoreboard players operation $kills rs = $t0 rs
|
|
4
|
-
scoreboard players set $t1 rs 0
|
|
5
|
-
execute if score $t0 rs > $top_kills rs run scoreboard players set $t1 rs 1
|
|
6
|
-
execute if score $t1 rs matches 1.. run function arena:announce_leaders/foreach_0/then_0
|
|
7
|
-
execute if score $t1 rs matches ..0 run function arena:announce_leaders/foreach_0/merge_2
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
# block: merge_2
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
# block: entry
|
|
2
|
-
execute store result score $t0 rs run scoreboard players get @s kills
|
|
3
|
-
scoreboard players set $t1 rs 0
|
|
4
|
-
execute if score $t0 rs = $top_kills rs run scoreboard players set $t1 rs 1
|
|
5
|
-
execute if score $t1 rs matches 1.. run function arena:announce_leaders/foreach_1/then_0
|
|
6
|
-
execute if score $t1 rs matches ..0 run function arena:announce_leaders/foreach_1/merge_2
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
# block: merge_2
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
# block: entry
|
|
2
|
-
execute as @a run function arena:announce_leaders/foreach_0
|
|
3
|
-
scoreboard players set $t0 rs 0
|
|
4
|
-
execute if score $const_0 rs > $const_0 rs run scoreboard players set $t0 rs 1
|
|
5
|
-
execute if score $t0 rs matches 1.. run function arena:announce_leaders/then_0
|
|
6
|
-
execute if score $t0 rs matches ..0 run function arena:announce_leaders/else_1
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
# block: merge_2
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# block: entry
|
|
2
|
-
execute store result score $t0 rs run scoreboard players get arena ticks
|
|
3
|
-
scoreboard players operation $t1 rs = $t0 rs
|
|
4
|
-
scoreboard players operation $t1 rs += $const_1 rs
|
|
5
|
-
execute store result score arena ticks run scoreboard players get $t2 rs
|
|
6
|
-
scoreboard players operation $t3 rs = $t1 rs
|
|
7
|
-
scoreboard players operation $t3 rs %= $const_200 rs
|
|
8
|
-
scoreboard players set $t4 rs 0
|
|
9
|
-
execute if score $t3 rs = $const_0 rs run scoreboard players set $t4 rs 1
|
|
10
|
-
execute if score $t4 rs matches 1.. run function arena:arena_tick/then_0
|
|
11
|
-
execute if score $t4 rs matches ..0 run function arena:arena_tick/merge_2
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
# block: merge_2
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
# block: entry
|
|
2
|
-
execute store result score $t0 rs run scoreboard players get counter ticks
|
|
3
|
-
scoreboard players operation $t1 rs = $t0 rs
|
|
4
|
-
scoreboard players operation $t1 rs += $const_1 rs
|
|
5
|
-
execute store result score counter ticks run scoreboard players get $t2 rs
|
|
6
|
-
scoreboard players operation $t3 rs = $t1 rs
|
|
7
|
-
scoreboard players operation $t3 rs %= $const_100 rs
|
|
8
|
-
scoreboard players set $t4 rs 0
|
|
9
|
-
execute if score $t3 rs = $const_0 rs run scoreboard players set $t4 rs 1
|
|
10
|
-
execute if score $t4 rs matches 1.. run function counter:counter_tick/then_0
|
|
11
|
-
execute if score $t4 rs matches ..0 run function counter:counter_tick/merge_2
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# RedScript runtime init
|
|
2
|
-
scoreboard objectives add rs dummy
|
|
3
|
-
scoreboard objectives add quiz_start trigger
|
|
4
|
-
scoreboard players enable @a quiz_start
|
|
5
|
-
scoreboard objectives add quiz_a trigger
|
|
6
|
-
scoreboard players enable @a quiz_a
|
|
7
|
-
scoreboard objectives add quiz_b trigger
|
|
8
|
-
scoreboard players enable @a quiz_b
|
|
9
|
-
scoreboard objectives add quiz_c trigger
|
|
10
|
-
scoreboard players enable @a quiz_c
|
|
11
|
-
scoreboard players set $const_1 rs 1
|
|
12
|
-
scoreboard players set $const_2 rs 2
|
|
13
|
-
scoreboard players set $const_3 rs 3
|
|
14
|
-
scoreboard players set $const_1 rs 1
|
|
15
|
-
scoreboard players set $const_2 rs 2
|
|
16
|
-
scoreboard players set $const_3 rs 3
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
# RedScript tick dispatcher
|
|
2
|
-
# Trigger checks
|
|
3
|
-
execute as @a[scores={quiz_start=1..}] run function quiz:__trigger_quiz_start_dispatch
|
|
4
|
-
execute as @a[scores={quiz_a=1..}] run function quiz:__trigger_quiz_a_dispatch
|
|
5
|
-
execute as @a[scores={quiz_b=1..}] run function quiz:__trigger_quiz_b_dispatch
|
|
6
|
-
execute as @a[scores={quiz_c=1..}] run function quiz:__trigger_quiz_c_dispatch
|