Optimize member access via slots
This commit is contained in:
parent
9b580bafb6
commit
74d73540c6
@ -44,6 +44,10 @@ class Compiler(
|
||||
private val currentLocalNames: MutableSet<String>?
|
||||
get() = localNamesStack.lastOrNull()
|
||||
|
||||
private data class SlotPlan(val slots: MutableMap<String, Int>, var nextIndex: Int)
|
||||
private data class SlotLocation(val slot: Int, val depth: Int)
|
||||
private val slotPlanStack = mutableListOf<SlotPlan>()
|
||||
|
||||
// Track declared local variables count per function for precise capacity hints
|
||||
private val localDeclCountStack = mutableListOf<Int>()
|
||||
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<String>): SlotPlan {
|
||||
val map = mutableMapOf<String, Int>()
|
||||
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: <lvalue>, '.' , <id>
|
||||
// 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,19 +780,31 @@ 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("<lambda>")) {
|
||||
withLocalNames(paramNames.toSet()) {
|
||||
slotPlanStack.add(paramSlotPlan)
|
||||
val body = try {
|
||||
inCodeContext(CodeContext.Function("<lambda>")) {
|
||||
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 ->
|
||||
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) {
|
||||
@ -727,13 +823,15 @@ class Compiler(
|
||||
// assign vars as declared the standard way
|
||||
argsDeclaration.assignToContext(context, defaultAccessType = AccessType.Val)
|
||||
}
|
||||
try {
|
||||
return 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))
|
||||
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)
|
||||
else LocalVarRef(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
|
||||
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(scp)
|
||||
block.execute(scope)
|
||||
} finally {
|
||||
scp.currentClassCtx = saved
|
||||
scope.currentClassCtx = saved
|
||||
}
|
||||
ObjVoid
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
object : Statement() {
|
||||
override val pos: Pos = id.pos
|
||||
override suspend fun execute(scope: Scope): Obj {
|
||||
scope.currentClassCtx?.instanceInitializers?.add(initStmt)
|
||||
return ObjVoid
|
||||
}
|
||||
statement {
|
||||
currentClassCtx?.instanceInitializers?.add(initStmt)
|
||||
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 {
|
||||
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(this)
|
||||
whenValue = value.execute(scope)
|
||||
var found = false
|
||||
for (c in cases)
|
||||
if (c.condition.execute(this).toBool()) {
|
||||
result = c.block.execute(this)
|
||||
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,10 +1922,12 @@ 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)
|
||||
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 = sc.createChildScope(pos = start)
|
||||
val throwScope = scope.createChildScope(pos = start)
|
||||
if (errorObject is ObjString) {
|
||||
errorObject = ObjException(throwScope, errorObject.value).apply { getStackTrace() }
|
||||
}
|
||||
@ -1794,9 +1944,11 @@ class Compiler(
|
||||
).apply { getStackTrace() }
|
||||
throwScope.raiseError(errorObject)
|
||||
} else {
|
||||
val msg = errorObject.invokeInstanceMethod(sc, "message").toString(sc).value
|
||||
val msg = errorObject.invokeInstanceMethod(scope, "message").toString(scope).value
|
||||
throwScope.raiseError(errorObject, start, msg)
|
||||
}
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1868,11 +2020,14 @@ class Compiler(
|
||||
if (catches.isEmpty() && finallyClause == null)
|
||||
throw ScriptError(cc.currentPos(), "try block must have either catch or finally clause or both")
|
||||
|
||||
return statement {
|
||||
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(this)
|
||||
result = body.execute(scope)
|
||||
} catch (e: ReturnException) {
|
||||
throw e
|
||||
} catch (e: LoopBreakContinueException) {
|
||||
@ -1881,22 +2036,22 @@ class Compiler(
|
||||
// convert to appropriate exception
|
||||
val caughtObj = when (e) {
|
||||
is ExecutionError -> e.errorObject
|
||||
else -> ObjUnknownException(this, e.message ?: e.toString())
|
||||
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 = this[exceptionClassName]?.value as? ObjClass
|
||||
?: raiseSymbolNotFound("error class does not exist or is not a class: $exceptionClassName")
|
||||
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 = this.createChildScope(pos = cdata.catchVar.pos)
|
||||
val catchContext = scope.createChildScope(pos = cdata.catchVar.pos)
|
||||
catchContext.addItem(cdata.catchVar.value, false, caughtObj)
|
||||
result = cdata.block.execute(catchContext)
|
||||
isCaught = true
|
||||
@ -1908,9 +2063,10 @@ class Compiler(
|
||||
throw e
|
||||
} finally {
|
||||
// finally clause does not alter result!
|
||||
finallyClause?.execute(this)
|
||||
finallyClause?.execute(scope)
|
||||
}
|
||||
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,9 +2212,11 @@ class Compiler(
|
||||
|
||||
val initScope = popInitScope()
|
||||
|
||||
return statement(startPos) { context ->
|
||||
return object : Statement() {
|
||||
override val pos: Pos = startPos
|
||||
override suspend fun execute(scope: Scope): Obj {
|
||||
val parentClasses = baseSpecs.map { baseSpec ->
|
||||
val rec = context[baseSpec.name] ?: throw ScriptError(startPos, "unknown base class: ${baseSpec.name}")
|
||||
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")
|
||||
}
|
||||
|
||||
@ -2067,7 +2229,7 @@ class Compiler(
|
||||
if (argsList != null) newClass.directParentArgs[parentClasses[i]] = argsList
|
||||
}
|
||||
|
||||
val classScope = context.createChildScope(newThisObj = newClass)
|
||||
val classScope = scope.createChildScope(newThisObj = newClass)
|
||||
classScope.currentClassCtx = newClass
|
||||
newClass.classScope = classScope
|
||||
classScope.addConst("object", newClass)
|
||||
@ -2075,10 +2237,11 @@ class Compiler(
|
||||
bodyInit?.execute(classScope)
|
||||
|
||||
// Create instance (singleton)
|
||||
val instance = newClass.callOn(context.createChildScope(Arguments.EMPTY))
|
||||
val instance = newClass.callOn(scope.createChildScope(Arguments.EMPTY))
|
||||
if (nameToken != null)
|
||||
context.addItem(className, false, instance)
|
||||
instance
|
||||
scope.addItem(className, false, instance)
|
||||
return instance
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2198,24 +2361,29 @@ class Compiler(
|
||||
// create instance constructor
|
||||
// create custom objClass with all fields and instance constructor
|
||||
|
||||
val constructorCode = statement {
|
||||
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 = thisObj as ObjInstance
|
||||
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.
|
||||
instance
|
||||
return instance
|
||||
}
|
||||
statement {
|
||||
}
|
||||
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 =
|
||||
this[baseSpec.name] ?: throw ScriptError(nameToken.pos, "unknown base class: ${baseSpec.name}")
|
||||
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")
|
||||
}
|
||||
|
||||
@ -2247,9 +2415,9 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
|
||||
addItem(className, false, newClass)
|
||||
scope.addItem(className, false, newClass)
|
||||
// Prepare class scope for class-scope members (static) and future registrations
|
||||
val classScope = createChildScope(newThisObj = newClass)
|
||||
val classScope = scope.createChildScope(newThisObj = newClass)
|
||||
// Set lexical class context for visibility tagging inside class body
|
||||
classScope.currentClassCtx = newClass
|
||||
newClass.classScope = classScope
|
||||
@ -2261,7 +2429,8 @@ class Compiler(
|
||||
}
|
||||
newClass.checkAbstractSatisfaction(nameToken.pos)
|
||||
// Debug summary: list registered instance methods and class-scope functions for this class
|
||||
newClass
|
||||
return newClass
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2319,8 +2488,10 @@ 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)
|
||||
@ -2329,7 +2500,7 @@ class Compiler(
|
||||
val sourceObj = source.execute(forContext)
|
||||
|
||||
if (sourceObj is ObjRange && sourceObj.isIntRange && PerfFlags.PRIMITIVE_FASTOPS) {
|
||||
loopIntRange(
|
||||
return loopIntRange(
|
||||
forContext,
|
||||
sourceObj.start!!.toLong(),
|
||||
if (sourceObj.isEndInclusive)
|
||||
@ -2343,7 +2514,7 @@ class Compiler(
|
||||
canBreak
|
||||
)
|
||||
} else if (sourceObj.isInstanceOf(ObjIterable)) {
|
||||
loopIterable(forContext, sourceObj, loopSO, body, elseStatement, label, canBreak)
|
||||
return loopIterable(forContext, sourceObj, loopSO, body, elseStatement, label, canBreak)
|
||||
} else {
|
||||
val size = runCatching { sourceObj.readField(forContext, "size").value.toInt() }
|
||||
.getOrElse {
|
||||
@ -2362,7 +2533,7 @@ class Compiler(
|
||||
.getOrElse {
|
||||
throw ScriptError(
|
||||
tOp.pos,
|
||||
"object is not enumerable: no index access for ${sourceObj.inspect(cxt)}",
|
||||
"object is not enumerable: no index access for ${sourceObj.inspect(scope)}",
|
||||
it
|
||||
)
|
||||
}
|
||||
@ -2387,9 +2558,10 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
if (!breakCaught && elseStatement != null) {
|
||||
result = elseStatement.execute(cxt)
|
||||
result = elseStatement.execute(scope)
|
||||
}
|
||||
return result
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -2484,11 +2656,13 @@ class Compiler(
|
||||
null
|
||||
}
|
||||
|
||||
return statement(body.pos) {
|
||||
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 = it.createChildScope().apply { skipScopeCreation = true }
|
||||
val doScope = scope.createChildScope().apply { skipScopeCreation = true }
|
||||
try {
|
||||
result = body.execute(doScope)
|
||||
} catch (e: LoopBreakContinueException) {
|
||||
@ -2507,8 +2681,9 @@ class Compiler(
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!wasBroken) elseStatement?.let { s -> result = s.execute(it) }
|
||||
result
|
||||
if (!wasBroken) elseStatement?.let { s -> result = s.execute(scope) }
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2532,11 +2707,13 @@ class Compiler(
|
||||
cc.previous()
|
||||
null
|
||||
}
|
||||
return statement(body.pos) {
|
||||
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(it).toBool()) {
|
||||
val loopScope = it.createChildScope()
|
||||
while (condition.execute(scope).toBool()) {
|
||||
val loopScope = scope.createChildScope()
|
||||
if (canBreak) {
|
||||
try {
|
||||
result = body.execute(loopScope)
|
||||
@ -2554,8 +2731,9 @@ class Compiler(
|
||||
} else
|
||||
result = body.execute(loopScope)
|
||||
}
|
||||
if (!wasBroken) elseStatement?.let { s -> result = s.execute(it) }
|
||||
result
|
||||
if (!wasBroken) elseStatement?.let { s -> result = s.execute(scope) }
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2590,8 +2768,10 @@ class Compiler(
|
||||
|
||||
cc.addBreak()
|
||||
|
||||
return statement(start) {
|
||||
val returnValue = resultExpr?.execute(it)// ?: 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,
|
||||
@ -2599,6 +2779,7 @@ class Compiler(
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseContinueStatement(start: Pos): Statement {
|
||||
val t = cc.next()
|
||||
@ -2614,13 +2795,16 @@ class Compiler(
|
||||
}
|
||||
cc.addBreak()
|
||||
|
||||
return statement(start) {
|
||||
return object : Statement() {
|
||||
override val pos: Pos = start
|
||||
override suspend fun execute(scope: Scope): Obj {
|
||||
throw LoopBreakContinueException(
|
||||
doContinue = true,
|
||||
label = label,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun parseReturnStatement(start: Pos): Statement {
|
||||
var t = cc.next()
|
||||
@ -2648,11 +2832,14 @@ class Compiler(
|
||||
parseExpression()
|
||||
} else null
|
||||
|
||||
return statement(start) {
|
||||
val returnValue = resultExpr?.execute(it) ?: ObjVoid
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ensureRparen(): Pos {
|
||||
val t = cc.next()
|
||||
@ -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)
|
||||
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(it)
|
||||
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,13 +2995,22 @@ class Compiler(
|
||||
cc.labels.add(name)
|
||||
outerLabel?.let { cc.labels.add(it) }
|
||||
|
||||
val paramNames: Set<String> = argsDeclaration.params.map { it.name }.toSet()
|
||||
val paramNamesList = argsDeclaration.params.map { it.name }
|
||||
val paramNames: Set<String> = 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") }
|
||||
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 if (isAbstract || isDelegated) {
|
||||
null
|
||||
} else
|
||||
@ -2821,19 +3022,26 @@ class Compiler(
|
||||
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)
|
||||
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 ->
|
||||
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
|
||||
@ -2845,23 +3053,27 @@ class Compiler(
|
||||
// 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 {
|
||||
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 ->
|
||||
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)
|
||||
@ -2877,7 +3089,7 @@ class Compiler(
|
||||
context.addExtension(type, name, ObjRecord(ObjUnset, isMutable = false, visibility = visibility, declaringClass = null, type = ObjRecord.Type.Delegated).apply {
|
||||
delegate = finalDelegate
|
||||
})
|
||||
return@statement ObjVoid
|
||||
return ObjVoid
|
||||
}
|
||||
|
||||
val th = context.thisObj
|
||||
@ -2892,7 +3104,9 @@ class Compiler(
|
||||
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 ->
|
||||
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 {
|
||||
@ -2903,14 +3117,15 @@ class Compiler(
|
||||
scp.addItem(storageName, false, ObjUnset, visibility, null, recordType = ObjRecord.Type.Delegated, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride, isTransient = isTransient).apply {
|
||||
delegate = finalDelegate2
|
||||
}
|
||||
ObjVoid
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
} else {
|
||||
context.addItem(name, false, ObjUnset, visibility, recordType = ObjRecord.Type.Delegated, isTransient = isTransient).apply {
|
||||
delegate = finalDelegate
|
||||
}
|
||||
}
|
||||
return@statement ObjVoid
|
||||
return ObjVoid
|
||||
}
|
||||
|
||||
// we added fn in the context. now we must save closure
|
||||
@ -2925,13 +3140,17 @@ class Compiler(
|
||||
// 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 {
|
||||
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
|
||||
(thisObj as? ObjInstance)?.let { i ->
|
||||
annotatedFnBody.execute(ClosureScope(this, i.instanceScope))
|
||||
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(thisObj.autoInstanceScope(this))
|
||||
?: annotatedFnBody.execute(scope.thisObj.autoInstanceScope(scope))
|
||||
return result
|
||||
}
|
||||
}
|
||||
context.addExtension(type, name, ObjRecord(stmt, isMutable = false, visibility = visibility, declaringClass = null))
|
||||
}
|
||||
@ -2971,7 +3190,8 @@ class Compiler(
|
||||
}
|
||||
// as the function can be called from anywhere, we have
|
||||
// saved the proper context in the closure
|
||||
annotatedFnBody
|
||||
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) {
|
||||
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:
|
||||
block.execute(if (it.skipScopeCreation) it else it.createChildScope(startPos))
|
||||
}.also {
|
||||
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,7 +3312,9 @@ class Compiler(
|
||||
val names = mutableListOf<String>()
|
||||
pattern.forEachVariable { names.add(it) }
|
||||
|
||||
return statement(start) { context ->
|
||||
return object : Statement() {
|
||||
override val pos: Pos = start
|
||||
override suspend fun execute(context: Scope): Obj {
|
||||
val value = initialExpression.execute(context)
|
||||
for (name in names) {
|
||||
context.addItem(name, true, ObjVoid, visibility, isTransient = isTransient)
|
||||
@ -3094,7 +3329,8 @@ class Compiler(
|
||||
context.updateSlotFor(name, immutableRec)
|
||||
}
|
||||
}
|
||||
ObjVoid
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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<String, Int>) {
|
||||
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
|
||||
|
||||
@ -114,8 +114,7 @@ open class ObjClass(
|
||||
val classId: Long = ClassIdGen.nextId()
|
||||
var layoutVersion: Int = 0
|
||||
|
||||
private val mangledNameCache = mutableMapOf<String, String>()
|
||||
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<String, ObjRecord>()
|
||||
|
||||
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<String, FieldSlot> = emptyMap()
|
||||
private var fieldSlotCount: Int = 0
|
||||
private var instanceMemberLayoutVersion: Int = -1
|
||||
private var instanceMemberCache: Map<String, ResolvedMember> = emptyMap()
|
||||
private var methodSlotLayoutVersion: Int = -1
|
||||
private var methodSlotMap: Map<String, MethodSlot> = emptyMap()
|
||||
private var methodSlotCount: Int = 0
|
||||
|
||||
private fun ensureFieldSlots(): Map<String, FieldSlot> {
|
||||
if (fieldSlotLayoutVersion == layoutVersion) return fieldSlotMap
|
||||
val res = mutableMapOf<String, FieldSlot>()
|
||||
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<String, ResolvedMember> {
|
||||
if (instanceMemberLayoutVersion == layoutVersion) return instanceMemberCache
|
||||
val res = mutableMapOf<String, ResolvedMember>()
|
||||
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<String, MethodSlot> {
|
||||
if (methodSlotLayoutVersion == layoutVersion) return methodSlotMap
|
||||
val res = mutableMapOf<String, MethodSlot>()
|
||||
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<String, FieldSlot> = 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<String, MethodSlot> = 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()
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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<ObjRecord?> = emptyArray()
|
||||
internal var methodSlots: Array<ObjRecord?> = 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,10 +175,15 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
val d = decl ?: obj.declaringClass
|
||||
if (d != null) {
|
||||
val mangled = d.mangledName(name)
|
||||
fieldRecordForKey(mangled)?.let {
|
||||
targetRec = it
|
||||
}
|
||||
if (targetRec === obj) {
|
||||
instanceScope.objects[mangled]?.let {
|
||||
targetRec = it
|
||||
}
|
||||
}
|
||||
}
|
||||
if (targetRec === obj) {
|
||||
instanceScope.objects[name]?.let { rec ->
|
||||
// Check if this record in instanceScope is the one we want.
|
||||
@ -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,13 +404,23 @@ 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) {
|
||||
// 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)) {
|
||||
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 = cls.mangledName(name)
|
||||
val storageName = decl.mangledName(name)
|
||||
val del = instanceScope[storageName]?.delegate ?: rec.delegate
|
||||
?: scope.raiseError("Internal error: delegated member $name has no delegate (tried $storageName)")
|
||||
|
||||
@ -276,10 +429,9 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
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)
|
||||
propVal.invoke(scope, this, args, rec.declaringClass ?: decl)
|
||||
})
|
||||
}
|
||||
val decl = rec.declaringClass ?: cls
|
||||
val effectiveCaller = caller ?: if (scope.thisObj === this) objClass else null
|
||||
if (!canAccessMember(rec.visibility, decl, effectiveCaller, name))
|
||||
scope.raiseError(
|
||||
@ -303,7 +455,6 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
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
|
||||
|
||||
@ -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<ObjInstance, ObjClass> {
|
||||
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<ParsedArgument>,
|
||||
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]
|
||||
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) {
|
||||
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,37 +1650,51 @@ 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hierarchyMember != null) {
|
||||
val visibility = hierarchyMember.visibility
|
||||
val callable = hierarchyMember.value
|
||||
val decl = hierarchyMember.declaringClass ?: base.objClass
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Fallback to name-based lookup per call (handles extensions and root members)
|
||||
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a -> obj.invokeInstanceMethod(sc, name, a) }
|
||||
@ -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<ParsedArgument>,
|
||||
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<ParsedArgument>,
|
||||
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<ListEntry>) : 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)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user