Added delegation support: Delegate, lazy, "by" keyword for valr/var/fun and object {} singletons
This commit is contained in:
1 parent
8e766490d9
commit
5f819dc87a
20 files changed
+1183
-754
No files matched your search
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -138,7 +138,7 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
||||
} else {
|
||||
val value = if (hp < callArgs.size) callArgs[hp++]
|
||||
else a.defaultValue?.execute(scope)
|
||||
?: scope.raiseIllegalArgument("too few arguments for the call")
|
||||
?: scope.raiseIllegalArgument("too few arguments for the call (missing ${a.name})")
|
||||
assign(a, value)
|
||||
}
|
||||
i++
|
||||
|
||||
@@ -286,7 +286,7 @@ class Compiler(
|
||||
while (true) {
|
||||
val t = cc.next()
|
||||
return when (t.type) {
|
||||
Token.Type.ID -> {
|
||||
Token.Type.ID, Token.Type.OBJECT -> {
|
||||
parseKeywordStatement(t)
|
||||
?: run {
|
||||
cc.previous()
|
||||
@@ -337,7 +337,7 @@ class Compiler(
|
||||
|
||||
private suspend fun parseExpression(): Statement? {
|
||||
val pos = cc.currentPos()
|
||||
return parseExpressionLevel()?.let { a -> statement(pos) { a.get(it).value } }
|
||||
return parseExpressionLevel()?.let { a -> statement(pos) { a.evalValue(it) } }
|
||||
}
|
||||
|
||||
private suspend fun parseExpressionLevel(level: Int = 0): ObjRef? {
|
||||
@@ -1063,7 +1063,7 @@ 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.get(it).value }, t1.pos, isSplat = false, name = name)
|
||||
return ParsedArgument(statement(t1.pos) { localVar.evalValue(it) }, 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)
|
||||
@@ -1135,7 +1135,7 @@ 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.get(it).value }, t1.pos, isSplat = false, name = name)
|
||||
return ParsedArgument(statement(t1.pos) { localVar.evalValue(it) }, 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)
|
||||
@@ -1356,6 +1356,12 @@ class Compiler(
|
||||
parseClassDeclaration(isAbstract)
|
||||
}
|
||||
|
||||
"object" -> {
|
||||
if (isStatic || isClosed || isOverride || isExtern || isAbstract)
|
||||
throw ScriptError(currentToken.pos, "unsupported modifiers for object: ${modifiers.joinToString(" ")}")
|
||||
parseObjectDeclaration()
|
||||
}
|
||||
|
||||
"interface" -> {
|
||||
if (isStatic || isClosed || isOverride || isExtern || isAbstract)
|
||||
throw ScriptError(
|
||||
@@ -1421,6 +1427,12 @@ class Compiler(
|
||||
parseClassDeclaration()
|
||||
}
|
||||
|
||||
"object" -> {
|
||||
pendingDeclStart = id.pos
|
||||
pendingDeclDoc = consumePendingDoc()
|
||||
parseObjectDeclaration()
|
||||
}
|
||||
|
||||
"init" -> {
|
||||
if (codeContexts.lastOrNull() is CodeContext.ClassBody && cc.peekNextNonWhitespace().type == Token.Type.LBRACE) {
|
||||
val block = parseBlock()
|
||||
@@ -1847,6 +1859,81 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun parseObjectDeclaration(): Statement {
|
||||
val nameToken = cc.requireToken(Token.Type.ID)
|
||||
val startPos = pendingDeclStart ?: nameToken.pos
|
||||
val doc = pendingDeclDoc ?: consumePendingDoc()
|
||||
pendingDeclDoc = null
|
||||
pendingDeclStart = null
|
||||
|
||||
// Optional base list: ":" Base ("," Base)* where Base := ID ( "(" args? ")" )?
|
||||
data class BaseSpec(val name: String, val args: List<ParsedArgument>?)
|
||||
|
||||
val baseSpecs = mutableListOf<BaseSpec>()
|
||||
if (cc.skipTokenOfType(Token.Type.COLON, isOptional = true)) {
|
||||
do {
|
||||
val baseId = cc.requireToken(Token.Type.ID, "base class name expected")
|
||||
var argsList: List<ParsedArgument>? = null
|
||||
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true)) {
|
||||
argsList = parseArgsNoTailBlock()
|
||||
}
|
||||
baseSpecs += BaseSpec(baseId.value, argsList)
|
||||
} while (cc.skipTokenOfType(Token.Type.COMMA, isOptional = true))
|
||||
}
|
||||
|
||||
cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
|
||||
|
||||
pushInitScope()
|
||||
|
||||
// Robust body detection
|
||||
var classBodyRange: MiniRange? = null
|
||||
val bodyInit: Statement? = run {
|
||||
val saved = cc.savePos()
|
||||
val next = cc.nextNonWhitespace()
|
||||
if (next.type == Token.Type.LBRACE) {
|
||||
val bodyStart = next.pos
|
||||
val st = withLocalNames(emptySet()) {
|
||||
parseScript()
|
||||
}
|
||||
val rbTok = cc.next()
|
||||
if (rbTok.type != Token.Type.RBRACE) throw ScriptError(rbTok.pos, "unbalanced braces in object body")
|
||||
classBodyRange = MiniRange(bodyStart, rbTok.pos)
|
||||
st
|
||||
} else {
|
||||
cc.restorePos(saved)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
val initScope = popInitScope()
|
||||
val className = nameToken.value
|
||||
|
||||
return statement(startPos) { context ->
|
||||
val parentClasses = baseSpecs.map { baseSpec ->
|
||||
val rec = context[baseSpec.name] ?: throw ScriptError(nameToken.pos, "unknown base class: ${baseSpec.name}")
|
||||
(rec.value as? ObjClass) ?: throw ScriptError(nameToken.pos, "${baseSpec.name} is not a class")
|
||||
}
|
||||
|
||||
val newClass = ObjInstanceClass(className, *parentClasses.toTypedArray())
|
||||
for (i in parentClasses.indices) {
|
||||
val argsList = baseSpecs[i].args
|
||||
// In object, we evaluate parent args once at creation time
|
||||
if (argsList != null) newClass.directParentArgs[parentClasses[i]] = argsList
|
||||
}
|
||||
|
||||
val classScope = context.createChildScope(newThisObj = newClass)
|
||||
classScope.currentClassCtx = newClass
|
||||
newClass.classScope = classScope
|
||||
|
||||
bodyInit?.execute(classScope)
|
||||
|
||||
// Create instance (singleton)
|
||||
val instance = newClass.callOn(context.createChildScope(Arguments.EMPTY))
|
||||
context.addItem(className, false, instance)
|
||||
instance
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun parseClassDeclaration(isAbstract: Boolean = false): Statement {
|
||||
val nameToken = cc.requireToken(Token.Type.ID)
|
||||
val startPos = pendingDeclStart ?: nameToken.pos
|
||||
@@ -2457,9 +2544,9 @@ class Compiler(
|
||||
val annotation = lastAnnotation
|
||||
val parentContext = codeContexts.last()
|
||||
|
||||
t = cc.next()
|
||||
// Is extension?
|
||||
if (t.type == Token.Type.DOT) {
|
||||
if (cc.peekNextNonWhitespace().type == Token.Type.DOT) {
|
||||
cc.nextNonWhitespace() // consume DOT
|
||||
extTypeName = name
|
||||
val receiverEnd = Pos(start.source, start.line, start.column + name.length)
|
||||
receiverMini = MiniTypeName(
|
||||
@@ -2472,24 +2559,33 @@ class Compiler(
|
||||
throw ScriptError(t.pos, "illegal extension format: expected function name")
|
||||
name = t.value
|
||||
nameStartPos = t.pos
|
||||
t = cc.next()
|
||||
}
|
||||
|
||||
if (t.type != Token.Type.LPAREN)
|
||||
throw ScriptError(t.pos, "Bad function definition: expected '(' after 'fn ${name}'")
|
||||
val argsDeclaration: ArgsDeclaration =
|
||||
if (cc.peekNextNonWhitespace().type == Token.Type.LPAREN) {
|
||||
cc.nextNonWhitespace() // consume (
|
||||
parseArgsDeclaration() ?: ArgsDeclaration(emptyList(), Token.Type.RPAREN)
|
||||
} else ArgsDeclaration(emptyList(), Token.Type.RPAREN)
|
||||
|
||||
val argsDeclaration = parseArgsDeclaration()
|
||||
if (argsDeclaration == null || argsDeclaration.endTokenType != Token.Type.RPAREN)
|
||||
// Optional return type
|
||||
val returnTypeMini: MiniTypeRef? = if (cc.peekNextNonWhitespace().type == Token.Type.COLON) {
|
||||
parseTypeDeclarationWithMini().second
|
||||
} else null
|
||||
|
||||
var isDelegated = false
|
||||
var delegateExpression: Statement? = null
|
||||
if (cc.peekNextNonWhitespace().type == Token.Type.BY) {
|
||||
cc.nextNonWhitespace() // consume by
|
||||
isDelegated = true
|
||||
delegateExpression = parseExpression() ?: throw ScriptError(cc.current().pos, "Expected delegate expression")
|
||||
}
|
||||
|
||||
if (!isDelegated && argsDeclaration.endTokenType != Token.Type.RPAREN)
|
||||
throw ScriptError(
|
||||
t.pos,
|
||||
"Bad function definition: expected valid argument declaration or () after 'fn ${name}'"
|
||||
)
|
||||
|
||||
// Optional return type
|
||||
val returnTypeMini: MiniTypeRef? = if (cc.current().type == Token.Type.COLON) {
|
||||
parseTypeDeclarationWithMini().second
|
||||
} else null
|
||||
|
||||
// Capture doc locally to reuse even if we need to emit later
|
||||
val declDocLocal = pendingDeclDoc
|
||||
|
||||
@@ -2526,17 +2622,13 @@ class Compiler(
|
||||
localDeclCountStack.add(0)
|
||||
val fnStatements = if (isExtern)
|
||||
statement { raiseError("extern function not provided: $name") }
|
||||
else if (isAbstract) {
|
||||
val next = cc.peekNextNonWhitespace()
|
||||
if (next.type == Token.Type.ASSIGN || next.type == Token.Type.LBRACE)
|
||||
throw ScriptError(next.pos, "abstract function $name cannot have a body")
|
||||
else if (isAbstract || isDelegated) {
|
||||
null
|
||||
} else
|
||||
withLocalNames(paramNames) {
|
||||
val next = cc.peekNextNonWhitespace()
|
||||
if (next.type == Token.Type.ASSIGN) {
|
||||
cc.skipWsTokens()
|
||||
cc.next() // consume '='
|
||||
cc.nextNonWhitespace() // consume '='
|
||||
val expr = parseExpression() ?: throw ScriptError(cc.current().pos, "Expected function body expression")
|
||||
// Shorthand function returns the expression value
|
||||
statement(expr.pos) { scope ->
|
||||
@@ -2573,6 +2665,57 @@ class Compiler(
|
||||
}
|
||||
// parentContext
|
||||
val fnCreateStatement = statement(start) { context ->
|
||||
if (isDelegated) {
|
||||
val accessType = context.resolveQualifiedIdentifier("DelegateAccess.Callable")
|
||||
val initValue = delegateExpression!!.execute(context)
|
||||
val finalDelegate = try {
|
||||
initValue.invokeInstanceMethod(context, "bind", Arguments(ObjString(name), accessType, context.thisObj))
|
||||
} catch (e: Exception) {
|
||||
initValue
|
||||
}
|
||||
|
||||
if (extTypeName != null) {
|
||||
val type = context[extTypeName!!]?.value ?: context.raiseSymbolNotFound("class $extTypeName not found")
|
||||
if (type !is ObjClass) context.raiseClassCastError("$extTypeName is not the class instance")
|
||||
context.addExtension(type, name, ObjRecord(ObjUnset, isMutable = false, visibility = visibility, declaringClass = null, type = ObjRecord.Type.Delegated).apply {
|
||||
delegate = finalDelegate
|
||||
})
|
||||
return@statement ObjVoid
|
||||
}
|
||||
|
||||
val th = context.thisObj
|
||||
if (isStatic) {
|
||||
(th as ObjClass).createClassField(name, ObjUnset, false, visibility, null, start, type = ObjRecord.Type.Delegated).apply {
|
||||
delegate = finalDelegate
|
||||
}
|
||||
context.addItem(name, false, ObjUnset, visibility, recordType = ObjRecord.Type.Delegated).apply {
|
||||
delegate = finalDelegate
|
||||
}
|
||||
} else if (th is ObjClass) {
|
||||
val cls: ObjClass = th
|
||||
val storageName = "${cls.className}::$name"
|
||||
cls.createField(name, ObjUnset, false, visibility, null, start, declaringClass = cls, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride, type = ObjRecord.Type.Delegated)
|
||||
cls.instanceInitializers += statement(start) { scp ->
|
||||
val accessType2 = scp.resolveQualifiedIdentifier("DelegateAccess.Callable")
|
||||
val initValue2 = delegateExpression!!.execute(scp)
|
||||
val finalDelegate2 = try {
|
||||
initValue2.invokeInstanceMethod(scp, "bind", Arguments(ObjString(name), accessType2, scp.thisObj))
|
||||
} catch (e: Exception) {
|
||||
initValue2
|
||||
}
|
||||
scp.addItem(storageName, false, ObjUnset, visibility, null, recordType = ObjRecord.Type.Delegated, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride).apply {
|
||||
delegate = finalDelegate2
|
||||
}
|
||||
ObjVoid
|
||||
}
|
||||
} else {
|
||||
context.addItem(name, false, ObjUnset, visibility, recordType = ObjRecord.Type.Delegated).apply {
|
||||
delegate = finalDelegate
|
||||
}
|
||||
}
|
||||
return@statement ObjVoid
|
||||
}
|
||||
|
||||
// we added fn in the context. now we must save closure
|
||||
// for the function, unless we're in the class scope:
|
||||
if (isStatic || parentContext !is CodeContext.ClassBody)
|
||||
@@ -2803,14 +2946,14 @@ class Compiler(
|
||||
if (!isStatic) declareLocalName(name)
|
||||
|
||||
val isDelegate = if (isAbstract) {
|
||||
if (!isProperty && (eqToken.type == Token.Type.ASSIGN || eqToken.isId("by")))
|
||||
if (!isProperty && (eqToken.type == Token.Type.ASSIGN || eqToken.type == Token.Type.BY))
|
||||
throw ScriptError(eqToken.pos, "abstract variable $name cannot have an initializer or delegate")
|
||||
// Abstract variables don't have initializers
|
||||
cc.restorePos(markBeforeEq)
|
||||
cc.skipWsTokens()
|
||||
setNull = true
|
||||
false
|
||||
} else if (!isProperty && eqToken.isId("by")) {
|
||||
} else if (!isProperty && eqToken.type == Token.Type.BY) {
|
||||
true
|
||||
} else {
|
||||
if (!isProperty && eqToken.type != Token.Type.ASSIGN) {
|
||||
@@ -2858,11 +3001,35 @@ class Compiler(
|
||||
// when creating instance, but we need to execute it in the class initializer which
|
||||
// is missing as for now. Add it to the compiler context?
|
||||
|
||||
// if (isDelegate) throw ScriptError(start, "static delegates are not yet implemented")
|
||||
currentInitScope += statement {
|
||||
val initValue = initialExpression?.execute(this)?.byValueCopy() ?: ObjNull
|
||||
(thisObj as ObjClass).createClassField(name, initValue, isMutable, visibility, null, pos)
|
||||
addItem(name, isMutable, initValue, visibility, null, ObjRecord.Type.Field)
|
||||
if (isDelegate) {
|
||||
val accessTypeStr = if (isMutable) "Var" else "Val"
|
||||
val accessType = resolveQualifiedIdentifier("DelegateAccess.$accessTypeStr")
|
||||
val finalDelegate = try {
|
||||
initValue.invokeInstanceMethod(this, "bind", Arguments(ObjString(name), accessType, thisObj))
|
||||
} catch (e: Exception) {
|
||||
initValue
|
||||
}
|
||||
(thisObj as ObjClass).createClassField(
|
||||
name,
|
||||
ObjUnset,
|
||||
isMutable,
|
||||
visibility,
|
||||
null,
|
||||
start,
|
||||
type = ObjRecord.Type.Delegated
|
||||
).apply {
|
||||
delegate = finalDelegate
|
||||
}
|
||||
// Also expose in current init scope
|
||||
addItem(name, isMutable, ObjUnset, visibility, null, ObjRecord.Type.Delegated).apply {
|
||||
delegate = finalDelegate
|
||||
}
|
||||
} else {
|
||||
(thisObj as ObjClass).createClassField(name, initValue, isMutable, visibility, null, start)
|
||||
addItem(name, isMutable, initValue, visibility, null, ObjRecord.Type.Field)
|
||||
}
|
||||
ObjVoid
|
||||
}
|
||||
return NopStatement
|
||||
@@ -3004,7 +3171,83 @@ class Compiler(
|
||||
if (!isStatic) declareLocalName(name)
|
||||
|
||||
if (isDelegate) {
|
||||
TODO()
|
||||
val declaringClassName = declaringClassNameCaptured
|
||||
if (declaringClassName != null) {
|
||||
val storageName = "$declaringClassName::$name"
|
||||
val isClassScope = context.thisObj is ObjClass && (context.thisObj !is ObjInstance)
|
||||
if (isClassScope) {
|
||||
val cls = context.thisObj as ObjClass
|
||||
cls.createField(
|
||||
name,
|
||||
ObjUnset,
|
||||
isMutable,
|
||||
visibility,
|
||||
setterVisibility,
|
||||
start,
|
||||
type = ObjRecord.Type.Delegated,
|
||||
isAbstract = isAbstract,
|
||||
isClosed = isClosed,
|
||||
isOverride = isOverride
|
||||
)
|
||||
cls.instanceInitializers += statement(start) { scp ->
|
||||
val initValue = initialExpression!!.execute(scp)
|
||||
val accessTypeStr = if (isMutable) "Var" else "Val"
|
||||
val accessType = scp.resolveQualifiedIdentifier("DelegateAccess.$accessTypeStr")
|
||||
val finalDelegate = try {
|
||||
initValue.invokeInstanceMethod(scp, "bind", Arguments(ObjString(name), accessType, scp.thisObj))
|
||||
} catch (e: Exception) {
|
||||
initValue
|
||||
}
|
||||
scp.addItem(
|
||||
storageName, isMutable, ObjUnset, visibility, setterVisibility,
|
||||
recordType = ObjRecord.Type.Delegated,
|
||||
isAbstract = isAbstract,
|
||||
isClosed = isClosed,
|
||||
isOverride = isOverride
|
||||
).apply {
|
||||
delegate = finalDelegate
|
||||
}
|
||||
ObjVoid
|
||||
}
|
||||
return@statement ObjVoid
|
||||
} else {
|
||||
val initValue = initialExpression!!.execute(context)
|
||||
val accessTypeStr = if (isMutable) "Var" else "Val"
|
||||
val accessType = context.resolveQualifiedIdentifier("DelegateAccess.$accessTypeStr")
|
||||
val finalDelegate = try {
|
||||
initValue.invokeInstanceMethod(context, "bind", Arguments(ObjString(name), accessType, context.thisObj))
|
||||
} catch (e: Exception) {
|
||||
initValue
|
||||
}
|
||||
val rec = context.addItem(
|
||||
storageName, isMutable, ObjUnset, visibility, setterVisibility,
|
||||
recordType = ObjRecord.Type.Delegated,
|
||||
isAbstract = isAbstract,
|
||||
isClosed = isClosed,
|
||||
isOverride = isOverride
|
||||
)
|
||||
rec.delegate = finalDelegate
|
||||
return@statement finalDelegate
|
||||
}
|
||||
} else {
|
||||
val initValue = initialExpression!!.execute(context)
|
||||
val accessTypeStr = if (isMutable) "Var" else "Val"
|
||||
val accessType = context.resolveQualifiedIdentifier("DelegateAccess.$accessTypeStr")
|
||||
val finalDelegate = try {
|
||||
initValue.invokeInstanceMethod(context, "bind", Arguments(ObjString(name), accessType, ObjNull))
|
||||
} catch (e: Exception) {
|
||||
initValue
|
||||
}
|
||||
val rec = context.addItem(
|
||||
name, isMutable, ObjUnset, visibility, setterVisibility,
|
||||
recordType = ObjRecord.Type.Delegated,
|
||||
isAbstract = isAbstract,
|
||||
isClosed = isClosed,
|
||||
isOverride = isOverride
|
||||
)
|
||||
rec.delegate = finalDelegate
|
||||
return@statement finalDelegate
|
||||
}
|
||||
} else if (getter != null || setter != null) {
|
||||
val declaringClassName = declaringClassNameCaptured!!
|
||||
val storageName = "$declaringClassName::$name"
|
||||
|
||||
@@ -356,6 +356,8 @@ private class Parser(fromPos: Pos) {
|
||||
when (text) {
|
||||
"in" -> Token("in", from, Token.Type.IN)
|
||||
"is" -> Token("is", from, Token.Type.IS)
|
||||
"by" -> Token("by", from, Token.Type.BY)
|
||||
"object" -> Token("object", from, Token.Type.OBJECT)
|
||||
"as" -> {
|
||||
// support both `as` and tight `as?` without spaces
|
||||
if (currentChar == '?') { pos.advance(); Token("as?", from, Token.Type.ASNULL) }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -621,6 +621,36 @@ open class Scope(
|
||||
return ref.evalValue(this)
|
||||
}
|
||||
|
||||
suspend fun resolve(rec: ObjRecord, name: String): Obj {
|
||||
if (rec.type == ObjRecord.Type.Delegated) {
|
||||
val del = rec.delegate ?: raiseError("Internal error: delegated property $name has no delegate")
|
||||
val th = if (thisObj === ObjVoid) ObjNull else thisObj
|
||||
return del.invokeInstanceMethod(this, "getValue", Arguments(th, ObjString(name)), onNotFoundResult = {
|
||||
// If getValue not found, return a wrapper that calls invoke
|
||||
object : Statement() {
|
||||
override val pos: Pos = Pos.builtIn
|
||||
override suspend fun execute(scope: Scope): Obj {
|
||||
val th2 = if (scope.thisObj === ObjVoid) ObjNull else scope.thisObj
|
||||
val allArgs = (listOf(th2, ObjString(name)) + scope.args.list).toTypedArray()
|
||||
return del.invokeInstanceMethod(scope, "invoke", Arguments(*allArgs))
|
||||
}
|
||||
}
|
||||
})!!
|
||||
}
|
||||
return rec.value
|
||||
}
|
||||
|
||||
suspend fun assign(rec: ObjRecord, name: String, newValue: Obj) {
|
||||
if (rec.type == ObjRecord.Type.Delegated) {
|
||||
val del = rec.delegate ?: raiseError("Internal error: delegated property $name has no delegate")
|
||||
val th = if (thisObj === ObjVoid) ObjNull else thisObj
|
||||
del.invokeInstanceMethod(this, "setValue", Arguments(th, ObjString(name), newValue))
|
||||
return
|
||||
}
|
||||
if (!rec.isMutable && rec.value !== ObjUnset) raiseIllegalAssignment("can't reassign val $name")
|
||||
rec.value = newValue
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun new(): Scope =
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -36,7 +36,7 @@ data class Token(val value: String, val pos: Pos, val type: Type) {
|
||||
PLUS, MINUS, STAR, SLASH, PERCENT,
|
||||
ASSIGN, PLUSASSIGN, MINUSASSIGN, STARASSIGN, SLASHASSIGN, PERCENTASSIGN,
|
||||
PLUS2, MINUS2,
|
||||
IN, NOTIN, IS, NOTIS,
|
||||
IN, NOTIN, IS, NOTIS, BY,
|
||||
EQ, NEQ, LT, LTE, GT, GTE, REF_EQ, REF_NEQ, MATCH, NOTMATCH,
|
||||
SHUTTLE,
|
||||
AND, BITAND, OR, BITOR, BITXOR, NOT, BITNOT, DOT, ARROW, EQARROW, QUESTION, COLONCOLON,
|
||||
@@ -44,7 +44,7 @@ data class Token(val value: String, val pos: Pos, val type: Type) {
|
||||
SINLGE_LINE_COMMENT, MULTILINE_COMMENT,
|
||||
LABEL, ATLABEL, // label@ at@label
|
||||
// type-checking/casting
|
||||
AS, ASNULL,
|
||||
AS, ASNULL, OBJECT,
|
||||
//PUBLIC, PROTECTED, INTERNAL, EXPORT, OPEN, INLINE, OVERRIDE, ABSTRACT, SEALED, EXTERNAL, VAL, VAR, CONST, TYPE, FUN, CLASS, INTERFACE, ENUM, OBJECT, TRAIT, THIS,
|
||||
ELLIPSIS, DOTDOT, DOTDOTLT,
|
||||
NEWLINE,
|
||||
|
||||
@@ -74,7 +74,7 @@ private fun kindOf(type: Type, value: String): HighlightKind? = when (type) {
|
||||
Type.COMMA, Type.SEMICOLON, Type.COLON -> HighlightKind.Punctuation
|
||||
|
||||
// textual control keywords
|
||||
Type.IN, Type.NOTIN, Type.IS, Type.NOTIS, Type.AS, Type.ASNULL,
|
||||
Type.IN, Type.NOTIN, Type.IS, Type.NOTIS, Type.AS, Type.ASNULL, Type.BY, Type.OBJECT,
|
||||
Type.AND, Type.OR, Type.NOT -> HighlightKind.Keyword
|
||||
|
||||
// labels / annotations
|
||||
|
||||
@@ -94,13 +94,7 @@ open class Obj {
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(rec.visibility, decl, caller))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't invoke ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})"))
|
||||
val saved = scope.currentClassCtx
|
||||
scope.currentClassCtx = decl
|
||||
try {
|
||||
return rec.value.invoke(scope, this, args)
|
||||
} finally {
|
||||
scope.currentClassCtx = saved
|
||||
}
|
||||
return rec.value.invoke(scope, this, args, decl)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,13 +112,7 @@ open class Obj {
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(rec.visibility, decl, caller))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't invoke ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})"))
|
||||
val saved = scope.currentClassCtx
|
||||
scope.currentClassCtx = decl
|
||||
try {
|
||||
return rec.value.invoke(scope, this, args)
|
||||
} finally {
|
||||
scope.currentClassCtx = saved
|
||||
}
|
||||
return rec.value.invoke(scope, this, args, decl)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -376,7 +364,14 @@ open class Obj {
|
||||
)
|
||||
}
|
||||
|
||||
protected suspend fun resolveRecord(scope: Scope, obj: ObjRecord, name: String, decl: ObjClass?): ObjRecord {
|
||||
protected open suspend fun resolveRecord(scope: Scope, obj: ObjRecord, name: String, decl: ObjClass?): ObjRecord {
|
||||
if (obj.type == ObjRecord.Type.Delegated) {
|
||||
val del = obj.delegate ?: scope.raiseError("Internal error: delegated property $name has no delegate")
|
||||
return obj.copy(
|
||||
value = del.invokeInstanceMethod(scope, "getValue", Arguments(this, ObjString(name))),
|
||||
type = ObjRecord.Type.Other
|
||||
)
|
||||
}
|
||||
val value = obj.value
|
||||
if (value is ObjProperty) {
|
||||
return ObjRecord(value.callGetter(scope, this, decl), obj.isMutable)
|
||||
@@ -425,7 +420,10 @@ open class Obj {
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(field.effectiveWriteVisibility, decl, caller))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't assign field ${name}: not visible (declared in ${decl?.className ?: "?"}, caller ${caller?.className ?: "?"})"))
|
||||
if (field.value is ObjProperty) {
|
||||
if (field.type == ObjRecord.Type.Delegated) {
|
||||
val del = field.delegate ?: scope.raiseError("Internal error: delegated property $name has no delegate")
|
||||
del.invokeInstanceMethod(scope, "setValue", Arguments(this, ObjString(name), newValue))
|
||||
} else if (field.value is ObjProperty) {
|
||||
(field.value as ObjProperty).callSetter(scope, this, newValue, decl)
|
||||
} else if (field.isMutable) field.value = newValue else scope.raiseError("can't assign to read-only field: $name")
|
||||
}
|
||||
@@ -444,13 +442,16 @@ open class Obj {
|
||||
scope.raiseNotImplemented()
|
||||
}
|
||||
|
||||
suspend fun invoke(scope: Scope, thisObj: Obj, args: Arguments): Obj =
|
||||
suspend fun invoke(scope: Scope, thisObj: Obj, args: Arguments, declaringClass: ObjClass? = null): Obj =
|
||||
if (PerfFlags.SCOPE_POOL)
|
||||
scope.withChildFrame(args, newThisObj = thisObj) { child ->
|
||||
if (declaringClass != null) child.currentClassCtx = declaringClass
|
||||
callOn(child)
|
||||
}
|
||||
else
|
||||
callOn(scope.createChildScope(scope.pos, args = args, newThisObj = thisObj))
|
||||
callOn(scope.createChildScope(scope.pos, args = args, newThisObj = thisObj).also {
|
||||
if (declaringClass != null) it.currentClassCtx = declaringClass
|
||||
})
|
||||
|
||||
suspend fun invoke(scope: Scope, thisObj: Obj, vararg args: Obj): Obj =
|
||||
callOn(
|
||||
|
||||
@@ -197,7 +197,7 @@ open class ObjClass(
|
||||
val list = mutableListOf<String>()
|
||||
if (includeSelf) list += className
|
||||
mroParents.forEach { list += it.className }
|
||||
return list.joinToString(" → ")
|
||||
return list.joinToString(", ")
|
||||
}
|
||||
|
||||
override val objClass: ObjClass by lazy { ObjClassType }
|
||||
@@ -234,13 +234,13 @@ open class ObjClass(
|
||||
// This mirrors Obj.autoInstanceScope behavior for ad-hoc scopes and makes fb.method() resolution robust
|
||||
// 1) members-defined methods
|
||||
for ((k, v) in members) {
|
||||
if (v.value is Statement) {
|
||||
if (v.value is Statement || v.type == ObjRecord.Type.Delegated) {
|
||||
instance.instanceScope.objects[k] = v
|
||||
}
|
||||
}
|
||||
// 2) class-scope methods registered during class-body execution
|
||||
classScope?.objects?.forEach { (k, rec) ->
|
||||
if (rec.value is Statement) {
|
||||
if (rec.value is Statement || rec.type == ObjRecord.Type.Delegated) {
|
||||
// if not already present, copy reference for dispatch
|
||||
if (!instance.instanceScope.objects.containsKey(k)) {
|
||||
instance.instanceScope.objects[k] = rec
|
||||
@@ -361,7 +361,7 @@ open class ObjClass(
|
||||
isClosed: Boolean = false,
|
||||
isOverride: Boolean = false,
|
||||
type: ObjRecord.Type = ObjRecord.Type.Field,
|
||||
) {
|
||||
): ObjRecord {
|
||||
// Validation of override rules: only for non-system declarations
|
||||
if (pos != Pos.builtIn) {
|
||||
val existing = getInstanceMemberOrNull(name)
|
||||
@@ -396,7 +396,7 @@ open class ObjClass(
|
||||
throw ScriptError(pos, "$name is already defined in $objClass")
|
||||
|
||||
// Install/override in this class
|
||||
members[name] = ObjRecord(
|
||||
val rec = ObjRecord(
|
||||
initialValue, isMutable, visibility, writeVisibility,
|
||||
declaringClass = declaringClass,
|
||||
isAbstract = isAbstract,
|
||||
@@ -404,8 +404,10 @@ open class ObjClass(
|
||||
isOverride = isOverride,
|
||||
type = type
|
||||
)
|
||||
members[name] = rec
|
||||
// Structural change: bump layout version for PIC invalidation
|
||||
layoutVersion += 1
|
||||
return rec
|
||||
}
|
||||
|
||||
private fun initClassScope(): Scope {
|
||||
@@ -419,15 +421,17 @@ open class ObjClass(
|
||||
isMutable: Boolean = false,
|
||||
visibility: Visibility = Visibility.Public,
|
||||
writeVisibility: Visibility? = null,
|
||||
pos: Pos = Pos.builtIn
|
||||
) {
|
||||
pos: Pos = Pos.builtIn,
|
||||
type: ObjRecord.Type = ObjRecord.Type.Field
|
||||
): ObjRecord {
|
||||
initClassScope()
|
||||
val existing = classScope!!.objects[name]
|
||||
if (existing != null)
|
||||
throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
|
||||
classScope!!.addItem(name, isMutable, initialValue, visibility, writeVisibility)
|
||||
val rec = classScope!!.addItem(name, isMutable, initialValue, visibility, writeVisibility, recordType = type)
|
||||
// Structural change: bump layout version for PIC invalidation
|
||||
layoutVersion += 1
|
||||
return rec
|
||||
}
|
||||
|
||||
fun addFn(
|
||||
@@ -558,15 +562,21 @@ open class ObjClass(
|
||||
|
||||
override suspend fun readField(scope: Scope, name: String): ObjRecord {
|
||||
classScope?.objects?.get(name)?.let {
|
||||
if (it.visibility.isPublic) return it
|
||||
if (it.visibility.isPublic) return resolveRecord(scope, it, name, this)
|
||||
}
|
||||
return super.readField(scope, name)
|
||||
}
|
||||
|
||||
override suspend fun writeField(scope: Scope, name: String, newValue: Obj) {
|
||||
initClassScope().objects[name]?.let {
|
||||
if (it.isMutable) it.value = newValue
|
||||
initClassScope().objects[name]?.let { rec ->
|
||||
if (rec.type == ObjRecord.Type.Delegated) {
|
||||
val del = rec.delegate ?: scope.raiseError("Internal error: delegated property $name has no delegate")
|
||||
del.invokeInstanceMethod(scope, "setValue", Arguments(this, ObjString(name), newValue))
|
||||
return
|
||||
}
|
||||
if (rec.isMutable) rec.value = newValue
|
||||
else scope.raiseIllegalAssignment("can't assign $name is not mutable")
|
||||
return
|
||||
}
|
||||
?: super.writeField(scope, name, newValue)
|
||||
}
|
||||
@@ -575,8 +585,16 @@ open class ObjClass(
|
||||
scope: Scope, name: String, args: Arguments,
|
||||
onNotFoundResult: (suspend () -> Obj?)?
|
||||
): Obj {
|
||||
return classScope?.objects?.get(name)?.value?.invoke(scope, this, args)
|
||||
?: super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
|
||||
getInstanceMemberOrNull(name)?.let { rec ->
|
||||
val decl = rec.declaringClass ?: findDeclaringClassOf(name) ?: this
|
||||
if (rec.type == ObjRecord.Type.Delegated) {
|
||||
val del = rec.delegate ?: scope.raiseError("Internal error: delegated function $name has no delegate")
|
||||
val allArgs = (listOf(this, ObjString(name)) + args.list).toTypedArray()
|
||||
return del.invokeInstanceMethod(scope, "invoke", Arguments(*allArgs))
|
||||
}
|
||||
return rec.value.invoke(scope, this, args, decl)
|
||||
}
|
||||
return super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
|
||||
}
|
||||
|
||||
open suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj =
|
||||
|
||||
@@ -31,7 +31,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
internal lateinit var instanceScope: Scope
|
||||
|
||||
override suspend fun readField(scope: Scope, name: String): ObjRecord {
|
||||
// Direct (unmangled) lookup first
|
||||
// 1. Direct (unmangled) lookup first
|
||||
instanceScope[name]?.let { rec ->
|
||||
val decl = rec.declaringClass
|
||||
// Allow unconditional access when accessing through `this` of the same instance
|
||||
@@ -46,33 +46,20 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
)
|
||||
)
|
||||
}
|
||||
if (rec.type == ObjRecord.Type.Property) {
|
||||
val prop = rec.value as ObjProperty
|
||||
return rec.copy(value = prop.callGetter(scope, this, decl))
|
||||
}
|
||||
return rec
|
||||
return resolveRecord(scope, rec, name, decl)
|
||||
}
|
||||
// Try MI-mangled lookup along linearization (C3 MRO): ClassName::name
|
||||
val cls = objClass
|
||||
|
||||
// self first, then parents
|
||||
fun findMangled(): ObjRecord? {
|
||||
// self
|
||||
instanceScope.objects["${cls.className}::$name"]?.let {
|
||||
if (name == "c") println("[DEBUG_LOG] findMangled('c') found in self (${cls.className}): value=${it.value}")
|
||||
return it
|
||||
}
|
||||
// ancestors in deterministic C3 order
|
||||
// 2. MI-mangled instance scope lookup
|
||||
val cls = objClass
|
||||
fun findMangledInRead(): ObjRecord? {
|
||||
instanceScope.objects["${cls.className}::$name"]?.let { return it }
|
||||
for (p in cls.mroParents) {
|
||||
instanceScope.objects["${p.className}::$name"]?.let {
|
||||
if (name == "c") println("[DEBUG_LOG] findMangled('c') found in parent (${p.className}): value=${it.value}")
|
||||
return it
|
||||
}
|
||||
instanceScope.objects["${p.className}::$name"]?.let { return it }
|
||||
}
|
||||
return null
|
||||
}
|
||||
findMangled()?.let { rec ->
|
||||
// derive declaring class by mangled prefix: try self then parents
|
||||
|
||||
findMangledInRead()?.let { rec ->
|
||||
val declaring = when {
|
||||
instanceScope.objects.containsKey("${cls.className}::$name") -> cls
|
||||
else -> cls.mroParents.firstOrNull { instanceScope.objects.containsKey("${it.className}::$name") }
|
||||
@@ -87,16 +74,32 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
)
|
||||
)
|
||||
}
|
||||
if (rec.type == ObjRecord.Type.Property) {
|
||||
val prop = rec.value as ObjProperty
|
||||
return rec.copy(value = prop.callGetter(scope, this, declaring))
|
||||
}
|
||||
return rec
|
||||
return resolveRecord(scope, rec, name, declaring)
|
||||
}
|
||||
// Fall back to methods/properties on class
|
||||
|
||||
// 3. Fall back to super (handles class members and extensions)
|
||||
return super.readField(scope, name)
|
||||
}
|
||||
|
||||
override suspend fun resolveRecord(scope: Scope, obj: ObjRecord, name: String, decl: ObjClass?): ObjRecord {
|
||||
if (obj.type == ObjRecord.Type.Delegated) {
|
||||
val storageName = "${decl?.className}::$name"
|
||||
var del = instanceScope[storageName]?.delegate
|
||||
if (del == null) {
|
||||
for (c in objClass.mro) {
|
||||
del = instanceScope["${c.className}::$name"]?.delegate
|
||||
if (del != null) break
|
||||
}
|
||||
}
|
||||
del = del ?: obj.delegate ?: scope.raiseError("Internal error: delegated property $name has no delegate (tried $storageName)")
|
||||
return obj.copy(
|
||||
value = del.invokeInstanceMethod(scope, "getValue", Arguments(this, ObjString(name))),
|
||||
type = ObjRecord.Type.Other
|
||||
)
|
||||
}
|
||||
return super.resolveRecord(scope, obj, name, decl)
|
||||
}
|
||||
|
||||
override suspend fun writeField(scope: Scope, name: String, newValue: Obj) {
|
||||
// Direct (unmangled) first
|
||||
instanceScope[name]?.let { f ->
|
||||
@@ -114,6 +117,19 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
prop.callSetter(scope, this, newValue, decl)
|
||||
return
|
||||
}
|
||||
if (f.type == ObjRecord.Type.Delegated) {
|
||||
val storageName = "${decl?.className}::$name"
|
||||
var del = instanceScope[storageName]?.delegate
|
||||
if (del == null) {
|
||||
for (c in objClass.mro) {
|
||||
del = instanceScope["${c.className}::$name"]?.delegate
|
||||
if (del != null) break
|
||||
}
|
||||
}
|
||||
del = del ?: f.delegate ?: scope.raiseError("Internal error: delegated property $name has no delegate (tried $storageName)")
|
||||
del.invokeInstanceMethod(scope, "setValue", Arguments(this, ObjString(name), newValue))
|
||||
return
|
||||
}
|
||||
if (!f.isMutable && f.value !== ObjUnset) ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
|
||||
if (f.value.assign(scope, newValue) == null)
|
||||
f.value = newValue
|
||||
@@ -131,7 +147,6 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
|
||||
val rec = findMangled()
|
||||
if (rec != null) {
|
||||
if (name == "c") println("[DEBUG_LOG] writeField('c') found in mangled: value was ${rec.value}, setting to $newValue")
|
||||
val declaring = when {
|
||||
instanceScope.objects.containsKey("${cls.className}::$name") -> cls
|
||||
else -> cls.mroParents.firstOrNull { instanceScope.objects.containsKey("${it.className}::$name") }
|
||||
@@ -149,6 +164,19 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
prop.callSetter(scope, this, newValue, declaring)
|
||||
return
|
||||
}
|
||||
if (rec.type == ObjRecord.Type.Delegated) {
|
||||
val storageName = "${declaring?.className}::$name"
|
||||
var del = instanceScope[storageName]?.delegate
|
||||
if (del == null) {
|
||||
for (c in objClass.mro) {
|
||||
del = instanceScope["${c.className}::$name"]?.delegate
|
||||
if (del != null) break
|
||||
}
|
||||
}
|
||||
del = del ?: rec.delegate ?: scope.raiseError("Internal error: delegated property $name has no delegate (tried $storageName)")
|
||||
del.invokeInstanceMethod(scope, "setValue", Arguments(this, ObjString(name), newValue))
|
||||
return
|
||||
}
|
||||
if (!rec.isMutable && rec.value !== ObjUnset) ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
|
||||
if (rec.value.assign(scope, newValue) == null)
|
||||
rec.value = newValue
|
||||
@@ -160,45 +188,42 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
override suspend fun invokeInstanceMethod(
|
||||
scope: Scope, name: String, args: Arguments,
|
||||
onNotFoundResult: (suspend () -> Obj?)?
|
||||
): Obj =
|
||||
instanceScope[name]?.let { rec ->
|
||||
if (rec.type == ObjRecord.Type.Property || rec.isAbstract) null
|
||||
else {
|
||||
val decl = rec.declaringClass
|
||||
val caller = scope.currentClassCtx ?: if (scope.thisObj === this) objClass else null
|
||||
if (!canAccessMember(rec.visibility, decl, caller))
|
||||
scope.raiseError(
|
||||
ObjIllegalAccessException(
|
||||
scope,
|
||||
"can't invoke method $name (declared in ${decl?.className ?: "?"})"
|
||||
)
|
||||
)
|
||||
rec.value.invoke(
|
||||
instanceScope,
|
||||
this,
|
||||
args
|
||||
)
|
||||
}
|
||||
}
|
||||
?: run {
|
||||
// fallback: class-scope function (registered during class body execution)
|
||||
objClass.classScope?.objects?.get(name)?.let { rec ->
|
||||
if (rec.type == ObjRecord.Type.Property || rec.isAbstract) null
|
||||
else {
|
||||
val decl = rec.declaringClass
|
||||
val caller = scope.currentClassCtx ?: if (scope.thisObj === this) objClass else null
|
||||
if (!canAccessMember(rec.visibility, decl, caller))
|
||||
scope.raiseError(
|
||||
ObjIllegalAccessException(
|
||||
scope,
|
||||
"can't invoke method $name (declared in ${decl?.className ?: "?"})"
|
||||
)
|
||||
): 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) {
|
||||
if (rec.type == ObjRecord.Type.Delegated) {
|
||||
val storageName = "${cls.className}::$name"
|
||||
val del = instanceScope[storageName]?.delegate ?: rec.delegate
|
||||
?: scope.raiseError("Internal error: delegated function $name has no delegate (tried $storageName)")
|
||||
val allArgs = (listOf(this, ObjString(name)) + args.list).toTypedArray()
|
||||
return del.invokeInstanceMethod(scope, "invoke", Arguments(*allArgs))
|
||||
}
|
||||
if (rec.type != ObjRecord.Type.Property && !rec.isAbstract) {
|
||||
val decl = rec.declaringClass ?: cls
|
||||
val caller = scope.currentClassCtx ?: if (scope.thisObj === this) objClass else null
|
||||
if (!canAccessMember(rec.visibility, decl, caller))
|
||||
scope.raiseError(
|
||||
ObjIllegalAccessException(
|
||||
scope,
|
||||
"can't invoke method $name (declared in ${decl.className ?: "?"})"
|
||||
)
|
||||
rec.value.invoke(instanceScope, this, args)
|
||||
}
|
||||
)
|
||||
return rec.value.invoke(
|
||||
instanceScope,
|
||||
this,
|
||||
args,
|
||||
decl
|
||||
)
|
||||
}
|
||||
}
|
||||
?: super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
|
||||
}
|
||||
|
||||
// 2. Fall back to super (handles extensions and root fallback)
|
||||
return super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
|
||||
}
|
||||
|
||||
private val publicFields: Map<String, ObjRecord>
|
||||
get() = instanceScope.objects.filter {
|
||||
@@ -320,7 +345,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(rec.visibility, decl, caller))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't access field $name (declared in ${decl.className})"))
|
||||
return rec
|
||||
return resolveRecord(scope, rec, name, decl)
|
||||
}
|
||||
// Then try instance locals (unmangled) only if startClass is the dynamic class itself
|
||||
if (startClass === instance.objClass) {
|
||||
@@ -334,7 +359,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
|
||||
"can't access field $name (declared in ${decl?.className ?: "?"})"
|
||||
)
|
||||
)
|
||||
return rec
|
||||
return resolveRecord(scope, rec, name, decl)
|
||||
}
|
||||
}
|
||||
// Finally try methods/properties starting from ancestor
|
||||
@@ -343,18 +368,8 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(r.visibility, decl, caller))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't access field $name (declared in ${decl.className})"))
|
||||
return when (val value = r.value) {
|
||||
is net.sergeych.lyng.Statement -> ObjRecord(
|
||||
value.execute(
|
||||
instance.instanceScope.createChildScope(
|
||||
scope.pos,
|
||||
newThisObj = instance
|
||||
)
|
||||
), r.isMutable
|
||||
)
|
||||
|
||||
else -> r
|
||||
}
|
||||
|
||||
return resolveRecord(scope, r, name, decl)
|
||||
}
|
||||
|
||||
override suspend fun writeField(scope: Scope, name: String, newValue: Obj) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -35,6 +35,7 @@ data class ObjRecord(
|
||||
val isAbstract: Boolean = false,
|
||||
val isClosed: Boolean = false,
|
||||
val isOverride: Boolean = false,
|
||||
var delegate: Obj? = null,
|
||||
) {
|
||||
val effectiveWriteVisibility: Visibility get() = writeVisibility ?: visibility
|
||||
enum class Type(val comparable: Boolean = false,val serializable: Boolean = false) {
|
||||
@@ -48,6 +49,7 @@ data class ObjRecord(
|
||||
Class,
|
||||
Enum,
|
||||
Property,
|
||||
Delegated,
|
||||
Other;
|
||||
|
||||
val isArgument get() = this == Argument
|
||||
|
||||
@@ -32,7 +32,11 @@ sealed interface ObjRef {
|
||||
* Fast path for evaluating an expression to a raw Obj value without wrapping it into ObjRecord.
|
||||
* Default implementation calls [get] and returns its value. Nodes can override to avoid record traffic.
|
||||
*/
|
||||
suspend fun evalValue(scope: Scope): Obj = get(scope).value
|
||||
suspend fun evalValue(scope: Scope): Obj {
|
||||
val rec = get(scope)
|
||||
if (rec.type == ObjRecord.Type.Delegated) return scope.resolve(rec, "unknown")
|
||||
return rec.value
|
||||
}
|
||||
suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
||||
throw ScriptError(pos, "can't assign value")
|
||||
}
|
||||
@@ -79,8 +83,7 @@ enum class BinOp {
|
||||
/** R-value reference for unary operations. */
|
||||
class UnaryOpRef(private val op: UnaryOp, private val a: ObjRef) : ObjRef {
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
val fastRval = PerfFlags.RVAL_FASTPATH
|
||||
val v = if (fastRval) a.evalValue(scope) else a.get(scope).value
|
||||
val v = a.evalValue(scope)
|
||||
if (PerfFlags.PRIMITIVE_FASTOPS) {
|
||||
val rFast: Obj? = when (op) {
|
||||
UnaryOp.NOT -> if (v is ObjBool) if (!v.value) ObjTrue else ObjFalse else null
|
||||
@@ -108,8 +111,8 @@ class UnaryOpRef(private val op: UnaryOp, private val a: ObjRef) : ObjRef {
|
||||
/** R-value reference for binary operations. */
|
||||
class BinaryOpRef(private val op: BinOp, private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
val a = if (PerfFlags.RVAL_FASTPATH) left.evalValue(scope) else left.get(scope).value
|
||||
val b = if (PerfFlags.RVAL_FASTPATH) right.evalValue(scope) else right.get(scope).value
|
||||
val a = left.evalValue(scope)
|
||||
val b = right.evalValue(scope)
|
||||
|
||||
// Primitive fast paths for common cases (guarded by PerfFlags.PRIMITIVE_FASTOPS)
|
||||
if (PerfFlags.PRIMITIVE_FASTOPS) {
|
||||
@@ -277,7 +280,7 @@ class ConditionalRef(
|
||||
private val ifFalse: ObjRef
|
||||
) : ObjRef {
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
val condVal = if (PerfFlags.RVAL_FASTPATH) condition.evalValue(scope) else condition.get(scope).value
|
||||
val condVal = condition.evalValue(scope)
|
||||
val condTrue = when (condVal) {
|
||||
is ObjBool -> condVal.value
|
||||
is ObjInt -> condVal.value != 0L
|
||||
@@ -296,8 +299,8 @@ class CastRef(
|
||||
private val atPos: Pos,
|
||||
) : ObjRef {
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
val v0 = if (PerfFlags.RVAL_FASTPATH) valueRef.evalValue(scope) else valueRef.get(scope).value
|
||||
val t = if (PerfFlags.RVAL_FASTPATH) typeRef.evalValue(scope) else typeRef.get(scope).value
|
||||
val v0 = valueRef.evalValue(scope)
|
||||
val t = typeRef.evalValue(scope)
|
||||
val target = (t as? ObjClass) ?: scope.raiseClassCastError("${'$'}t is not the class instance")
|
||||
// unwrap qualified views
|
||||
val v = when (v0) {
|
||||
@@ -339,8 +342,8 @@ class AssignOpRef(
|
||||
private val atPos: Pos,
|
||||
) : ObjRef {
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
val x = target.get(scope).value
|
||||
val y = if (PerfFlags.RVAL_FASTPATH) value.evalValue(scope) else value.get(scope).value
|
||||
val x = target.evalValue(scope)
|
||||
val y = value.evalValue(scope)
|
||||
val inPlace: Obj? = when (op) {
|
||||
BinOp.PLUS -> x.plusAssign(scope, y)
|
||||
BinOp.MINUS -> x.minusAssign(scope, y)
|
||||
@@ -373,7 +376,7 @@ class IncDecRef(
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
val rec = target.get(scope)
|
||||
if (!rec.isMutable) scope.raiseError("Cannot ${if (isIncrement) "increment" else "decrement"} immutable value")
|
||||
val v = rec.value
|
||||
val v = scope.resolve(rec, "unknown")
|
||||
val one = ObjInt.One
|
||||
// We now treat numbers as immutable and always perform write-back via setAt.
|
||||
// This avoids issues where literals are shared and mutated in-place.
|
||||
@@ -387,9 +390,8 @@ class IncDecRef(
|
||||
/** Elvis operator reference: a ?: b */
|
||||
class ElvisRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
val fastRval = PerfFlags.RVAL_FASTPATH
|
||||
val a = if (fastRval) left.evalValue(scope) else left.get(scope).value
|
||||
val r = if (a != ObjNull) a else if (fastRval) right.evalValue(scope) else right.get(scope).value
|
||||
val a = left.evalValue(scope)
|
||||
val r = if (a != ObjNull) a else right.evalValue(scope)
|
||||
return r.asReadonly
|
||||
}
|
||||
}
|
||||
@@ -397,12 +399,10 @@ class ElvisRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
||||
/** Logical OR with short-circuit: a || b */
|
||||
class LogicalOrRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
val fastRval = PerfFlags.RVAL_FASTPATH
|
||||
val fastPrim = PerfFlags.PRIMITIVE_FASTOPS
|
||||
val a = if (fastRval) left.evalValue(scope) else left.get(scope).value
|
||||
val a = left.evalValue(scope)
|
||||
if ((a as? ObjBool)?.value == true) return ObjTrue.asReadonly
|
||||
val b = if (fastRval) right.evalValue(scope) else right.get(scope).value
|
||||
if (fastPrim) {
|
||||
val b = right.evalValue(scope)
|
||||
if (PerfFlags.PRIMITIVE_FASTOPS) {
|
||||
if (a is ObjBool && b is ObjBool) {
|
||||
return if (a.value || b.value) ObjTrue.asReadonly else ObjFalse.asReadonly
|
||||
}
|
||||
@@ -414,13 +414,10 @@ class LogicalOrRef(private val left: ObjRef, private val right: ObjRef) : ObjRef
|
||||
/** Logical AND with short-circuit: a && b */
|
||||
class LogicalAndRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
// Hoist flags to locals for JIT friendliness
|
||||
val fastRval = PerfFlags.RVAL_FASTPATH
|
||||
val fastPrim = PerfFlags.PRIMITIVE_FASTOPS
|
||||
val a = if (fastRval) left.evalValue(scope) else left.get(scope).value
|
||||
val a = left.evalValue(scope)
|
||||
if ((a as? ObjBool)?.value == false) return ObjFalse.asReadonly
|
||||
val b = if (fastRval) right.evalValue(scope) else right.get(scope).value
|
||||
if (fastPrim) {
|
||||
val b = right.evalValue(scope)
|
||||
if (PerfFlags.PRIMITIVE_FASTOPS) {
|
||||
if (a is ObjBool && b is ObjBool) {
|
||||
return if (a.value && b.value) ObjTrue.asReadonly else ObjFalse.asReadonly
|
||||
}
|
||||
@@ -505,10 +502,9 @@ class FieldRef(
|
||||
}
|
||||
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
val fastRval = PerfFlags.RVAL_FASTPATH
|
||||
val fieldPic = PerfFlags.FIELD_PIC
|
||||
val picCounters = PerfFlags.PIC_DEBUG_COUNTERS
|
||||
val base = if (fastRval) target.evalValue(scope) else target.get(scope).value
|
||||
val base = target.evalValue(scope)
|
||||
if (base == ObjNull && isOptional) return ObjNull.asMutable
|
||||
if (fieldPic) {
|
||||
val (key, ver) = receiverKeyAndVersion(base)
|
||||
@@ -800,11 +796,10 @@ class IndexRef(
|
||||
else -> 0L to -1
|
||||
}
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
val fastRval = PerfFlags.RVAL_FASTPATH
|
||||
val base = if (fastRval) target.evalValue(scope) else target.get(scope).value
|
||||
val base = target.evalValue(scope)
|
||||
if (base == ObjNull && isOptional) return ObjNull.asMutable
|
||||
val idx = if (fastRval) index.evalValue(scope) else index.get(scope).value
|
||||
if (fastRval) {
|
||||
val idx = index.evalValue(scope)
|
||||
if (PerfFlags.RVAL_FASTPATH) {
|
||||
// Primitive list index fast path: avoid virtual dispatch to getAt when shapes match
|
||||
if (base is ObjList && idx is ObjInt) {
|
||||
val i = idx.toInt()
|
||||
@@ -874,11 +869,10 @@ class IndexRef(
|
||||
}
|
||||
|
||||
override suspend fun evalValue(scope: Scope): Obj {
|
||||
val fastRval = PerfFlags.RVAL_FASTPATH
|
||||
val base = if (fastRval) target.evalValue(scope) else target.get(scope).value
|
||||
val base = target.evalValue(scope)
|
||||
if (base == ObjNull && isOptional) return ObjNull
|
||||
val idx = if (fastRval) index.evalValue(scope) else index.get(scope).value
|
||||
if (fastRval) {
|
||||
val idx = index.evalValue(scope)
|
||||
if (PerfFlags.RVAL_FASTPATH) {
|
||||
// Fast list[int] path
|
||||
if (base is ObjList && idx is ObjInt) {
|
||||
val i = idx.toInt()
|
||||
@@ -1027,9 +1021,8 @@ class CallRef(
|
||||
private val isOptionalInvoke: Boolean,
|
||||
) : ObjRef {
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
val fastRval = PerfFlags.RVAL_FASTPATH
|
||||
val usePool = PerfFlags.SCOPE_POOL
|
||||
val callee = if (fastRval) target.evalValue(scope) else target.get(scope).value
|
||||
val callee = target.evalValue(scope)
|
||||
if (callee == ObjNull && isOptionalInvoke) return ObjNull.asReadonly
|
||||
val callArgs = args.toArguments(scope, tailBlock)
|
||||
val result: Obj = if (usePool) {
|
||||
@@ -1117,10 +1110,9 @@ class MethodCallRef(
|
||||
}
|
||||
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
val fastRval = PerfFlags.RVAL_FASTPATH
|
||||
val methodPic = PerfFlags.METHOD_PIC
|
||||
val picCounters = PerfFlags.PIC_DEBUG_COUNTERS
|
||||
val base = if (fastRval) receiver.evalValue(scope) else receiver.get(scope).value
|
||||
val base = receiver.evalValue(scope)
|
||||
if (base == ObjNull && isOptional) return ObjNull.asReadonly
|
||||
val callArgs = args.toArguments(scope, tailBlock)
|
||||
if (methodPic) {
|
||||
@@ -1327,21 +1319,21 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef {
|
||||
override suspend fun evalValue(scope: Scope): Obj {
|
||||
scope.pos = atPos
|
||||
if (!PerfFlags.LOCAL_SLOT_PIC) {
|
||||
scope.getSlotIndexOf(name)?.let { return scope.getSlotRecord(it).value }
|
||||
scope.getSlotIndexOf(name)?.let { return scope.resolve(scope.getSlotRecord(it), name) }
|
||||
// fallback to current-scope object or field on `this`
|
||||
scope[name]?.let { return it.value }
|
||||
scope[name]?.let { return scope.resolve(it, name) }
|
||||
run {
|
||||
var s: Scope? = scope
|
||||
val visited = HashSet<Long>(4)
|
||||
while (s != null) {
|
||||
if (!visited.add(s.frameId)) break
|
||||
if (s is ClosureScope) {
|
||||
s.closureScope.chainLookupWithMembers(name)?.let { return it.value }
|
||||
s.closureScope.chainLookupWithMembers(name)?.let { return s.resolve(it, name) }
|
||||
}
|
||||
s = s.parent
|
||||
}
|
||||
}
|
||||
scope.chainLookupIgnoreClosure(name)?.let { return it.value }
|
||||
scope.chainLookupIgnoreClosure(name)?.let { return scope.resolve(it, name) }
|
||||
return try {
|
||||
scope.thisObj.readField(scope, name).value
|
||||
} catch (e: ExecutionError) {
|
||||
@@ -1353,25 +1345,29 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef {
|
||||
val slot = if (hit) cachedSlot else resolveSlot(scope)
|
||||
if (slot >= 0) {
|
||||
val rec = scope.getSlotRecord(slot)
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx))
|
||||
return rec.value
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx)) {
|
||||
return scope.resolve(rec, name)
|
||||
}
|
||||
}
|
||||
// Fallback name in scope or field on `this`
|
||||
scope[name]?.let { return it.value }
|
||||
scope[name]?.let {
|
||||
return scope.resolve(it, name)
|
||||
}
|
||||
run {
|
||||
var s: Scope? = scope
|
||||
val visited = HashSet<Long>(4)
|
||||
while (s != null) {
|
||||
if (!visited.add(s.frameId)) break
|
||||
if (s is ClosureScope) {
|
||||
s.closureScope.chainLookupWithMembers(name)?.let { return it.value }
|
||||
s.closureScope.chainLookupWithMembers(name)?.let { return s.resolve(it, name) }
|
||||
}
|
||||
s = s.parent
|
||||
}
|
||||
}
|
||||
scope.chainLookupIgnoreClosure(name)?.let { return it.value }
|
||||
scope.chainLookupIgnoreClosure(name)?.let { return scope.resolve(it, name) }
|
||||
return try {
|
||||
scope.thisObj.readField(scope, name).value
|
||||
val res = scope.thisObj.readField(scope, name).value
|
||||
res
|
||||
} catch (e: ExecutionError) {
|
||||
if ((e.message ?: "").contains("no such field: $name")) scope.raiseSymbolNotFound(name)
|
||||
throw e
|
||||
@@ -1383,13 +1379,11 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef {
|
||||
if (!PerfFlags.LOCAL_SLOT_PIC) {
|
||||
scope.getSlotIndexOf(name)?.let {
|
||||
val rec = scope.getSlotRecord(it)
|
||||
if (!rec.isMutable) scope.raiseError("Cannot assign to immutable value")
|
||||
rec.value = newValue
|
||||
scope.assign(rec, name, newValue)
|
||||
return
|
||||
}
|
||||
scope[name]?.let { stored ->
|
||||
if (stored.isMutable) stored.value = newValue
|
||||
else scope.raiseError("Cannot assign to immutable value")
|
||||
scope.assign(stored, name, newValue)
|
||||
return
|
||||
}
|
||||
run {
|
||||
@@ -1399,8 +1393,7 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef {
|
||||
if (!visited.add(s.frameId)) break
|
||||
if (s is ClosureScope) {
|
||||
s.closureScope.chainLookupWithMembers(name)?.let { stored ->
|
||||
if (stored.isMutable) stored.value = newValue
|
||||
else scope.raiseError("Cannot assign to immutable value")
|
||||
s.assign(stored, name, newValue)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1408,8 +1401,7 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef {
|
||||
}
|
||||
}
|
||||
scope.chainLookupIgnoreClosure(name)?.let { stored ->
|
||||
if (stored.isMutable) stored.value = newValue
|
||||
else scope.raiseError("Cannot assign to immutable value")
|
||||
scope.assign(stored, name, newValue)
|
||||
return
|
||||
}
|
||||
// Fallback: write to field on `this`
|
||||
@@ -1420,14 +1412,12 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef {
|
||||
if (slot >= 0) {
|
||||
val rec = scope.getSlotRecord(slot)
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx)) {
|
||||
if (!rec.isMutable) scope.raiseError("Cannot assign to immutable value")
|
||||
rec.value = newValue
|
||||
scope.assign(rec, name, newValue)
|
||||
return
|
||||
}
|
||||
}
|
||||
scope[name]?.let { stored ->
|
||||
if (stored.isMutable) stored.value = newValue
|
||||
else scope.raiseError("Cannot assign to immutable value")
|
||||
scope.assign(stored, name, newValue)
|
||||
return
|
||||
}
|
||||
run {
|
||||
@@ -1437,8 +1427,7 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef {
|
||||
if (!visited.add(s.frameId)) break
|
||||
if (s is ClosureScope) {
|
||||
s.closureScope.chainLookupWithMembers(name)?.let { stored ->
|
||||
if (stored.isMutable) stored.value = newValue
|
||||
else scope.raiseError("Cannot assign to immutable value")
|
||||
s.assign(stored, name, newValue)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1446,8 +1435,7 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef {
|
||||
}
|
||||
}
|
||||
scope.chainLookupIgnoreClosure(name)?.let { stored ->
|
||||
if (stored.isMutable) stored.value = newValue
|
||||
else scope.raiseError("Cannot assign to immutable value")
|
||||
scope.assign(stored, name, newValue)
|
||||
return
|
||||
}
|
||||
scope.thisObj.writeField(scope, name, newValue)
|
||||
@@ -1476,7 +1464,10 @@ class BoundLocalVarRef(
|
||||
val rec = scope.getSlotRecord(slot)
|
||||
if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "private field access"))
|
||||
return rec.value
|
||||
// We might not have the name in BoundLocalVarRef, but let's try to find it or use a placeholder
|
||||
// Actually BoundLocalVarRef is mostly used for parameters which are not delegated yet.
|
||||
// But for consistency:
|
||||
return scope.resolve(rec, "local")
|
||||
}
|
||||
|
||||
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
||||
@@ -1484,8 +1475,7 @@ class BoundLocalVarRef(
|
||||
val rec = scope.getSlotRecord(slot)
|
||||
if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "private field access"))
|
||||
if (!rec.isMutable) scope.raiseError("Cannot assign to immutable value")
|
||||
rec.value = newValue
|
||||
scope.assign(rec, "local", newValue)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1592,15 +1582,18 @@ class FastLocalVarRef(
|
||||
val actualOwner = cachedOwnerScope
|
||||
if (slot >= 0 && actualOwner != null) {
|
||||
val rec = actualOwner.getSlotRecord(slot)
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx))
|
||||
return rec.value
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx)) {
|
||||
return actualOwner.resolve(rec, name)
|
||||
}
|
||||
}
|
||||
// Try per-frame local binding maps in the ancestry first
|
||||
run {
|
||||
var s: Scope? = scope
|
||||
var guard = 0
|
||||
while (s != null) {
|
||||
s.localBindings[name]?.let { return it.value }
|
||||
s.localBindings[name]?.let {
|
||||
return s.resolve(it, name)
|
||||
}
|
||||
val next = s.parent
|
||||
if (next === s) break
|
||||
s = next
|
||||
@@ -1612,7 +1605,9 @@ class FastLocalVarRef(
|
||||
var s: Scope? = scope
|
||||
var guard = 0
|
||||
while (s != null) {
|
||||
s.objects[name]?.let { return it.value }
|
||||
s.objects[name]?.let {
|
||||
return s.resolve(it, name)
|
||||
}
|
||||
val next = s.parent
|
||||
if (next === s) break
|
||||
s = next
|
||||
@@ -1620,7 +1615,9 @@ class FastLocalVarRef(
|
||||
}
|
||||
}
|
||||
// Fallback to standard name lookup (locals or closure chain)
|
||||
scope[name]?.let { return it.value }
|
||||
scope[name]?.let {
|
||||
return scope.resolve(it, name)
|
||||
}
|
||||
return scope.thisObj.readField(scope, name).value
|
||||
}
|
||||
|
||||
@@ -1632,8 +1629,7 @@ class FastLocalVarRef(
|
||||
if (slot >= 0 && actualOwner != null) {
|
||||
val rec = actualOwner.getSlotRecord(slot)
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx)) {
|
||||
if (!rec.isMutable) scope.raiseError("Cannot assign to immutable value")
|
||||
rec.value = newValue
|
||||
scope.assign(rec, name, newValue)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1644,8 +1640,7 @@ class FastLocalVarRef(
|
||||
while (s != null) {
|
||||
val rec = s.localBindings[name]
|
||||
if (rec != null) {
|
||||
if (!rec.isMutable) scope.raiseError("Cannot assign to immutable value")
|
||||
rec.value = newValue
|
||||
s.assign(rec, name, newValue)
|
||||
return
|
||||
}
|
||||
val next = s.parent
|
||||
@@ -1656,8 +1651,7 @@ class FastLocalVarRef(
|
||||
}
|
||||
// Fallback to standard name lookup
|
||||
scope[name]?.let { stored ->
|
||||
if (stored.isMutable) stored.value = newValue
|
||||
else scope.raiseError("Cannot assign to immutable value")
|
||||
scope.assign(stored, name, newValue)
|
||||
return
|
||||
}
|
||||
scope.thisObj.writeField(scope, name, newValue)
|
||||
@@ -1691,11 +1685,11 @@ class ListLiteralRef(private val entries: List<ListEntry>) : ObjRef {
|
||||
for (e in entries) {
|
||||
when (e) {
|
||||
is ListEntry.Element -> {
|
||||
val v = if (PerfFlags.RVAL_FASTPATH) e.ref.evalValue(scope) else e.ref.get(scope).value
|
||||
val v = if (PerfFlags.RVAL_FASTPATH) e.ref.evalValue(scope) else e.ref.evalValue(scope)
|
||||
list += v
|
||||
}
|
||||
is ListEntry.Spread -> {
|
||||
val elements = if (PerfFlags.RVAL_FASTPATH) e.ref.evalValue(scope) else e.ref.get(scope).value
|
||||
val elements = e.ref.evalValue(scope)
|
||||
when (elements) {
|
||||
is ObjList -> {
|
||||
// Grow underlying array once when possible
|
||||
@@ -1771,11 +1765,11 @@ class MapLiteralRef(private val entries: List<MapLiteralEntry>) : ObjRef {
|
||||
for (e in entries) {
|
||||
when (e) {
|
||||
is MapLiteralEntry.Named -> {
|
||||
val v = if (PerfFlags.RVAL_FASTPATH) e.value.evalValue(scope) else e.value.get(scope).value
|
||||
val v = e.value.evalValue(scope)
|
||||
result.map[ObjString(e.key)] = v
|
||||
}
|
||||
is MapLiteralEntry.Spread -> {
|
||||
val m = if (PerfFlags.RVAL_FASTPATH) e.ref.evalValue(scope) else e.ref.get(scope).value
|
||||
val m = e.ref.evalValue(scope)
|
||||
if (m !is ObjMap) scope.raiseIllegalArgument("spread element in map literal must be a Map")
|
||||
for ((k, v) in m.map) {
|
||||
result.map[k] = v
|
||||
@@ -1796,8 +1790,8 @@ class RangeRef(
|
||||
private val isEndInclusive: Boolean
|
||||
) : ObjRef {
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
val l = left?.let { if (PerfFlags.RVAL_FASTPATH) it.evalValue(scope) else it.get(scope).value } ?: ObjNull
|
||||
val r = right?.let { if (PerfFlags.RVAL_FASTPATH) it.evalValue(scope) else it.get(scope).value } ?: ObjNull
|
||||
val l = left?.evalValue(scope) ?: ObjNull
|
||||
val r = right?.evalValue(scope) ?: ObjNull
|
||||
return ObjRange(l, r, isEndInclusive = isEndInclusive).asReadonly
|
||||
}
|
||||
}
|
||||
@@ -1809,7 +1803,7 @@ class AssignRef(
|
||||
private val atPos: Pos,
|
||||
) : ObjRef {
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
val v = if (PerfFlags.RVAL_FASTPATH) value.evalValue(scope) else value.get(scope).value
|
||||
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) {
|
||||
|
||||
Reference in new issue
Block a user