diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index 098f901..35a982a 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -44,6 +44,10 @@ class Compiler( private val currentLocalNames: MutableSet? get() = localNamesStack.lastOrNull() + private data class SlotPlan(val slots: MutableMap, var nextIndex: Int) + private data class SlotLocation(val slot: Int, val depth: Int) + private val slotPlanStack = mutableListOf() + // Track declared local variables count per function for precise capacity hints private val localDeclCountStack = mutableListOf() private val currentLocalDeclCount: Int @@ -64,6 +68,35 @@ class Compiler( if (added && localDeclCountStack.isNotEmpty()) { localDeclCountStack[localDeclCountStack.lastIndex] = currentLocalDeclCount + 1 } + declareSlotName(name) + } + + private fun declareSlotName(name: String) { + val plan = slotPlanStack.lastOrNull() ?: return + if (plan.slots.containsKey(name)) return + plan.slots[name] = plan.nextIndex + plan.nextIndex += 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++ + } + } + return SlotPlan(map, idx) + } + + private fun lookupSlotLocation(name: String): SlotLocation? { + for (i in slotPlanStack.indices.reversed()) { + val slot = slotPlanStack[i].slots[name] ?: continue + val depth = slotPlanStack.size - 1 - i + return SlotLocation(slot, depth) + } + return null } var packageName: String? = null @@ -243,9 +276,12 @@ class Compiler( } } val module = importManager.prepareImport(pos, name, null) - statements += statement { - module.importInto(this, null) - ObjVoid + statements += object : Statement() { + override val pos: Pos = pos + override suspend fun execute(scope: Scope): Obj { + module.importInto(scope, null) + return ObjVoid + } } continue } @@ -376,7 +412,13 @@ class Compiler( private suspend fun parseExpression(): Statement? { val pos = cc.currentPos() - return parseExpressionLevel()?.let { a -> statement(pos) { a.evalValue(it) } } + return parseExpressionLevel()?.let { ref -> + val stmtPos = pos + object : Statement() { + override val pos: Pos = stmtPos + override suspend fun execute(scope: Scope): Obj = ref.evalValue(scope) + } + } } private suspend fun parseExpressionLevel(level: Int = 0): ObjRef? { @@ -414,6 +456,10 @@ class Compiler( val name = when (target) { is LocalVarRef -> target.name is FastLocalVarRef -> target.name + is LocalSlotRef -> target.name + is ImplicitThisMemberRef -> target.name + is ThisFieldSlotRef -> target.name + is QualifiedThisFieldSlotRef -> target.name is FieldRef -> if (target.target is LocalVarRef && target.target.name == "this") target.name else null else -> null } @@ -487,7 +533,16 @@ class Compiler( val args = parsed.first val tailBlock = parsed.second isCall = true - operand = MethodCallRef(left, next.value, args, tailBlock, isOptional) + operand = when (left) { + is LocalVarRef -> if (left.name == "this") { + ThisMethodSlotCallRef(next.value, args, tailBlock, isOptional) + } else { + MethodCallRef(left, next.value, args, tailBlock, isOptional) + } + is QualifiedThisRef -> + QualifiedThisMethodSlotCallRef(left.typeName, next.value, args, tailBlock, isOptional) + else -> MethodCallRef(left, next.value, args, tailBlock, isOptional) + } } @@ -496,16 +551,37 @@ class Compiler( cc.next() isCall = true val lambda = parseLambdaExpression() - val argStmt = statement { lambda.get(this).value } + val argPos = next.pos + val argStmt = object : Statement() { + override val pos: Pos = argPos + override suspend fun execute(scope: Scope): Obj = lambda.get(scope).value + } val args = listOf(ParsedArgument(argStmt, next.pos)) - operand = MethodCallRef(left, next.value, args, true, isOptional) + operand = when (left) { + is LocalVarRef -> if (left.name == "this") { + ThisMethodSlotCallRef(next.value, args, true, isOptional) + } else { + MethodCallRef(left, next.value, args, true, isOptional) + } + is QualifiedThisRef -> + QualifiedThisMethodSlotCallRef(left.typeName, next.value, args, true, isOptional) + else -> MethodCallRef(left, next.value, args, true, isOptional) + } } else -> {} } } if (!isCall) { - operand = FieldRef(left, next.value, isOptional) + operand = when (left) { + is LocalVarRef -> if (left.name == "this") { + ThisFieldSlotRef(next.value, isOptional) + } else { + FieldRef(left, next.value, isOptional) + } + is QualifiedThisRef -> QualifiedThisFieldSlotRef(left.typeName, next.value, isOptional) + else -> FieldRef(left, next.value, isOptional) + } } } @@ -597,7 +673,15 @@ class Compiler( // selector: , '.' , // we replace operand with selector code, that // is RW: - operand = FieldRef(left, t.value, false) + operand = when (left) { + is LocalVarRef -> if (left.name == "this") { + ThisFieldSlotRef(t.value, false) + } else { + FieldRef(left, t.value, false) + } + is QualifiedThisRef -> QualifiedThisFieldSlotRef(left.typeName, t.value, false) + else -> FieldRef(left, t.value, false) + } } ?: run { // variable to read or like cc.previous() @@ -696,44 +780,58 @@ class Compiler( ) val paramNames = argsDeclaration?.params?.map { it.name } ?: emptyList() + val hasImplicitIt = argsDeclaration == null + val slotParamNames = if (hasImplicitIt) paramNames + "it" else paramNames + val paramSlotPlan = buildParamSlotPlan(slotParamNames) label?.let { cc.labels.add(it) } - val body = inCodeContext(CodeContext.Function("")) { - withLocalNames(paramNames.toSet()) { - parseBlock(skipLeadingBrace = true) + slotPlanStack.add(paramSlotPlan) + val body = try { + inCodeContext(CodeContext.Function("")) { + withLocalNames(slotParamNames.toSet()) { + parseBlock(skipLeadingBrace = true) + } } + } finally { + slotPlanStack.removeLast() } label?.let { cc.labels.remove(it) } + val paramSlotPlanSnapshot = if (paramSlotPlan.slots.isEmpty()) emptyMap() else paramSlotPlan.slots.toMap() return ValueFnRef { closureScope -> - statement(body.pos) { scope -> - // and the source closure of the lambda which might have other thisObj. - val context = scope.applyClosure(closureScope) - // Execute lambda body in a closure-aware context. Blocks inside the lambda - // will create child scopes as usual, so re-declarations inside loops work. - if (argsDeclaration == null) { - // no args: automatic var 'it' - val l = scope.args.list - val itValue: Obj = when (l.size) { - // no args: it == void - 0 -> ObjVoid - // one args: it is this arg - 1 -> l[0] - // more args: it is a list of args - else -> ObjList(l.toMutableList()) + val stmt = object : Statement() { + override val pos: Pos = body.pos + override suspend fun execute(scope: Scope): Obj { + // and the source closure of the lambda which might have other thisObj. + val context = scope.applyClosure(closureScope) + if (paramSlotPlanSnapshot.isNotEmpty()) context.applySlotPlan(paramSlotPlanSnapshot) + // Execute lambda body in a closure-aware context. Blocks inside the lambda + // will create child scopes as usual, so re-declarations inside loops work. + if (argsDeclaration == null) { + // no args: automatic var 'it' + val l = scope.args.list + val itValue: Obj = when (l.size) { + // no args: it == void + 0 -> ObjVoid + // one args: it is this arg + 1 -> l[0] + // more args: it is a list of args + else -> ObjList(l.toMutableList()) + } + context.addItem("it", false, itValue, recordType = ObjRecord.Type.Argument) + } else { + // assign vars as declared the standard way + argsDeclaration.assignToContext(context, defaultAccessType = AccessType.Val) + } + return try { + body.execute(context) + } catch (e: ReturnException) { + if (e.label == null || e.label == label) e.result + else throw e } - context.addItem("it", false, itValue, recordType = ObjRecord.Type.Argument) - } else { - // assign vars as declared the standard way - argsDeclaration.assignToContext(context, defaultAccessType = AccessType.Val) } - try { - body.execute(context) - } catch (e: ReturnException) { - if (e.label == null || e.label == label) e.result - else throw e - } - }.asReadonly + } + stmt.asReadonly } } @@ -1122,7 +1220,12 @@ class Compiler( val next = cc.peekNextNonWhitespace() if (next.type == Token.Type.COMMA || next.type == Token.Type.RPAREN) { val localVar = LocalVarRef(name, t1.pos) - return ParsedArgument(statement(t1.pos) { localVar.evalValue(it) }, t1.pos, isSplat = false, name = name) + val argPos = t1.pos + val argStmt = object : Statement() { + override val pos: Pos = argPos + override suspend fun execute(scope: Scope): Obj = localVar.evalValue(scope) + } + return ParsedArgument(argStmt, t1.pos, isSplat = false, name = name) } val rhs = parseExpression() ?: t2.raiseSyntax("expected expression after named argument '${name}:'") return ParsedArgument(rhs, t1.pos, isSplat = false, name = name) @@ -1166,8 +1269,9 @@ class Compiler( val callableAccessor = parseLambdaExpression() args += ParsedArgument( // transform ObjRef to the callable value - statement { - callableAccessor.get(this).value + object : Statement() { + override val pos: Pos = end.pos + override suspend fun execute(scope: Scope): Obj = callableAccessor.get(scope).value }, end.pos ) @@ -1194,7 +1298,12 @@ class Compiler( val next = cc.peekNextNonWhitespace() if (next.type == Token.Type.COMMA || next.type == Token.Type.RPAREN) { val localVar = LocalVarRef(name, t1.pos) - return ParsedArgument(statement(t1.pos) { localVar.evalValue(it) }, t1.pos, isSplat = false, name = name) + val argPos = t1.pos + val argStmt = object : Statement() { + override val pos: Pos = argPos + override suspend fun execute(scope: Scope): Obj = localVar.evalValue(scope) + } + return ParsedArgument(argStmt, t1.pos, isSplat = false, name = name) } val rhs = parseExpression() ?: t2.raiseSyntax("expected expression after named argument '${name}:'") return ParsedArgument(rhs, t1.pos, isSplat = false, name = name) @@ -1246,14 +1355,21 @@ class Compiler( // into the lambda body. This ensures expected order: // foo { ... }.bar() == (foo { ... }).bar() val callableAccessor = parseLambdaExpression() - val argStmt = statement { callableAccessor.get(this).value } + val argStmt = object : Statement() { + override val pos: Pos = cc.currentPos() + override suspend fun execute(scope: Scope): Obj = callableAccessor.get(scope).value + } listOf(ParsedArgument(argStmt, cc.currentPos())) } else { val r = parseArgs() detectedBlockArgument = r.second r.first } - return CallRef(left, args, detectedBlockArgument, isOptional) + return when (left) { + is ImplicitThisMemberRef -> + ImplicitThisMethodCallRef(left.name, args, detectedBlockArgument, isOptional, left.atPos) + else -> CallRef(left, args, detectedBlockArgument, isOptional) + } } private suspend fun parseAccessor(): ObjRef? { @@ -1301,9 +1417,17 @@ class Compiler( "null" -> ConstRef(ObjNull.asReadonly) "true" -> ConstRef(ObjTrue.asReadonly) "false" -> ConstRef(ObjFalse.asReadonly) - else -> if (PerfFlags.EMIT_FAST_LOCAL_REFS && (currentLocalNames?.contains(t.value) == true)) - FastLocalVarRef(t.value, t.pos) - else LocalVarRef(t.value, t.pos) + else -> { + val slotLoc = lookupSlotLocation(t.value) + val inClassCtx = codeContexts.any { it is CodeContext.ClassBody } + when { + slotLoc != null -> LocalSlotRef(t.value, slotLoc.slot, slotLoc.depth, t.pos) + PerfFlags.EMIT_FAST_LOCAL_REFS && (currentLocalNames?.contains(t.value) == true) -> + FastLocalVarRef(t.value, t.pos) + inClassCtx -> ImplicitThisMemberRef(t.value, t.pos) + else -> LocalVarRef(t.value, t.pos) + } + } } } @@ -1526,20 +1650,27 @@ class Compiler( lastParsedBlockRange?.let { range -> miniSink?.onInitDecl(MiniInitDecl(MiniRange(id.pos, range.end), id.pos)) } - val initStmt = statement(id.pos) { scp -> - val cls = scp.thisObj.objClass - val saved = scp.currentClassCtx - scp.currentClassCtx = cls - try { - block.execute(scp) - } finally { - scp.currentClassCtx = saved + val initPos = id.pos + val initStmt = object : Statement() { + override val pos: Pos = initPos + override suspend fun execute(scope: Scope): Obj { + val cls = scope.thisObj.objClass + val saved = scope.currentClassCtx + scope.currentClassCtx = cls + try { + block.execute(scope) + } finally { + scope.currentClassCtx = saved + } + return ObjVoid } - ObjVoid } - statement { - currentClassCtx?.instanceInitializers?.add(initStmt) - ObjVoid + object : Statement() { + override val pos: Pos = id.pos + override suspend fun execute(scope: Scope): Obj { + scope.currentClassCtx?.instanceInitializers?.add(initStmt) + return ObjVoid + } } } else null } @@ -1695,9 +1826,13 @@ class Compiler( // we need a copy in the closure: val isIn = t.type == Token.Type.IN val container = parseExpression() ?: throw ScriptError(cc.currentPos(), "type expected") - currentCondition += statement { - val r = container.execute(this).contains(this, whenValue) - ObjBool(if (isIn) r else !r) + val condPos = t.pos + currentCondition += object : Statement() { + override val pos: Pos = condPos + override suspend fun execute(scope: Scope): Obj { + val r = container.execute(scope).contains(scope, whenValue) + return ObjBool(if (isIn) r else !r) + } } } @@ -1705,9 +1840,13 @@ class Compiler( // we need a copy in the closure: val isIn = t.type == Token.Type.IS val caseType = parseExpression() ?: throw ScriptError(cc.currentPos(), "type expected") - currentCondition += statement { - val r = whenValue.isInstanceOf(caseType.execute(this)) - ObjBool(if (isIn) r else !r) + val condPos = t.pos + currentCondition += object : Statement() { + override val pos: Pos = condPos + override suspend fun execute(scope: Scope): Obj { + val r = whenValue.isInstanceOf(caseType.execute(scope)) + return ObjBool(if (isIn) r else !r) + } } } @@ -1737,8 +1876,12 @@ class Compiler( cc.previous() val x = parseExpression() ?: throw ScriptError(cc.currentPos(), "when case condition expected") - currentCondition += statement { - ObjBool(x.execute(this).compareTo(this, whenValue) == 0) + val condPos = t.pos + currentCondition += object : Statement() { + override val pos: Pos = condPos + override suspend fun execute(scope: Scope): Obj { + return ObjBool(x.execute(scope).compareTo(scope, whenValue) == 0) + } } } } @@ -1750,19 +1893,24 @@ class Compiler( for (c in currentCondition) cases += WhenCase(c, block) } } - statement { - var result: Obj = ObjVoid - // in / is and like uses whenValue from closure: - whenValue = value.execute(this) - var found = false - for (c in cases) - if (c.condition.execute(this).toBool()) { - result = c.block.execute(this) - found = true - break + val whenPos = t.pos + object : Statement() { + override val pos: Pos = whenPos + override suspend fun execute(scope: Scope): Obj { + var result: Obj = ObjVoid + // in / is and like uses whenValue from closure: + whenValue = value.execute(scope) + var found = false + for (c in cases) { + if (c.condition.execute(scope).toBool()) { + result = c.block.execute(scope) + found = true + break + } } - if (!found && elseCase != null) result = elseCase.execute(this) - result + if (!found && elseCase != null) result = elseCase.execute(scope) + return result + } } } else { // when { cond -> ... } @@ -1774,28 +1922,32 @@ class Compiler( val throwStatement = parseStatement() ?: throw ScriptError(cc.currentPos(), "throw object expected") // Important: bind the created statement to the position of the `throw` keyword so that // any raised error reports the correct source location. - return statement(start) { sc -> - var errorObject = throwStatement.execute(sc) - // Rebind error scope to the throw-site position so ScriptError.pos is accurate - val throwScope = sc.createChildScope(pos = start) - if (errorObject is ObjString) { - errorObject = ObjException(throwScope, errorObject.value).apply { getStackTrace() } - } - if (!errorObject.isInstanceOf(ObjException.Root)) { - throwScope.raiseError("this is not an exception object: $errorObject") - } - if (errorObject is ObjException) { - errorObject = ObjException( - errorObject.exceptionClass, - throwScope, - errorObject.message, - errorObject.extraData, - errorObject.useStackTrace - ).apply { getStackTrace() } - throwScope.raiseError(errorObject) - } else { - val msg = errorObject.invokeInstanceMethod(sc, "message").toString(sc).value - throwScope.raiseError(errorObject, start, msg) + return object : Statement() { + override val pos: Pos = start + override suspend fun execute(scope: Scope): Obj { + var errorObject = throwStatement.execute(scope) + // Rebind error scope to the throw-site position so ScriptError.pos is accurate + val throwScope = scope.createChildScope(pos = start) + if (errorObject is ObjString) { + errorObject = ObjException(throwScope, errorObject.value).apply { getStackTrace() } + } + if (!errorObject.isInstanceOf(ObjException.Root)) { + throwScope.raiseError("this is not an exception object: $errorObject") + } + if (errorObject is ObjException) { + errorObject = ObjException( + errorObject.exceptionClass, + throwScope, + errorObject.message, + errorObject.extraData, + errorObject.useStackTrace + ).apply { getStackTrace() } + throwScope.raiseError(errorObject) + } else { + val msg = errorObject.invokeInstanceMethod(scope, "message").toString(scope).value + throwScope.raiseError(errorObject, start, msg) + } + return ObjVoid } } } @@ -1868,49 +2020,53 @@ class Compiler( if (catches.isEmpty() && finallyClause == null) throw ScriptError(cc.currentPos(), "try block must have either catch or finally clause or both") - return statement { - var result: Obj = ObjVoid - try { - // body is a parsed block, it already has separate context - result = body.execute(this) - } catch (e: ReturnException) { - throw e - } catch (e: LoopBreakContinueException) { - throw e - } catch (e: Exception) { - // convert to appropriate exception - val caughtObj = when (e) { - is ExecutionError -> e.errorObject - else -> ObjUnknownException(this, e.message ?: e.toString()) - } - // let's see if we should catch it: - var isCaught = false - for (cdata in catches) { - var match: Obj? = null - for (exceptionClassName in cdata.classNames) { - val exObj = this[exceptionClassName]?.value as? ObjClass - ?: raiseSymbolNotFound("error class does not exist or is not a class: $exceptionClassName") - if (caughtObj.isInstanceOf(exObj)) { - match = caughtObj + val stmtPos = body.pos + return object : Statement() { + override val pos: Pos = stmtPos + override suspend fun execute(scope: Scope): Obj { + var result: Obj = ObjVoid + try { + // body is a parsed block, it already has separate context + result = body.execute(scope) + } catch (e: ReturnException) { + throw e + } catch (e: LoopBreakContinueException) { + throw e + } catch (e: Exception) { + // convert to appropriate exception + val caughtObj = when (e) { + is ExecutionError -> e.errorObject + else -> ObjUnknownException(scope, e.message ?: e.toString()) + } + // let's see if we should catch it: + var isCaught = false + for (cdata in catches) { + var match: Obj? = null + for (exceptionClassName in cdata.classNames) { + val exObj = scope[exceptionClassName]?.value as? ObjClass + ?: scope.raiseSymbolNotFound("error class does not exist or is not a class: $exceptionClassName") + if (caughtObj.isInstanceOf(exObj)) { + match = caughtObj + break + } + } + if (match != null) { + val catchContext = scope.createChildScope(pos = cdata.catchVar.pos) + catchContext.addItem(cdata.catchVar.value, false, caughtObj) + result = cdata.block.execute(catchContext) + isCaught = true break } } - if (match != null) { - val catchContext = this.createChildScope(pos = cdata.catchVar.pos) - catchContext.addItem(cdata.catchVar.value, false, caughtObj) - result = cdata.block.execute(catchContext) - isCaught = true - break - } + // rethrow if not caught this exception + if (!isCaught) + throw e + } finally { + // finally clause does not alter result! + finallyClause?.execute(scope) } - // rethrow if not caught this exception - if (!isCaught) - throw e - } finally { - // finally clause does not alter result! - finallyClause?.execute(this) + return result } - result } } @@ -1964,9 +2120,13 @@ class Compiler( ) ) - return statement { - ObjEnumClass.createSimpleEnum(nameToken.value, names).also { - addItem(nameToken.value, false, it, recordType = ObjRecord.Type.Enum) + val stmtPos = startPos + return object : Statement() { + override val pos: Pos = stmtPos + override suspend fun execute(scope: Scope): Obj { + val enumClass = ObjEnumClass.createSimpleEnum(nameToken.value, names) + scope.addItem(nameToken.value, false, enumClass, recordType = ObjRecord.Type.Enum) + return enumClass } } } @@ -2052,33 +2212,36 @@ class Compiler( val initScope = popInitScope() - return statement(startPos) { context -> - val parentClasses = baseSpecs.map { baseSpec -> - val rec = context[baseSpec.name] ?: throw ScriptError(startPos, "unknown base class: ${baseSpec.name}") - (rec.value as? ObjClass) ?: throw ScriptError(startPos, "${baseSpec.name} is not a class") + return object : Statement() { + override val pos: Pos = startPos + override suspend fun execute(scope: Scope): Obj { + val parentClasses = baseSpecs.map { baseSpec -> + val rec = scope[baseSpec.name] ?: throw ScriptError(startPos, "unknown base class: ${baseSpec.name}") + (rec.value as? ObjClass) ?: throw ScriptError(startPos, "${baseSpec.name} is not a class") + } + + val newClass = ObjInstanceClass(className, *parentClasses.toTypedArray()) + newClass.isAnonymous = nameToken == null + newClass.constructorMeta = ArgsDeclaration(emptyList(), Token.Type.RPAREN) + for (i in parentClasses.indices) { + val argsList = baseSpecs[i].args + // In object, we evaluate parent args once at creation time + if (argsList != null) newClass.directParentArgs[parentClasses[i]] = argsList + } + + val classScope = scope.createChildScope(newThisObj = newClass) + classScope.currentClassCtx = newClass + newClass.classScope = classScope + classScope.addConst("object", newClass) + + bodyInit?.execute(classScope) + + // Create instance (singleton) + val instance = newClass.callOn(scope.createChildScope(Arguments.EMPTY)) + if (nameToken != null) + scope.addItem(className, false, instance) + return instance } - - val newClass = ObjInstanceClass(className, *parentClasses.toTypedArray()) - newClass.isAnonymous = nameToken == null - newClass.constructorMeta = ArgsDeclaration(emptyList(), Token.Type.RPAREN) - for (i in parentClasses.indices) { - val argsList = baseSpecs[i].args - // In object, we evaluate parent args once at creation time - if (argsList != null) newClass.directParentArgs[parentClasses[i]] = argsList - } - - val classScope = context.createChildScope(newThisObj = newClass) - classScope.currentClassCtx = newClass - newClass.classScope = classScope - classScope.addConst("object", newClass) - - bodyInit?.execute(classScope) - - // Create instance (singleton) - val instance = newClass.callOn(context.createChildScope(Arguments.EMPTY)) - if (nameToken != null) - context.addItem(className, false, instance) - instance } } @@ -2198,70 +2361,76 @@ class Compiler( // create instance constructor // create custom objClass with all fields and instance constructor - val constructorCode = statement { - // constructor code is registered with class instance and is called over - // new `thisObj` already set by class to ObjInstance.instanceContext - val instance = thisObj as ObjInstance - // Constructor parameters have been assigned to instance scope by ObjClass.callOn before - // invoking parent/child constructors. - // IMPORTANT: do not execute class body here; class body was executed once in the class scope - // to register methods and prepare initializers. Instance constructor should be empty unless - // we later add explicit constructor body syntax. - instance - } - statement { - // the main statement should create custom ObjClass instance with field - // accessors, constructor registration, etc. - // Resolve parent classes by name at execution time - val parentClasses = baseSpecs.map { baseSpec -> - val rec = - this[baseSpec.name] ?: throw ScriptError(nameToken.pos, "unknown base class: ${baseSpec.name}") - (rec.value as? ObjClass) ?: throw ScriptError(nameToken.pos, "${baseSpec.name} is not a class") + val constructorCode = object : Statement() { + override val pos: Pos = startPos + override suspend fun execute(scope: Scope): Obj { + // constructor code is registered with class instance and is called over + // new `thisObj` already set by class to ObjInstance.instanceContext + val instance = scope.thisObj as ObjInstance + // Constructor parameters have been assigned to instance scope by ObjClass.callOn before + // invoking parent/child constructors. + // IMPORTANT: do not execute class body here; class body was executed once in the class scope + // to register methods and prepare initializers. Instance constructor should be empty unless + // we later add explicit constructor body syntax. + return instance } - - val newClass = ObjInstanceClass(className, *parentClasses.toTypedArray()).also { - it.isAbstract = isAbstract - it.instanceConstructor = constructorCode - it.constructorMeta = constructorArgsDeclaration - // Attach per-parent constructor args (thunks) if provided - for (i in parentClasses.indices) { - val argsList = baseSpecs[i].args - if (argsList != null) it.directParentArgs[parentClasses[i]] = argsList + } + object : Statement() { + override val pos: Pos = startPos + override suspend fun execute(scope: Scope): Obj { + // the main statement should create custom ObjClass instance with field + // accessors, constructor registration, etc. + // Resolve parent classes by name at execution time + val parentClasses = baseSpecs.map { baseSpec -> + val rec = + scope[baseSpec.name] ?: throw ScriptError(nameToken.pos, "unknown base class: ${baseSpec.name}") + (rec.value as? ObjClass) ?: throw ScriptError(nameToken.pos, "${baseSpec.name} is not a class") } - // Register constructor fields in the class members - constructorArgsDeclaration?.params?.forEach { p -> - if (p.accessType != null) { - it.createField( - p.name, ObjNull, - isMutable = p.accessType == AccessType.Var, - visibility = p.visibility ?: Visibility.Public, - declaringClass = it, - // Constructor fields are not currently supporting override/closed in parser - // but we should pass Pos.builtIn to skip validation for now if needed, - // or p.pos to allow it. - pos = Pos.builtIn, - isTransient = p.isTransient, - type = ObjRecord.Type.ConstructorField - ) + + val newClass = ObjInstanceClass(className, *parentClasses.toTypedArray()).also { + it.isAbstract = isAbstract + it.instanceConstructor = constructorCode + it.constructorMeta = constructorArgsDeclaration + // Attach per-parent constructor args (thunks) if provided + for (i in parentClasses.indices) { + val argsList = baseSpecs[i].args + if (argsList != null) it.directParentArgs[parentClasses[i]] = argsList + } + // Register constructor fields in the class members + constructorArgsDeclaration?.params?.forEach { p -> + if (p.accessType != null) { + it.createField( + p.name, ObjNull, + isMutable = p.accessType == AccessType.Var, + visibility = p.visibility ?: Visibility.Public, + declaringClass = it, + // Constructor fields are not currently supporting override/closed in parser + // but we should pass Pos.builtIn to skip validation for now if needed, + // or p.pos to allow it. + pos = Pos.builtIn, + isTransient = p.isTransient, + type = ObjRecord.Type.ConstructorField + ) + } } } - } - addItem(className, false, newClass) - // Prepare class scope for class-scope members (static) and future registrations - val classScope = createChildScope(newThisObj = newClass) - // Set lexical class context for visibility tagging inside class body - classScope.currentClassCtx = newClass - newClass.classScope = classScope - // Execute class body once in class scope to register instance methods and prepare instance field initializers - bodyInit?.execute(classScope) - if (initScope.isNotEmpty()) { - for (s in initScope) - s.execute(classScope) + scope.addItem(className, false, newClass) + // Prepare class scope for class-scope members (static) and future registrations + val classScope = scope.createChildScope(newThisObj = newClass) + // Set lexical class context for visibility tagging inside class body + classScope.currentClassCtx = newClass + newClass.classScope = classScope + // Execute class body once in class scope to register instance methods and prepare instance field initializers + bodyInit?.execute(classScope) + if (initScope.isNotEmpty()) { + for (s in initScope) + s.execute(classScope) + } + newClass.checkAbstractSatisfaction(nameToken.pos) + // Debug summary: list registered instance methods and class-scope functions for this class + return newClass } - newClass.checkAbstractSatisfaction(nameToken.pos) - // Debug summary: list registered instance methods and class-scope functions for this class - newClass } } @@ -2319,77 +2488,80 @@ class Compiler( Triple(loopParsed.first, loopParsed.second, elseStmt) } - return statement(body.pos) { cxt -> - val forContext = cxt.createChildScope(start) + return object : Statement() { + override val pos: Pos = body.pos + override suspend fun execute(scope: Scope): Obj { + val forContext = scope.createChildScope(start) - // loop var: StoredObject - val loopSO = forContext.addItem(tVar.value, true, ObjNull) + // loop var: StoredObject + val loopSO = forContext.addItem(tVar.value, true, ObjNull) - // insofar we suggest source object is enumerable. Later we might need to add checks - val sourceObj = source.execute(forContext) + // insofar we suggest source object is enumerable. Later we might need to add checks + val sourceObj = source.execute(forContext) - if (sourceObj is ObjRange && sourceObj.isIntRange && PerfFlags.PRIMITIVE_FASTOPS) { - loopIntRange( - forContext, - sourceObj.start!!.toLong(), - if (sourceObj.isEndInclusive) - sourceObj.end!!.toLong() + 1 - else - sourceObj.end!!.toLong(), - loopSO, - body, - elseStatement, - label, - canBreak - ) - } else if (sourceObj.isInstanceOf(ObjIterable)) { - loopIterable(forContext, sourceObj, loopSO, body, elseStatement, label, canBreak) - } else { - val size = runCatching { sourceObj.readField(forContext, "size").value.toInt() } - .getOrElse { - throw ScriptError( - tOp.pos, - "object is not enumerable: no size in $sourceObj", - it - ) - } - - var result: Obj = ObjVoid - var breakCaught = false - - if (size > 0) { - var current = runCatching { sourceObj.getAt(forContext, ObjInt.of(0)) } + if (sourceObj is ObjRange && sourceObj.isIntRange && PerfFlags.PRIMITIVE_FASTOPS) { + return loopIntRange( + forContext, + sourceObj.start!!.toLong(), + if (sourceObj.isEndInclusive) + sourceObj.end!!.toLong() + 1 + else + sourceObj.end!!.toLong(), + loopSO, + body, + elseStatement, + label, + canBreak + ) + } else if (sourceObj.isInstanceOf(ObjIterable)) { + return loopIterable(forContext, sourceObj, loopSO, body, elseStatement, label, canBreak) + } else { + val size = runCatching { sourceObj.readField(forContext, "size").value.toInt() } .getOrElse { throw ScriptError( tOp.pos, - "object is not enumerable: no index access for ${sourceObj.inspect(cxt)}", + "object is not enumerable: no size in $sourceObj", it ) } - var index = 0 - while (true) { - loopSO.value = current - try { - result = body.execute(forContext) - } catch (lbe: LoopBreakContinueException) { - if (lbe.label == label || lbe.label == null) { - breakCaught = true - if (lbe.doContinue) continue - else { - result = lbe.result - break - } - } else - throw lbe + + var result: Obj = ObjVoid + var breakCaught = false + + if (size > 0) { + var current = runCatching { sourceObj.getAt(forContext, ObjInt.of(0)) } + .getOrElse { + throw ScriptError( + tOp.pos, + "object is not enumerable: no index access for ${sourceObj.inspect(scope)}", + it + ) + } + var index = 0 + while (true) { + loopSO.value = current + try { + result = body.execute(forContext) + } catch (lbe: LoopBreakContinueException) { + if (lbe.label == label || lbe.label == null) { + breakCaught = true + if (lbe.doContinue) continue + else { + result = lbe.result + break + } + } else + throw lbe + } + if (++index >= size) break + current = sourceObj.getAt(forContext, ObjInt.of(index.toLong())) } - if (++index >= size) break - current = sourceObj.getAt(forContext, ObjInt.of(index.toLong())) } + if (!breakCaught && elseStatement != null) { + result = elseStatement.execute(scope) + } + return result } - if (!breakCaught && elseStatement != null) { - result = elseStatement.execute(cxt) - } - result } } } else { @@ -2484,31 +2656,34 @@ class Compiler( null } - return statement(body.pos) { - var wasBroken = false - var result: Obj = ObjVoid - while (true) { - val doScope = it.createChildScope().apply { skipScopeCreation = true } - try { - result = body.execute(doScope) - } catch (e: LoopBreakContinueException) { - if (e.label == label || e.label == null) { - if (!e.doContinue) { - result = e.result - wasBroken = true - break + return object : Statement() { + override val pos: Pos = body.pos + override suspend fun execute(scope: Scope): Obj { + var wasBroken = false + var result: Obj = ObjVoid + while (true) { + val doScope = scope.createChildScope().apply { skipScopeCreation = true } + try { + result = body.execute(doScope) + } catch (e: LoopBreakContinueException) { + if (e.label == label || e.label == null) { + if (!e.doContinue) { + result = e.result + wasBroken = true + break + } + // for continue: just fall through to condition check below + } else { + throw e } - // for continue: just fall through to condition check below - } else { - throw e + } + if (!condition.execute(doScope).toBool()) { + break } } - if (!condition.execute(doScope).toBool()) { - break - } + if (!wasBroken) elseStatement?.let { s -> result = s.execute(scope) } + return result } - if (!wasBroken) elseStatement?.let { s -> result = s.execute(it) } - result } } @@ -2532,30 +2707,33 @@ class Compiler( cc.previous() null } - return statement(body.pos) { - var result: Obj = ObjVoid - var wasBroken = false - while (condition.execute(it).toBool()) { - val loopScope = it.createChildScope() - if (canBreak) { - try { + return object : Statement() { + override val pos: Pos = body.pos + override suspend fun execute(scope: Scope): Obj { + var result: Obj = ObjVoid + var wasBroken = false + while (condition.execute(scope).toBool()) { + val loopScope = scope.createChildScope() + if (canBreak) { + try { + result = body.execute(loopScope) + } catch (lbe: LoopBreakContinueException) { + if (lbe.label == label || lbe.label == null) { + if (lbe.doContinue) continue + else { + result = lbe.result + wasBroken = true + break + } + } else + throw lbe + } + } else result = body.execute(loopScope) - } catch (lbe: LoopBreakContinueException) { - if (lbe.label == label || lbe.label == null) { - if (lbe.doContinue) continue - else { - result = lbe.result - wasBroken = true - break - } - } else - throw lbe - } - } else - result = body.execute(loopScope) + } + if (!wasBroken) elseStatement?.let { s -> result = s.execute(scope) } + return result } - if (!wasBroken) elseStatement?.let { s -> result = s.execute(it) } - result } } @@ -2590,13 +2768,16 @@ class Compiler( cc.addBreak() - return statement(start) { - val returnValue = resultExpr?.execute(it)// ?: ObjVoid - throw LoopBreakContinueException( - doContinue = false, - label = label, - result = returnValue ?: ObjVoid - ) + return object : Statement() { + override val pos: Pos = start + override suspend fun execute(scope: Scope): Obj { + val returnValue = resultExpr?.execute(scope)// ?: ObjVoid + throw LoopBreakContinueException( + doContinue = false, + label = label, + result = returnValue ?: ObjVoid + ) + } } } @@ -2614,11 +2795,14 @@ class Compiler( } cc.addBreak() - return statement(start) { - throw LoopBreakContinueException( - doContinue = true, - label = label, - ) + return object : Statement() { + override val pos: Pos = start + override suspend fun execute(scope: Scope): Obj { + throw LoopBreakContinueException( + doContinue = true, + label = label, + ) + } } } @@ -2648,9 +2832,12 @@ class Compiler( parseExpression() } else null - return statement(start) { - val returnValue = resultExpr?.execute(it) ?: ObjVoid - throw ReturnException(returnValue, label) + return object : Statement() { + override val pos: Pos = start + override suspend fun execute(scope: Scope): Obj { + val returnValue = resultExpr?.execute(scope) ?: ObjVoid + throw ReturnException(returnValue, label) + } } } @@ -2686,19 +2873,24 @@ class Compiler( return if (t2.type == Token.Type.ID && t2.value == "else") { val elseBody = parseStatement() ?: throw ScriptError(pos, "Bad else statement: expected statement") - return statement(start) { - if (condition.execute(it).toBool()) - ifBody.execute(it) - else - elseBody.execute(it) + return object : Statement() { + override val pos: Pos = start + override suspend fun execute(scope: Scope): Obj { + return if (condition.execute(scope).toBool()) + ifBody.execute(scope) + else + elseBody.execute(scope) + } } } else { cc.previous() - statement(start) { - if (condition.execute(it).toBool()) - ifBody.execute(it) - else - ObjVoid + object : Statement() { + override val pos: Pos = start + override suspend fun execute(scope: Scope): Obj { + if (condition.execute(scope).toBool()) + return ifBody.execute(scope) + return ObjVoid + } } } } @@ -2803,175 +2995,203 @@ class Compiler( cc.labels.add(name) outerLabel?.let { cc.labels.add(it) } - val paramNames: Set = argsDeclaration.params.map { it.name }.toSet() + val paramNamesList = argsDeclaration.params.map { it.name } + val paramNames: Set = paramNamesList.toSet() + val paramSlotPlan = buildParamSlotPlan(paramNamesList) // Parse function body while tracking declared locals to compute precise capacity hints currentLocalDeclCount localDeclCountStack.add(0) - val fnStatements = if (actualExtern) - statement { raiseError("extern function not provided: $name") } - else if (isAbstract || isDelegated) { - null - } else - withLocalNames(paramNames) { - val next = cc.peekNextNonWhitespace() - if (next.type == Token.Type.ASSIGN) { - cc.nextNonWhitespace() // consume '=' - if (cc.peekNextNonWhitespace().value == "return") - throw ScriptError(cc.currentPos(), "return is not allowed in shorthand function") - val expr = parseExpression() ?: throw ScriptError(cc.currentPos(), "Expected function body expression") - // Shorthand function returns the expression value - statement(expr.pos) { scope -> - expr.execute(scope) + slotPlanStack.add(paramSlotPlan) + val fnStatements = try { + if (actualExtern) + object : Statement() { + override val pos: Pos = start + override suspend fun execute(scope: Scope): Obj { + scope.raiseError("extern function not provided: $name") } - } else { - parseBlock() } - } + else if (isAbstract || isDelegated) { + null + } else + withLocalNames(paramNames) { + val next = cc.peekNextNonWhitespace() + if (next.type == Token.Type.ASSIGN) { + cc.nextNonWhitespace() // consume '=' + if (cc.peekNextNonWhitespace().value == "return") + throw ScriptError(cc.currentPos(), "return is not allowed in shorthand function") + val expr = parseExpression() ?: throw ScriptError(cc.currentPos(), "Expected function body expression") + // Shorthand function returns the expression value + object : Statement() { + override val pos: Pos = expr.pos + override suspend fun execute(scope: Scope): Obj = expr.execute(scope) + } + } else { + parseBlock() + } + } + } finally { + slotPlanStack.removeLast() + } // Capture and pop the local declarations count for this function val fnLocalDecls = localDeclCountStack.removeLastOrNull() ?: 0 var closure: Scope? = null - val fnBody = statement(t.pos) { callerContext -> - callerContext.pos = start + val paramSlotPlanSnapshot = if (paramSlotPlan.slots.isEmpty()) emptyMap() else paramSlotPlan.slots.toMap() + val fnBody = object : Statement() { + override val pos: Pos = t.pos + override suspend fun execute(callerContext: Scope): Obj { + callerContext.pos = start - // restore closure where the function was defined, and making a copy of it - // for local space. If there is no closure, we are in, say, class context where - // the closure is in the class initialization and we needn't more: - val context = closure?.let { ClosureScope(callerContext, it) } - ?: callerContext + // restore closure where the function was defined, and making a copy of it + // for local space. If there is no closure, we are in, say, class context where + // the closure is in the class initialization and we needn't more: + val context = closure?.let { ClosureScope(callerContext, it) } + ?: callerContext - // Capacity hint: parameters + declared locals + small overhead - val capacityHint = paramNames.size + fnLocalDecls + 4 - context.hintLocalCapacity(capacityHint) + // Capacity hint: parameters + declared locals + small overhead + val capacityHint = paramNames.size + fnLocalDecls + 4 + context.hintLocalCapacity(capacityHint) + if (paramSlotPlanSnapshot.isNotEmpty()) context.applySlotPlan(paramSlotPlanSnapshot) - // load params from caller context - argsDeclaration.assignToContext(context, callerContext.args, defaultAccessType = AccessType.Val) - if (extTypeName != null) { - context.thisObj = callerContext.thisObj - } - try { - fnStatements?.execute(context) ?: ObjVoid - } catch (e: ReturnException) { - if (e.label == null || e.label == name || e.label == outerLabel) e.result - else throw e + // load params from caller context + argsDeclaration.assignToContext(context, callerContext.args, defaultAccessType = AccessType.Val) + if (extTypeName != null) { + context.thisObj = callerContext.thisObj + } + return try { + fnStatements?.execute(context) ?: ObjVoid + } catch (e: ReturnException) { + if (e.label == null || e.label == name || e.label == outerLabel) e.result + else throw e + } } } cc.labels.remove(name) outerLabel?.let { cc.labels.remove(it) } // parentContext - val fnCreateStatement = statement(start) { context -> - if (isDelegated) { - val accessType = context.resolveQualifiedIdentifier("DelegateAccess.Callable") - val initValue = delegateExpression!!.execute(context) - val finalDelegate = try { - initValue.invokeInstanceMethod(context, "bind", Arguments(ObjString(name), accessType, context.thisObj)) - } catch (e: Exception) { - initValue - } - - if (extTypeName != null) { - val type = context[extTypeName]?.value ?: context.raiseSymbolNotFound("class $extTypeName not found") - if (type !is ObjClass) context.raiseClassCastError("$extTypeName is not the class instance") - context.addExtension(type, name, ObjRecord(ObjUnset, isMutable = false, visibility = visibility, declaringClass = null, type = ObjRecord.Type.Delegated).apply { - delegate = finalDelegate - }) - return@statement ObjVoid - } - - val th = context.thisObj - if (isStatic) { - (th as ObjClass).createClassField(name, ObjUnset, false, visibility, null, start, isTransient = isTransient, type = ObjRecord.Type.Delegated).apply { - delegate = finalDelegate + val fnCreateStatement = object : Statement() { + override val pos: Pos = start + override suspend fun execute(context: Scope): Obj { + if (isDelegated) { + val accessType = context.resolveQualifiedIdentifier("DelegateAccess.Callable") + val initValue = delegateExpression!!.execute(context) + val finalDelegate = try { + initValue.invokeInstanceMethod(context, "bind", Arguments(ObjString(name), accessType, context.thisObj)) + } catch (e: Exception) { + initValue } - context.addItem(name, false, ObjUnset, visibility, recordType = ObjRecord.Type.Delegated, isTransient = isTransient).apply { - delegate = finalDelegate - } - } else if (th is ObjClass) { - val cls: ObjClass = th - val storageName = "${cls.className}::$name" - cls.createField(name, ObjUnset, false, visibility, null, start, declaringClass = cls, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride, isTransient = isTransient, type = ObjRecord.Type.Delegated) - cls.instanceInitializers += statement(start) { scp -> - val accessType2 = scp.resolveQualifiedIdentifier("DelegateAccess.Callable") - val initValue2 = delegateExpression.execute(scp) - val finalDelegate2 = try { - initValue2.invokeInstanceMethod(scp, "bind", Arguments(ObjString(name), accessType2, scp.thisObj)) - } catch (e: Exception) { - initValue2 - } - scp.addItem(storageName, false, ObjUnset, visibility, null, recordType = ObjRecord.Type.Delegated, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride, isTransient = isTransient).apply { - delegate = finalDelegate2 - } - ObjVoid - } - } else { - context.addItem(name, false, ObjUnset, visibility, recordType = ObjRecord.Type.Delegated, isTransient = isTransient).apply { - delegate = finalDelegate - } - } - return@statement ObjVoid - } - // we added fn in the context. now we must save closure - // for the function, unless we're in the class scope: - if (isStatic || parentContext !is CodeContext.ClassBody) - closure = context - - val annotatedFnBody = annotation?.invoke(context, ObjString(name), fnBody) - ?: fnBody - - extTypeName?.let { typeName -> - // class extension method - val type = context[typeName]?.value ?: context.raiseSymbolNotFound("class $typeName not found") - if (type !is ObjClass) context.raiseClassCastError("$typeName is not the class instance") - val stmt = statement { - // ObjInstance has a fixed instance scope, so we need to build a closure - (thisObj as? ObjInstance)?.let { i -> - annotatedFnBody.execute(ClosureScope(this, i.instanceScope)) + if (extTypeName != null) { + val type = context[extTypeName]?.value ?: context.raiseSymbolNotFound("class $extTypeName not found") + if (type !is ObjClass) context.raiseClassCastError("$extTypeName is not the class instance") + context.addExtension(type, name, ObjRecord(ObjUnset, isMutable = false, visibility = visibility, declaringClass = null, type = ObjRecord.Type.Delegated).apply { + delegate = finalDelegate + }) + return ObjVoid } - // other classes can create one-time scope for this rare case: - ?: annotatedFnBody.execute(thisObj.autoInstanceScope(this)) - } - context.addExtension(type, name, ObjRecord(stmt, isMutable = false, visibility = visibility, declaringClass = null)) - } - // regular function/method - ?: run { + val th = context.thisObj - if (!isStatic && th is ObjClass) { - // Instance method declared inside a class body: register on the class + if (isStatic) { + (th as ObjClass).createClassField(name, ObjUnset, false, visibility, null, start, isTransient = isTransient, type = ObjRecord.Type.Delegated).apply { + delegate = finalDelegate + } + context.addItem(name, false, ObjUnset, visibility, recordType = ObjRecord.Type.Delegated, isTransient = isTransient).apply { + delegate = finalDelegate + } + } else if (th is ObjClass) { val cls: ObjClass = th - cls.addFn( - name, - isMutable = true, - visibility = visibility, - isAbstract = isAbstract, - isClosed = isClosed, - isOverride = isOverride, - pos = start - ) { - // Execute with the instance as receiver; set caller lexical class for visibility - val savedCtx = this.currentClassCtx - this.currentClassCtx = cls - try { - (thisObj as? ObjInstance)?.let { i -> - annotatedFnBody.execute(ClosureScope(this, i.instanceScope)) - } ?: annotatedFnBody.execute(thisObj.autoInstanceScope(this)) - } finally { - this.currentClassCtx = savedCtx + val storageName = "${cls.className}::$name" + cls.createField(name, ObjUnset, false, visibility, null, start, declaringClass = cls, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride, isTransient = isTransient, type = ObjRecord.Type.Delegated) + cls.instanceInitializers += object : Statement() { + override val pos: Pos = start + override suspend fun execute(scp: Scope): Obj { + val accessType2 = scp.resolveQualifiedIdentifier("DelegateAccess.Callable") + val initValue2 = delegateExpression.execute(scp) + val finalDelegate2 = try { + initValue2.invokeInstanceMethod(scp, "bind", Arguments(ObjString(name), accessType2, scp.thisObj)) + } catch (e: Exception) { + initValue2 + } + scp.addItem(storageName, false, ObjUnset, visibility, null, recordType = ObjRecord.Type.Delegated, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride, isTransient = isTransient).apply { + delegate = finalDelegate2 + } + return ObjVoid } } - // also expose the symbol in the class scope for possible references - context.addItem(name, false, annotatedFnBody, visibility) - annotatedFnBody } else { - // top-level or nested function - context.addItem(name, false, annotatedFnBody, visibility) + context.addItem(name, false, ObjUnset, visibility, recordType = ObjRecord.Type.Delegated, isTransient = isTransient).apply { + delegate = finalDelegate + } } + return ObjVoid } - // as the function can be called from anywhere, we have - // saved the proper context in the closure - annotatedFnBody + + // we added fn in the context. now we must save closure + // for the function, unless we're in the class scope: + if (isStatic || parentContext !is CodeContext.ClassBody) + closure = context + + val annotatedFnBody = annotation?.invoke(context, ObjString(name), fnBody) + ?: fnBody + + extTypeName?.let { typeName -> + // class extension method + val type = context[typeName]?.value ?: context.raiseSymbolNotFound("class $typeName not found") + if (type !is ObjClass) context.raiseClassCastError("$typeName is not the class instance") + val stmt = object : Statement() { + override val pos: Pos = start + override suspend fun execute(scope: Scope): Obj { + // ObjInstance has a fixed instance scope, so we need to build a closure + val result = (scope.thisObj as? ObjInstance)?.let { i -> + annotatedFnBody.execute(ClosureScope(scope, i.instanceScope)) + } + // other classes can create one-time scope for this rare case: + ?: annotatedFnBody.execute(scope.thisObj.autoInstanceScope(scope)) + return result + } + } + context.addExtension(type, name, ObjRecord(stmt, isMutable = false, visibility = visibility, declaringClass = null)) + } + // regular function/method + ?: run { + val th = context.thisObj + if (!isStatic && th is ObjClass) { + // Instance method declared inside a class body: register on the class + val cls: ObjClass = th + cls.addFn( + name, + isMutable = true, + visibility = visibility, + isAbstract = isAbstract, + isClosed = isClosed, + isOverride = isOverride, + pos = start + ) { + // Execute with the instance as receiver; set caller lexical class for visibility + val savedCtx = this.currentClassCtx + this.currentClassCtx = cls + try { + (thisObj as? ObjInstance)?.let { i -> + annotatedFnBody.execute(ClosureScope(this, i.instanceScope)) + } ?: annotatedFnBody.execute(thisObj.autoInstanceScope(this)) + } finally { + this.currentClassCtx = savedCtx + } + } + // also expose the symbol in the class scope for possible references + context.addItem(name, false, annotatedFnBody, visibility) + annotatedFnBody + } else { + // top-level or nested function + context.addItem(name, false, annotatedFnBody, visibility) + } + } + // as the function can be called from anywhere, we have + // saved the proper context in the closure + return annotatedFnBody + } } if (isStatic) { currentInitScope += fnCreateStatement @@ -3013,11 +3233,24 @@ class Compiler( if (t.type != Token.Type.LBRACE) throw ScriptError(t.pos, "Expected block body start: {") } - val block = parseScript() - return statement(startPos) { - // block run on inner context: - block.execute(if (it.skipScopeCreation) it else it.createChildScope(startPos)) - }.also { + val blockSlotPlan = SlotPlan(mutableMapOf(), 0) + slotPlanStack.add(blockSlotPlan) + val block = try { + parseScript() + } finally { + slotPlanStack.removeLast() + } + val planSnapshot = if (blockSlotPlan.slots.isEmpty()) emptyMap() else blockSlotPlan.slots.toMap() + val stmt = object : Statement() { + override val pos: Pos = startPos + override suspend fun execute(scope: Scope): Obj { + // block run on inner context: + val target = if (scope.skipScopeCreation) scope else scope.createChildScope(startPos) + if (planSnapshot.isNotEmpty()) target.applySlotPlan(planSnapshot) + return block.execute(target) + } + } + return stmt.also { val t1 = cc.next() if (t1.type != Token.Type.RBRACE) throw ScriptError(t1.pos, "unbalanced braces: expected block body end: }") @@ -3079,22 +3312,25 @@ class Compiler( val names = mutableListOf() pattern.forEachVariable { names.add(it) } - return statement(start) { context -> - val value = initialExpression.execute(context) - for (name in names) { - context.addItem(name, true, ObjVoid, visibility, isTransient = isTransient) - } - pattern.setAt(start, context, value) - if (!isMutable) { + return object : Statement() { + override val pos: Pos = start + override suspend fun execute(context: Scope): Obj { + val value = initialExpression.execute(context) for (name in names) { - val rec = context.objects[name]!! - val immutableRec = rec.copy(isMutable = false) - context.objects[name] = immutableRec - context.localBindings[name] = immutableRec - context.updateSlotFor(name, immutableRec) + context.addItem(name, true, ObjVoid, visibility, isTransient = isTransient) } + pattern.setAt(start, context, value) + if (!isMutable) { + for (name in names) { + val rec = context.objects[name]!! + val immutableRec = rec.copy(isMutable = false) + context.objects[name] = immutableRec + context.localBindings[name] = immutableRec + context.updateSlotFor(name, immutableRec) + } + } + return ObjVoid } - ObjVoid } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt index 4a1b0cc..86ca7cf 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt @@ -392,6 +392,24 @@ open class Scope( nameToSlot[name]?.let { slots[it] = record } } + /** + * Apply a precomputed slot plan (name -> slot index) for this scope. + * This enables direct slot references to bypass name-based lookup. + */ + fun applySlotPlan(plan: Map) { + if (plan.isEmpty()) return + val maxIndex = plan.values.maxOrNull() ?: return + if (slots.size <= maxIndex) { + val targetSize = maxIndex + 1 + while (slots.size < targetSize) { + slots.add(ObjRecord(ObjUnset, isMutable = true)) + } + } + for ((name, idx) in plan) { + nameToSlot[name] = idx + } + } + /** * Clear all references and maps to prevent memory leaks when pooled. */ @@ -503,6 +521,7 @@ open class Scope( if (this is ClosureScope) { callScope.localBindings[name] = it } + bumpClassLayoutIfNeeded(name, value, recordType) it } ?: addItem(name, true, value, visibility, writeVisibility, recordType, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride) @@ -529,6 +548,24 @@ open class Scope( isTransient = isTransient ) objects[name] = rec + bumpClassLayoutIfNeeded(name, value, recordType) + if (recordType == ObjRecord.Type.Field || recordType == ObjRecord.Type.ConstructorField) { + val inst = thisObj as? net.sergeych.lyng.obj.ObjInstance + if (inst != null) { + val slot = inst.objClass.fieldSlotForKey(name) + if (slot != null) inst.setFieldSlotRecord(slot.slot, rec) + } + } + if (value is Statement || + recordType == ObjRecord.Type.Fun || + recordType == ObjRecord.Type.Delegated || + recordType == ObjRecord.Type.Property) { + val inst = thisObj as? net.sergeych.lyng.obj.ObjInstance + if (inst != null) { + val slot = inst.objClass.methodSlotForKey(name) + if (slot != null) inst.setMethodSlotRecord(slot.slot, rec) + } + } // Index this binding within the current frame to help resolve locals across suspension localBindings[name] = rec // If we are a ClosureScope, mirror binding into the caller frame to keep it discoverable @@ -558,6 +595,14 @@ open class Scope( return rec } + private fun bumpClassLayoutIfNeeded(name: String, value: Obj, recordType: ObjRecord.Type) { + val cls = thisObj as? net.sergeych.lyng.obj.ObjClass ?: return + if (cls.classScope !== this) return + if (!(value is Statement || recordType == ObjRecord.Type.Fun || recordType == ObjRecord.Type.Delegated)) return + if (cls.members.containsKey(name)) return + cls.layoutVersion += 1 + } + fun getOrCreateNamespace(name: String): ObjClass { val ns = objects.getOrPut(name) { ObjRecord(ObjNamespace(name), isMutable = false) }.value return ns.objClass diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt index d00ebb4..52855e3 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt @@ -114,8 +114,7 @@ open class ObjClass( val classId: Long = ClassIdGen.nextId() var layoutVersion: Int = 0 - private val mangledNameCache = mutableMapOf() - fun mangledName(name: String): String = mangledNameCache.getOrPut(name) { "$className::$name" } + fun mangledName(name: String): String = "$className::$name" /** * Map of public member names to their effective storage keys in instanceScope.objects. @@ -128,7 +127,7 @@ open class ObjClass( if (cls.className == "Obj") continue for ((name, rec) in cls.members) { if (rec.visibility == Visibility.Public) { - val key = if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name + val key = if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name res[name] = key } } @@ -267,6 +266,119 @@ open class ObjClass( */ internal val members = mutableMapOf() + internal data class FieldSlot(val slot: Int, val record: ObjRecord) + internal data class ResolvedMember(val record: ObjRecord, val declaringClass: ObjClass) + internal data class MethodSlot(val slot: Int, val record: ObjRecord) + private var fieldSlotLayoutVersion: Int = -1 + private var fieldSlotMap: Map = emptyMap() + private var fieldSlotCount: Int = 0 + private var instanceMemberLayoutVersion: Int = -1 + private var instanceMemberCache: Map = emptyMap() + private var methodSlotLayoutVersion: Int = -1 + private var methodSlotMap: Map = emptyMap() + private var methodSlotCount: Int = 0 + + private fun ensureFieldSlots(): Map { + if (fieldSlotLayoutVersion == layoutVersion) return fieldSlotMap + val res = mutableMapOf() + var idx = 0 + for (cls in mro) { + for ((name, rec) in cls.members) { + if (rec.isAbstract) continue + if (rec.type != ObjRecord.Type.Field && rec.type != ObjRecord.Type.ConstructorField) continue + val key = cls.mangledName(name) + if (res.containsKey(key)) continue + res[key] = FieldSlot(idx, rec) + idx += 1 + } + } + fieldSlotMap = res + fieldSlotCount = idx + fieldSlotLayoutVersion = layoutVersion + return fieldSlotMap + } + + private fun ensureInstanceMemberCache(): Map { + if (instanceMemberLayoutVersion == layoutVersion) return instanceMemberCache + val res = mutableMapOf() + for (cls in mro) { + if (cls.className == "Obj") break + for ((name, rec) in cls.members) { + if (rec.isAbstract) continue + if (res.containsKey(name)) continue + val decl = rec.declaringClass ?: cls + res[name] = ResolvedMember(rec, decl) + } + cls.classScope?.objects?.forEach { (name, rec) -> + if (rec.isAbstract) return@forEach + if (res.containsKey(name)) return@forEach + val decl = rec.declaringClass ?: cls + res[name] = ResolvedMember(rec, decl) + } + } + instanceMemberCache = res + instanceMemberLayoutVersion = layoutVersion + return instanceMemberCache + } + + private fun ensureMethodSlots(): Map { + if (methodSlotLayoutVersion == layoutVersion) return methodSlotMap + val res = mutableMapOf() + var idx = 0 + for (cls in mro) { + if (cls.className == "Obj") break + for ((name, rec) in cls.members) { + if (rec.isAbstract) continue + if (rec.value !is Statement && + rec.type != ObjRecord.Type.Delegated && + rec.type != ObjRecord.Type.Fun && + rec.type != ObjRecord.Type.Property) { + continue + } + val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name + if (res.containsKey(key)) continue + res[key] = MethodSlot(idx, rec) + idx += 1 + } + cls.classScope?.objects?.forEach { (name, rec) -> + if (rec.isAbstract) return@forEach + if (rec.value !is Statement && + rec.type != ObjRecord.Type.Delegated && + rec.type != ObjRecord.Type.Property) return@forEach + val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name + if (res.containsKey(key)) return@forEach + res[key] = MethodSlot(idx, rec) + idx += 1 + } + } + methodSlotMap = res + methodSlotCount = idx + methodSlotLayoutVersion = layoutVersion + return methodSlotMap + } + + internal fun fieldSlotCount(): Int { + ensureFieldSlots() + return fieldSlotCount + } + + internal fun fieldSlotForKey(key: String): FieldSlot? { + ensureFieldSlots() + return fieldSlotMap[key] + } + + internal fun fieldSlotMap(): Map = ensureFieldSlots() + internal fun resolveInstanceMember(name: String): ResolvedMember? = ensureInstanceMemberCache()[name] + internal fun methodSlotCount(): Int { + ensureMethodSlots() + return methodSlotCount + } + internal fun methodSlotForKey(key: String): MethodSlot? { + ensureMethodSlots() + return methodSlotMap[key] + } + internal fun methodSlotMap(): Map = ensureMethodSlots() + override fun toString(): String = className override suspend fun compareTo(scope: Scope, other: Obj): Int = if (other === this) 0 else -1 @@ -284,8 +396,8 @@ open class ObjClass( for (cls in mro) { // 1) members-defined methods and fields for ((k, v) in cls.members) { - if (!v.isAbstract && (v.value is Statement || v.type == ObjRecord.Type.Delegated || v.type == ObjRecord.Type.Field)) { - val key = if (v.visibility == Visibility.Private || v.type == ObjRecord.Type.Field || v.type == ObjRecord.Type.Delegated) cls.mangledName(k) else k + if (!v.isAbstract && (v.value is Statement || v.type == ObjRecord.Type.Delegated || v.type == ObjRecord.Type.Field || v.type == ObjRecord.Type.ConstructorField)) { + val key = if (v.visibility == Visibility.Private || v.type == ObjRecord.Type.Field || v.type == ObjRecord.Type.ConstructorField || v.type == ObjRecord.Type.Delegated) cls.mangledName(k) else k if (!res.containsKey(key)) { res[key] = v } @@ -327,12 +439,47 @@ open class ObjClass( val stableParent = classScope ?: scope.parent instance.instanceScope = Scope(stableParent, scope.args, scope.pos, instance) instance.instanceScope.currentClassCtx = null + val fieldSlots = fieldSlotMap() + if (fieldSlots.isNotEmpty()) { + instance.initFieldSlots(fieldSlotCount()) + } + val methodSlots = methodSlotMap() + if (methodSlots.isNotEmpty()) { + instance.initMethodSlots(methodSlotCount()) + } // Expose instance methods (and other callable members) directly in the instance scope for fast lookup // This mirrors Obj.autoInstanceScope behavior for ad-hoc scopes and makes fb.method() resolution robust instance.instanceScope.objects.putAll(templateMethods) + if (methodSlots.isNotEmpty()) { + for ((key, rec) in templateMethods) { + val slot = methodSlots[key] + if (slot != null) { + instance.setMethodSlotRecord(slot.slot, rec) + } + } + } for (p in templateOthers) { - instance.instanceScope.objects[p.first] = p.second.copy() + val rec = p.second.copy() + instance.instanceScope.objects[p.first] = rec + val slot = fieldSlots[p.first] + if (slot != null) { + instance.setFieldSlotRecord(slot.slot, rec) + } + if (methodSlots.isNotEmpty()) { + val mSlot = methodSlots[p.first] + if (mSlot != null) { + instance.setMethodSlotRecord(mSlot.slot, rec) + } + } + } + if (methodSlots.isNotEmpty()) { + for ((_, mSlot) in methodSlots) { + val idx = mSlot.slot + if (idx >= 0 && idx < instance.methodSlots.size && instance.methodSlots[idx] == null) { + instance.setMethodSlotRecord(idx, mSlot.record) + } + } } return instance } @@ -417,6 +564,10 @@ open class ObjClass( if (rec != null) { val mangled = c.mangledName(p.name) instance.instanceScope.objects[mangled] = rec + val slot = instance.objClass.fieldSlotForKey(mangled) + if (slot != null) { + instance.setFieldSlotRecord(slot.slot, rec) + } } } } @@ -738,5 +889,3 @@ open class ObjClass( scope.raiseNotImplemented() } - - diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstance.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstance.kt index 52bf542..4f5e9ad 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstance.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstance.kt @@ -30,6 +30,36 @@ import net.sergeych.lynon.LynonType class ObjInstance(override val objClass: ObjClass) : Obj() { internal lateinit var instanceScope: Scope + internal var fieldSlots: Array = emptyArray() + internal var methodSlots: Array = emptyArray() + + internal fun initFieldSlots(size: Int) { + fieldSlots = arrayOfNulls(size) + } + + internal fun setFieldSlotRecord(slot: Int, rec: ObjRecord) { + if (slot >= 0 && slot < fieldSlots.size) fieldSlots[slot] = rec + } + + internal fun initMethodSlots(size: Int) { + methodSlots = arrayOfNulls(size) + } + + internal fun setMethodSlotRecord(slot: Int, rec: ObjRecord) { + if (slot >= 0 && slot < methodSlots.size) methodSlots[slot] = rec + } + + internal fun fieldRecordForKey(key: String): ObjRecord? { + val slot = objClass.fieldSlotForKey(key) ?: return null + val idx = slot.slot + return if (idx >= 0 && idx < fieldSlots.size) fieldSlots[idx] else null + } + + internal fun methodRecordForKey(key: String): ObjRecord? { + val slot = objClass.methodSlotForKey(key) ?: return null + val idx = slot.slot + return if (idx >= 0 && idx < methodSlots.size) methodSlots[idx] else null + } override suspend fun readField(scope: Scope, name: String): ObjRecord { val caller = scope.currentClassCtx @@ -37,6 +67,16 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { // Fast path for public members when outside any class context if (caller == null) { objClass.publicMemberResolution[name]?.let { key -> + fieldRecordForKey(key)?.let { rec -> + if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) + return rec + } + methodRecordForKey(key)?.let { rec -> + if (!rec.isAbstract) { + val decl = rec.declaringClass ?: objClass.findDeclaringClassOf(name) ?: objClass + return resolveRecord(scope, rec, name, decl) + } + } instanceScope.objects[key]?.let { rec -> // Directly return fields to bypass resolveRecord overhead if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) @@ -56,6 +96,16 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { } // Check for private fields (stored in instanceScope) val mangled = c.mangledName(name) + fieldRecordForKey(mangled)?.let { rec -> + if (rec.visibility == Visibility.Private) { + return resolveRecord(scope, rec, name, c) + } + } + methodRecordForKey(mangled)?.let { rec -> + if (rec.visibility == Visibility.Private) { + return resolveRecord(scope, rec, name, c) + } + } instanceScope.objects[mangled]?.let { rec -> if (rec.visibility == Visibility.Private) { return resolveRecord(scope, rec, name, c) @@ -67,6 +117,16 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { for (cls in objClass.mro) { if (cls.className == "Obj") break val mangled = cls.mangledName(name) + fieldRecordForKey(mangled)?.let { rec -> + if (canAccessMember(rec.visibility, cls, caller, name)) { + return resolveRecord(scope, rec, name, cls) + } + } + methodRecordForKey(mangled)?.let { rec -> + if (canAccessMember(rec.visibility, cls, caller, name)) { + return resolveRecord(scope, rec, name, cls) + } + } instanceScope.objects[mangled]?.let { rec -> if (canAccessMember(rec.visibility, cls, caller, name)) { return resolveRecord(scope, rec, name, cls) @@ -81,6 +141,12 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { return resolveRecord(scope, rec, name, decl) } } + methodRecordForKey(name)?.let { rec -> + val decl = rec.declaringClass ?: objClass.findDeclaringClassOf(name) + if (canAccessMember(rec.visibility, decl, caller, name)) { + return resolveRecord(scope, rec, name, decl) + } + } // 3. Fall back to super (handles class members and extensions) return super.readField(scope, name) @@ -109,8 +175,13 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { val d = decl ?: obj.declaringClass if (d != null) { val mangled = d.mangledName(name) - instanceScope.objects[mangled]?.let { - targetRec = it + fieldRecordForKey(mangled)?.let { + targetRec = it + } + if (targetRec === obj) { + instanceScope.objects[mangled]?.let { + targetRec = it + } } } if (targetRec === obj) { @@ -134,10 +205,29 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { // Fast path for public members when outside any class context if (caller == null) { objClass.publicMemberResolution[name]?.let { key -> + fieldRecordForKey(key)?.let { rec -> + if (rec.effectiveWriteVisibility == Visibility.Public) { + // Skip property/delegated overhead if it's a plain mutable field + if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && rec.isMutable && !rec.isAbstract) { + if (rec.value.assign(scope, newValue) == null) + rec.value = newValue + return + } + updateRecord(scope, rec, name, newValue, rec.declaringClass) + return + } + } + methodRecordForKey(key)?.let { rec -> + if (rec.effectiveWriteVisibility == Visibility.Public && + (rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) { + updateRecord(scope, rec, name, newValue, rec.declaringClass) + return + } + } instanceScope.objects[key]?.let { rec -> if (rec.effectiveWriteVisibility == Visibility.Public) { // Skip property/delegated overhead if it's a plain mutable field - if (rec.type == ObjRecord.Type.Field && rec.isMutable && !rec.isAbstract) { + if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && rec.isMutable && !rec.isAbstract) { if (rec.value.assign(scope, newValue) == null) rec.value = newValue return @@ -160,6 +250,19 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { } // Check for private fields (stored in instanceScope) val mangled = c.mangledName(name) + fieldRecordForKey(mangled)?.let { rec -> + if (rec.visibility == Visibility.Private) { + updateRecord(scope, rec, name, newValue, c) + return + } + } + methodRecordForKey(mangled)?.let { rec -> + if (rec.visibility == Visibility.Private && + (rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) { + updateRecord(scope, rec, name, newValue, c) + return + } + } instanceScope.objects[mangled]?.let { rec -> if (rec.visibility == Visibility.Private) { updateRecord(scope, rec, name, newValue, c) @@ -172,6 +275,19 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { for (cls in objClass.mro) { if (cls.className == "Obj") break val mangled = cls.mangledName(name) + fieldRecordForKey(mangled)?.let { rec -> + if (canAccessMember(rec.effectiveWriteVisibility, cls, caller, name)) { + updateRecord(scope, rec, name, newValue, cls) + return + } + } + methodRecordForKey(mangled)?.let { rec -> + if (canAccessMember(rec.effectiveWriteVisibility, cls, caller, name) && + (rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) { + updateRecord(scope, rec, name, newValue, cls) + return + } + } instanceScope.objects[mangled]?.let { rec -> if (canAccessMember(rec.effectiveWriteVisibility, cls, caller, name)) { updateRecord(scope, rec, name, newValue, cls) @@ -188,6 +304,14 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { return } } + methodRecordForKey(name)?.let { rec -> + val decl = rec.declaringClass ?: objClass.findDeclaringClassOf(name) + if (canAccessMember(rec.effectiveWriteVisibility, decl, caller, name) && + (rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) { + updateRecord(scope, rec, name, newValue, decl) + return + } + } super.writeField(scope, name, newValue) } @@ -225,6 +349,16 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { // Fast path for public members when outside any class context if (caller == null) { objClass.publicMemberResolution[name]?.let { key -> + methodRecordForKey(key)?.let { rec -> + if (rec.visibility == Visibility.Public && !rec.isAbstract) { + val decl = rec.declaringClass + if (rec.type == ObjRecord.Type.Property) { + if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl) + } else if (rec.type == ObjRecord.Type.Fun) { + return rec.value.invoke(instanceScope, this, args, decl) + } + } + } instanceScope.objects[key]?.let { rec -> if (rec.visibility == Visibility.Public && !rec.isAbstract) { val decl = rec.declaringClass @@ -241,6 +375,15 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { // 0. Prefer private member of current class context caller?.let { c -> val mangled = c.mangledName(name) + methodRecordForKey(mangled)?.let { rec -> + if (rec.visibility == Visibility.Private && !rec.isAbstract) { + if (rec.type == ObjRecord.Type.Property) { + if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, c) + } else if (rec.type == ObjRecord.Type.Fun) { + return rec.value.invoke(instanceScope, this, args, c) + } + } + } instanceScope.objects[mangled]?.let { rec -> if (rec.visibility == Visibility.Private && !rec.isAbstract) { if (rec.type == ObjRecord.Type.Property) { @@ -261,50 +404,58 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { } } - // 1. Walk MRO to find member, handling delegation - for (cls in objClass.mro) { - if (cls.className == "Obj") break - val rec = cls.members[name] ?: cls.classScope?.objects?.get(name) - if (rec != null && !rec.isAbstract) { - if (rec.type == ObjRecord.Type.Delegated) { - val storageName = cls.mangledName(name) - val del = instanceScope[storageName]?.delegate ?: rec.delegate - ?: scope.raiseError("Internal error: delegated member $name has no delegate (tried $storageName)") - - // For delegated member, try 'invoke' first if it's a function-like call - val allArgs = (listOf(this, ObjString(name)) + args.list).toTypedArray() - return del.invokeInstanceMethod(scope, "invoke", Arguments(*allArgs), onNotFoundResult = { - // Fallback: property delegation (getValue then call result) - val propVal = del.invokeInstanceMethod(scope, "getValue", Arguments(this, ObjString(name))) - propVal.invoke(scope, this, args, rec.declaringClass ?: cls) - }) - } - val decl = rec.declaringClass ?: cls + // Fast path for non-delegated instance methods in class context + methodRecordForKey(name)?.let { rec -> + if (!rec.isAbstract && rec.type == ObjRecord.Type.Fun) { + val decl = rec.declaringClass ?: objClass.findDeclaringClassOf(name) ?: objClass val effectiveCaller = caller ?: if (scope.thisObj === this) objClass else null - if (!canAccessMember(rec.visibility, decl, effectiveCaller, name)) - scope.raiseError( - ObjIllegalAccessException( - scope, - "can't invoke method $name (declared in ${decl.className})" - ) - ) - - if (rec.type == ObjRecord.Type.Property) { - if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl) - } else if (rec.type == ObjRecord.Type.Fun) { - return rec.value.invoke( - instanceScope, - this, - args, - decl - ) - } else if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField || rec.type == ObjRecord.Type.Argument) { - val resolved = readField(scope, name) - return resolved.value.invoke(scope, this, args, resolved.declaringClass) + if (canAccessMember(rec.visibility, decl, effectiveCaller, name)) { + return rec.value.invoke(instanceScope, this, args, decl) } } } + // 1. Resolve instance member via cached MRO lookup, handling delegation + objClass.resolveInstanceMember(name)?.let { resolvedMember -> + val rec = resolvedMember.record + val decl = resolvedMember.declaringClass + if (rec.type == ObjRecord.Type.Delegated) { + val storageName = decl.mangledName(name) + val del = instanceScope[storageName]?.delegate ?: rec.delegate + ?: scope.raiseError("Internal error: delegated member $name has no delegate (tried $storageName)") + + // For delegated member, try 'invoke' first if it's a function-like call + val allArgs = (listOf(this, ObjString(name)) + args.list).toTypedArray() + return del.invokeInstanceMethod(scope, "invoke", Arguments(*allArgs), onNotFoundResult = { + // Fallback: property delegation (getValue then call result) + val propVal = del.invokeInstanceMethod(scope, "getValue", Arguments(this, ObjString(name))) + propVal.invoke(scope, this, args, rec.declaringClass ?: decl) + }) + } + val effectiveCaller = caller ?: if (scope.thisObj === this) objClass else null + if (!canAccessMember(rec.visibility, decl, effectiveCaller, name)) + scope.raiseError( + ObjIllegalAccessException( + scope, + "can't invoke method $name (declared in ${decl.className})" + ) + ) + + if (rec.type == ObjRecord.Type.Property) { + if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl) + } else if (rec.type == ObjRecord.Type.Fun) { + return rec.value.invoke( + instanceScope, + this, + args, + decl + ) + } else if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField || rec.type == ObjRecord.Type.Argument) { + val resolved = readField(scope, name) + return resolved.value.invoke(scope, this, args, resolved.declaringClass) + } + } + // 2. Fall back to super (handles extensions and root fallback) return super.invokeInstanceMethod(scope, name, args, onNotFoundResult) } @@ -431,6 +582,14 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla override suspend fun readField(scope: Scope, name: String): ObjRecord { // Qualified field access: prefer mangled storage for the qualified ancestor val mangled = "${startClass.className}::$name" + instance.fieldRecordForKey(mangled)?.let { rec -> + // Visibility: declaring class is the qualified ancestor for mangled storage + val decl = rec.declaringClass ?: startClass + val caller = scope.currentClassCtx + if (!canAccessMember(rec.visibility, decl, caller, name)) + scope.raiseError(ObjIllegalAccessException(scope, "can't access field $name (declared in ${decl.className})")) + return instance.resolveRecord(scope, rec, name, decl) + } instance.instanceScope.objects[mangled]?.let { rec -> // Visibility: declaring class is the qualified ancestor for mangled storage val decl = rec.declaringClass ?: startClass @@ -467,6 +626,18 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla override suspend fun writeField(scope: Scope, name: String, newValue: Obj) { // Qualified write: target mangled storage for the ancestor val mangled = "${startClass.className}::$name" + instance.fieldRecordForKey(mangled)?.let { f -> + val decl = f.declaringClass ?: startClass + val caller = scope.currentClassCtx + if (!canAccessMember(f.effectiveWriteVisibility, decl, caller, name)) + ObjIllegalAccessException( + scope, + "can't assign to field $name (declared in ${decl.className})" + ).raise() + if (!f.isMutable && f.value !== ObjUnset) ObjIllegalAssignmentException(scope, "can't reassign val $name").raise() + if (f.value.assign(scope, newValue) == null) f.value = newValue + return + } instance.instanceScope.objects[mangled]?.let { f -> val decl = f.declaringClass ?: startClass val caller = scope.currentClassCtx @@ -548,4 +719,4 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla } override fun toString(): String = instance.toString() -} \ No newline at end of file +} diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRef.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRef.kt index 52153ad..2dd0ae6 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRef.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRef.kt @@ -381,7 +381,7 @@ class CastRef( } /** Qualified `this@Type`: resolves to a view of current `this` starting dispatch from the ancestor Type. */ -class QualifiedThisRef(private val typeName: String, private val atPos: Pos) : ObjRef { +class QualifiedThisRef(val typeName: String, private val atPos: Pos) : ObjRef { override suspend fun get(scope: Scope): ObjRecord { val t = scope[typeName]?.value as? ObjClass ?: scope.raiseError("unknown type $typeName") @@ -403,6 +403,188 @@ class QualifiedThisRef(private val typeName: String, private val atPos: Pos) : O } } +private suspend fun resolveQualifiedThisInstance(scope: Scope, typeName: String): Pair { + val t = scope[typeName]?.value as? ObjClass + ?: scope.raiseError("unknown type $typeName") + var s: Scope? = scope + while (s != null) { + val inst = s.thisObj as? ObjInstance + if (inst != null && (inst.objClass === t || inst.objClass.allParentsSet.contains(t))) { + return inst to t + } + s = s.parent + } + scope.raiseClassCastError( + "No instance of type ${t.className} found in the scope chain" + ) +} + +/** + * Fast path for direct `this@Type.name` access using slot maps when possible. + */ +class QualifiedThisFieldSlotRef( + private val typeName: String, + val name: String, + private val isOptional: Boolean +) : ObjRef { + override suspend fun get(scope: Scope): ObjRecord { + val (inst, startClass) = resolveQualifiedThisInstance(scope, typeName) + if (isOptional && inst == ObjNull) return ObjNull.asMutable + + if (startClass !== inst.objClass) { + return ObjQualifiedView(inst, startClass).readField(scope, name) + } + + val caller = scope.currentClassCtx + if (caller != null) { + val mangled = caller.mangledName(name) + inst.fieldRecordForKey(mangled)?.let { rec -> + if (rec.visibility == Visibility.Private) { + return inst.resolveRecord(scope, rec, name, caller) + } + } + inst.methodRecordForKey(mangled)?.let { rec -> + if (rec.visibility == Visibility.Private) { + return inst.resolveRecord(scope, rec, name, caller) + } + } + } + + val key = inst.objClass.publicMemberResolution[name] ?: name + inst.fieldRecordForKey(key)?.let { rec -> + if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) + return rec + } + inst.methodRecordForKey(key)?.let { rec -> + if (!rec.isAbstract) { + val decl = rec.declaringClass ?: inst.objClass.findDeclaringClassOf(name) ?: inst.objClass + return inst.resolveRecord(scope, rec, name, decl) + } + } + + return inst.readField(scope, name) + } + + override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) { + val (inst, startClass) = resolveQualifiedThisInstance(scope, typeName) + if (isOptional && inst == ObjNull) return + + if (startClass !== inst.objClass) { + ObjQualifiedView(inst, startClass).writeField(scope, name, newValue) + return + } + + val caller = scope.currentClassCtx + if (caller != null) { + val mangled = caller.mangledName(name) + inst.fieldRecordForKey(mangled)?.let { rec -> + if (rec.visibility == Visibility.Private) { + writeDirectOrFallback(scope, inst, rec, name, newValue, caller) + return + } + } + inst.methodRecordForKey(mangled)?.let { rec -> + if (rec.visibility == Visibility.Private && + (rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) { + inst.writeField(scope, name, newValue) + return + } + } + } + + val key = inst.objClass.publicMemberResolution[name] ?: name + inst.fieldRecordForKey(key)?.let { rec -> + val decl = rec.declaringClass ?: inst.objClass.findDeclaringClassOf(name) + if (canAccessMember(rec.effectiveWriteVisibility, decl, caller, name)) { + writeDirectOrFallback(scope, inst, rec, name, newValue, decl) + return + } + } + inst.methodRecordForKey(key)?.let { rec -> + if (rec.effectiveWriteVisibility == Visibility.Public && + (rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) { + inst.writeField(scope, name, newValue) + return + } + } + + inst.writeField(scope, name, newValue) + } + + private suspend fun writeDirectOrFallback( + scope: Scope, + inst: ObjInstance, + rec: ObjRecord, + name: String, + newValue: Obj, + decl: ObjClass? + ) { + if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) { + if (!rec.isMutable && rec.value !== ObjUnset) { + ObjIllegalAssignmentException(scope, "can't reassign val $name").raise() + } + if (rec.value.assign(scope, newValue) == null) rec.value = newValue + } else { + inst.writeField(scope, name, newValue) + } + } +} + +/** + * Fast path for direct `this@Type.method(...)` calls using slots when the qualifier is the + * dynamic class. Otherwise falls back to a qualified view dispatch. + */ +class QualifiedThisMethodSlotCallRef( + private val typeName: String, + private val name: String, + private val args: List, + private val tailBlock: Boolean, + private val isOptional: Boolean +) : ObjRef { + override suspend fun get(scope: Scope): ObjRecord = evalValue(scope).asReadonly + + override suspend fun evalValue(scope: Scope): Obj { + val (inst, startClass) = resolveQualifiedThisInstance(scope, typeName) + if (isOptional && inst == ObjNull) return ObjNull + val callArgs = args.toArguments(scope, tailBlock) + + if (startClass !== inst.objClass) { + return ObjQualifiedView(inst, startClass).invokeInstanceMethod(scope, name, callArgs, null) + } + + val caller = scope.currentClassCtx + if (caller != null) { + val mangled = caller.mangledName(name) + inst.methodRecordForKey(mangled)?.let { rec -> + if (rec.visibility == Visibility.Private && !rec.isAbstract) { + if (rec.type == ObjRecord.Type.Property) { + if (callArgs.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, inst, caller) + } else if (rec.type == ObjRecord.Type.Fun) { + return rec.value.invoke(inst.instanceScope, inst, callArgs, caller) + } + } + } + } + + val key = inst.objClass.publicMemberResolution[name] ?: name + inst.methodRecordForKey(key)?.let { rec -> + if (!rec.isAbstract) { + val decl = rec.declaringClass ?: inst.objClass.findDeclaringClassOf(name) ?: inst.objClass + val effectiveCaller = caller ?: if (scope.thisObj === inst) inst.objClass else null + if (!canAccessMember(rec.visibility, decl, effectiveCaller, name)) + scope.raiseError(ObjIllegalAccessException(scope, "can't invoke method $name (declared in ${decl.className})")) + if (rec.type == ObjRecord.Type.Property) { + if (callArgs.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, inst, decl) + } else if (rec.type == ObjRecord.Type.Fun) { + return rec.value.invoke(inst.instanceScope, inst, callArgs, decl) + } + } + } + + return inst.invokeInstanceMethod(scope, name, callArgs) + } +} + /** Assignment compound op: target op= value */ class AssignOpRef( private val op: BinOp, @@ -691,9 +873,20 @@ class FieldRef( if (effectiveKey != null) { rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc -> if (obj is ObjInstance && obj.objClass === cls) { - val rec = obj.instanceScope.objects[effectiveKey] - if (rec != null && rec.type != ObjRecord.Type.Delegated) rec - else obj.readField(sc, name) + val slot = cls.fieldSlotForKey(effectiveKey) + if (slot != null) { + val idx = slot.slot + val rec = if (idx >= 0 && idx < obj.fieldSlots.size) obj.fieldSlots[idx] else null + if (rec != null && + (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && + !rec.isAbstract) { + rec + } else obj.readField(sc, name) + } else { + val rec = obj.fieldRecordForKey(effectiveKey) ?: obj.instanceScope.objects[effectiveKey] + if (rec != null && rec.type != ObjRecord.Type.Delegated) rec + else obj.readField(sc, name) + } } else obj.readField(sc, name) } } else { @@ -809,10 +1002,24 @@ class FieldRef( if (effectiveKey != null) { wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, nv -> if (obj is ObjInstance && obj.objClass === cls) { - val rec = obj.instanceScope.objects[effectiveKey] - if (rec != null && rec.effectiveWriteVisibility == Visibility.Public && rec.isMutable && rec.type == ObjRecord.Type.Field) { - if (rec.value.assign(sc, nv) == null) rec.value = nv - } else obj.writeField(sc, name, nv) + val slot = cls.fieldSlotForKey(effectiveKey) + if (slot != null) { + val idx = slot.slot + val rec = if (idx >= 0 && idx < obj.fieldSlots.size) obj.fieldSlots[idx] else null + if (rec != null && + rec.effectiveWriteVisibility == Visibility.Public && + rec.isMutable && + (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && + !rec.isAbstract) { + if (rec.value.assign(sc, nv) == null) rec.value = nv + } else obj.writeField(sc, name, nv) + } else { + val rec = obj.fieldRecordForKey(effectiveKey) ?: obj.instanceScope.objects[effectiveKey] + if (rec != null && rec.effectiveWriteVisibility == Visibility.Public && rec.isMutable && + (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField)) { + if (rec.value.assign(sc, nv) == null) rec.value = nv + } else obj.writeField(sc, name, nv) + } } else obj.writeField(sc, name, nv) } } else { @@ -890,6 +1097,113 @@ class FieldRef( } } +/** + * Fast path for direct `this.name` access using slot maps. + * Falls back to normal member resolution when needed. + */ +class ThisFieldSlotRef( + val name: String, + private val isOptional: Boolean +) : ObjRef { + override suspend fun get(scope: Scope): ObjRecord { + val th = scope.thisObj + if (th == ObjNull && isOptional) return ObjNull.asMutable + if (th !is ObjInstance) return th.readField(scope, name) + + val caller = scope.currentClassCtx + if (caller != null) { + val mangled = caller.mangledName(name) + th.fieldRecordForKey(mangled)?.let { rec -> + if (rec.visibility == Visibility.Private) { + return th.resolveRecord(scope, rec, name, caller) + } + } + th.methodRecordForKey(mangled)?.let { rec -> + if (rec.visibility == Visibility.Private) { + return th.resolveRecord(scope, rec, name, caller) + } + } + } + + val key = th.objClass.publicMemberResolution[name] ?: name + th.fieldRecordForKey(key)?.let { rec -> + if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) + return rec + } + th.methodRecordForKey(key)?.let { rec -> + if (!rec.isAbstract) { + val decl = rec.declaringClass ?: th.objClass.findDeclaringClassOf(name) ?: th.objClass + return th.resolveRecord(scope, rec, name, decl) + } + } + + return th.readField(scope, name) + } + + override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) { + val th = scope.thisObj + if (th == ObjNull && isOptional) return + if (th !is ObjInstance) { + th.writeField(scope, name, newValue) + return + } + + val caller = scope.currentClassCtx + if (caller != null) { + val mangled = caller.mangledName(name) + th.fieldRecordForKey(mangled)?.let { rec -> + if (rec.visibility == Visibility.Private) { + writeDirectOrFallback(scope, th, rec, name, newValue, caller) + return + } + } + th.methodRecordForKey(mangled)?.let { rec -> + if (rec.visibility == Visibility.Private && + (rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) { + th.writeField(scope, name, newValue) + return + } + } + } + + val key = th.objClass.publicMemberResolution[name] ?: name + th.fieldRecordForKey(key)?.let { rec -> + val decl = rec.declaringClass ?: th.objClass.findDeclaringClassOf(name) + if (canAccessMember(rec.effectiveWriteVisibility, decl, caller, name)) { + writeDirectOrFallback(scope, th, rec, name, newValue, decl) + return + } + } + th.methodRecordForKey(key)?.let { rec -> + if (rec.effectiveWriteVisibility == Visibility.Public && + (rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) { + th.writeField(scope, name, newValue) + return + } + } + + th.writeField(scope, name, newValue) + } + + private suspend fun writeDirectOrFallback( + scope: Scope, + inst: ObjInstance, + rec: ObjRecord, + name: String, + newValue: Obj, + decl: ObjClass? + ) { + if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) { + if (!rec.isMutable && rec.value !== ObjUnset) { + ObjIllegalAssignmentException(scope, "can't reassign val $name").raise() + } + if (rec.value.assign(scope, newValue) == null) rec.value = newValue + } else { + inst.writeField(scope, name, newValue) + } + } +} + /** * Reference to index access (a[i]) with optional chaining. */ @@ -1336,36 +1650,50 @@ class MethodCallRef( is ObjInstance -> { // Prefer resolved class member to avoid per-call lookup on hit // BUT only if it's NOT a root object member (which can be shadowed by extensions) - var hierarchyMember: ObjRecord? = null val cls0 = base.objClass val keyInScope = cls0.publicMemberResolution[name] - if (keyInScope != null) { - val rec = base.instanceScope.objects[keyInScope] - if (rec != null && rec.type == ObjRecord.Type.Fun) { - hierarchyMember = rec - } - } + val methodSlot = if (keyInScope != null) cls0.methodSlotForKey(keyInScope) else null + val fastRec = if (methodSlot != null) { + val idx = methodSlot.slot + if (idx >= 0 && idx < base.methodSlots.size) base.methodSlots[idx] else null + } else if (keyInScope != null) { + base.methodRecordForKey(keyInScope) ?: base.instanceScope.objects[keyInScope] + } else null + val resolved = if (fastRec != null) null else cls0.resolveInstanceMember(name) - if (hierarchyMember == null) { - for (cls in base.objClass.mro) { - if (cls.className == "Obj") break - val rec = cls.members[name] ?: cls.classScope?.objects?.get(name) - if (rec != null && !rec.isAbstract && rec.type != ObjRecord.Type.Field) { - hierarchyMember = rec - break + val targetRec = when { + fastRec != null && fastRec.type == ObjRecord.Type.Fun -> fastRec + resolved != null && resolved.record.type == ObjRecord.Type.Fun && !resolved.record.isAbstract -> resolved.record + else -> null + } + if (targetRec != null) { + val visibility = targetRec.visibility + val decl = targetRec.declaringClass ?: (resolved?.declaringClass ?: cls0) + if (methodSlot != null && targetRec.type == ObjRecord.Type.Fun) { + val slotIndex = methodSlot.slot + mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a -> + val inst = obj as ObjInstance + if (inst.objClass === cls0) { + val rec = if (slotIndex >= 0 && slotIndex < inst.methodSlots.size) inst.methodSlots[slotIndex] else null + if (rec != null && rec.type == ObjRecord.Type.Fun && !rec.isAbstract) { + if (!visibility.isPublic && !canAccessMember(visibility, decl, sc.currentClassCtx, name)) + sc.raiseError(ObjIllegalAccessException(sc, "can't invoke non-public method $name")) + rec.value.invoke(inst.instanceScope, inst, a, decl) + } else { + obj.invokeInstanceMethod(sc, name, a) + } + } else { + obj.invokeInstanceMethod(sc, name, a) + } + } + } else { + val callable = targetRec.value + mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a -> + val inst = obj as ObjInstance + if (!visibility.isPublic && !canAccessMember(visibility, decl, sc.currentClassCtx, name)) + sc.raiseError(ObjIllegalAccessException(sc, "can't invoke non-public method $name")) + callable.invoke(inst.instanceScope, inst, a) } - } - } - - if (hierarchyMember != null) { - val visibility = hierarchyMember.visibility - val callable = hierarchyMember.value - val decl = hierarchyMember.declaringClass ?: base.objClass - mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a -> - val inst = obj as ObjInstance - if (!visibility.isPublic && !canAccessMember(visibility, decl, sc.currentClassCtx, name)) - sc.raiseError(ObjIllegalAccessException(sc, "can't invoke non-public method $name")) - callable.invoke(inst.instanceScope, inst, a) } } else { // Fallback to name-based lookup per call (handles extensions and root members) @@ -1399,6 +1727,57 @@ class MethodCallRef( } } +/** + * Fast path for direct `this.method(...)` calls using slot maps. + * Falls back to normal invoke semantics when needed. + */ +class ThisMethodSlotCallRef( + private val name: String, + private val args: List, + private val tailBlock: Boolean, + private val isOptional: Boolean +) : ObjRef { + override suspend fun get(scope: Scope): ObjRecord = evalValue(scope).asReadonly + + override suspend fun evalValue(scope: Scope): Obj { + val base = scope.thisObj + if (base == ObjNull && isOptional) return ObjNull + val callArgs = args.toArguments(scope, tailBlock) + if (base !is ObjInstance) return base.invokeInstanceMethod(scope, name, callArgs) + + val caller = scope.currentClassCtx + if (caller != null) { + val mangled = caller.mangledName(name) + base.methodRecordForKey(mangled)?.let { rec -> + if (rec.visibility == Visibility.Private && !rec.isAbstract) { + if (rec.type == ObjRecord.Type.Property) { + if (callArgs.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, base, caller) + } else if (rec.type == ObjRecord.Type.Fun) { + return rec.value.invoke(base.instanceScope, base, callArgs, caller) + } + } + } + } + + val key = base.objClass.publicMemberResolution[name] ?: name + base.methodRecordForKey(key)?.let { rec -> + if (!rec.isAbstract) { + val decl = rec.declaringClass ?: base.objClass.findDeclaringClassOf(name) ?: base.objClass + val effectiveCaller = caller ?: if (scope.thisObj === base) base.objClass else null + if (!canAccessMember(rec.visibility, decl, effectiveCaller, name)) + scope.raiseError(ObjIllegalAccessException(scope, "can't invoke method $name (declared in ${decl.className})")) + if (rec.type == ObjRecord.Type.Property) { + if (callArgs.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, base, decl) + } else if (rec.type == ObjRecord.Type.Fun) { + return rec.value.invoke(base.instanceScope, base, callArgs, decl) + } + } + } + + return base.invokeInstanceMethod(scope, name, callArgs) + } +} + /** * Reference to a local/visible variable by name (Phase A: scope lookup). */ @@ -1729,6 +2108,240 @@ class FastLocalVarRef( } } +/** + * Identifier reference in class context that prefers member slots on `this` after local lookup. + * Falls back to normal scope lookup for globals/outer scopes. + */ +class ImplicitThisMemberRef( + val name: String, + val atPos: Pos +) : ObjRef { + override fun forEachVariable(block: (String) -> Unit) { + block(name) + } + + override fun forEachVariableWithPos(block: (String, Pos) -> Unit) { + block(name, atPos) + } + + override suspend fun get(scope: Scope): ObjRecord { + scope.pos = atPos + val caller = scope.currentClassCtx + val th = scope.thisObj + + // 1) locals in the same `this` chain + var s: Scope? = scope + while (s != null && s.thisObj === th) { + scope.tryGetLocalRecord(s, name, caller)?.let { return it } + s = s.parent + } + + // 2) member slots on this instance + if (th is ObjInstance) { + // private member access for current class context + caller?.let { c -> + val mangled = c.mangledName(name) + th.fieldRecordForKey(mangled)?.let { rec -> + if (rec.visibility == Visibility.Private) { + return th.resolveRecord(scope, rec, name, c) + } + } + th.methodRecordForKey(mangled)?.let { rec -> + if (rec.visibility == Visibility.Private) { + return th.resolveRecord(scope, rec, name, c) + } + } + } + + val key = th.objClass.publicMemberResolution[name] ?: name + th.fieldRecordForKey(key)?.let { rec -> + if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) + return rec + } + th.methodRecordForKey(key)?.let { rec -> + if (!rec.isAbstract) { + val decl = rec.declaringClass ?: th.objClass.findDeclaringClassOf(name) ?: th.objClass + return th.resolveRecord(scope, rec, name, decl) + } + } + } + + // 3) fallback to normal scope resolution (globals/outer scopes) + scope[name]?.let { return it } + try { + return th.readField(scope, name) + } catch (e: ExecutionError) { + if ((e.message ?: "").contains("no such field: $name")) scope.raiseSymbolNotFound(name) + throw e + } + } + + override suspend fun evalValue(scope: Scope): Obj { + val rec = get(scope) + return scope.resolve(rec, name) + } + + override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) { + scope.pos = atPos + val caller = scope.currentClassCtx + val th = scope.thisObj + + // 1) locals in the same `this` chain + var s: Scope? = scope + while (s != null && s.thisObj === th) { + val rec = scope.tryGetLocalRecord(s, name, caller) + if (rec != null) { + scope.assign(rec, name, newValue) + return + } + s = s.parent + } + + // 2) member slots on this instance + if (th is ObjInstance) { + val key = th.objClass.publicMemberResolution[name] ?: name + th.fieldRecordForKey(key)?.let { rec -> + val decl = rec.declaringClass ?: th.objClass.findDeclaringClassOf(name) + if (canAccessMember(rec.effectiveWriteVisibility, decl, caller, name)) { + if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) { + if (!rec.isMutable && rec.value !== ObjUnset) { + ObjIllegalAssignmentException(scope, "can't reassign val $name").raise() + } + if (rec.value.assign(scope, newValue) == null) rec.value = newValue + } else { + th.writeField(scope, name, newValue) + } + return + } + } + th.methodRecordForKey(key)?.let { rec -> + if (rec.effectiveWriteVisibility == Visibility.Public && + (rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) { + th.writeField(scope, name, newValue) + return + } + } + } + + // 3) fallback to normal scope resolution + scope[name]?.let { stored -> + scope.assign(stored, name, newValue) + return + } + th.writeField(scope, name, newValue) + } +} + +/** + * Fast path for implicit member calls in class bodies: `foo(...)` resolves locals first, + * then falls back to member lookup on `this`. + */ +class ImplicitThisMethodCallRef( + private val name: String, + private val args: List, + private val tailBlock: Boolean, + private val isOptional: Boolean, + private val atPos: Pos +) : ObjRef { + private val memberRef = ImplicitThisMemberRef(name, atPos) + + override suspend fun get(scope: Scope): ObjRecord = evalValue(scope).asReadonly + + override suspend fun evalValue(scope: Scope): Obj { + scope.pos = atPos + val callee = memberRef.evalValue(scope) + if (callee == ObjNull && isOptional) return ObjNull + val callArgs = args.toArguments(scope, tailBlock) + val usePool = PerfFlags.SCOPE_POOL + return if (usePool) { + scope.withChildFrame(callArgs) { child -> + callee.callOn(child) + } + } else { + callee.callOn(scope.createChildScope(scope.pos, callArgs)) + } + } +} + +/** + * Direct local slot reference with known slot index and lexical depth. + * Depth=0 means current scope, depth=1 means parent scope, etc. + */ +class LocalSlotRef( + val name: String, + private val slot: Int, + private val depth: Int, + private val atPos: Pos, +) : ObjRef { + override fun forEachVariable(block: (String) -> Unit) { + block(name) + } + + private val fallbackRef = LocalVarRef(name, atPos) + private var cachedFrameId: Long = 0L + private var cachedOwner: Scope? = null + private var cachedOwnerVerified: Boolean = false + + private fun resolveOwner(scope: Scope): Scope? { + if (cachedOwner != null && cachedFrameId == scope.frameId && cachedOwnerVerified) return cachedOwner + var s: Scope? = scope + var remaining = depth + while (s != null && remaining > 0) { + s = s.parent + remaining-- + } + if (s == null || s.getSlotIndexOf(name) != slot) { + cachedOwner = null + cachedOwnerVerified = false + cachedFrameId = scope.frameId + return null + } + cachedOwner = s + cachedOwnerVerified = true + cachedFrameId = scope.frameId + return s + } + + override suspend fun get(scope: Scope): ObjRecord { + scope.pos = atPos + val owner = resolveOwner(scope) ?: return fallbackRef.get(scope) + if (slot < 0 || slot >= owner.slotCount()) return fallbackRef.get(scope) + val rec = owner.getSlotRecord(slot) + if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) { + scope.raiseError(ObjIllegalAccessException(scope, "private field access")) + } + return rec + } + + override suspend fun evalValue(scope: Scope): Obj { + scope.pos = atPos + val owner = resolveOwner(scope) ?: return fallbackRef.evalValue(scope) + if (slot < 0 || slot >= owner.slotCount()) return fallbackRef.evalValue(scope) + val rec = owner.getSlotRecord(slot) + if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) { + scope.raiseError(ObjIllegalAccessException(scope, "private field access")) + } + return scope.resolve(rec, name) + } + + override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) { + scope.pos = atPos + val owner = resolveOwner(scope) ?: run { + fallbackRef.setAt(pos, scope, newValue) + return + } + if (slot < 0 || slot >= owner.slotCount()) { + fallbackRef.setAt(pos, scope, newValue) + return + } + val rec = owner.getSlotRecord(slot) + if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) { + scope.raiseError(ObjIllegalAccessException(scope, "private field access")) + } + scope.assign(rec, name, newValue) + } +} + class ListLiteralRef(private val entries: List) : ObjRef { override fun forEachVariable(block: (String) -> Unit) { for (e in entries) { @@ -1910,7 +2523,15 @@ class AssignRef( val v = value.evalValue(scope) // For properties, we should not call get() on target because it invokes the getter. // Instead, we call setAt directly. - if (target is FieldRef || target is IndexRef || target is LocalVarRef || target is FastLocalVarRef || target is BoundLocalVarRef) { + if (target is FieldRef || + target is IndexRef || + target is LocalVarRef || + target is FastLocalVarRef || + target is BoundLocalVarRef || + target is LocalSlotRef || + target is ThisFieldSlotRef || + target is QualifiedThisFieldSlotRef || + target is ImplicitThisMemberRef) { target.setAt(atPos, scope, v) } else { val rec = target.get(scope) @@ -1923,4 +2544,4 @@ class AssignRef( } } - // (duplicate LocalVarRef removed; the canonical implementation is defined earlier in this file) \ No newline at end of file + // (duplicate LocalVarRef removed; the canonical implementation is defined earlier in this file)