redscript-mc 1.1.0 → 1.2.0
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/CHANGELOG.md +54 -0
- package/dist/__tests__/cli.test.js +138 -0
- package/dist/__tests__/codegen.test.js +25 -0
- package/dist/__tests__/e2e.test.js +190 -12
- package/dist/__tests__/lexer.test.js +12 -2
- package/dist/__tests__/lowering.test.js +164 -9
- package/dist/__tests__/mc-integration.test.js +145 -51
- package/dist/__tests__/optimizer-advanced.test.js +3 -3
- package/dist/__tests__/parser.test.js +80 -0
- package/dist/__tests__/runtime.test.js +8 -8
- package/dist/__tests__/typechecker.test.js +158 -0
- package/dist/ast/types.d.ts +20 -1
- package/dist/codegen/mcfunction/index.js +30 -1
- package/dist/codegen/structure/index.js +25 -0
- package/dist/compile.d.ts +10 -0
- package/dist/compile.js +36 -5
- package/dist/events/types.d.ts +35 -0
- package/dist/events/types.js +59 -0
- package/dist/index.js +3 -2
- package/dist/ir/types.d.ts +4 -0
- package/dist/lexer/index.d.ts +1 -1
- package/dist/lexer/index.js +2 -0
- package/dist/lowering/index.d.ts +32 -1
- package/dist/lowering/index.js +439 -15
- package/dist/parser/index.d.ts +2 -0
- package/dist/parser/index.js +79 -10
- package/dist/typechecker/index.d.ts +17 -0
- package/dist/typechecker/index.js +343 -17
- package/docs/ENTITY_TYPE_SYSTEM.md +242 -0
- package/editors/vscode/CHANGELOG.md +9 -0
- package/editors/vscode/out/extension.js +1144 -72
- package/editors/vscode/package-lock.json +2 -2
- package/editors/vscode/package.json +1 -1
- package/package.json +1 -1
- package/src/__tests__/cli.test.ts +166 -0
- package/src/__tests__/codegen.test.ts +27 -0
- package/src/__tests__/e2e.test.ts +201 -12
- package/src/__tests__/fixtures/event-test.mcrs +13 -0
- package/src/__tests__/fixtures/impl-test.mcrs +46 -0
- package/src/__tests__/fixtures/interval-test.mcrs +11 -0
- package/src/__tests__/fixtures/is-check-test.mcrs +20 -0
- package/src/__tests__/fixtures/timeout-test.mcrs +7 -0
- package/src/__tests__/lexer.test.ts +14 -2
- package/src/__tests__/lowering.test.ts +178 -9
- package/src/__tests__/mc-integration.test.ts +166 -51
- package/src/__tests__/optimizer-advanced.test.ts +3 -3
- package/src/__tests__/parser.test.ts +91 -5
- package/src/__tests__/runtime.test.ts +8 -8
- package/src/__tests__/typechecker.test.ts +171 -0
- package/src/ast/types.ts +25 -1
- package/src/codegen/mcfunction/index.ts +31 -1
- package/src/codegen/structure/index.ts +27 -0
- package/src/compile.ts +54 -6
- package/src/events/types.ts +69 -0
- package/src/index.ts +4 -3
- package/src/ir/types.ts +4 -0
- package/src/lexer/index.ts +3 -1
- package/src/lowering/index.ts +528 -16
- package/src/parser/index.ts +90 -12
- package/src/stdlib/README.md +34 -4
- package/src/stdlib/tags.mcrs +951 -0
- package/src/stdlib/timer.mcrs +54 -33
- package/src/typechecker/index.ts +404 -18
package/src/lowering/index.ts
CHANGED
|
@@ -9,11 +9,14 @@ import type { IRBuilder } from '../ir/builder'
|
|
|
9
9
|
import { buildModule } from '../ir/builder'
|
|
10
10
|
import type { IRFunction, IRModule, Operand, BinOp, CmpOp } from '../ir/types'
|
|
11
11
|
import { DiagnosticError } from '../diagnostics'
|
|
12
|
+
import type { SourceRange } from '../compile'
|
|
12
13
|
import type {
|
|
13
14
|
Block, ConstDecl, Decorator, EntitySelector, Expr, FnDecl, GlobalDecl, Program, RangeExpr, Span, Stmt,
|
|
14
|
-
StructDecl, TypeNode, ExecuteSubcommand, BlockPosExpr, CoordComponent
|
|
15
|
+
StructDecl, TypeNode, ExecuteSubcommand, BlockPosExpr, CoordComponent, EntityTypeName
|
|
15
16
|
} from '../ast/types'
|
|
16
17
|
import type { GlobalVar } from '../ir/types'
|
|
18
|
+
import * as path from 'path'
|
|
19
|
+
import { EVENT_TYPES, getEventParamSpecs, isEventTypeName } from '../events/types'
|
|
17
20
|
|
|
18
21
|
// ---------------------------------------------------------------------------
|
|
19
22
|
// Builtin Functions
|
|
@@ -88,6 +91,9 @@ const BUILTINS: Record<string, (args: string[]) => string | null> = {
|
|
|
88
91
|
set_contains: () => null, // Special handling (returns 1/0)
|
|
89
92
|
set_remove: () => null, // Special handling
|
|
90
93
|
set_clear: () => null, // Special handling
|
|
94
|
+
setTimeout: () => null, // Special handling
|
|
95
|
+
setInterval: () => null, // Special handling
|
|
96
|
+
clearInterval: () => null, // Special handling
|
|
91
97
|
}
|
|
92
98
|
|
|
93
99
|
export interface Warning {
|
|
@@ -97,6 +103,12 @@ export interface Warning {
|
|
|
97
103
|
col?: number
|
|
98
104
|
}
|
|
99
105
|
|
|
106
|
+
interface StdlibCallSiteContext {
|
|
107
|
+
filePath?: string
|
|
108
|
+
line: number
|
|
109
|
+
col: number
|
|
110
|
+
}
|
|
111
|
+
|
|
100
112
|
function getSpan(node: unknown): Span | undefined {
|
|
101
113
|
return (node as { span?: Span } | undefined)?.span
|
|
102
114
|
}
|
|
@@ -104,6 +116,23 @@ function getSpan(node: unknown): Span | undefined {
|
|
|
104
116
|
const NAMESPACED_ENTITY_TYPE_RE = /^[a-z0-9_.-]+:[a-z0-9_./-]+$/
|
|
105
117
|
const BARE_ENTITY_TYPE_RE = /^[a-z0-9_./-]+$/
|
|
106
118
|
|
|
119
|
+
const ENTITY_TO_MC_TYPE: Partial<Record<EntityTypeName, string>> = {
|
|
120
|
+
Player: 'player',
|
|
121
|
+
Zombie: 'zombie',
|
|
122
|
+
Skeleton: 'skeleton',
|
|
123
|
+
Creeper: 'creeper',
|
|
124
|
+
Spider: 'spider',
|
|
125
|
+
Enderman: 'enderman',
|
|
126
|
+
Pig: 'pig',
|
|
127
|
+
Cow: 'cow',
|
|
128
|
+
Sheep: 'sheep',
|
|
129
|
+
Chicken: 'chicken',
|
|
130
|
+
Villager: 'villager',
|
|
131
|
+
ArmorStand: 'armor_stand',
|
|
132
|
+
Item: 'item',
|
|
133
|
+
Arrow: 'arrow',
|
|
134
|
+
}
|
|
135
|
+
|
|
107
136
|
function normalizeSelector(selector: string, warnings: Warning[]): string {
|
|
108
137
|
return selector.replace(/type=([^,\]]+)/g, (match, entityType) => {
|
|
109
138
|
const trimmed = entityType.trim()
|
|
@@ -156,20 +185,27 @@ function emitBlockPos(pos: BlockPosExpr): string {
|
|
|
156
185
|
|
|
157
186
|
export class Lowering {
|
|
158
187
|
private namespace: string
|
|
188
|
+
private readonly sourceRanges: SourceRange[]
|
|
159
189
|
private functions: IRFunction[] = []
|
|
160
190
|
private globals: GlobalVar[] = []
|
|
161
191
|
private globalNames: Map<string, { mutable: boolean }> = new Map()
|
|
162
192
|
private fnDecls: Map<string, FnDecl> = new Map()
|
|
193
|
+
private implMethods: Map<string, Map<string, { fn: FnDecl; loweredName: string }>> = new Map()
|
|
163
194
|
private specializedFunctions: Map<string, string> = new Map()
|
|
164
195
|
private currentFn: string = ''
|
|
196
|
+
private currentStdlibCallSite?: StdlibCallSiteContext
|
|
165
197
|
private foreachCounter: number = 0
|
|
166
198
|
private lambdaCounter: number = 0
|
|
199
|
+
private timeoutCounter: number = 0
|
|
200
|
+
private intervalCounter: number = 0
|
|
167
201
|
readonly warnings: Warning[] = []
|
|
168
202
|
|
|
169
203
|
// Builder state for current function
|
|
170
204
|
private builder!: LoweringBuilder
|
|
171
205
|
private varMap: Map<string, string> = new Map()
|
|
172
206
|
private lambdaBindings: Map<string, string> = new Map()
|
|
207
|
+
private intervalBindings: Map<string, string> = new Map()
|
|
208
|
+
private intervalFunctions: Map<number, string> = new Map()
|
|
173
209
|
private currentCallbackBindings: Map<string, string> = new Map()
|
|
174
210
|
private currentContext: { binding?: string } = {}
|
|
175
211
|
private blockPosVars: Map<string, BlockPosExpr> = new Map()
|
|
@@ -187,8 +223,9 @@ export class Lowering {
|
|
|
187
223
|
// World object counter for unique tags
|
|
188
224
|
private worldObjCounter: number = 0
|
|
189
225
|
|
|
190
|
-
constructor(namespace: string) {
|
|
226
|
+
constructor(namespace: string, sourceRanges: SourceRange[] = []) {
|
|
191
227
|
this.namespace = namespace
|
|
228
|
+
this.sourceRanges = sourceRanges
|
|
192
229
|
LoweringBuilder.resetTempCounter()
|
|
193
230
|
}
|
|
194
231
|
|
|
@@ -230,10 +267,31 @@ export class Lowering {
|
|
|
230
267
|
this.functionDefaults.set(fn.name, fn.params.map(param => param.default))
|
|
231
268
|
}
|
|
232
269
|
|
|
270
|
+
for (const implBlock of program.implBlocks ?? []) {
|
|
271
|
+
let methods = this.implMethods.get(implBlock.typeName)
|
|
272
|
+
if (!methods) {
|
|
273
|
+
methods = new Map()
|
|
274
|
+
this.implMethods.set(implBlock.typeName, methods)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
for (const method of implBlock.methods) {
|
|
278
|
+
const loweredName = `${implBlock.typeName}_${method.name}`
|
|
279
|
+
methods.set(method.name, { fn: method, loweredName })
|
|
280
|
+
this.fnDecls.set(loweredName, method)
|
|
281
|
+
this.functionDefaults.set(loweredName, method.params.map(param => param.default))
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
233
285
|
for (const fn of program.declarations) {
|
|
234
286
|
this.lowerFn(fn)
|
|
235
287
|
}
|
|
236
288
|
|
|
289
|
+
for (const implBlock of program.implBlocks ?? []) {
|
|
290
|
+
for (const method of implBlock.methods) {
|
|
291
|
+
this.lowerFn(method, { name: `${implBlock.typeName}_${method.name}` })
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
237
295
|
return buildModule(this.namespace, this.functions, this.globals)
|
|
238
296
|
}
|
|
239
297
|
|
|
@@ -246,16 +304,25 @@ export class Lowering {
|
|
|
246
304
|
options: {
|
|
247
305
|
name?: string
|
|
248
306
|
callbackBindings?: Map<string, string>
|
|
307
|
+
stdlibCallSite?: StdlibCallSiteContext
|
|
249
308
|
} = {}
|
|
250
309
|
): void {
|
|
251
310
|
const loweredName = options.name ?? fn.name
|
|
252
311
|
const callbackBindings = options.callbackBindings ?? new Map<string, string>()
|
|
253
|
-
const
|
|
312
|
+
const stdlibCallSite = options.stdlibCallSite
|
|
313
|
+
const staticEventDec = fn.decorators.find(d => d.name === 'on')
|
|
314
|
+
const eventType = staticEventDec?.args?.eventType
|
|
315
|
+
const eventParamSpecs = eventType && isEventTypeName(eventType) ? getEventParamSpecs(eventType) : []
|
|
316
|
+
const runtimeParams = staticEventDec
|
|
317
|
+
? []
|
|
318
|
+
: fn.params.filter(param => !callbackBindings.has(param.name))
|
|
254
319
|
|
|
255
320
|
this.currentFn = loweredName
|
|
321
|
+
this.currentStdlibCallSite = stdlibCallSite
|
|
256
322
|
this.foreachCounter = 0
|
|
257
323
|
this.varMap = new Map()
|
|
258
324
|
this.lambdaBindings = new Map()
|
|
325
|
+
this.intervalBindings = new Map()
|
|
259
326
|
this.currentCallbackBindings = new Map(callbackBindings)
|
|
260
327
|
this.currentContext = {}
|
|
261
328
|
this.blockPosVars = new Map()
|
|
@@ -263,10 +330,31 @@ export class Lowering {
|
|
|
263
330
|
this.builder = new LoweringBuilder()
|
|
264
331
|
|
|
265
332
|
// Map parameters
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
333
|
+
if (staticEventDec) {
|
|
334
|
+
for (let i = 0; i < fn.params.length; i++) {
|
|
335
|
+
const param = fn.params[i]
|
|
336
|
+
const expected = eventParamSpecs[i]
|
|
337
|
+
const normalizedType = this.normalizeType(param.type)
|
|
338
|
+
this.varTypes.set(param.name, normalizedType)
|
|
339
|
+
|
|
340
|
+
if (expected?.type.kind === 'entity') {
|
|
341
|
+
this.varMap.set(param.name, '@s')
|
|
342
|
+
continue
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (expected?.type.kind === 'named' && expected.type.name === 'string') {
|
|
346
|
+
this.stringValues.set(param.name, '')
|
|
347
|
+
continue
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
this.varMap.set(param.name, `$${param.name}`)
|
|
351
|
+
}
|
|
352
|
+
} else {
|
|
353
|
+
for (const param of runtimeParams) {
|
|
354
|
+
const paramName = param.name
|
|
355
|
+
this.varMap.set(paramName, `$${paramName}`)
|
|
356
|
+
this.varTypes.set(paramName, this.normalizeType(param.type))
|
|
357
|
+
}
|
|
270
358
|
}
|
|
271
359
|
for (const param of fn.params) {
|
|
272
360
|
if (callbackBindings.has(param.name)) {
|
|
@@ -284,6 +372,16 @@ export class Lowering {
|
|
|
284
372
|
this.builder.emitAssign(varName, { kind: 'var', name: `$p${i}` })
|
|
285
373
|
}
|
|
286
374
|
|
|
375
|
+
if (staticEventDec) {
|
|
376
|
+
for (let i = 0; i < fn.params.length; i++) {
|
|
377
|
+
const param = fn.params[i]
|
|
378
|
+
const expected = eventParamSpecs[i]
|
|
379
|
+
if (expected?.type.kind === 'named' && expected.type.name !== 'string') {
|
|
380
|
+
this.builder.emitAssign(`$${param.name}`, { kind: 'const', value: 0 })
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
287
385
|
// Lower body
|
|
288
386
|
this.lowerBlock(fn.body)
|
|
289
387
|
|
|
@@ -336,6 +434,13 @@ export class Lowering {
|
|
|
336
434
|
}
|
|
337
435
|
}
|
|
338
436
|
|
|
437
|
+
if (eventType && isEventTypeName(eventType)) {
|
|
438
|
+
irFn.eventHandler = {
|
|
439
|
+
eventType,
|
|
440
|
+
tag: EVENT_TYPES[eventType].tag,
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
339
444
|
// Check for @load decorator
|
|
340
445
|
if (fn.decorators.some(d => d.name === 'load')) {
|
|
341
446
|
irFn.isLoadInit = true
|
|
@@ -489,6 +594,16 @@ export class Lowering {
|
|
|
489
594
|
return
|
|
490
595
|
}
|
|
491
596
|
|
|
597
|
+
if (stmt.init.kind === 'call' && stmt.init.fn === 'setInterval') {
|
|
598
|
+
const value = this.lowerExpr(stmt.init)
|
|
599
|
+
const intervalFn = this.intervalFunctions.get(value.kind === 'const' ? value.value : NaN)
|
|
600
|
+
if (intervalFn) {
|
|
601
|
+
this.intervalBindings.set(stmt.name, intervalFn)
|
|
602
|
+
}
|
|
603
|
+
this.builder.emitAssign(varName, value)
|
|
604
|
+
return
|
|
605
|
+
}
|
|
606
|
+
|
|
492
607
|
// Handle struct literal initialization
|
|
493
608
|
if (stmt.init.kind === 'struct_lit' && stmt.type?.kind === 'struct') {
|
|
494
609
|
const structName = stmt.type.name.toLowerCase()
|
|
@@ -567,6 +682,11 @@ export class Lowering {
|
|
|
567
682
|
}
|
|
568
683
|
|
|
569
684
|
private lowerIfStmt(stmt: Extract<Stmt, { kind: 'if' }>): void {
|
|
685
|
+
if (stmt.cond.kind === 'is_check') {
|
|
686
|
+
this.lowerIsCheckIfStmt(stmt)
|
|
687
|
+
return
|
|
688
|
+
}
|
|
689
|
+
|
|
570
690
|
const condVar = this.lowerExpr(stmt.cond)
|
|
571
691
|
const condName = this.operandToVar(condVar)
|
|
572
692
|
|
|
@@ -596,6 +716,66 @@ export class Lowering {
|
|
|
596
716
|
this.builder.startBlock(mergeLabel)
|
|
597
717
|
}
|
|
598
718
|
|
|
719
|
+
private lowerIsCheckIfStmt(stmt: Extract<Stmt, { kind: 'if' }>): void {
|
|
720
|
+
const cond = stmt.cond
|
|
721
|
+
if (cond.kind !== 'is_check') {
|
|
722
|
+
throw new DiagnosticError(
|
|
723
|
+
'LoweringError',
|
|
724
|
+
"Internal error: expected 'is' check condition",
|
|
725
|
+
stmt.span ?? { line: 0, col: 0 }
|
|
726
|
+
)
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
if (stmt.else_) {
|
|
730
|
+
throw new DiagnosticError(
|
|
731
|
+
'LoweringError',
|
|
732
|
+
"'is' checks with else branches are not yet supported",
|
|
733
|
+
cond.span ?? stmt.span ?? { line: 0, col: 0 }
|
|
734
|
+
)
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
const selector = this.exprToEntitySelector(cond.expr)
|
|
738
|
+
if (!selector) {
|
|
739
|
+
throw new DiagnosticError(
|
|
740
|
+
'LoweringError',
|
|
741
|
+
"'is' checks require an entity selector or entity binding",
|
|
742
|
+
cond.span ?? stmt.span ?? { line: 0, col: 0 }
|
|
743
|
+
)
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
const mcType = ENTITY_TO_MC_TYPE[cond.entityType]
|
|
747
|
+
if (!mcType) {
|
|
748
|
+
throw new DiagnosticError(
|
|
749
|
+
'LoweringError',
|
|
750
|
+
`Cannot lower entity type check for '${cond.entityType}'`,
|
|
751
|
+
cond.span ?? stmt.span ?? { line: 0, col: 0 }
|
|
752
|
+
)
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
const thenFnName = `${this.currentFn}/then_${this.foreachCounter++}`
|
|
756
|
+
this.builder.emitRaw(`execute if entity ${this.appendTypeFilter(selector, mcType)} run function ${this.namespace}:${thenFnName}`)
|
|
757
|
+
|
|
758
|
+
const savedBuilder = this.builder
|
|
759
|
+
const savedVarMap = new Map(this.varMap)
|
|
760
|
+
const savedBlockPosVars = new Map(this.blockPosVars)
|
|
761
|
+
|
|
762
|
+
this.builder = new LoweringBuilder()
|
|
763
|
+
this.varMap = new Map(savedVarMap)
|
|
764
|
+
this.blockPosVars = new Map(savedBlockPosVars)
|
|
765
|
+
|
|
766
|
+
this.builder.startBlock('entry')
|
|
767
|
+
this.lowerBlock(stmt.then)
|
|
768
|
+
if (!this.builder.isBlockSealed()) {
|
|
769
|
+
this.builder.emitReturn()
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
this.functions.push(this.builder.build(thenFnName, [], false))
|
|
773
|
+
|
|
774
|
+
this.builder = savedBuilder
|
|
775
|
+
this.varMap = savedVarMap
|
|
776
|
+
this.blockPosVars = savedBlockPosVars
|
|
777
|
+
}
|
|
778
|
+
|
|
599
779
|
private lowerWhileStmt(stmt: Extract<Stmt, { kind: 'while' }>): void {
|
|
600
780
|
const checkLabel = this.builder.freshLabel('loop_check')
|
|
601
781
|
const bodyLabel = this.builder.freshLabel('loop_body')
|
|
@@ -1098,6 +1278,13 @@ export class Lowering {
|
|
|
1098
1278
|
case 'binary':
|
|
1099
1279
|
return this.lowerBinaryExpr(expr)
|
|
1100
1280
|
|
|
1281
|
+
case 'is_check':
|
|
1282
|
+
throw new DiagnosticError(
|
|
1283
|
+
'LoweringError',
|
|
1284
|
+
"'is' checks are only supported as if conditions",
|
|
1285
|
+
expr.span ?? { line: 0, col: 0 }
|
|
1286
|
+
)
|
|
1287
|
+
|
|
1101
1288
|
case 'unary':
|
|
1102
1289
|
return this.lowerUnaryExpr(expr)
|
|
1103
1290
|
|
|
@@ -1107,6 +1294,9 @@ export class Lowering {
|
|
|
1107
1294
|
case 'call':
|
|
1108
1295
|
return this.lowerCallExpr(expr)
|
|
1109
1296
|
|
|
1297
|
+
case 'static_call':
|
|
1298
|
+
return this.lowerStaticCallExpr(expr)
|
|
1299
|
+
|
|
1110
1300
|
case 'invoke':
|
|
1111
1301
|
return this.lowerInvokeExpr(expr)
|
|
1112
1302
|
|
|
@@ -1454,6 +1644,11 @@ export class Lowering {
|
|
|
1454
1644
|
return this.emitDirectFunctionCall(callbackTarget, expr.args)
|
|
1455
1645
|
}
|
|
1456
1646
|
|
|
1647
|
+
const implMethod = this.resolveInstanceMethod(expr)
|
|
1648
|
+
if (implMethod) {
|
|
1649
|
+
return this.emitMethodCall(implMethod.loweredName, implMethod.fn, expr.args)
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1457
1652
|
// Regular function call
|
|
1458
1653
|
const fnDecl = this.fnDecls.get(expr.fn)
|
|
1459
1654
|
const defaultArgs = this.functionDefaults.get(expr.fn) ?? []
|
|
@@ -1483,8 +1678,9 @@ export class Lowering {
|
|
|
1483
1678
|
runtimeArgs.push(fullArgs[i])
|
|
1484
1679
|
}
|
|
1485
1680
|
|
|
1486
|
-
const
|
|
1487
|
-
|
|
1681
|
+
const stdlibCallSite = this.getStdlibCallSiteContext(fnDecl, getSpan(expr))
|
|
1682
|
+
const targetFn = callbackBindings.size > 0 || stdlibCallSite
|
|
1683
|
+
? this.ensureSpecializedFunctionWithContext(fnDecl, callbackBindings, stdlibCallSite)
|
|
1488
1684
|
: expr.fn
|
|
1489
1685
|
return this.emitDirectFunctionCall(targetFn, runtimeArgs)
|
|
1490
1686
|
}
|
|
@@ -1492,6 +1688,12 @@ export class Lowering {
|
|
|
1492
1688
|
return this.emitDirectFunctionCall(expr.fn, fullArgs)
|
|
1493
1689
|
}
|
|
1494
1690
|
|
|
1691
|
+
private lowerStaticCallExpr(expr: Extract<Expr, { kind: 'static_call' }>): Operand {
|
|
1692
|
+
const method = this.implMethods.get(expr.type)?.get(expr.method)
|
|
1693
|
+
const targetFn = method?.loweredName ?? `${expr.type}_${expr.method}`
|
|
1694
|
+
return this.emitMethodCall(targetFn, method?.fn, expr.args)
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1495
1697
|
private lowerInvokeExpr(expr: Extract<Expr, { kind: 'invoke' }>): Operand {
|
|
1496
1698
|
if (expr.callee.kind === 'lambda') {
|
|
1497
1699
|
if (!Array.isArray(expr.callee.body)) {
|
|
@@ -1543,6 +1745,19 @@ export class Lowering {
|
|
|
1543
1745
|
return { kind: 'var', name: dst }
|
|
1544
1746
|
}
|
|
1545
1747
|
|
|
1748
|
+
private emitMethodCall(fn: string, fnDecl: FnDecl | undefined, args: Expr[]): Operand {
|
|
1749
|
+
const defaultArgs = this.functionDefaults.get(fn) ?? fnDecl?.params.map(param => param.default) ?? []
|
|
1750
|
+
const fullArgs = [...args]
|
|
1751
|
+
for (let i = fullArgs.length; i < defaultArgs.length; i++) {
|
|
1752
|
+
const defaultExpr = defaultArgs[i]
|
|
1753
|
+
if (!defaultExpr) {
|
|
1754
|
+
break
|
|
1755
|
+
}
|
|
1756
|
+
fullArgs.push(defaultExpr)
|
|
1757
|
+
}
|
|
1758
|
+
return this.emitDirectFunctionCall(fn, fullArgs)
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1546
1761
|
private resolveFunctionRefExpr(expr: Expr): string | null {
|
|
1547
1762
|
if (expr.kind === 'lambda') {
|
|
1548
1763
|
return this.lowerLambdaExpr(expr)
|
|
@@ -1558,9 +1773,21 @@ export class Lowering {
|
|
|
1558
1773
|
}
|
|
1559
1774
|
|
|
1560
1775
|
private ensureSpecializedFunction(fn: FnDecl, callbackBindings: Map<string, string>): string {
|
|
1776
|
+
return this.ensureSpecializedFunctionWithContext(fn, callbackBindings)
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
private ensureSpecializedFunctionWithContext(
|
|
1780
|
+
fn: FnDecl,
|
|
1781
|
+
callbackBindings: Map<string, string>,
|
|
1782
|
+
stdlibCallSite?: StdlibCallSiteContext
|
|
1783
|
+
): string {
|
|
1561
1784
|
const parts = [...callbackBindings.entries()]
|
|
1562
1785
|
.sort(([left], [right]) => left.localeCompare(right))
|
|
1563
1786
|
.map(([param, target]) => `${param}_${target.replace(/[^a-zA-Z0-9_]/g, '_')}`)
|
|
1787
|
+
const callSiteHash = stdlibCallSite ? this.shortHash(this.serializeCallSite(stdlibCallSite)) : null
|
|
1788
|
+
if (callSiteHash) {
|
|
1789
|
+
parts.push(`callsite_${callSiteHash}`)
|
|
1790
|
+
}
|
|
1564
1791
|
const key = `${fn.name}::${parts.join('::')}`
|
|
1565
1792
|
const cached = this.specializedFunctions.get(key)
|
|
1566
1793
|
if (cached) {
|
|
@@ -1570,7 +1797,7 @@ export class Lowering {
|
|
|
1570
1797
|
const specializedName = `${fn.name}__${parts.join('__')}`
|
|
1571
1798
|
this.specializedFunctions.set(key, specializedName)
|
|
1572
1799
|
this.withSavedFunctionState(() => {
|
|
1573
|
-
this.lowerFn(fn, { name: specializedName, callbackBindings })
|
|
1800
|
+
this.lowerFn(fn, { name: specializedName, callbackBindings, stdlibCallSite })
|
|
1574
1801
|
})
|
|
1575
1802
|
return specializedName
|
|
1576
1803
|
}
|
|
@@ -1595,10 +1822,12 @@ export class Lowering {
|
|
|
1595
1822
|
|
|
1596
1823
|
private withSavedFunctionState<T>(callback: () => T): T {
|
|
1597
1824
|
const savedCurrentFn = this.currentFn
|
|
1825
|
+
const savedStdlibCallSite = this.currentStdlibCallSite
|
|
1598
1826
|
const savedForeachCounter = this.foreachCounter
|
|
1599
1827
|
const savedBuilder = this.builder
|
|
1600
1828
|
const savedVarMap = new Map(this.varMap)
|
|
1601
1829
|
const savedLambdaBindings = new Map(this.lambdaBindings)
|
|
1830
|
+
const savedIntervalBindings = new Map(this.intervalBindings)
|
|
1602
1831
|
const savedCallbackBindings = new Map(this.currentCallbackBindings)
|
|
1603
1832
|
const savedContext = this.currentContext
|
|
1604
1833
|
const savedBlockPosVars = new Map(this.blockPosVars)
|
|
@@ -1609,10 +1838,12 @@ export class Lowering {
|
|
|
1609
1838
|
return callback()
|
|
1610
1839
|
} finally {
|
|
1611
1840
|
this.currentFn = savedCurrentFn
|
|
1841
|
+
this.currentStdlibCallSite = savedStdlibCallSite
|
|
1612
1842
|
this.foreachCounter = savedForeachCounter
|
|
1613
1843
|
this.builder = savedBuilder
|
|
1614
1844
|
this.varMap = savedVarMap
|
|
1615
1845
|
this.lambdaBindings = savedLambdaBindings
|
|
1846
|
+
this.intervalBindings = savedIntervalBindings
|
|
1616
1847
|
this.currentCallbackBindings = savedCallbackBindings
|
|
1617
1848
|
this.currentContext = savedContext
|
|
1618
1849
|
this.blockPosVars = savedBlockPosVars
|
|
@@ -1628,6 +1859,18 @@ export class Lowering {
|
|
|
1628
1859
|
return { kind: 'const', value: 0 }
|
|
1629
1860
|
}
|
|
1630
1861
|
|
|
1862
|
+
if (name === 'setTimeout') {
|
|
1863
|
+
return this.lowerSetTimeout(args)
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
if (name === 'setInterval') {
|
|
1867
|
+
return this.lowerSetInterval(args)
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
if (name === 'clearInterval') {
|
|
1871
|
+
return this.lowerClearInterval(args, callSpan)
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1631
1874
|
// Special case: random - legacy scoreboard RNG for pre-1.20.3 compatibility
|
|
1632
1875
|
if (name === 'random') {
|
|
1633
1876
|
const dst = this.builder.freshTemp()
|
|
@@ -1658,7 +1901,7 @@ export class Lowering {
|
|
|
1658
1901
|
if (name === 'scoreboard_get' || name === 'score') {
|
|
1659
1902
|
const dst = this.builder.freshTemp()
|
|
1660
1903
|
const player = this.exprToTargetString(args[0])
|
|
1661
|
-
const objective = this.
|
|
1904
|
+
const objective = this.resolveScoreboardObjective(args[0], args[1], callSpan)
|
|
1662
1905
|
this.builder.emitRaw(`execute store result score ${dst} rs run scoreboard players get ${player} ${objective}`)
|
|
1663
1906
|
return { kind: 'var', name: dst }
|
|
1664
1907
|
}
|
|
@@ -1666,7 +1909,7 @@ export class Lowering {
|
|
|
1666
1909
|
// Special case: scoreboard_set — write to vanilla MC scoreboard
|
|
1667
1910
|
if (name === 'scoreboard_set') {
|
|
1668
1911
|
const player = this.exprToTargetString(args[0])
|
|
1669
|
-
const objective = this.
|
|
1912
|
+
const objective = this.resolveScoreboardObjective(args[0], args[1], callSpan)
|
|
1670
1913
|
const value = this.lowerExpr(args[2])
|
|
1671
1914
|
if (value.kind === 'const') {
|
|
1672
1915
|
this.builder.emitRaw(`scoreboard players set ${player} ${objective} ${value.value}`)
|
|
@@ -1680,7 +1923,7 @@ export class Lowering {
|
|
|
1680
1923
|
|
|
1681
1924
|
if (name === 'scoreboard_display') {
|
|
1682
1925
|
const slot = this.exprToString(args[0])
|
|
1683
|
-
const objective = this.
|
|
1926
|
+
const objective = this.resolveScoreboardObjective(undefined, args[1], callSpan)
|
|
1684
1927
|
this.builder.emitRaw(`scoreboard objectives setdisplay ${slot} ${objective}`)
|
|
1685
1928
|
return { kind: 'const', value: 0 }
|
|
1686
1929
|
}
|
|
@@ -1692,7 +1935,7 @@ export class Lowering {
|
|
|
1692
1935
|
}
|
|
1693
1936
|
|
|
1694
1937
|
if (name === 'scoreboard_add_objective') {
|
|
1695
|
-
const objective = this.
|
|
1938
|
+
const objective = this.resolveScoreboardObjective(undefined, args[0], callSpan)
|
|
1696
1939
|
const criteria = this.exprToString(args[1])
|
|
1697
1940
|
const displayName = args[2] ? ` ${this.exprToQuotedString(args[2])}` : ''
|
|
1698
1941
|
this.builder.emitRaw(`scoreboard objectives add ${objective} ${criteria}${displayName}`)
|
|
@@ -1700,7 +1943,7 @@ export class Lowering {
|
|
|
1700
1943
|
}
|
|
1701
1944
|
|
|
1702
1945
|
if (name === 'scoreboard_remove_objective') {
|
|
1703
|
-
const objective = this.
|
|
1946
|
+
const objective = this.resolveScoreboardObjective(undefined, args[0], callSpan)
|
|
1704
1947
|
this.builder.emitRaw(`scoreboard objectives remove ${objective}`)
|
|
1705
1948
|
return { kind: 'const', value: 0 }
|
|
1706
1949
|
}
|
|
@@ -1902,6 +2145,119 @@ export class Lowering {
|
|
|
1902
2145
|
return { kind: 'const', value: 0 }
|
|
1903
2146
|
}
|
|
1904
2147
|
|
|
2148
|
+
private lowerSetTimeout(args: Expr[]): Operand {
|
|
2149
|
+
const delay = this.exprToLiteral(args[0])
|
|
2150
|
+
const callback = args[1]
|
|
2151
|
+
if (!callback || callback.kind !== 'lambda') {
|
|
2152
|
+
throw new DiagnosticError(
|
|
2153
|
+
'LoweringError',
|
|
2154
|
+
'setTimeout requires a lambda callback',
|
|
2155
|
+
getSpan(callback) ?? { line: 1, col: 1 }
|
|
2156
|
+
)
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
const fnName = `__timeout_${this.timeoutCounter++}`
|
|
2160
|
+
this.lowerNamedLambdaFunction(fnName, callback)
|
|
2161
|
+
this.builder.emitRaw(`schedule function ${this.namespace}:${fnName} ${delay}t`)
|
|
2162
|
+
return { kind: 'const', value: 0 }
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
private lowerSetInterval(args: Expr[]): Operand {
|
|
2166
|
+
const delay = this.exprToLiteral(args[0])
|
|
2167
|
+
const callback = args[1]
|
|
2168
|
+
if (!callback || callback.kind !== 'lambda') {
|
|
2169
|
+
throw new DiagnosticError(
|
|
2170
|
+
'LoweringError',
|
|
2171
|
+
'setInterval requires a lambda callback',
|
|
2172
|
+
getSpan(callback) ?? { line: 1, col: 1 }
|
|
2173
|
+
)
|
|
2174
|
+
}
|
|
2175
|
+
|
|
2176
|
+
const id = this.intervalCounter++
|
|
2177
|
+
const bodyName = `__interval_body_${id}`
|
|
2178
|
+
const fnName = `__interval_${id}`
|
|
2179
|
+
|
|
2180
|
+
this.lowerNamedLambdaFunction(bodyName, callback)
|
|
2181
|
+
this.lowerIntervalWrapperFunction(fnName, bodyName, delay)
|
|
2182
|
+
this.intervalFunctions.set(id, fnName)
|
|
2183
|
+
this.builder.emitRaw(`schedule function ${this.namespace}:${fnName} ${delay}t`)
|
|
2184
|
+
|
|
2185
|
+
return { kind: 'const', value: id }
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
private lowerClearInterval(args: Expr[], callSpan?: Span): Operand {
|
|
2189
|
+
const fnName = this.resolveIntervalFunctionName(args[0])
|
|
2190
|
+
if (!fnName) {
|
|
2191
|
+
throw new DiagnosticError(
|
|
2192
|
+
'LoweringError',
|
|
2193
|
+
'clearInterval requires an interval ID returned from setInterval',
|
|
2194
|
+
callSpan ?? getSpan(args[0]) ?? { line: 1, col: 1 }
|
|
2195
|
+
)
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
this.builder.emitRaw(`schedule clear ${this.namespace}:${fnName}`)
|
|
2199
|
+
return { kind: 'const', value: 0 }
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2202
|
+
private lowerNamedLambdaFunction(name: string, expr: Extract<Expr, { kind: 'lambda' }>): void {
|
|
2203
|
+
const lambdaFn: FnDecl = {
|
|
2204
|
+
name,
|
|
2205
|
+
params: expr.params.map(param => ({
|
|
2206
|
+
name: param.name,
|
|
2207
|
+
type: param.type ?? { kind: 'named', name: 'int' },
|
|
2208
|
+
})),
|
|
2209
|
+
returnType: expr.returnType ?? this.inferLambdaReturnType(expr),
|
|
2210
|
+
decorators: [],
|
|
2211
|
+
body: Array.isArray(expr.body) ? expr.body : [{ kind: 'return', value: expr.body }],
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
this.withSavedFunctionState(() => {
|
|
2215
|
+
this.lowerFn(lambdaFn)
|
|
2216
|
+
})
|
|
2217
|
+
}
|
|
2218
|
+
|
|
2219
|
+
private lowerIntervalWrapperFunction(name: string, bodyName: string, delay: string): void {
|
|
2220
|
+
const intervalFn: FnDecl = {
|
|
2221
|
+
name,
|
|
2222
|
+
params: [],
|
|
2223
|
+
returnType: { kind: 'named', name: 'void' },
|
|
2224
|
+
decorators: [],
|
|
2225
|
+
body: [
|
|
2226
|
+
{ kind: 'raw', cmd: `function ${this.namespace}:${bodyName}` },
|
|
2227
|
+
{ kind: 'raw', cmd: `schedule function ${this.namespace}:${name} ${delay}t` },
|
|
2228
|
+
],
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
this.withSavedFunctionState(() => {
|
|
2232
|
+
this.lowerFn(intervalFn)
|
|
2233
|
+
})
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
private resolveIntervalFunctionName(expr: Expr | undefined): string | null {
|
|
2237
|
+
if (!expr) {
|
|
2238
|
+
return null
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
if (expr.kind === 'ident') {
|
|
2242
|
+
const boundInterval = this.intervalBindings.get(expr.name)
|
|
2243
|
+
if (boundInterval) {
|
|
2244
|
+
return boundInterval
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
const constValue = this.constValues.get(expr.name)
|
|
2248
|
+
if (constValue?.kind === 'int_lit') {
|
|
2249
|
+
return this.intervalFunctions.get(constValue.value) ?? null
|
|
2250
|
+
}
|
|
2251
|
+
return null
|
|
2252
|
+
}
|
|
2253
|
+
|
|
2254
|
+
if (expr.kind === 'int_lit') {
|
|
2255
|
+
return this.intervalFunctions.get(expr.value) ?? null
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
return null
|
|
2259
|
+
}
|
|
2260
|
+
|
|
1905
2261
|
private lowerRichTextBuiltin(name: string, args: Expr[]): string | null {
|
|
1906
2262
|
const messageArgIndex = this.getRichTextArgIndex(name)
|
|
1907
2263
|
if (messageArgIndex === null) {
|
|
@@ -2078,6 +2434,32 @@ export class Lowering {
|
|
|
2078
2434
|
}
|
|
2079
2435
|
}
|
|
2080
2436
|
|
|
2437
|
+
private exprToEntitySelector(expr: Expr): string | null {
|
|
2438
|
+
if (expr.kind === 'selector') {
|
|
2439
|
+
return this.selectorToString(expr.sel)
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
if (expr.kind === 'ident') {
|
|
2443
|
+
const constValue = this.constValues.get(expr.name)
|
|
2444
|
+
if (constValue) {
|
|
2445
|
+
return this.exprToEntitySelector(constValue)
|
|
2446
|
+
}
|
|
2447
|
+
const mapped = this.varMap.get(expr.name)
|
|
2448
|
+
if (mapped?.startsWith('@')) {
|
|
2449
|
+
return mapped
|
|
2450
|
+
}
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
return null
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2456
|
+
private appendTypeFilter(selector: string, mcType: string): string {
|
|
2457
|
+
if (selector.endsWith(']')) {
|
|
2458
|
+
return `${selector.slice(0, -1)},type=${mcType}]`
|
|
2459
|
+
}
|
|
2460
|
+
return `${selector}[type=${mcType}]`
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2081
2463
|
private exprToSnbt(expr: Expr): string {
|
|
2082
2464
|
switch (expr.kind) {
|
|
2083
2465
|
case 'struct_lit': {
|
|
@@ -2152,6 +2534,113 @@ export class Lowering {
|
|
|
2152
2534
|
return option === 'displayName' || option === 'prefix' || option === 'suffix'
|
|
2153
2535
|
}
|
|
2154
2536
|
|
|
2537
|
+
private exprToScoreboardObjective(expr: Expr, span?: Span): string {
|
|
2538
|
+
if (expr.kind === 'mc_name') {
|
|
2539
|
+
return expr.value
|
|
2540
|
+
}
|
|
2541
|
+
|
|
2542
|
+
const objective = this.exprToString(expr)
|
|
2543
|
+
if (objective.startsWith('#') || objective.includes('.')) {
|
|
2544
|
+
return objective.startsWith('#') ? objective.slice(1) : objective
|
|
2545
|
+
}
|
|
2546
|
+
|
|
2547
|
+
return `${this.getObjectiveNamespace(span)}.${objective}`
|
|
2548
|
+
}
|
|
2549
|
+
|
|
2550
|
+
private resolveScoreboardObjective(playerExpr: Expr | undefined, objectiveExpr: Expr, span?: Span): string {
|
|
2551
|
+
const stdlibInternalObjective = this.tryGetStdlibInternalObjective(playerExpr, objectiveExpr, span)
|
|
2552
|
+
if (stdlibInternalObjective) {
|
|
2553
|
+
return stdlibInternalObjective
|
|
2554
|
+
}
|
|
2555
|
+
return this.exprToScoreboardObjective(objectiveExpr, span)
|
|
2556
|
+
}
|
|
2557
|
+
|
|
2558
|
+
private getObjectiveNamespace(span?: Span): string {
|
|
2559
|
+
const filePath = this.filePathForSpan(span)
|
|
2560
|
+
if (!filePath) {
|
|
2561
|
+
return this.namespace
|
|
2562
|
+
}
|
|
2563
|
+
|
|
2564
|
+
return this.isStdlibFile(filePath) ? 'rs' : this.namespace
|
|
2565
|
+
}
|
|
2566
|
+
|
|
2567
|
+
private tryGetStdlibInternalObjective(playerExpr: Expr | undefined, objectiveExpr: Expr, span?: Span): string | null {
|
|
2568
|
+
if (!span || !this.currentStdlibCallSite || objectiveExpr.kind !== 'mc_name' || objectiveExpr.value !== 'rs') {
|
|
2569
|
+
return null
|
|
2570
|
+
}
|
|
2571
|
+
|
|
2572
|
+
const filePath = this.filePathForSpan(span)
|
|
2573
|
+
if (!filePath || !this.isStdlibFile(filePath)) {
|
|
2574
|
+
return null
|
|
2575
|
+
}
|
|
2576
|
+
|
|
2577
|
+
const resourceBase = this.getStdlibInternalResourceBase(playerExpr)
|
|
2578
|
+
if (!resourceBase) {
|
|
2579
|
+
return null
|
|
2580
|
+
}
|
|
2581
|
+
|
|
2582
|
+
const hash = this.shortHash(this.serializeCallSite(this.currentStdlibCallSite))
|
|
2583
|
+
return `rs._${resourceBase}_${hash}`
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2586
|
+
private getStdlibInternalResourceBase(playerExpr: Expr | undefined): string | null {
|
|
2587
|
+
if (!playerExpr || playerExpr.kind !== 'str_lit') {
|
|
2588
|
+
return null
|
|
2589
|
+
}
|
|
2590
|
+
|
|
2591
|
+
const match = playerExpr.value.match(/^([a-z0-9]+)_/)
|
|
2592
|
+
return match?.[1] ?? null
|
|
2593
|
+
}
|
|
2594
|
+
|
|
2595
|
+
private getStdlibCallSiteContext(fn: FnDecl, exprSpan?: Span): StdlibCallSiteContext | undefined {
|
|
2596
|
+
const fnFilePath = this.filePathForSpan(getSpan(fn))
|
|
2597
|
+
if (!fnFilePath || !this.isStdlibFile(fnFilePath)) {
|
|
2598
|
+
return undefined
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
if (this.currentStdlibCallSite) {
|
|
2602
|
+
return this.currentStdlibCallSite
|
|
2603
|
+
}
|
|
2604
|
+
|
|
2605
|
+
if (!exprSpan) {
|
|
2606
|
+
return undefined
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2609
|
+
return {
|
|
2610
|
+
filePath: this.filePathForSpan(exprSpan),
|
|
2611
|
+
line: exprSpan.line,
|
|
2612
|
+
col: exprSpan.col,
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
private serializeCallSite(callSite: StdlibCallSiteContext): string {
|
|
2617
|
+
return `${callSite.filePath ?? '<memory>'}:${callSite.line}:${callSite.col}`
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
private shortHash(input: string): string {
|
|
2621
|
+
let hash = 2166136261
|
|
2622
|
+
for (let i = 0; i < input.length; i++) {
|
|
2623
|
+
hash ^= input.charCodeAt(i)
|
|
2624
|
+
hash = Math.imul(hash, 16777619)
|
|
2625
|
+
}
|
|
2626
|
+
return (hash >>> 0).toString(16).padStart(8, '0').slice(0, 4)
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2629
|
+
private isStdlibFile(filePath: string): boolean {
|
|
2630
|
+
const normalized = path.normalize(filePath)
|
|
2631
|
+
const stdlibSegment = `${path.sep}src${path.sep}stdlib${path.sep}`
|
|
2632
|
+
return normalized.includes(stdlibSegment)
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
private filePathForSpan(span?: Span): string | undefined {
|
|
2636
|
+
if (!span) {
|
|
2637
|
+
return undefined
|
|
2638
|
+
}
|
|
2639
|
+
|
|
2640
|
+
const line = span.line
|
|
2641
|
+
return this.sourceRanges.find(range => line >= range.startLine && line <= range.endLine)?.filePath
|
|
2642
|
+
}
|
|
2643
|
+
|
|
2155
2644
|
private lowerCoordinateBuiltin(name: string, args: Expr[]): string | null {
|
|
2156
2645
|
const pos0 = args[0] ? this.resolveBlockPosExpr(args[0]) : null
|
|
2157
2646
|
const pos1 = args[1] ? this.resolveBlockPosExpr(args[1]) : null
|
|
@@ -2268,7 +2757,11 @@ export class Lowering {
|
|
|
2268
2757
|
}
|
|
2269
2758
|
}
|
|
2270
2759
|
if (expr.kind === 'call') {
|
|
2271
|
-
|
|
2760
|
+
const resolved = this.resolveFunctionRefByName(expr.fn) ?? this.resolveInstanceMethod(expr)?.loweredName ?? expr.fn
|
|
2761
|
+
return this.fnDecls.get(resolved)?.returnType
|
|
2762
|
+
}
|
|
2763
|
+
if (expr.kind === 'static_call') {
|
|
2764
|
+
return this.implMethods.get(expr.type)?.get(expr.method)?.fn.returnType
|
|
2272
2765
|
}
|
|
2273
2766
|
if (expr.kind === 'invoke') {
|
|
2274
2767
|
const calleeType = this.inferExprType(expr.callee)
|
|
@@ -2297,6 +2790,25 @@ export class Lowering {
|
|
|
2297
2790
|
return undefined
|
|
2298
2791
|
}
|
|
2299
2792
|
|
|
2793
|
+
private resolveInstanceMethod(expr: Extract<Expr, { kind: 'call' }>): { fn: FnDecl; loweredName: string } | null {
|
|
2794
|
+
const receiver = expr.args[0]
|
|
2795
|
+
if (!receiver) {
|
|
2796
|
+
return null
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2799
|
+
const receiverType = this.inferExprType(receiver)
|
|
2800
|
+
if (receiverType?.kind !== 'struct') {
|
|
2801
|
+
return null
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2804
|
+
const method = this.implMethods.get(receiverType.name)?.get(expr.fn)
|
|
2805
|
+
if (!method || method.fn.params[0]?.name !== 'self') {
|
|
2806
|
+
return null
|
|
2807
|
+
}
|
|
2808
|
+
|
|
2809
|
+
return method
|
|
2810
|
+
}
|
|
2811
|
+
|
|
2300
2812
|
private normalizeType(type: TypeNode): TypeNode {
|
|
2301
2813
|
if (type.kind === 'array') {
|
|
2302
2814
|
return { kind: 'array', elem: this.normalizeType(type.elem) }
|