/* * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package net.sergeych.lyng import net.sergeych.lyng.Compiler.Companion.compile import net.sergeych.lyng.bytecode.* import net.sergeych.lyng.miniast.* import net.sergeych.lyng.obj.* import net.sergeych.lyng.pacman.ImportManager import net.sergeych.lyng.pacman.ImportProvider import net.sergeych.lyng.resolution.* /** * The LYNG compiler. */ class Compiler( val cc: CompilerContext, val importManager: ImportProvider, @Suppress("UNUSED_PARAMETER") settings: Settings = Settings() ) { // Stack of parameter-to-slot plans for current function being parsed (by declaration index) @Suppress("unused") private val paramSlotPlanStack = mutableListOf>() // private val currentParamSlotPlan: Map? // get() = paramSlotPlanStack.lastOrNull() // Track identifiers known to be locals/parameters in the current function for fast local emission private val localNamesStack = mutableListOf>() private val localShadowedNamesStack = mutableListOf>() private val currentLocalNames: MutableSet? get() = localNamesStack.lastOrNull() private val currentShadowedLocalNames: MutableSet? get() = localShadowedNamesStack.lastOrNull() private data class SlotEntry(val index: Int, val isMutable: Boolean, val isDelegated: Boolean) private data class SlotPlan(val slots: MutableMap, var nextIndex: Int, val id: Int) private data class SlotLocation( val slot: Int, val depth: Int, val scopeId: Int, val isMutable: Boolean, val isDelegated: Boolean ) private val slotPlanStack = mutableListOf() private var nextScopeId = 0 private val genericFunctionDeclsStack = mutableListOf>(mutableMapOf()) // Track declared local variables count per function for precise capacity hints private val localDeclCountStack = mutableListOf() private val currentLocalDeclCount: Int get() = localDeclCountStack.lastOrNull() ?: 0 private data class GenericFunctionDecl( val typeParams: List, val params: List, val pos: Pos ) private fun pushGenericFunctionScope() { genericFunctionDeclsStack.add(mutableMapOf()) } private fun popGenericFunctionScope() { genericFunctionDeclsStack.removeLast() } private fun currentGenericFunctionDecls(): MutableMap { return genericFunctionDeclsStack.last() } private fun lookupGenericFunctionDecl(name: String): GenericFunctionDecl? { for (i in genericFunctionDeclsStack.indices.reversed()) { genericFunctionDeclsStack[i][name]?.let { return it } } return null } private inline fun withLocalNames(names: Set, block: () -> T): T { localNamesStack.add(names.toMutableSet()) localShadowedNamesStack.add(mutableSetOf()) return try { block() } finally { localShadowedNamesStack.removeLast() localNamesStack.removeLast() } } private fun declareLocalName(name: String, isMutable: Boolean, isDelegated: Boolean = false) { // Add to current function's local set; only count if it was newly added (avoid duplicates) val added = currentLocalNames?.add(name) == true if (!added) { currentShadowedLocalNames?.add(name) } if (added) { scopeSeedNames.remove(name) } if (added && localDeclCountStack.isNotEmpty()) { localDeclCountStack[localDeclCountStack.lastIndex] = currentLocalDeclCount + 1 } capturePlanStack.lastOrNull()?.let { plan -> if (plan.captureMap.remove(name) != null) { plan.captureOwners.remove(name) plan.captures.removeAll { it.name == name } } } declareSlotName(name, isMutable, isDelegated) } private fun declareSlotName(name: String, isMutable: Boolean, isDelegated: Boolean) { if (codeContexts.lastOrNull() is CodeContext.ClassBody) return val plan = slotPlanStack.lastOrNull() ?: return if (plan.slots.containsKey(name)) return plan.slots[name] = SlotEntry(plan.nextIndex, isMutable, isDelegated) plan.nextIndex += 1 if (!seedingSlotPlan && plan == moduleSlotPlan()) { moduleDeclaredNames.add(name) } } private fun declareSlotNameIn(plan: SlotPlan, name: String, isMutable: Boolean, isDelegated: Boolean) { if (plan.slots.containsKey(name)) return plan.slots[name] = SlotEntry(plan.nextIndex, isMutable, isDelegated) plan.nextIndex += 1 } private fun declareSlotNameAt( plan: SlotPlan, name: String, slotIndex: Int, isMutable: Boolean, isDelegated: Boolean ) { if (plan.slots.containsKey(name)) return plan.slots[name] = SlotEntry(slotIndex, isMutable, isDelegated) if (slotIndex >= plan.nextIndex) { plan.nextIndex = slotIndex + 1 } } private fun moduleSlotPlan(): SlotPlan? = slotPlanStack.firstOrNull() private val slotTypeByScopeId: MutableMap> = mutableMapOf() private val nameObjClass: MutableMap = mutableMapOf() private val scopeSeedNames: MutableSet = mutableSetOf() private val slotTypeDeclByScopeId: MutableMap> = mutableMapOf() private val nameTypeDecl: MutableMap = mutableMapOf() private data class TypeAliasDecl( val name: String, val typeParams: List, val body: TypeDecl, val pos: Pos ) private val typeAliases: MutableMap = mutableMapOf() private val methodReturnTypeDeclByRef: MutableMap = mutableMapOf() private val callReturnTypeDeclByRef: MutableMap = mutableMapOf() private val callableReturnTypeByScopeId: MutableMap> = mutableMapOf() private val callableReturnTypeByName: MutableMap = mutableMapOf() private val lambdaReturnTypeByRef: MutableMap = mutableMapOf() private val lambdaCaptureEntriesByRef: MutableMap> = mutableMapOf() private val classFieldTypesByName: MutableMap> = mutableMapOf() private val classMethodReturnTypeByName: MutableMap> = mutableMapOf() private val classMethodReturnTypeDeclByName: MutableMap> = mutableMapOf() private val classScopeMembersByClassName: MutableMap> = mutableMapOf() private val classScopeCallableMembersByClassName: MutableMap> = mutableMapOf() private val encodedPayloadTypeByScopeId: MutableMap> = mutableMapOf() private val encodedPayloadTypeByName: MutableMap = mutableMapOf() private val objectDeclNames: MutableSet = mutableSetOf() private val externCallableNames: MutableSet = mutableSetOf() private val moduleDeclaredNames: MutableSet = mutableSetOf() private var seedingSlotPlan: Boolean = false private fun moduleForcedLocalSlotInfo(): Map { val plan = moduleSlotPlan() ?: return emptyMap() if (plan.slots.isEmpty()) return emptyMap() val result = LinkedHashMap(plan.slots.size) for ((name, entry) in plan.slots) { result[name] = ForcedLocalSlotInfo( index = entry.index, isMutable = entry.isMutable, isDelegated = entry.isDelegated ) } return result } private fun seedSlotPlanFromScope(scope: Scope, includeParents: Boolean = false) { val plan = moduleSlotPlan() ?: return seedingSlotPlan = true try { var current: Scope? = scope while (current != null) { for ((name, record) in current.objects) { if (!record.visibility.isPublic) continue if (plan.slots.containsKey(name)) continue declareSlotNameIn(plan, name, record.isMutable, record.type == ObjRecord.Type.Delegated) scopeSeedNames.add(name) if (record.typeDecl != null && nameTypeDecl[name] == null) { nameTypeDecl[name] = record.typeDecl if (nameObjClass[name] == null) { resolveTypeDeclObjClass(record.typeDecl)?.let { nameObjClass[name] = it } } } val instance = record.value as? ObjInstance if (instance != null && nameObjClass[name] == null) { nameObjClass[name] = instance.objClass } } for ((cls, map) in current.extensions) { for ((name, record) in map) { if (!record.visibility.isPublic) continue when (record.type) { ObjRecord.Type.Property -> { val getterName = extensionPropertyGetterName(cls.className, name) if (!plan.slots.containsKey(getterName)) { declareSlotNameIn( plan, getterName, isMutable = false, isDelegated = false ) scopeSeedNames.add(getterName) } val prop = record.value as? ObjProperty if (prop?.setter != null) { val setterName = extensionPropertySetterName(cls.className, name) if (!plan.slots.containsKey(setterName)) { declareSlotNameIn( plan, setterName, isMutable = false, isDelegated = false ) scopeSeedNames.add(setterName) } } } else -> { val callableName = extensionCallableName(cls.className, name) if (!plan.slots.containsKey(callableName)) { declareSlotNameIn( plan, callableName, isMutable = false, isDelegated = false ) scopeSeedNames.add(callableName) } } } } } for ((name, slotIndex) in current.slotNameToIndexSnapshot()) { val record = current.getSlotRecord(slotIndex) if (!record.visibility.isPublic) continue if (plan.slots.containsKey(name)) continue declareSlotNameIn(plan, name, record.isMutable, record.type == ObjRecord.Type.Delegated) scopeSeedNames.add(name) } if (!includeParents) return current = current.parent } } finally { seedingSlotPlan = false } } private fun seedSlotPlanFromSeedScope(scope: Scope) { val plan = moduleSlotPlan() ?: return for ((name, slotIndex) in scope.slotNameToIndexSnapshot()) { val record = scope.getSlotRecord(slotIndex) declareSlotNameAt( plan, name, slotIndex, record.isMutable, record.type == ObjRecord.Type.Delegated ) scopeSeedNames.add(name) if (record.typeDecl != null && nameTypeDecl[name] == null) { nameTypeDecl[name] = record.typeDecl if (nameObjClass[name] == null) { resolveTypeDeclObjClass(record.typeDecl)?.let { nameObjClass[name] = it } } } if (record.typeDecl != null) { slotTypeDeclByScopeId.getOrPut(plan.id) { mutableMapOf() }[slotIndex] = record.typeDecl } val resolved = when (val raw = record.value) { is FrameSlotRef -> raw.peekValue() ?: raw.read() is RecordSlotRef -> raw.peekValue() ?: raw.read() else -> raw } when (resolved) { is ObjClass -> { slotTypeByScopeId.getOrPut(plan.id) { mutableMapOf() }[slotIndex] = resolved if (nameObjClass[name] == null) nameObjClass[name] = resolved } is ObjInstance -> { slotTypeByScopeId.getOrPut(plan.id) { mutableMapOf() }[slotIndex] = resolved.objClass if (nameObjClass[name] == null) nameObjClass[name] = resolved.objClass } is ObjDynamic -> { slotTypeByScopeId.getOrPut(plan.id) { mutableMapOf() }[slotIndex] = resolved.objClass if (nameObjClass[name] == null) nameObjClass[name] = resolved.objClass } } } } private fun predeclareTopLevelSymbols() { val plan = moduleSlotPlan() ?: return val saved = cc.savePos() var depth = 0 var parenDepth = 0 var bracketDepth = 0 fun nextNonWs(): Token { var t = cc.next() while (t.type == Token.Type.NEWLINE || t.type == Token.Type.SINGLE_LINE_COMMENT || t.type == Token.Type.MULTILINE_COMMENT) { t = cc.next() } return t } try { while (cc.hasNext()) { val t = cc.next() when (t.type) { Token.Type.LBRACE -> depth++ Token.Type.RBRACE -> if (depth > 0) depth-- Token.Type.LPAREN -> parenDepth++ Token.Type.RPAREN -> if (parenDepth > 0) parenDepth-- Token.Type.LBRACKET -> bracketDepth++ Token.Type.RBRACKET -> if (bracketDepth > 0) bracketDepth-- Token.Type.ID -> if (depth == 0) { if (parenDepth > 0 || bracketDepth > 0) continue when (t.value) { "fun", "fn" -> { val nameToken = nextNonWs() if (nameToken.type != Token.Type.ID) continue val afterName = cc.peekNextNonWhitespace() if (afterName.type == Token.Type.DOT) { cc.nextNonWhitespace() val actual = cc.nextNonWhitespace() if (actual.type == Token.Type.ID) { extensionNames.add(actual.value) registerExtensionName(nameToken.value, actual.value) declareSlotNameIn(plan, extensionCallableName(nameToken.value, actual.value), isMutable = false, isDelegated = false) moduleDeclaredNames.add(extensionCallableName(nameToken.value, actual.value)) } continue } declareSlotNameIn(plan, nameToken.value, isMutable = false, isDelegated = false) moduleDeclaredNames.add(nameToken.value) } "val", "var" -> { val nameToken = nextNonWs() if (nameToken.type != Token.Type.ID) continue val afterName = cc.peekNextNonWhitespace() if (afterName.type == Token.Type.DOT) { cc.nextNonWhitespace() val actual = cc.nextNonWhitespace() if (actual.type == Token.Type.ID) { extensionNames.add(actual.value) registerExtensionName(nameToken.value, actual.value) declareSlotNameIn(plan, extensionPropertyGetterName(nameToken.value, actual.value), isMutable = false, isDelegated = false) moduleDeclaredNames.add(extensionPropertyGetterName(nameToken.value, actual.value)) if (t.value == "var") { declareSlotNameIn(plan, extensionPropertySetterName(nameToken.value, actual.value), isMutable = false, isDelegated = false) moduleDeclaredNames.add(extensionPropertySetterName(nameToken.value, actual.value)) } } continue } declareSlotNameIn(plan, nameToken.value, isMutable = t.value == "var", isDelegated = false) moduleDeclaredNames.add(nameToken.value) } "class", "object" -> { val nameToken = nextNonWs() if (nameToken.type == Token.Type.ID) { declareSlotNameIn(plan, nameToken.value, isMutable = false, isDelegated = false) scopeSeedNames.add(nameToken.value) moduleDeclaredNames.add(nameToken.value) } } "enum" -> { val next = nextNonWs() val nameToken = if (next.type == Token.Type.ID && next.value == "class") nextNonWs() else next if (nameToken.type == Token.Type.ID) { declareSlotNameIn(plan, nameToken.value, isMutable = false, isDelegated = false) scopeSeedNames.add(nameToken.value) moduleDeclaredNames.add(nameToken.value) } } } } else -> {} } } } finally { cc.restorePos(saved) } } private fun predeclareClassMembers(target: MutableSet, overrides: MutableMap) { val saved = cc.savePos() var depth = 0 val modifiers = setOf( "public", "private", "protected", "internal", "override", "abstract", "extern", "static", "transient" ) fun nextNonWs(): Token { var t = cc.next() while (t.type == Token.Type.NEWLINE || t.type == Token.Type.SINGLE_LINE_COMMENT || t.type == Token.Type.MULTILINE_COMMENT) { t = cc.next() } return t } try { while (cc.hasNext()) { var t = cc.next() when (t.type) { Token.Type.LBRACE -> depth++ Token.Type.RBRACE -> if (depth == 0) break else depth-- Token.Type.ID -> if (depth == 0) { var sawOverride = false while (t.type == Token.Type.ID && t.value in modifiers) { if (t.value == "override") sawOverride = true t = nextNonWs() } when (t.value) { "fun", "fn", "val", "var" -> { val nameToken = nextNonWs() if (nameToken.type == Token.Type.ID) { val afterName = cc.peekNextNonWhitespace() if (afterName.type != Token.Type.DOT) { target.add(nameToken.value) overrides[nameToken.value] = sawOverride } } } } } else -> {} } } } finally { cc.restorePos(saved) } } private fun predeclareClassScopeMembers( className: String, target: MutableSet, callableTarget: MutableSet ) { val saved = cc.savePos() var depth = 0 val modifiers = setOf( "public", "private", "protected", "internal", "override", "abstract", "extern", "static", "transient", "open", "closed" ) fun nextNonWs(): Token { var t = cc.next() while (t.type == Token.Type.NEWLINE || t.type == Token.Type.SINGLE_LINE_COMMENT || t.type == Token.Type.MULTILINE_COMMENT) { t = cc.next() } return t } try { while (cc.hasNext()) { var t = cc.next() when (t.type) { Token.Type.LBRACE -> depth++ Token.Type.RBRACE -> if (depth == 0) break else depth-- Token.Type.ID -> if (depth == 0) { var sawStatic = false while (t.type == Token.Type.ID && t.value in modifiers) { if (t.value == "static") sawStatic = true t = nextNonWs() } when (t.value) { "class" -> { val nameToken = nextNonWs() if (nameToken.type == Token.Type.ID) { target.add(nameToken.value) callableTarget.add(nameToken.value) registerClassScopeMember(className, nameToken.value) registerClassScopeCallableMember(className, nameToken.value) } } "object" -> { val nameToken = nextNonWs() if (nameToken.type == Token.Type.ID) { target.add(nameToken.value) registerClassScopeMember(className, nameToken.value) } } "enum" -> { val next = nextNonWs() val nameToken = if (next.type == Token.Type.ID && next.value == "class") nextNonWs() else next if (nameToken.type == Token.Type.ID) { target.add(nameToken.value) callableTarget.add(nameToken.value) registerClassScopeMember(className, nameToken.value) registerClassScopeCallableMember(className, nameToken.value) } } "type" -> { val nameToken = nextNonWs() if (nameToken.type == Token.Type.ID) { target.add(nameToken.value) registerClassScopeMember(className, nameToken.value) } } "fun", "fn", "val", "var" -> { if (sawStatic) { val nameToken = nextNonWs() if (nameToken.type == Token.Type.ID) { target.add(nameToken.value) registerClassScopeMember(className, nameToken.value) } } } } } else -> {} } } } finally { cc.restorePos(saved) } } private fun registerClassScopeMember(className: String, name: String) { classScopeMembersByClassName.getOrPut(className) { mutableSetOf() }.add(name) } private fun registerClassScopeCallableMember(className: String, name: String) { classScopeCallableMembersByClassName.getOrPut(className) { mutableSetOf() }.add(name) } private fun isClassScopeCallableMember(className: String, name: String): Boolean { return classScopeCallableMembersByClassName[className]?.contains(name) == true } private fun registerClassScopeFieldType(ownerClassName: String?, memberName: String, memberClassName: String) { if (ownerClassName == null) return val memberClass = resolveClassByName(memberClassName) ?: return classFieldTypesByName.getOrPut(ownerClassName) { mutableMapOf() }[memberName] = memberClass } private fun resolveCompileClassInfo(name: String): CompileClassInfo? { compileClassInfos[name]?.let { return it } val scopeRec = seedScope?.get(name) ?: importManager.rootScope.get(name) val clsFromScope = scopeRec?.value as? ObjClass val clsFromImports = if (clsFromScope == null) { importedModules.asReversed().firstNotNullOfOrNull { it.scope.get(name)?.value as? ObjClass } } else { null } val cls = clsFromScope ?: clsFromImports ?: resolveClassByName(name) ?: return null val fieldIds = cls.instanceFieldIdMap() val methodIds = cls.instanceMethodIdMap(includeAbstract = true) val baseNames = cls.directParents.map { it.className } val nextFieldId = (fieldIds.values.maxOrNull() ?: -1) + 1 val nextMethodId = (methodIds.values.maxOrNull() ?: -1) + 1 return CompileClassInfo(name, fieldIds, methodIds, nextFieldId, nextMethodId, baseNames) } private data class BaseMemberIds( val fieldIds: Map, val methodIds: Map, val fieldConflicts: Set, val methodConflicts: Set, val nextFieldId: Int, val nextMethodId: Int ) private fun collectBaseMemberIds(baseNames: List): BaseMemberIds { val allBaseNames = if (baseNames.contains("Object")) baseNames else baseNames + "Object" val fieldIds = mutableMapOf() val methodIds = mutableMapOf() val fieldConflicts = mutableSetOf() val methodConflicts = mutableSetOf() var maxFieldId = -1 var maxMethodId = -1 for (base in allBaseNames) { val info = resolveCompileClassInfo(base) ?: continue for ((name, id) in info.fieldIds) { val prev = fieldIds[name] if (prev == null) fieldIds[name] = id else if (prev != id) fieldConflicts.add(name) if (id > maxFieldId) maxFieldId = id } for ((name, id) in info.methodIds) { val prev = methodIds[name] if (prev == null) methodIds[name] = id else if (prev != id) methodConflicts.add(name) if (id > maxMethodId) maxMethodId = id } } return BaseMemberIds( fieldIds = fieldIds, methodIds = methodIds, fieldConflicts = fieldConflicts, methodConflicts = methodConflicts, nextFieldId = maxFieldId + 1, nextMethodId = maxMethodId + 1 ) } private fun buildParamSlotPlan(names: List): SlotPlan { val map = mutableMapOf() var idx = 0 for (name in names) { if (!map.containsKey(name)) { map[name] = idx idx++ } } val entries = mutableMapOf() for ((name, index) in map) { entries[name] = SlotEntry(index, isMutable = false, isDelegated = false) } return SlotPlan(entries, idx, nextScopeId++) } private fun markDelegatedSlot(name: String) { val plan = slotPlanStack.lastOrNull() ?: return val entry = plan.slots[name] ?: return if (!entry.isDelegated) { plan.slots[name] = entry.copy(isDelegated = true) } } private fun slotPlanIndices(plan: SlotPlan): Map { if (plan.slots.isEmpty()) return emptyMap() val result = LinkedHashMap(plan.slots.size) for ((name, entry) in plan.slots) { result[name] = entry.index } return result } private fun callSignatureForName(name: String): CallSignature? { seedScope?.getLocalRecordDirect(name)?.callSignature?.let { return it } return seedScope?.get(name)?.callSignature ?: importManager.rootScope.getLocalRecordDirect(name)?.callSignature } internal data class MemberIds(val fieldId: Int?, val methodId: Int?) private fun resolveMemberIds(name: String, pos: Pos, qualifier: String? = null): MemberIds { val ctx = if (qualifier == null) { codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody } else null if (ctx != null) { val fieldId = ctx.memberFieldIds[name] val methodId = ctx.memberMethodIds[name] if (fieldId == null && methodId == null) { if (allowUnresolvedRefs) return MemberIds(null, null) throw ScriptError(pos, "unknown member $name") } return MemberIds(fieldId, methodId) } if (qualifier != null) { val classCtx = codeContexts.asReversed() .firstOrNull { it is CodeContext.ClassBody && it.name == qualifier } as? CodeContext.ClassBody if (classCtx != null) { val fieldId = classCtx.memberFieldIds[name] val methodId = classCtx.memberMethodIds[name] if (fieldId != null || methodId != null) return MemberIds(fieldId, methodId) } val info = resolveCompileClassInfo(qualifier) ?: if (allowUnresolvedRefs) return MemberIds(null, null) else throw ScriptError(pos, "unknown type $qualifier") val fieldId = info.fieldIds[name] val methodId = info.methodIds[name] if (fieldId == null && methodId == null) { if (allowUnresolvedRefs) return MemberIds(null, null) throw ScriptError(pos, "unknown member $name on $qualifier") } return MemberIds(fieldId, methodId) } if (allowUnresolvedRefs) return MemberIds(null, null) throw ScriptError(pos, "member $name is not available without class context") } private fun tailBlockReceiverType(left: ObjRef): String? { val name = when (left) { is LocalVarRef -> left.name is LocalSlotRef -> left.name is ImplicitThisMemberRef -> left.name else -> null } if (name == null) return null val signature = callSignatureForName(name) return signature?.tailBlockReceiverType ?: if (name == "flow") "FlowBuilder" else null } private fun currentImplicitThisTypeName(): String? { for (ctx in codeContexts.asReversed()) { val fn = ctx as? CodeContext.Function ?: continue if (fn.implicitThisTypeName != null) return fn.implicitThisTypeName } return null } private fun implicitReceiverTypeForMember(name: String): String? { for (ctx in codeContexts.asReversed()) { val fn = ctx as? CodeContext.Function ?: continue if (!fn.implicitThisMembers) continue val typeName = fn.implicitThisTypeName ?: continue if (hasImplicitThisMember(name, typeName)) return typeName } return null } private fun currentEnclosingClassName(): String? { val ctx = codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody return ctx?.name } private fun currentTypeParams(): Set { val result = mutableSetOf() pendingTypeParamStack.lastOrNull()?.let { result.addAll(it) } for (ctx in codeContexts.asReversed()) { when (ctx) { is CodeContext.Function -> result.addAll(ctx.typeParams) is CodeContext.ClassBody -> result.addAll(ctx.typeParams) else -> {} } } return result } private val pendingTypeParamStack = mutableListOf>() private fun parseTypeParamList(): List { if (cc.peekNextNonWhitespace().type != Token.Type.LT) return emptyList() val typeParams = mutableListOf() cc.nextNonWhitespace() while (true) { val varianceToken = cc.peekNextNonWhitespace() val variance = when (varianceToken.value) { "in" -> { cc.nextNonWhitespace() TypeDecl.Variance.In } "out" -> { cc.nextNonWhitespace() TypeDecl.Variance.Out } else -> TypeDecl.Variance.Invariant } val idTok = cc.requireToken(Token.Type.ID, "type parameter name expected") var bound: TypeDecl? = null var defaultType: TypeDecl? = null if (cc.skipTokenOfType(Token.Type.COLON, isOptional = true)) { bound = parseTypeExpressionWithMini().first } if (cc.skipTokenOfType(Token.Type.ASSIGN, isOptional = true)) { defaultType = parseTypeExpressionWithMini().first } typeParams.add(TypeDecl.TypeParam(idTok.value, variance, bound, defaultType)) val sep = cc.nextNonWhitespace() when (sep.type) { Token.Type.COMMA -> continue Token.Type.GT -> break Token.Type.SHR -> { cc.pushPendingGT() break } else -> sep.raiseSyntax("expected ',' or '>' in type parameter list") } } return typeParams } private fun looksLikeTypeAliasDeclaration(): Boolean { val saved = cc.savePos() try { val nameTok = cc.nextNonWhitespace() if (nameTok.type != Token.Type.ID) return false val afterName = cc.savePos() if (cc.skipTokenOfType(Token.Type.LT, isOptional = true)) { var depth = 1 while (depth > 0) { val t = cc.nextNonWhitespace() when (t.type) { Token.Type.LT -> depth += 1 Token.Type.GT -> depth -= 1 Token.Type.SHR -> { cc.pushPendingGT() depth -= 1 } Token.Type.EOF -> return false else -> {} } } } else { cc.restorePos(afterName) } cc.skipWsTokens() return cc.peekNextNonWhitespace().type == Token.Type.ASSIGN } finally { cc.restorePos(saved) } } private suspend fun parseTypeAliasDeclaration(): Statement { val nameToken = cc.requireToken(Token.Type.ID, "type alias name expected") val startPos = pendingDeclStart ?: nameToken.pos val doc = pendingDeclDoc ?: consumePendingDoc() pendingDeclDoc = null pendingDeclStart = null val declaredName = nameToken.value val outerClassName = currentEnclosingClassName() val qualifiedName = if (outerClassName != null) "$outerClassName.$declaredName" else declaredName if (typeAliases.containsKey(qualifiedName)) { throw ScriptError(nameToken.pos, "type alias $qualifiedName already declared") } if (resolveTypeDeclObjClass(TypeDecl.Simple(qualifiedName, false)) != null) { throw ScriptError(nameToken.pos, "type alias $qualifiedName conflicts with existing class") } val typeParams = parseTypeParamList() val uniqueParams = typeParams.map { it.name }.toSet() if (uniqueParams.size != typeParams.size) { throw ScriptError(nameToken.pos, "type alias $qualifiedName has duplicate type parameters") } val typeParamNames = uniqueParams if (typeParamNames.isNotEmpty()) pendingTypeParamStack.add(typeParamNames) val (body, bodyMini) = try { cc.skipWsTokens() val eq = cc.nextNonWhitespace() if (eq.type != Token.Type.ASSIGN) { throw ScriptError(eq.pos, "type alias $qualifiedName expects '='") } parseTypeExpressionWithMini() } finally { if (typeParamNames.isNotEmpty()) pendingTypeParamStack.removeLast() } val alias = TypeAliasDecl(qualifiedName, typeParams, body, nameToken.pos) typeAliases[qualifiedName] = alias declareLocalName(declaredName, isMutable = false) resolutionSink?.declareSymbol(declaredName, SymbolKind.LOCAL, isMutable = false, pos = nameToken.pos) if (outerClassName != null) { val outerCtx = codeContexts.asReversed().firstOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody outerCtx?.classScopeMembers?.add(declaredName) registerClassScopeMember(outerClassName, declaredName) } miniSink?.onTypeAliasDecl( MiniTypeAliasDecl( range = MiniRange(startPos, cc.currentPos()), name = declaredName, typeParams = typeParams.map { it.name }, target = bodyMini, doc = doc, nameStart = nameToken.pos ) ) val aliasExpr = net.sergeych.lyng.obj.TypeDeclRef(body, nameToken.pos) val initStmt = ExpressionStatement(aliasExpr, nameToken.pos) val slotPlan = slotPlanStack.lastOrNull() val slotIndex = slotPlan?.slots?.get(declaredName)?.index val scopeId = slotPlan?.id return VarDeclStatement( name = declaredName, isMutable = false, visibility = Visibility.Public, initializer = initStmt, isTransient = false, typeDecl = null, slotIndex = slotIndex, scopeId = scopeId, startPos = nameToken.pos, initializerObjClass = null ) } private fun lookupSlotLocation(name: String, includeModule: Boolean = true): SlotLocation? { for (i in slotPlanStack.indices.reversed()) { if (!includeModule && i == 0) continue val slot = slotPlanStack[i].slots[name] ?: continue val depth = slotPlanStack.size - 1 - i return SlotLocation(slot.index, depth, slotPlanStack[i].id, slot.isMutable, slot.isDelegated) } return null } private fun resolveIdentifierRef(name: String, pos: Pos): ObjRef { if (name == "__PACKAGE__") { resolutionSink?.reference(name, pos) val value = ObjString(packageName ?: "unknown").asReadonly return ConstRef(value) } if (name == "this") { resolutionSink?.reference(name, pos) return LocalVarRef(name, pos) } val slotLoc = lookupSlotLocation(name, includeModule = false) if (slotLoc != null) { val classCtx = codeContexts.lastOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody if (classCtx?.slotPlanId == slotLoc.scopeId && classCtx.declaredMembers.contains(name) ) { val fieldId = classCtx.memberFieldIds[name] val methodId = classCtx.memberMethodIds[name] if (fieldId != null || methodId != null) { resolutionSink?.referenceMember(name, pos) return ImplicitThisMemberRef(name, pos, fieldId, methodId, classCtx.name) } } captureLocalRef(name, slotLoc, pos)?.let { ref -> resolutionSink?.reference(name, pos) return ref } val captureOwner = capturePlanStack.lastOrNull()?.captureOwners?.get(name) if (useFastLocalRefs && slotLoc.depth == 0 && captureOwner == null && currentLocalNames?.contains(name) == true && currentShadowedLocalNames?.contains(name) != true && !slotLoc.isDelegated ) { resolutionSink?.reference(name, pos) return FastLocalVarRef(name, pos) } if (slotLoc.depth == 0 && captureOwner != null) { val ref = LocalSlotRef( name, slotLoc.slot, slotLoc.scopeId, slotLoc.isMutable, slotLoc.isDelegated, pos, strictSlotRefs, captureOwnerScopeId = captureOwner.scopeId, captureOwnerSlot = captureOwner.slot ) resolutionSink?.reference(name, pos) return ref } val ref = LocalSlotRef( name, slotLoc.slot, slotLoc.scopeId, slotLoc.isMutable, slotLoc.isDelegated, pos, strictSlotRefs ) resolutionSink?.reference(name, pos) return ref } val moduleLoc = if (slotPlanStack.size == 1) lookupSlotLocation(name, includeModule = true) else null if (moduleLoc != null) { val moduleDeclaredNames = localNamesStack.firstOrNull() if (moduleDeclaredNames == null || !moduleDeclaredNames.contains(name)) { resolveImportBinding(name, pos)?.let { resolved -> registerImportBinding(name, resolved.binding, pos) } } val ref = LocalSlotRef( name, moduleLoc.slot, moduleLoc.scopeId, moduleLoc.isMutable, moduleLoc.isDelegated, pos, strictSlotRefs ) resolutionSink?.reference(name, pos) return ref } val classCtx = codeContexts.lastOrNull { it is CodeContext.ClassBody } as? CodeContext.ClassBody if (classCtx != null && classCtx.declaredMembers.contains(name)) { resolutionSink?.referenceMember(name, pos) val ids = resolveMemberIds(name, pos, null) return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, currentImplicitThisTypeName()) } val implicitTypeFromFunc = implicitReceiverTypeForMember(name) val hasImplicitClassMember = classCtx != null && hasImplicitThisMember(name, classCtx.name) if (implicitTypeFromFunc == null && !hasImplicitClassMember) { val modulePlan = moduleSlotPlan() val moduleEntry = modulePlan?.slots?.get(name) if (moduleEntry != null) { val moduleDeclaredNames = localNamesStack.firstOrNull() if (moduleDeclaredNames == null || !moduleDeclaredNames.contains(name)) { resolveImportBinding(name, pos)?.let { resolved -> registerImportBinding(name, resolved.binding, pos) } } val moduleLoc = SlotLocation( moduleEntry.index, slotPlanStack.size - 1, modulePlan.id, moduleEntry.isMutable, moduleEntry.isDelegated ) captureLocalRef(name, moduleLoc, pos)?.let { ref -> resolutionSink?.reference(name, pos) return ref } val ref = if (!useScopeSlots && capturePlanStack.isEmpty() && moduleLoc.depth > 0) { LocalSlotRef( name, moduleLoc.slot, moduleLoc.scopeId, moduleLoc.isMutable, moduleLoc.isDelegated, pos, strictSlotRefs, captureOwnerScopeId = moduleLoc.scopeId, captureOwnerSlot = moduleLoc.slot ) } else { LocalSlotRef( name, moduleLoc.slot, moduleLoc.scopeId, moduleLoc.isMutable, moduleLoc.isDelegated, pos, strictSlotRefs ) } resolutionSink?.reference(name, pos) return ref } resolveImportBinding(name, pos)?.let { resolved -> val sourceRecord = resolved.record if (modulePlan != null && !modulePlan.slots.containsKey(name)) { val seedSlotIndex = if (resolved.binding.source is ImportBindingSource.Seed) { seedScope?.getSlotIndexOf(name) } else { null } val seedSlotFree = seedSlotIndex != null && modulePlan.slots.values.none { it.index == seedSlotIndex } if (seedSlotFree) { declareSlotNameAt( modulePlan, name, seedSlotIndex, sourceRecord.isMutable, sourceRecord.type == ObjRecord.Type.Delegated ) } else { declareSlotNameIn( modulePlan, name, sourceRecord.isMutable, sourceRecord.type == ObjRecord.Type.Delegated ) } } registerImportBinding(name, resolved.binding, pos) val slot = lookupSlotLocation(name) if (slot != null) { captureLocalRef(name, slot, pos)?.let { ref -> resolutionSink?.reference(name, pos) return ref } val ref = if (!useScopeSlots && capturePlanStack.isEmpty() && slot.depth > 0) { LocalSlotRef( name, slot.slot, slot.scopeId, slot.isMutable, slot.isDelegated, pos, strictSlotRefs, captureOwnerScopeId = slot.scopeId, captureOwnerSlot = slot.slot ) } else { LocalSlotRef( name, slot.slot, slot.scopeId, slot.isMutable, slot.isDelegated, pos, strictSlotRefs ) } resolutionSink?.reference(name, pos) return ref } } } if (classCtx != null) { val implicitType = classCtx.name if (hasImplicitThisMember(name, implicitType)) { resolutionSink?.referenceMember(name, pos, implicitType) val ids = resolveImplicitThisMemberIds(name, pos, implicitType) val preferredType = if (currentImplicitThisTypeName() == null) null else implicitType return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, preferredType) } } val implicitType = implicitTypeFromFunc if (implicitType != null) { resolutionSink?.referenceMember(name, pos, implicitType) val ids = resolveImplicitThisMemberIds(name, pos, implicitType) val inClassContext = codeContexts.any { ctx -> ctx is CodeContext.ClassBody } val currentImplicitType = currentImplicitThisTypeName() val preferredType = when { inClassContext -> implicitType // Extension receiver aliases (extern class name -> host runtime class) can fail strict // variant casts; keep current receiver untyped but preserve non-current receivers. implicitType == currentImplicitType -> null else -> implicitType } return ImplicitThisMemberRef(name, pos, ids.fieldId, ids.methodId, preferredType) } if (classCtx != null && classCtx.classScopeMembers.contains(name)) { resolutionSink?.referenceMember(name, pos, classCtx.name) return ClassScopeMemberRef(name, pos, classCtx.name) } val classContext = codeContexts.any { ctx -> ctx is CodeContext.ClassBody } if (classContext && extensionNames.contains(name)) { resolutionSink?.referenceMember(name, pos) return LocalVarRef(name, pos) } resolutionSink?.reference(name, pos) if (allowUnresolvedRefs) { return LocalVarRef(name, pos) } throw ScriptError(pos, "unresolved name: $name") } private fun isRangeType(type: TypeDecl): Boolean { val name = when (type) { is TypeDecl.Simple -> type.name is TypeDecl.Generic -> type.name else -> return false } return name == "Range" || name == "IntRange" || name.endsWith(".Range") || name.endsWith(".IntRange") } var packageName: String? = null class Settings( val miniAstSink: MiniAstSink? = null, val resolutionSink: ResolutionSink? = null, val compileBytecode: Boolean = true, val strictSlotRefs: Boolean = true, val allowUnresolvedRefs: Boolean = false, val seedScope: Scope? = null, val useFastLocalRefs: Boolean = false, ) // Optional sink for mini-AST streaming (null by default, zero overhead when not used) private val miniSink: MiniAstSink? = settings.miniAstSink private val resolutionSink: ResolutionSink? = settings.resolutionSink private val compileBytecode: Boolean = settings.compileBytecode private val seedScope: Scope? = settings.seedScope private val useFastLocalRefs: Boolean = settings.useFastLocalRefs private var resolutionScriptDepth = 0 private val resolutionPredeclared = mutableSetOf() private data class ImportedModule(val scope: ModuleScope, val pos: Pos) private data class ImportBindingResolution(val binding: ImportBinding, val record: ObjRecord) private val importedModules = mutableListOf() private val importBindings = mutableMapOf() private val enumEntriesByName = mutableMapOf>() // --- Doc-comment collection state (for immediate preceding declarations) --- private val pendingDocLines = mutableListOf() private var pendingDocStart: Pos? = null private var prevWasComment: Boolean = false private fun stripCommentLexeme(raw: String): String { return when { raw.startsWith("//") -> raw.removePrefix("//") raw.startsWith("/*") && raw.endsWith("*/") -> { val inner = raw.substring(2, raw.length - 2) // Trim leading "*" prefixes per line like Javadoc style inner.lines().joinToString("\n") { line -> val t = line.trimStart() if (t.startsWith("*")) t.removePrefix("*").trimStart() else line } } else -> raw } } private fun seedResolutionFromScope(scope: Scope, pos: Pos) { val sink = resolutionSink ?: return var current: Scope? = scope while (current != null) { for ((name, record) in current.objects) { if (!record.visibility.isPublic) continue if (!resolutionPredeclared.add(name)) continue sink.declareSymbol(name, SymbolKind.LOCAL, record.isMutable, pos) } current = current.parent } } private fun seedNameObjClassFromScope(scope: Scope) { var current: Scope? = scope while (current != null) { for ((name, record) in current.objects) { if (!record.visibility.isPublic) continue if (nameObjClass.containsKey(name)) continue val declaredClass = record.typeDecl?.let { resolveTypeDeclObjClass(it) } if (declaredClass != null) { nameObjClass[name] = declaredClass continue } val resolved = when (val raw = record.value) { is FrameSlotRef -> raw.peekValue() ?: raw.read() is RecordSlotRef -> raw.peekValue() ?: raw.read() else -> raw } when (resolved) { is ObjClass -> nameObjClass[name] = resolved is ObjInstance -> nameObjClass[name] = resolved.objClass is ObjDynamic -> nameObjClass[name] = resolved.objClass } } current = current.parent } } private fun seedNameTypeDeclFromScope(scope: Scope) { var current: Scope? = scope while (current != null) { for ((name, record) in current.objects) { if (!record.visibility.isPublic) continue if (record.typeDecl != null && nameTypeDecl[name] == null) { nameTypeDecl[name] = record.typeDecl } } for ((name, slotIndex) in current.slotNameToIndexSnapshot()) { val record = current.getSlotRecord(slotIndex) if (record.typeDecl != null && nameTypeDecl[name] == null) { nameTypeDecl[name] = record.typeDecl } } current = current.parent } } private fun resolveImportBinding(name: String, pos: Pos): ImportBindingResolution? { val seedRecord = findSeedScopeRecord(name)?.takeIf { it.visibility.isPublic } val rootRecord = importManager.rootScope.objects[name]?.takeIf { it.visibility.isPublic } val moduleMatches = LinkedHashMap>() for (module in importedModules.asReversed()) { val found = LinkedHashMap>() collectModuleRecordMatches(module.scope, name, mutableSetOf(), found) for ((pkg, pair) in found) { if (!moduleMatches.containsKey(pkg)) { moduleMatches[pkg] = ImportedModule(pair.first, module.pos) to pair.second } } } if (seedRecord != null) { val value = seedRecord.value if (!nameObjClass.containsKey(name)) { when (value) { is ObjClass -> nameObjClass[name] = value is ObjInstance -> nameObjClass[name] = value.objClass } } return ImportBindingResolution(ImportBinding(name, ImportBindingSource.Seed), seedRecord) } if (rootRecord != null) { val value = rootRecord.value if (!nameObjClass.containsKey(name)) { when (value) { is ObjClass -> nameObjClass[name] = value is ObjInstance -> nameObjClass[name] = value.objClass } } return ImportBindingResolution(ImportBinding(name, ImportBindingSource.Root), rootRecord) } if (moduleMatches.isEmpty()) return null if (moduleMatches.size > 1) { val byOrigin = LinkedHashMap>>() for ((_, pair) in moduleMatches) { val origin = pair.second.importedFrom?.packageName ?: pair.first.scope.packageName byOrigin.getOrPut(origin) { mutableListOf() }.add(pair) } if (byOrigin.size == 1) { val origin = byOrigin.keys.first() val candidates = byOrigin[origin] ?: mutableListOf() val preferred = candidates.firstOrNull { it.first.scope.packageName == origin } ?: candidates.first() val binding = ImportBinding(name, ImportBindingSource.Module(origin, preferred.first.pos)) val value = preferred.second.value if (!nameObjClass.containsKey(name)) { when (value) { is ObjClass -> nameObjClass[name] = value is ObjInstance -> nameObjClass[name] = value.objClass } } return ImportBindingResolution(binding, preferred.second) } val moduleNames = moduleMatches.keys.toList() throw ScriptError(pos, "symbol $name is ambiguous between imports: ${moduleNames.joinToString(", ")}") } val (module, record) = moduleMatches.values.first() val binding = ImportBinding(name, ImportBindingSource.Module(module.scope.packageName, module.pos)) val value = record.value if (!nameObjClass.containsKey(name)) { when (value) { is ObjClass -> nameObjClass[name] = value is ObjInstance -> nameObjClass[name] = value.objClass } } return ImportBindingResolution(binding, record) } private fun collectModuleRecordMatches( scope: ModuleScope, name: String, visited: MutableSet, out: MutableMap> ) { if (!visited.add(scope.packageName)) return val record = scope.objects[name] if (record != null && record.visibility.isPublic) { if (!out.containsKey(scope.packageName)) { out[scope.packageName] = scope to record } } for (child in scope.importedModules) { collectModuleRecordMatches(child, name, visited, out) } } private fun registerImportBinding(name: String, binding: ImportBinding, pos: Pos) { val existing = importBindings[name] ?: run { importBindings[name] = binding scopeSeedNames.add(name) return } if (!sameImportBinding(existing, binding)) { throw ScriptError(pos, "symbol $name resolves to multiple imports") } } private fun sameImportBinding(left: ImportBinding, right: ImportBinding): Boolean { if (left.symbol != right.symbol) return false val leftSrc = left.source val rightSrc = right.source return when (leftSrc) { is ImportBindingSource.Module -> { rightSrc is ImportBindingSource.Module && leftSrc.name == rightSrc.name } ImportBindingSource.Root -> rightSrc is ImportBindingSource.Root ImportBindingSource.Seed -> rightSrc is ImportBindingSource.Seed } } private fun findSeedScopeRecord(name: String): ObjRecord? { var current = seedScope var hops = 0 while (current != null && hops++ < 1024) { current.objects[name]?.let { return it } current = current.parent } return null } private fun shouldSeedDefaultStdlib(): Boolean { if (seedScope != null) return false if (importManager !== Script.defaultImportManager) return false val sourceName = cc.tokens.firstOrNull()?.pos?.source?.fileName return sourceName != "lyng.stdlib" } private fun looksLikeExtensionReceiver(): Boolean { val saved = cc.savePos() try { if (cc.peekNextNonWhitespace().type != Token.Type.ID) return false cc.nextNonWhitespace() // consume qualified name segments while (cc.peekNextNonWhitespace().type == Token.Type.DOT) { val dotPos = cc.savePos() cc.nextNonWhitespace() if (cc.peekNextNonWhitespace().type != Token.Type.ID) { cc.restorePos(dotPos) break } cc.nextNonWhitespace() val afterSegment = cc.peekNextNonWhitespace() if (afterSegment.type != Token.Type.DOT && afterSegment.type != Token.Type.LT && afterSegment.type != Token.Type.QUESTION && afterSegment.type != Token.Type.IFNULLASSIGN ) { cc.restorePos(dotPos) break } } // optional generic arguments if (cc.peekNextNonWhitespace().type == Token.Type.LT) { var depth = 0 while (true) { val tok = cc.nextNonWhitespace() when (tok.type) { Token.Type.LT -> depth += 1 Token.Type.GT -> { depth -= 1 if (depth <= 0) break } Token.Type.SHR -> { depth -= 2 if (depth <= 0) break } Token.Type.EOF -> return false else -> {} } } } // nullable suffix if (cc.peekNextNonWhitespace().type == Token.Type.QUESTION || cc.peekNextNonWhitespace().type == Token.Type.IFNULLASSIGN ) { cc.nextNonWhitespace() } val dotTok = cc.peekNextNonWhitespace() if (dotTok.type != Token.Type.DOT) return false val savedDot = cc.savePos() cc.nextNonWhitespace() val nameTok = cc.peekNextNonWhitespace() cc.restorePos(savedDot) return nameTok.type == Token.Type.ID } finally { cc.restorePos(saved) } } private fun shouldImplicitTypeVar(name: String, explicit: Set): Boolean { if (explicit.contains(name)) return true if (name.contains('.')) return false if (resolveClassByName(name) != null) return false if (resolveTypeDeclObjClass(TypeDecl.Simple(name, false)) != null) return false return name.length == 1 || name in setOf("T", "R", "E", "K", "V") } private fun normalizeReceiverTypeDecl( receiver: TypeDecl?, explicitTypeParams: Set ): Pair> { if (receiver == null) return null to emptySet() val implicit = mutableSetOf() fun transform(decl: TypeDecl): TypeDecl = when (decl) { is TypeDecl.Simple -> { if (shouldImplicitTypeVar(decl.name, explicitTypeParams)) { if (!explicitTypeParams.contains(decl.name)) implicit += decl.name TypeDecl.TypeVar(decl.name, decl.isNullable) } else decl } is TypeDecl.TypeVar -> { if (!explicitTypeParams.contains(decl.name)) implicit += decl.name decl } is TypeDecl.Generic -> TypeDecl.Generic( decl.name, decl.args.map { transform(it) }, decl.isNullable ) is TypeDecl.Function -> TypeDecl.Function( receiver = decl.receiver?.let { transform(it) }, params = decl.params.map { transform(it) }, returnType = transform(decl.returnType), nullable = decl.isNullable ) is TypeDecl.Ellipsis -> TypeDecl.Ellipsis(transform(decl.elementType), decl.isNullable) is TypeDecl.Union -> TypeDecl.Union(decl.options.map { transform(it) }, decl.isNullable) is TypeDecl.Intersection -> TypeDecl.Intersection(decl.options.map { transform(it) }, decl.isNullable) else -> decl } return transform(receiver) to implicit } private var anonCounter = 0 private fun generateAnonName(pos: Pos): String { return "${"$"}${"Anon"}_${pos.line+1}_${pos.column}_${++anonCounter}" } private fun pushPendingDocToken(t: Token) { val s = stripCommentLexeme(t.value) if (pendingDocStart == null) pendingDocStart = t.pos pendingDocLines += s prevWasComment = true } private fun clearPendingDoc() { pendingDocLines.clear() pendingDocStart = null prevWasComment = false } private fun consumePendingDoc(): MiniDoc? { if (pendingDocLines.isEmpty()) return null val start = pendingDocStart ?: cc.currentPos() val doc = MiniDoc.parse(MiniRange(start, start), pendingDocLines) clearPendingDoc() return doc } private fun nextNonWhitespace(): Token { while (true) { val t = cc.next() when (t.type) { Token.Type.SINGLE_LINE_COMMENT, Token.Type.MULTILINE_COMMENT -> { pushPendingDocToken(t) } Token.Type.NEWLINE -> { if (!prevWasComment) clearPendingDoc() else prevWasComment = false } Token.Type.EOF -> return t else -> return t } } } // Set just before entering a declaration parse, taken from keyword token position private var pendingDeclStart: Pos? = null private var pendingDeclDoc: MiniDoc? = null private val initStack = mutableListOf>() private data class CompileClassInfo( val name: String, val fieldIds: Map, val methodIds: Map, val nextFieldId: Int, val nextMethodId: Int, val baseNames: List ) private val compileClassInfos = mutableMapOf() private val compileClassStubs = mutableMapOf() val currentInitScope: MutableList get() = initStack.lastOrNull() ?: cc.syntaxError("no initialization scope exists here") private fun pushInitScope(): MutableList = mutableListOf().also { initStack.add(it) } private fun popInitScope(): MutableList = initStack.removeLast() private val codeContexts = mutableListOf(CodeContext.Module(null)) // Last parsed block range (for Mini-AST function body attachment) private var lastParsedBlockRange: MiniRange? = null private suspend fun inCodeContext(context: CodeContext, f: suspend () -> T): T { codeContexts.add(context) pushGenericFunctionScope() try { val res = f() if (context is CodeContext.ClassBody) { if (context.pendingInitializations.isNotEmpty()) { val (name, pos) = context.pendingInitializations.entries.first() throw ScriptError(pos, "val '$name' must be initialized in the class body or init block") } } return res } finally { popGenericFunctionScope() codeContexts.removeLast() } } private suspend fun parseScript(): Script { val statements = mutableListOf() val start = cc.currentPos() val topLevelSink = if (resolutionScriptDepth == 0) resolutionSink else null val atTopLevel = topLevelSink != null if (atTopLevel) { topLevelSink.enterScope(ScopeKind.MODULE, start, null) seedScope?.let { seedResolutionFromScope(it, start) } seedResolutionFromScope(importManager.rootScope, start) } resolutionScriptDepth++ // Track locals at script level for fast local refs val needsSlotPlan = slotPlanStack.isEmpty() if (needsSlotPlan) { slotPlanStack.add(SlotPlan(mutableMapOf(), 0, nextScopeId++)) seedScope?.let { scope -> seedSlotPlanFromSeedScope(scope) } val plan = slotPlanStack.last() seedScope?.getSlotIndexOf("__PACKAGE__")?.let { slotIndex -> declareSlotNameAt(plan, "__PACKAGE__", slotIndex, isMutable = false, isDelegated = false) } seedScope?.getSlotIndexOf("$~")?.let { slotIndex -> declareSlotNameAt(plan, "$~", slotIndex, isMutable = true, isDelegated = false) } seedScope?.let { seedNameObjClassFromScope(it) } seedScope?.let { seedNameTypeDeclFromScope(it) } seedNameObjClassFromScope(importManager.rootScope) if (shouldSeedDefaultStdlib()) { val stdlib = importManager.prepareImport(start, "lyng.stdlib", null) seedResolutionFromScope(stdlib, start) seedNameObjClassFromScope(stdlib) seedSlotPlanFromScope(stdlib) importedModules.add(ImportedModule(stdlib, start)) } predeclareTopLevelSymbols() } return try { withLocalNames(emptySet()) { // package level declarations // Notify sink about script start miniSink?.onScriptStart(start) do { val t = cc.current() if (t.type == Token.Type.NEWLINE || t.type == Token.Type.SINGLE_LINE_COMMENT || t.type == Token.Type.MULTILINE_COMMENT) { when (t.type) { Token.Type.SINGLE_LINE_COMMENT, Token.Type.MULTILINE_COMMENT -> pushPendingDocToken(t) Token.Type.NEWLINE -> { // A standalone newline not immediately following a comment resets doc buffer if (!prevWasComment) clearPendingDoc() else prevWasComment = false } } cc.next() continue } if (t.type == Token.Type.ID) { when (t.value) { "package" -> { cc.next() val name = loadQualifiedName() if (name.isEmpty()) throw ScriptError(cc.currentPos(), "Expecting package name here") if (packageName != null) throw ScriptError( cc.currentPos(), "package name redefined, already set to $packageName" ) packageName = name continue } "import" -> { cc.next() val pos = cc.currentPos() val name = loadQualifiedName() // Emit MiniImport with approximate per-segment ranges run { try { val parts = name.split('.') if (parts.isNotEmpty()) { var col = pos.column val segs = parts.map { p -> val start = Pos(pos.source, pos.line, col) val end = Pos(pos.source, pos.line, col + p.length) col += p.length + 1 // account for following '.' between segments MiniImport.Segment( p, MiniRange(start, end) ) } val lastEnd = segs.last().range.end miniSink?.onImport( MiniImport( MiniRange(pos, lastEnd), segs ) ) } } catch (_: Throwable) { // best-effort; ignore import mini emission failures } } val module = importManager.prepareImport(pos, name, null) importedModules.add(ImportedModule(module, pos)) seedResolutionFromScope(module, pos) continue } } // Fast-path: top-level function declarations. Handle here to ensure // Mini-AST emission even if qualifier matcher paths change. if (t.value == "fun" || t.value == "fn") { // Consume the keyword and delegate to the function parser cc.next() pendingDeclStart = t.pos pendingDeclDoc = consumePendingDoc() val st = parseFunctionDeclaration(isExtern = false, isStatic = false) statements += st continue } } val s = parseStatement(braceMeansLambda = true)?.also { statements += it } if (s == null) { when (t.type) { Token.Type.RBRACE, Token.Type.EOF, Token.Type.SEMICOLON -> {} else -> throw ScriptError(t.pos, "unexpected `${t.value}` here") } break } } while (true) val modulePlan = if (needsSlotPlan) slotPlanIndices(slotPlanStack.last()) else emptyMap() val forcedLocalInfo = if (useScopeSlots) emptyMap() else moduleForcedLocalSlotInfo() val forcedLocalScopeId = if (useScopeSlots) null else moduleSlotPlan()?.id val allowedScopeNames = if (useScopeSlots) modulePlan.keys else null val scopeSlotNameSet = if (useScopeSlots) scopeSeedNames else null val moduleScopeId = moduleSlotPlan()?.id val isModuleScript = codeContexts.lastOrNull() is CodeContext.Module && resolutionScriptDepth == 1 val wrapScriptBytecode = compileBytecode && isModuleScript val (finalStatements, moduleBytecode) = if (wrapScriptBytecode) { val unwrapped = statements.map { unwrapBytecodeDeep(it) } val block = InlineBlockStatement(unwrapped, start) val bytecodeStmt = BytecodeStatement.wrap( block, "