added Multiple Inheritance
This commit is contained in:
1 parent
882df67909
commit
aeeec2d417
33 files changed
+3302
-130
No files matched your search
@@ -29,17 +29,23 @@ class ClosureScope(val callScope: Scope, val closureScope: Scope) :
|
||||
// we captured, not to the caller's `this` (e.g., FlowBuilder).
|
||||
Scope(callScope, callScope.args, thisObj = closureScope.thisObj) {
|
||||
|
||||
init {
|
||||
// Preserve the lexical class context from where the lambda was defined (closure),
|
||||
// so that visibility checks (private/protected) inside lambdas executed within other
|
||||
// methods (e.g., Mutex.withLock) still see the original declaring class context.
|
||||
this.currentClassCtx = closureScope.currentClassCtx
|
||||
}
|
||||
|
||||
override fun get(name: String): ObjRecord? {
|
||||
// Priority:
|
||||
// 1) Arguments from the caller scope (if present in this frame)
|
||||
// 1) Locals and arguments declared in this lambda frame (including values defined before suspension)
|
||||
// 2) Instance/class members of the captured receiver (`closureScope.thisObj`), e.g., fields like `coll`, `factor`
|
||||
// 3) Symbols from the captured closure scope (its locals and parents)
|
||||
// 4) Instance members of the caller's `this` (e.g., FlowBuilder.emit)
|
||||
// 5) Fallback to the standard chain (this frame -> parent (callScope) -> class members)
|
||||
|
||||
// note using super, not callScope, as arguments are assigned by the constructor
|
||||
// and are not yet exposed via callScope.get at this point:
|
||||
super.objects[name]?.let { if (it.type.isArgument) return it }
|
||||
// First, prefer locals/arguments bound in this frame
|
||||
super.objects[name]?.let { return it }
|
||||
|
||||
// Prefer instance fields/methods declared on the captured receiver:
|
||||
// First, resolve real instance fields stored in the instance scope (constructor vars like `coll`, `factor`)
|
||||
|
||||
@@ -479,6 +479,8 @@ class Compiler(
|
||||
val callStatement = statement {
|
||||
// and the source closure of the lambda which might have other thisObj.
|
||||
val context = this.applyClosure(closure!!)
|
||||
// Execute lambda body in a closure-aware context. Blocks inside the lambda
|
||||
// will create child scopes as usual, so re-declarations inside loops work.
|
||||
if (argsDeclaration == null) {
|
||||
// no args: automatic var 'it'
|
||||
val l = args.list
|
||||
@@ -700,6 +702,37 @@ class Compiler(
|
||||
return args to lastBlockArgument
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse arguments inside parentheses without consuming any optional trailing block after the RPAREN.
|
||||
* Useful in contexts where a following '{' has different meaning (e.g., class bodies after base lists).
|
||||
*/
|
||||
private suspend fun parseArgsNoTailBlock(): List<ParsedArgument> {
|
||||
val args = mutableListOf<ParsedArgument>()
|
||||
do {
|
||||
val t = cc.next()
|
||||
when (t.type) {
|
||||
Token.Type.NEWLINE,
|
||||
Token.Type.RPAREN, Token.Type.COMMA -> {
|
||||
}
|
||||
|
||||
Token.Type.ELLIPSIS -> {
|
||||
parseStatement()?.let { args += ParsedArgument(it, t.pos, isSplat = true) }
|
||||
?: throw ScriptError(t.pos, "Expecting arguments list")
|
||||
}
|
||||
|
||||
else -> {
|
||||
cc.previous()
|
||||
parseExpression()?.let { args += ParsedArgument(it, t.pos) }
|
||||
?: throw ScriptError(t.pos, "Expecting arguments list")
|
||||
if (cc.current().type == Token.Type.COLON)
|
||||
parseTypeDeclaration()
|
||||
}
|
||||
}
|
||||
} while (t.type != Token.Type.RPAREN)
|
||||
// Do NOT peek for a trailing block; leave it to the outer parser
|
||||
return args
|
||||
}
|
||||
|
||||
|
||||
private suspend fun parseFunctionCall(
|
||||
left: ObjRef,
|
||||
@@ -750,7 +783,18 @@ class Compiler(
|
||||
}
|
||||
|
||||
Token.Type.ID -> {
|
||||
when (t.value) {
|
||||
// Special case: qualified this -> this@Type
|
||||
if (t.value == "this") {
|
||||
val pos = cc.savePos()
|
||||
val next = cc.next()
|
||||
if (next.pos.line == t.pos.line && next.type == Token.Type.ATLABEL) {
|
||||
QualifiedThisRef(next.value, t.pos)
|
||||
} else {
|
||||
cc.restorePos(pos)
|
||||
// plain this
|
||||
LocalVarRef("this", t.pos)
|
||||
}
|
||||
} else when (t.value) {
|
||||
"void" -> ConstRef(ObjVoid.asReadonly)
|
||||
"null" -> ConstRef(ObjNull.asReadonly)
|
||||
"true" -> ConstRef(ObjTrue.asReadonly)
|
||||
@@ -818,6 +862,40 @@ class Compiler(
|
||||
private suspend fun parseKeywordStatement(id: Token): Statement? = when (id.value) {
|
||||
"val" -> parseVarDeclaration(false, Visibility.Public)
|
||||
"var" -> parseVarDeclaration(true, Visibility.Public)
|
||||
// Ensure function declarations are recognized in all contexts (including class bodies)
|
||||
"fun" -> parseFunctionDeclaration(isOpen = false, isExtern = false, isStatic = false)
|
||||
"fn" -> parseFunctionDeclaration(isOpen = false, isExtern = false, isStatic = false)
|
||||
// Visibility modifiers for declarations: private/protected val/var/fun/fn
|
||||
"private" -> {
|
||||
var k = cc.requireToken(Token.Type.ID, "declaration expected after 'private'")
|
||||
var isStatic = false
|
||||
if (k.value == "static") {
|
||||
isStatic = true
|
||||
k = cc.requireToken(Token.Type.ID, "declaration expected after 'private static'")
|
||||
}
|
||||
when (k.value) {
|
||||
"val" -> parseVarDeclaration(false, Visibility.Private, isStatic = isStatic)
|
||||
"var" -> parseVarDeclaration(true, Visibility.Private, isStatic = isStatic)
|
||||
"fun" -> parseFunctionDeclaration(visibility = Visibility.Private, isOpen = false, isExtern = false, isStatic = isStatic)
|
||||
"fn" -> parseFunctionDeclaration(visibility = Visibility.Private, isOpen = false, isExtern = false, isStatic = isStatic)
|
||||
else -> k.raiseSyntax("unsupported private declaration kind: ${k.value}")
|
||||
}
|
||||
}
|
||||
"protected" -> {
|
||||
var k = cc.requireToken(Token.Type.ID, "declaration expected after 'protected'")
|
||||
var isStatic = false
|
||||
if (k.value == "static") {
|
||||
isStatic = true
|
||||
k = cc.requireToken(Token.Type.ID, "declaration expected after 'protected static'")
|
||||
}
|
||||
when (k.value) {
|
||||
"val" -> parseVarDeclaration(false, Visibility.Protected, isStatic = isStatic)
|
||||
"var" -> parseVarDeclaration(true, Visibility.Protected, isStatic = isStatic)
|
||||
"fun" -> parseFunctionDeclaration(visibility = Visibility.Protected, isOpen = false, isExtern = false, isStatic = isStatic)
|
||||
"fn" -> parseFunctionDeclaration(visibility = Visibility.Protected, isOpen = false, isExtern = false, isStatic = isStatic)
|
||||
else -> k.raiseSyntax("unsupported protected declaration kind: ${k.value}")
|
||||
}
|
||||
}
|
||||
"while" -> parseWhileStatement()
|
||||
"do" -> parseDoWhileStatement()
|
||||
"for" -> parseForStatement()
|
||||
@@ -1162,19 +1240,40 @@ class Compiler(
|
||||
"Bad class declaration: expected ')' at the end of the primary constructor"
|
||||
)
|
||||
|
||||
// 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
|
||||
// Optional constructor args of the base — parse and ignore for now (MVP), just to consume tokens
|
||||
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true)) {
|
||||
// Parse args without consuming any following block so that a class body can follow safely
|
||||
argsList = parseArgsNoTailBlock()
|
||||
}
|
||||
baseSpecs += BaseSpec(baseId.value, argsList)
|
||||
} while (cc.skipTokenOfType(Token.Type.COMMA, isOptional = true))
|
||||
}
|
||||
|
||||
cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
|
||||
val t = cc.next()
|
||||
|
||||
pushInitScope()
|
||||
|
||||
val bodyInit: Statement? = if (t.type == Token.Type.LBRACE) {
|
||||
// parse body
|
||||
parseScript().also {
|
||||
cc.skipTokens(Token.Type.RBRACE)
|
||||
// Robust body detection: peek next non-whitespace token; if it's '{', consume and parse the body
|
||||
val bodyInit: Statement? = run {
|
||||
val saved = cc.savePos()
|
||||
val next = cc.nextNonWhitespace()
|
||||
if (next.type == Token.Type.LBRACE) {
|
||||
// parse body
|
||||
parseScript().also {
|
||||
cc.skipTokens(Token.Type.RBRACE)
|
||||
}
|
||||
} else {
|
||||
// restore if no body starts here
|
||||
cc.restorePos(saved)
|
||||
null
|
||||
}
|
||||
} else {
|
||||
cc.previous()
|
||||
null
|
||||
}
|
||||
|
||||
val initScope = popInitScope()
|
||||
@@ -1191,31 +1290,60 @@ class Compiler(
|
||||
val constructorCode = statement {
|
||||
// constructor code is registered with class instance and is called over
|
||||
// new `thisObj` already set by class to ObjInstance.instanceContext
|
||||
thisObj as ObjInstance
|
||||
|
||||
// the context now is a "class creation context", we must use its args to initialize
|
||||
// fields. Note that 'this' is already set by class
|
||||
constructorArgsDeclaration?.assignToContext(this)
|
||||
bodyInit?.execute(this)
|
||||
|
||||
thisObj
|
||||
val instance = thisObj as ObjInstance
|
||||
// Constructor parameters have been assigned to instance scope by ObjClass.callOn before
|
||||
// invoking parent/child constructors.
|
||||
// IMPORTANT: do not execute class body here; class body was executed once in the class scope
|
||||
// to register methods and prepare initializers. Instance constructor should be empty unless
|
||||
// we later add explicit constructor body syntax.
|
||||
instance
|
||||
}
|
||||
// inheritance must alter this code:
|
||||
val newClass = ObjInstanceClass(className).apply {
|
||||
instanceConstructor = constructorCode
|
||||
constructorMeta = constructorArgsDeclaration
|
||||
}
|
||||
|
||||
statement {
|
||||
// the main statement should create custom ObjClass instance with field
|
||||
// accessors, constructor registration, etc.
|
||||
// Resolve parent classes by name at execution time
|
||||
val parentClasses = baseSpecs.map { baseSpec ->
|
||||
val rec = this[baseSpec.name] ?: throw ScriptError(nameToken.pos, "unknown base class: ${baseSpec.name}")
|
||||
(rec.value as? ObjClass) ?: throw ScriptError(nameToken.pos, "${baseSpec.name} is not a class")
|
||||
}
|
||||
|
||||
val newClass = ObjInstanceClass(className, *parentClasses.toTypedArray()).also {
|
||||
it.instanceConstructor = constructorCode
|
||||
it.constructorMeta = constructorArgsDeclaration
|
||||
// Attach per-parent constructor args (thunks) if provided
|
||||
for (i in parentClasses.indices) {
|
||||
val argsList = baseSpecs[i].args
|
||||
if (argsList != null) it.directParentArgs[parentClasses[i]] = argsList
|
||||
}
|
||||
}
|
||||
|
||||
addItem(className, false, newClass)
|
||||
// Prepare class scope for class-scope members (static) and future registrations
|
||||
val classScope = createChildScope(newThisObj = newClass)
|
||||
// Set lexical class context for visibility tagging inside class body
|
||||
classScope.currentClassCtx = newClass
|
||||
newClass.classScope = classScope
|
||||
// Execute class body once in class scope to register instance methods and prepare instance field initializers
|
||||
bodyInit?.execute(classScope)
|
||||
if (initScope.isNotEmpty()) {
|
||||
val classScope = createChildScope(newThisObj = newClass)
|
||||
newClass.classScope = classScope
|
||||
for (s in initScope)
|
||||
s.execute(classScope)
|
||||
}
|
||||
// Fallback: ensure any functions declared in class scope are also present as instance methods
|
||||
// (defensive in case some paths skipped cls.addFn during parsing/execution ordering)
|
||||
for ((k, rec) in classScope.objects) {
|
||||
val v = rec.value
|
||||
if (v is Statement) {
|
||||
if (newClass.members[k] == null) {
|
||||
newClass.addFn(k, isOpen = true) {
|
||||
(thisObj as? ObjInstance)?.let { i ->
|
||||
v.execute(ClosureScope(this, i.instanceScope))
|
||||
} ?: v.execute(thisObj.autoInstanceScope(this))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Debug summary: list registered instance methods and class-scope functions for this class
|
||||
newClass
|
||||
}
|
||||
}
|
||||
@@ -1686,6 +1814,7 @@ class Compiler(
|
||||
}
|
||||
fnStatements.execute(context)
|
||||
}
|
||||
val enclosingCtx = parentContext
|
||||
val fnCreateStatement = statement(start) { context ->
|
||||
// we added fn in the context. now we must save closure
|
||||
// for the function, unless we're in the class scope:
|
||||
@@ -1699,7 +1828,7 @@ 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")
|
||||
type.addFn(name, isOpen = true) {
|
||||
type.addFn(name, isOpen = true, visibility = visibility) {
|
||||
// ObjInstance has a fixed instance scope, so we need to build a closure
|
||||
(thisObj as? ObjInstance)?.let { i ->
|
||||
annotatedFnBody.execute(ClosureScope(this, i.instanceScope))
|
||||
@@ -1709,7 +1838,31 @@ class Compiler(
|
||||
}
|
||||
}
|
||||
// regular function/method
|
||||
?: context.addItem(name, false, annotatedFnBody, visibility)
|
||||
?: run {
|
||||
val th = context.thisObj
|
||||
if (!isStatic && th is ObjClass) {
|
||||
// Instance method declared inside a class body: register on the class
|
||||
val cls: ObjClass = th
|
||||
cls.addFn(name, isOpen = true, visibility = visibility) {
|
||||
// Execute with the instance as receiver; set caller lexical class for visibility
|
||||
val savedCtx = this.currentClassCtx
|
||||
this.currentClassCtx = cls
|
||||
try {
|
||||
(thisObj as? ObjInstance)?.let { i ->
|
||||
annotatedFnBody.execute(ClosureScope(this, i.instanceScope))
|
||||
} ?: annotatedFnBody.execute(thisObj.autoInstanceScope(this))
|
||||
} finally {
|
||||
this.currentClassCtx = savedCtx
|
||||
}
|
||||
}
|
||||
// also expose the symbol in the class scope for possible references
|
||||
context.addItem(name, false, annotatedFnBody, visibility)
|
||||
annotatedFnBody
|
||||
} else {
|
||||
// top-level or nested function
|
||||
context.addItem(name, false, annotatedFnBody, visibility)
|
||||
}
|
||||
}
|
||||
// as the function can be called from anywhere, we have
|
||||
// saved the proper context in the closure
|
||||
annotatedFnBody
|
||||
@@ -1791,9 +1944,18 @@ class Compiler(
|
||||
return NopStatement
|
||||
}
|
||||
|
||||
// Determine declaring class (if inside class body) at compile time, capture it in the closure
|
||||
val declaringClassNameCaptured = (codeContexts.lastOrNull() as? CodeContext.ClassBody)?.name
|
||||
|
||||
return statement(nameToken.pos) { context ->
|
||||
if (context.containsLocal(name))
|
||||
throw ScriptError(nameToken.pos, "Variable $name is already defined")
|
||||
// In true class bodies (not inside a function), store fields under a class-qualified key to support MI collisions
|
||||
// Do NOT infer declaring class from runtime thisObj here; only the compile-time captured
|
||||
// ClassBody qualifies for class-field storage. Otherwise, this is a plain local.
|
||||
val declaringClassName = declaringClassNameCaptured
|
||||
if (declaringClassName == null) {
|
||||
if (context.containsLocal(name))
|
||||
throw ScriptError(nameToken.pos, "Variable $name is already defined")
|
||||
}
|
||||
|
||||
// Register the local name so subsequent identifiers can be emitted as fast locals
|
||||
if (!isStatic) declareLocalName(name)
|
||||
@@ -1818,11 +1980,32 @@ class Compiler(
|
||||
// context.addItem(name, isMutable, it, visibility, recordType = ObjRecord.Type.Field)
|
||||
// }
|
||||
} else {
|
||||
// init value could be a val; when we initialize by-value type var with it, we need to
|
||||
// create a separate copy:
|
||||
val initValue = initialExpression?.execute(context)?.byValueCopy() ?: ObjNull
|
||||
context.addItem(name, isMutable, initValue, visibility, recordType = ObjRecord.Type.Field)
|
||||
initValue
|
||||
if (declaringClassName != null && !isStatic) {
|
||||
val storageName = "$declaringClassName::$name"
|
||||
// If we are in class scope now (defining instance field), defer initialization to instance time
|
||||
val isClassScope = context.thisObj is ObjClass && (context.thisObj !is ObjInstance)
|
||||
if (isClassScope) {
|
||||
val cls = context.thisObj as ObjClass
|
||||
// Defer: at instance construction, evaluate initializer in instance scope and store under mangled name
|
||||
val initStmt = statement(nameToken.pos) { scp ->
|
||||
val initValue = initialExpression?.execute(scp)?.byValueCopy() ?: ObjNull
|
||||
scp.addOrUpdateItem(storageName, initValue, visibility, recordType = ObjRecord.Type.Field)
|
||||
ObjVoid
|
||||
}
|
||||
cls.instanceInitializers += initStmt
|
||||
ObjVoid
|
||||
} else {
|
||||
// We are in instance scope already: perform initialization immediately
|
||||
val initValue = initialExpression?.execute(context)?.byValueCopy() ?: ObjNull
|
||||
context.addOrUpdateItem(storageName, initValue, visibility, recordType = ObjRecord.Type.Field)
|
||||
initValue
|
||||
}
|
||||
} else {
|
||||
// Not in class body: regular local/var declaration
|
||||
val initValue = initialExpression?.execute(context)?.byValueCopy() ?: ObjNull
|
||||
context.addItem(name, isMutable, initValue, visibility, recordType = ObjRecord.Type.Field)
|
||||
initValue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2027,6 +2210,13 @@ class Compiler(
|
||||
Operator(Token.Type.NOTIS, lastPriority) { _, a, b ->
|
||||
BinaryOpRef(BinOp.NOTIS, a, b)
|
||||
},
|
||||
// casts: as / as?
|
||||
Operator(Token.Type.AS, lastPriority) { pos, a, b ->
|
||||
CastRef(a, b, false, pos)
|
||||
},
|
||||
Operator(Token.Type.ASNULL, lastPriority) { pos, a, b ->
|
||||
CastRef(a, b, true, pos)
|
||||
},
|
||||
|
||||
Operator(Token.Type.ELVIS, ++lastPriority, 2) { _, a, b ->
|
||||
ElvisRef(a, b)
|
||||
|
||||
@@ -342,6 +342,11 @@ private class Parser(fromPos: Pos) {
|
||||
when (text) {
|
||||
"in" -> Token("in", from, Token.Type.IN)
|
||||
"is" -> Token("is", from, Token.Type.IS)
|
||||
"as" -> {
|
||||
// support both `as` and tight `as?` without spaces
|
||||
if (currentChar == '?') { pos.advance(); Token("as?", from, Token.Type.ASNULL) }
|
||||
else Token("as", from, Token.Type.AS)
|
||||
}
|
||||
else -> Token(text, from, Token.Type.ID)
|
||||
}
|
||||
} else
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng
|
||||
|
||||
import net.sergeych.lyng.PerfProfiles.restore
|
||||
|
||||
|
||||
/**
|
||||
* Helper to quickly apply groups of [PerfFlags] for different workload profiles and restore them back.
|
||||
* JVM-first defaults; safe to use on all targets. Applying a preset returns a [Snapshot] that can be
|
||||
* passed to [restore] to revert the change.
|
||||
*/
|
||||
object PerfProfiles {
|
||||
data class Snapshot(
|
||||
val LOCAL_SLOT_PIC: Boolean,
|
||||
val EMIT_FAST_LOCAL_REFS: Boolean,
|
||||
|
||||
val ARG_BUILDER: Boolean,
|
||||
val ARG_SMALL_ARITY_12: Boolean,
|
||||
val SKIP_ARGS_ON_NULL_RECEIVER: Boolean,
|
||||
val SCOPE_POOL: Boolean,
|
||||
|
||||
val FIELD_PIC: Boolean,
|
||||
val METHOD_PIC: Boolean,
|
||||
val FIELD_PIC_SIZE_4: Boolean,
|
||||
val METHOD_PIC_SIZE_4: Boolean,
|
||||
val PIC_ADAPTIVE_2_TO_4: Boolean,
|
||||
val PIC_ADAPTIVE_METHODS_ONLY: Boolean,
|
||||
val PIC_ADAPTIVE_HEURISTIC: Boolean,
|
||||
|
||||
val INDEX_PIC: Boolean,
|
||||
val INDEX_PIC_SIZE_4: Boolean,
|
||||
|
||||
val PIC_DEBUG_COUNTERS: Boolean,
|
||||
|
||||
val PRIMITIVE_FASTOPS: Boolean,
|
||||
val RVAL_FASTPATH: Boolean,
|
||||
|
||||
val REGEX_CACHE: Boolean,
|
||||
val RANGE_FAST_ITER: Boolean,
|
||||
)
|
||||
|
||||
fun snapshot(): Snapshot = Snapshot(
|
||||
LOCAL_SLOT_PIC = PerfFlags.LOCAL_SLOT_PIC,
|
||||
EMIT_FAST_LOCAL_REFS = PerfFlags.EMIT_FAST_LOCAL_REFS,
|
||||
|
||||
ARG_BUILDER = PerfFlags.ARG_BUILDER,
|
||||
ARG_SMALL_ARITY_12 = PerfFlags.ARG_SMALL_ARITY_12,
|
||||
SKIP_ARGS_ON_NULL_RECEIVER = PerfFlags.SKIP_ARGS_ON_NULL_RECEIVER,
|
||||
SCOPE_POOL = PerfFlags.SCOPE_POOL,
|
||||
|
||||
FIELD_PIC = PerfFlags.FIELD_PIC,
|
||||
METHOD_PIC = PerfFlags.METHOD_PIC,
|
||||
FIELD_PIC_SIZE_4 = PerfFlags.FIELD_PIC_SIZE_4,
|
||||
METHOD_PIC_SIZE_4 = PerfFlags.METHOD_PIC_SIZE_4,
|
||||
PIC_ADAPTIVE_2_TO_4 = PerfFlags.PIC_ADAPTIVE_2_TO_4,
|
||||
PIC_ADAPTIVE_METHODS_ONLY = PerfFlags.PIC_ADAPTIVE_METHODS_ONLY,
|
||||
PIC_ADAPTIVE_HEURISTIC = PerfFlags.PIC_ADAPTIVE_HEURISTIC,
|
||||
|
||||
INDEX_PIC = PerfFlags.INDEX_PIC,
|
||||
INDEX_PIC_SIZE_4 = PerfFlags.INDEX_PIC_SIZE_4,
|
||||
|
||||
PIC_DEBUG_COUNTERS = PerfFlags.PIC_DEBUG_COUNTERS,
|
||||
|
||||
PRIMITIVE_FASTOPS = PerfFlags.PRIMITIVE_FASTOPS,
|
||||
RVAL_FASTPATH = PerfFlags.RVAL_FASTPATH,
|
||||
|
||||
REGEX_CACHE = PerfFlags.REGEX_CACHE,
|
||||
RANGE_FAST_ITER = PerfFlags.RANGE_FAST_ITER,
|
||||
)
|
||||
|
||||
fun restore(s: Snapshot) {
|
||||
PerfFlags.LOCAL_SLOT_PIC = s.LOCAL_SLOT_PIC
|
||||
PerfFlags.EMIT_FAST_LOCAL_REFS = s.EMIT_FAST_LOCAL_REFS
|
||||
|
||||
PerfFlags.ARG_BUILDER = s.ARG_BUILDER
|
||||
PerfFlags.ARG_SMALL_ARITY_12 = s.ARG_SMALL_ARITY_12
|
||||
PerfFlags.SKIP_ARGS_ON_NULL_RECEIVER = s.SKIP_ARGS_ON_NULL_RECEIVER
|
||||
PerfFlags.SCOPE_POOL = s.SCOPE_POOL
|
||||
|
||||
PerfFlags.FIELD_PIC = s.FIELD_PIC
|
||||
PerfFlags.METHOD_PIC = s.METHOD_PIC
|
||||
PerfFlags.FIELD_PIC_SIZE_4 = s.FIELD_PIC_SIZE_4
|
||||
PerfFlags.METHOD_PIC_SIZE_4 = s.METHOD_PIC_SIZE_4
|
||||
PerfFlags.PIC_ADAPTIVE_2_TO_4 = s.PIC_ADAPTIVE_2_TO_4
|
||||
PerfFlags.PIC_ADAPTIVE_METHODS_ONLY = s.PIC_ADAPTIVE_METHODS_ONLY
|
||||
PerfFlags.PIC_ADAPTIVE_HEURISTIC = s.PIC_ADAPTIVE_HEURISTIC
|
||||
|
||||
PerfFlags.INDEX_PIC = s.INDEX_PIC
|
||||
PerfFlags.INDEX_PIC_SIZE_4 = s.INDEX_PIC_SIZE_4
|
||||
|
||||
PerfFlags.PIC_DEBUG_COUNTERS = s.PIC_DEBUG_COUNTERS
|
||||
|
||||
PerfFlags.PRIMITIVE_FASTOPS = s.PRIMITIVE_FASTOPS
|
||||
PerfFlags.RVAL_FASTPATH = s.RVAL_FASTPATH
|
||||
|
||||
PerfFlags.REGEX_CACHE = s.REGEX_CACHE
|
||||
PerfFlags.RANGE_FAST_ITER = s.RANGE_FAST_ITER
|
||||
}
|
||||
|
||||
enum class Preset { BASELINE, BENCH, BOOKS }
|
||||
|
||||
/** Apply a preset and return a [Snapshot] of the previous state to allow restoring later. */
|
||||
fun apply(preset: Preset): Snapshot {
|
||||
val saved = snapshot()
|
||||
when (preset) {
|
||||
Preset.BASELINE -> applyBaseline()
|
||||
Preset.BENCH -> applyBench()
|
||||
Preset.BOOKS -> applyBooks()
|
||||
}
|
||||
return saved
|
||||
}
|
||||
|
||||
private fun applyBaseline() {
|
||||
// Restore platform defaults. Note: INDEX_PIC follows FIELD_PIC parity by convention.
|
||||
PerfFlags.LOCAL_SLOT_PIC = PerfDefaults.LOCAL_SLOT_PIC
|
||||
PerfFlags.EMIT_FAST_LOCAL_REFS = PerfDefaults.EMIT_FAST_LOCAL_REFS
|
||||
|
||||
PerfFlags.ARG_BUILDER = PerfDefaults.ARG_BUILDER
|
||||
PerfFlags.ARG_SMALL_ARITY_12 = PerfDefaults.ARG_SMALL_ARITY_12
|
||||
PerfFlags.SKIP_ARGS_ON_NULL_RECEIVER = PerfDefaults.SKIP_ARGS_ON_NULL_RECEIVER
|
||||
PerfFlags.SCOPE_POOL = PerfDefaults.SCOPE_POOL
|
||||
|
||||
PerfFlags.FIELD_PIC = PerfDefaults.FIELD_PIC
|
||||
PerfFlags.METHOD_PIC = PerfDefaults.METHOD_PIC
|
||||
PerfFlags.FIELD_PIC_SIZE_4 = PerfDefaults.FIELD_PIC_SIZE_4
|
||||
PerfFlags.METHOD_PIC_SIZE_4 = PerfDefaults.METHOD_PIC_SIZE_4
|
||||
PerfFlags.PIC_ADAPTIVE_2_TO_4 = PerfDefaults.PIC_ADAPTIVE_2_TO_4
|
||||
PerfFlags.PIC_ADAPTIVE_METHODS_ONLY = PerfDefaults.PIC_ADAPTIVE_METHODS_ONLY
|
||||
PerfFlags.PIC_ADAPTIVE_HEURISTIC = PerfDefaults.PIC_ADAPTIVE_HEURISTIC
|
||||
|
||||
PerfFlags.INDEX_PIC = PerfFlags.FIELD_PIC
|
||||
PerfFlags.INDEX_PIC_SIZE_4 = PerfDefaults.INDEX_PIC_SIZE_4
|
||||
|
||||
PerfFlags.PIC_DEBUG_COUNTERS = PerfDefaults.PIC_DEBUG_COUNTERS
|
||||
|
||||
PerfFlags.PRIMITIVE_FASTOPS = PerfDefaults.PRIMITIVE_FASTOPS
|
||||
PerfFlags.RVAL_FASTPATH = PerfDefaults.RVAL_FASTPATH
|
||||
|
||||
PerfFlags.REGEX_CACHE = PerfDefaults.REGEX_CACHE
|
||||
PerfFlags.RANGE_FAST_ITER = PerfDefaults.RANGE_FAST_ITER
|
||||
}
|
||||
|
||||
private fun applyBench() {
|
||||
// Expression-heavy micro-bench focus (JVM-first assumptions)
|
||||
PerfFlags.LOCAL_SLOT_PIC = true
|
||||
PerfFlags.EMIT_FAST_LOCAL_REFS = true
|
||||
|
||||
PerfFlags.ARG_BUILDER = true
|
||||
PerfFlags.ARG_SMALL_ARITY_12 = false
|
||||
PerfFlags.SKIP_ARGS_ON_NULL_RECEIVER = true
|
||||
PerfFlags.SCOPE_POOL = true
|
||||
|
||||
PerfFlags.FIELD_PIC = true
|
||||
PerfFlags.METHOD_PIC = true
|
||||
PerfFlags.FIELD_PIC_SIZE_4 = false
|
||||
PerfFlags.METHOD_PIC_SIZE_4 = false
|
||||
PerfFlags.PIC_ADAPTIVE_2_TO_4 = false
|
||||
PerfFlags.PIC_ADAPTIVE_METHODS_ONLY = false
|
||||
PerfFlags.PIC_ADAPTIVE_HEURISTIC = false
|
||||
|
||||
PerfFlags.INDEX_PIC = true
|
||||
PerfFlags.INDEX_PIC_SIZE_4 = true
|
||||
|
||||
PerfFlags.PIC_DEBUG_COUNTERS = false
|
||||
|
||||
PerfFlags.PRIMITIVE_FASTOPS = true
|
||||
PerfFlags.RVAL_FASTPATH = true
|
||||
|
||||
// Keep regex cache/platform setting; enable on JVM typically
|
||||
PerfFlags.REGEX_CACHE = PerfDefaults.REGEX_CACHE
|
||||
PerfFlags.RANGE_FAST_ITER = false
|
||||
}
|
||||
|
||||
private fun applyBooks() {
|
||||
// Documentation/book workload focus based on profiling observations.
|
||||
// Favor simpler paths when hot expression paths are not dominant.
|
||||
PerfFlags.LOCAL_SLOT_PIC = PerfDefaults.LOCAL_SLOT_PIC
|
||||
PerfFlags.EMIT_FAST_LOCAL_REFS = PerfDefaults.EMIT_FAST_LOCAL_REFS
|
||||
|
||||
PerfFlags.ARG_BUILDER = false
|
||||
PerfFlags.ARG_SMALL_ARITY_12 = false
|
||||
PerfFlags.SKIP_ARGS_ON_NULL_RECEIVER = true
|
||||
PerfFlags.SCOPE_POOL = false
|
||||
|
||||
PerfFlags.FIELD_PIC = false
|
||||
PerfFlags.METHOD_PIC = false
|
||||
PerfFlags.FIELD_PIC_SIZE_4 = false
|
||||
PerfFlags.METHOD_PIC_SIZE_4 = false
|
||||
PerfFlags.PIC_ADAPTIVE_2_TO_4 = false
|
||||
PerfFlags.PIC_ADAPTIVE_METHODS_ONLY = false
|
||||
PerfFlags.PIC_ADAPTIVE_HEURISTIC = false
|
||||
|
||||
PerfFlags.INDEX_PIC = false
|
||||
PerfFlags.INDEX_PIC_SIZE_4 = false
|
||||
|
||||
PerfFlags.PIC_DEBUG_COUNTERS = false
|
||||
|
||||
PerfFlags.PRIMITIVE_FASTOPS = false
|
||||
PerfFlags.RVAL_FASTPATH = false
|
||||
|
||||
// Keep regex cache/platform default; ON on JVM usually helps string APIs in books
|
||||
PerfFlags.REGEX_CACHE = PerfDefaults.REGEX_CACHE
|
||||
PerfFlags.RANGE_FAST_ITER = false
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,8 @@ open class Scope(
|
||||
var thisObj: Obj = ObjVoid,
|
||||
var skipScopeCreation: Boolean = false,
|
||||
) {
|
||||
/** Lexical class context for visibility checks (propagates from parent). */
|
||||
var currentClassCtx: net.sergeych.lyng.obj.ObjClass? = parent?.currentClassCtx
|
||||
// Unique id per scope frame for PICs; regenerated on each borrow from the pool.
|
||||
var frameId: Long = nextFrameId()
|
||||
|
||||
@@ -53,6 +55,12 @@ open class Scope(
|
||||
// Enabled by default for child scopes; module/class scopes can ignore it.
|
||||
private val slots: MutableList<ObjRecord> = mutableListOf()
|
||||
private val nameToSlot: MutableMap<String, Int> = mutableMapOf()
|
||||
/**
|
||||
* Auxiliary per-frame map of local bindings (locals declared in this frame).
|
||||
* This helps resolving locals across suspension when slot ownership isn't
|
||||
* directly discoverable from the current frame.
|
||||
*/
|
||||
internal val localBindings: MutableMap<String, ObjRecord> = mutableMapOf()
|
||||
|
||||
/**
|
||||
* Hint internal collections to reduce reallocations for upcoming parameter/local assignments.
|
||||
@@ -162,11 +170,15 @@ open class Scope(
|
||||
open operator fun get(name: String): ObjRecord? =
|
||||
if (name == "this") thisObj.asReadonly
|
||||
else {
|
||||
// Prefer direct locals/bindings declared in this frame
|
||||
(objects[name]
|
||||
// Then, check known local bindings in this frame (helps after suspension)
|
||||
?: localBindings[name]
|
||||
// Walk up ancestry
|
||||
?: parent?.get(name)
|
||||
?: thisObj.objClass
|
||||
.getInstanceMemberOrNull(name)
|
||||
)
|
||||
// Finally, fallback to class members on thisObj
|
||||
?: thisObj.objClass.getInstanceMemberOrNull(name)
|
||||
)
|
||||
}
|
||||
|
||||
// Slot fast-path API
|
||||
@@ -199,6 +211,7 @@ open class Scope(
|
||||
objects.clear()
|
||||
slots.clear()
|
||||
nameToSlot.clear()
|
||||
localBindings.clear()
|
||||
// Pre-size local slots for upcoming parameter assignment where possible
|
||||
reserveLocalCapacity(args.list.size + 4)
|
||||
}
|
||||
@@ -258,6 +271,13 @@ open class Scope(
|
||||
if( !it.isMutable )
|
||||
raiseIllegalAssignment("symbol is readonly: $name")
|
||||
it.value = value
|
||||
// keep local binding index consistent within the frame
|
||||
localBindings[name] = it
|
||||
// If we are a ClosureScope, mirror binding into the caller frame to keep it discoverable
|
||||
// across suspension when resumed on the call frame
|
||||
if (this is ClosureScope) {
|
||||
callScope.localBindings[name] = it
|
||||
}
|
||||
it
|
||||
} ?: addItem(name, true, value, visibility, recordType)
|
||||
|
||||
@@ -268,8 +288,23 @@ open class Scope(
|
||||
visibility: Visibility = Visibility.Public,
|
||||
recordType: ObjRecord.Type = ObjRecord.Type.Other
|
||||
): ObjRecord {
|
||||
val rec = ObjRecord(value, isMutable, visibility, type = recordType)
|
||||
val rec = ObjRecord(value, isMutable, visibility, declaringClass = currentClassCtx, type = recordType)
|
||||
objects[name] = 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
|
||||
// across suspension when resumed on the call frame
|
||||
if (this is ClosureScope) {
|
||||
callScope.localBindings[name] = rec
|
||||
// Additionally, expose the binding in caller's objects and slot map so identifier
|
||||
// resolution after suspension can still find it even if the active scope is a child
|
||||
// of the callScope (e.g., due to internal withChildFrame usage).
|
||||
// This keeps visibility within the method body but prevents leaking outside the caller frame.
|
||||
callScope.objects[name] = rec
|
||||
if (callScope.getSlotIndexOf(name) == null) {
|
||||
callScope.allocateSlotFor(name, rec)
|
||||
}
|
||||
}
|
||||
// Map to a slot for fast local access (if not already mapped)
|
||||
if (getSlotIndexOf(name) == null) {
|
||||
allocateSlotFor(name, rec)
|
||||
|
||||
@@ -43,6 +43,8 @@ data class Token(val value: String, val pos: Pos, val type: Type) {
|
||||
SHL, SHR,
|
||||
SINLGE_LINE_COMMENT, MULTILINE_COMMENT,
|
||||
LABEL, ATLABEL, // label@ at@label
|
||||
// type-checking/casting
|
||||
AS, ASNULL,
|
||||
//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,
|
||||
|
||||
@@ -22,4 +22,18 @@ enum class Visibility {
|
||||
val isPublic by lazy { this == Public }
|
||||
@Suppress("unused")
|
||||
val isProtected by lazy { this == Protected }
|
||||
}
|
||||
|
||||
/** MI-aware visibility check: whether [caller] can access a member declared in [decl] with [visibility]. */
|
||||
fun canAccessMember(visibility: Visibility, decl: net.sergeych.lyng.obj.ObjClass?, caller: net.sergeych.lyng.obj.ObjClass?): Boolean {
|
||||
return when (visibility) {
|
||||
Visibility.Public -> true
|
||||
Visibility.Private -> (decl != null && caller === decl)
|
||||
Visibility.Protected -> when {
|
||||
decl == null -> false
|
||||
caller == null -> false
|
||||
caller === decl -> true
|
||||
else -> (caller.allParentsSet.contains(decl))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,13 +80,26 @@ open class Obj {
|
||||
args: Arguments = Arguments.EMPTY,
|
||||
onNotFoundResult: (() -> Obj?)? = null
|
||||
): Obj {
|
||||
return objClass.getInstanceMemberOrNull(name)?.value?.invoke(
|
||||
scope,
|
||||
this,
|
||||
args
|
||||
)
|
||||
?: onNotFoundResult?.invoke()
|
||||
?: scope.raiseSymbolNotFound(name)
|
||||
val rec = objClass.getInstanceMemberOrNull(name)
|
||||
if (rec != null) {
|
||||
val decl = rec.declaringClass ?: objClass.findDeclaringClassOf(name)
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(rec.visibility, decl, caller))
|
||||
scope.raiseError(ObjAccessException(scope, "can't invoke ${name}: not visible (declared in ${decl?.className ?: "?"}, caller ${caller?.className ?: "?"})"))
|
||||
// Propagate declaring class as current class context during method execution
|
||||
val saved = scope.currentClassCtx
|
||||
scope.currentClassCtx = decl
|
||||
try {
|
||||
return rec.value.invoke(scope, this, args)
|
||||
} finally {
|
||||
scope.currentClassCtx = saved
|
||||
}
|
||||
}
|
||||
return onNotFoundResult?.invoke()
|
||||
?: scope.raiseError(
|
||||
"no such member: $name on ${objClass.className}. Considered order: ${objClass.renderLinearization(true)}. " +
|
||||
"Tip: try this@Base.$name(...) or (obj as Base).$name(...) if ambiguous"
|
||||
)
|
||||
}
|
||||
|
||||
open suspend fun getInstanceMethod(
|
||||
@@ -258,7 +271,13 @@ open class Obj {
|
||||
|
||||
open suspend fun readField(scope: Scope, name: String): ObjRecord {
|
||||
// could be property or class field:
|
||||
val obj = objClass.getInstanceMemberOrNull(name) ?: scope.raiseError("no such field: $name")
|
||||
val obj = objClass.getInstanceMemberOrNull(name) ?: scope.raiseError(
|
||||
"no such field: $name on ${objClass.className}. Considered order: ${objClass.renderLinearization(true)}"
|
||||
)
|
||||
val decl = obj.declaringClass ?: objClass.findDeclaringClassOf(name)
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(obj.visibility, decl, caller))
|
||||
scope.raiseError(ObjAccessException(scope, "can't access field ${name}: not visible (declared in ${decl?.className ?: "?"}, caller ${caller?.className ?: "?"})"))
|
||||
return when (val value = obj.value) {
|
||||
is Statement -> {
|
||||
ObjRecord(value.execute(scope.createChildScope(scope.pos, newThisObj = this)), obj.isMutable)
|
||||
@@ -271,7 +290,13 @@ open class Obj {
|
||||
|
||||
open suspend fun writeField(scope: Scope, name: String, newValue: Obj) {
|
||||
willMutate(scope)
|
||||
val field = objClass.getInstanceMemberOrNull(name) ?: scope.raiseError("no such field: $name")
|
||||
val field = objClass.getInstanceMemberOrNull(name) ?: scope.raiseError(
|
||||
"no such field: $name on ${objClass.className}. Considered order: ${objClass.renderLinearization(true)}"
|
||||
)
|
||||
val decl = field.declaringClass ?: objClass.findDeclaringClassOf(name)
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(field.visibility, decl, caller))
|
||||
scope.raiseError(ObjAccessException(scope, "can't assign field ${name}: not visible (declared in ${decl?.className ?: "?"}, caller ${caller?.className ?: "?"})"))
|
||||
if (field.isMutable) field.value = newValue else scope.raiseError("can't assign to read-only field: $name")
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,12 @@ open class ObjClass(
|
||||
var constructorMeta: ArgsDeclaration? = null
|
||||
var instanceConstructor: Statement? = null
|
||||
|
||||
/**
|
||||
* Per-instance initializers collected from class body (for instance fields). These are executed
|
||||
* during construction in the instance scope of the object, once per class in the hierarchy.
|
||||
*/
|
||||
val instanceInitializers: MutableList<Statement> = mutableListOf()
|
||||
|
||||
/**
|
||||
* the scope for class methods, initialize class vars, etc.
|
||||
*
|
||||
@@ -50,10 +56,77 @@ open class ObjClass(
|
||||
*/
|
||||
var classScope: Scope? = null
|
||||
|
||||
/** Direct parents in declaration order (kept deterministic). */
|
||||
val directParents: List<ObjClass> = parents.toList()
|
||||
|
||||
/** Optional constructor argument specs for each direct parent (set by compiler for user classes). */
|
||||
open val directParentArgs: MutableMap<ObjClass, List<ParsedArgument>> = mutableMapOf()
|
||||
|
||||
/**
|
||||
* All ancestors as a Set for fast `isInstanceOf` checks. Order is not guaranteed here and
|
||||
* must not be used for resolution — use [parentsLinearized] instead.
|
||||
*/
|
||||
val allParentsSet: Set<ObjClass> =
|
||||
parents.flatMap {
|
||||
listOf(it) + it.allParentsSet
|
||||
}.toMutableSet()
|
||||
buildSet {
|
||||
fun collect(c: ObjClass) {
|
||||
if (add(c)) c.directParents.forEach { collect(it) }
|
||||
}
|
||||
directParents.forEach { collect(it) }
|
||||
}
|
||||
|
||||
// --- C3 Method Resolution Order (MRO) ---
|
||||
private fun c3Merge(seqs: MutableList<MutableList<ObjClass>>): List<ObjClass> {
|
||||
val result = mutableListOf<ObjClass>()
|
||||
while (seqs.isNotEmpty()) {
|
||||
// remove empty lists
|
||||
seqs.removeAll { it.isEmpty() }
|
||||
if (seqs.isEmpty()) break
|
||||
var candidate: ObjClass? = null
|
||||
outer@ for (seq in seqs) {
|
||||
val head = seq.first()
|
||||
// head must not appear in any other list's tail
|
||||
var inTail = false
|
||||
for (other in seqs) {
|
||||
if (other === seq || other.size <= 1) continue
|
||||
if (other.drop(1).contains(head)) { inTail = true; break }
|
||||
}
|
||||
if (!inTail) { candidate = head; break@outer }
|
||||
}
|
||||
val picked = candidate ?: throw ScriptError(Pos.builtIn, "C3 MRO failed: inconsistent hierarchy for $className")
|
||||
result += picked
|
||||
// remove picked from heads
|
||||
for (seq in seqs) if (seq.isNotEmpty() && seq.first() === picked) seq.removeAt(0)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun c3Linearize(self: ObjClass, visited: MutableMap<ObjClass, List<ObjClass>>): List<ObjClass> {
|
||||
visited[self]?.let { return it }
|
||||
// Linearize parents first
|
||||
val parentLinearizations = self.directParents.map { c3Linearize(it, visited) }
|
||||
// Merge parent MROs with the direct parent list
|
||||
val toMerge: MutableList<MutableList<ObjClass>> = mutableListOf()
|
||||
parentLinearizations.forEach { toMerge += it.toMutableList() }
|
||||
toMerge += self.directParents.toMutableList()
|
||||
val merged = c3Merge(toMerge)
|
||||
val mro = listOf(self) + merged
|
||||
visited[self] = mro
|
||||
return mro
|
||||
}
|
||||
|
||||
/** Full C3 MRO including this class at index 0. */
|
||||
val mro: List<ObjClass> by lazy { c3Linearize(this, mutableMapOf()) }
|
||||
|
||||
/** Parents in C3 order (no self). */
|
||||
val mroParents: List<ObjClass> by lazy { mro.drop(1) }
|
||||
|
||||
/** Render current linearization order for diagnostics (C3). */
|
||||
fun renderLinearization(includeSelf: Boolean = true): String {
|
||||
val list = mutableListOf<String>()
|
||||
if (includeSelf) list += className
|
||||
mroParents.forEach { list += it.className }
|
||||
return list.joinToString(" → ")
|
||||
}
|
||||
|
||||
override val objClass: ObjClass by lazy { ObjClassType }
|
||||
|
||||
@@ -73,9 +146,70 @@ open class ObjClass(
|
||||
// remains stable even when call frames are pooled and reused.
|
||||
val stableParent = scope.parent
|
||||
instance.instanceScope = Scope(stableParent, scope.args, scope.pos, instance)
|
||||
if (instanceConstructor != null) {
|
||||
instanceConstructor!!.execute(instance.instanceScope)
|
||||
// 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
|
||||
// 1) members-defined methods
|
||||
for ((k, v) in members) {
|
||||
if (v.value is Statement) {
|
||||
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 not already present, copy reference for dispatch
|
||||
if (!instance.instanceScope.objects.containsKey(k)) {
|
||||
instance.instanceScope.objects[k] = rec
|
||||
}
|
||||
}
|
||||
}
|
||||
// Constructor chaining MVP: initialize base classes left-to-right, then this class.
|
||||
// Ensure each ancestor is initialized at most once (diamond-safe).
|
||||
val visited = hashSetOf<ObjClass>()
|
||||
|
||||
suspend fun initClass(c: ObjClass, argsForThis: Arguments?, isRoot: Boolean = false) {
|
||||
if (!visited.add(c)) return
|
||||
// For the most-derived class, bind its constructor params BEFORE parents so base arg thunks can see them
|
||||
if (isRoot) {
|
||||
c.constructorMeta?.let { meta ->
|
||||
val argsHere = argsForThis ?: Arguments.EMPTY
|
||||
meta.assignToContext(instance.instanceScope, argsHere)
|
||||
}
|
||||
}
|
||||
// Initialize direct parents first, in order
|
||||
for (p in c.directParents) {
|
||||
val raw = c.directParentArgs[p]?.toArguments(instance.instanceScope, false)
|
||||
val limited = if (raw != null) {
|
||||
val need = p.constructorMeta?.params?.size ?: 0
|
||||
if (need == 0) Arguments.EMPTY else Arguments(raw.list.take(need), tailBlockMode = false)
|
||||
} else Arguments.EMPTY
|
||||
initClass(p, limited)
|
||||
}
|
||||
// Execute per-instance initializers collected from class body for this class
|
||||
if (c.instanceInitializers.isNotEmpty()) {
|
||||
val savedCtx = instance.instanceScope.currentClassCtx
|
||||
instance.instanceScope.currentClassCtx = c
|
||||
try {
|
||||
for (initStmt in c.instanceInitializers) {
|
||||
initStmt.execute(instance.instanceScope)
|
||||
}
|
||||
} finally {
|
||||
instance.instanceScope.currentClassCtx = savedCtx
|
||||
}
|
||||
}
|
||||
// Then run this class' constructor, if any
|
||||
c.instanceConstructor?.let { ctor ->
|
||||
// Bind this class's constructor parameters into the instance scope now, right before ctor
|
||||
c.constructorMeta?.let { meta ->
|
||||
val argsHere = argsForThis ?: Arguments.EMPTY
|
||||
meta.assignToContext(instance.instanceScope, argsHere)
|
||||
}
|
||||
val execScope = instance.instanceScope.createChildScope(args = argsForThis ?: Arguments.EMPTY, newThisObj = instance)
|
||||
ctor.execute(execScope)
|
||||
}
|
||||
}
|
||||
|
||||
initClass(this, instance.instanceScope.args, isRoot = true)
|
||||
return instance
|
||||
}
|
||||
|
||||
@@ -91,10 +225,12 @@ open class ObjClass(
|
||||
visibility: Visibility = Visibility.Public,
|
||||
pos: Pos = Pos.builtIn
|
||||
) {
|
||||
val existing = members[name] ?: allParentsSet.firstNotNullOfOrNull { it.members[name] }
|
||||
if (existing?.isMutable == false)
|
||||
throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
|
||||
members[name] = ObjRecord(initialValue, isMutable, visibility)
|
||||
// Allow overriding ancestors: only prevent redefinition if THIS class already defines an immutable member
|
||||
val existingInSelf = members[name]
|
||||
if (existingInSelf != null && existingInSelf.isMutable == false)
|
||||
throw ScriptError(pos, "$name is already defined in $objClass")
|
||||
// Install/override in this class
|
||||
members[name] = ObjRecord(initialValue, isMutable, visibility, declaringClass = this)
|
||||
// Structural change: bump layout version for PIC invalidation
|
||||
layoutVersion += 1
|
||||
}
|
||||
@@ -120,8 +256,14 @@ open class ObjClass(
|
||||
layoutVersion += 1
|
||||
}
|
||||
|
||||
fun addFn(name: String, isOpen: Boolean = false, code: suspend Scope.() -> Obj) {
|
||||
createField(name, statement { code() }, isOpen)
|
||||
fun addFn(
|
||||
name: String,
|
||||
isOpen: Boolean = false,
|
||||
visibility: net.sergeych.lyng.Visibility = net.sergeych.lyng.Visibility.Public,
|
||||
code: suspend Scope.() -> Obj
|
||||
) {
|
||||
val stmt = statement { code() }
|
||||
createField(name, stmt, isOpen, visibility)
|
||||
}
|
||||
|
||||
fun addConst(name: String, value: Obj) = createField(name, value, isMutable = false)
|
||||
@@ -135,15 +277,46 @@ open class ObjClass(
|
||||
* Get instance member traversing the hierarchy if needed. Its meaning is different for different objects.
|
||||
*/
|
||||
fun getInstanceMemberOrNull(name: String): ObjRecord? {
|
||||
members[name]?.let { return it }
|
||||
allParentsSet.forEach { parent -> parent.getInstanceMemberOrNull(name)?.let { return it } }
|
||||
// Unified traversal in strict C3 order: self, then each ancestor, checking members before classScope
|
||||
for (cls in mro) {
|
||||
cls.members[name]?.let { return it }
|
||||
cls.classScope?.objects?.get(name)?.let { return it }
|
||||
}
|
||||
// Finally, allow root object fallback (rare; mostly built-ins like toString)
|
||||
return rootObjectType.members[name]
|
||||
}
|
||||
|
||||
/** Find the declaring class where a member with [name] is defined, starting from this class along MRO. */
|
||||
fun findDeclaringClassOf(name: String): ObjClass? {
|
||||
if (members.containsKey(name)) return this
|
||||
for (anc in mroParents) {
|
||||
if (anc.members.containsKey(name)) return anc
|
||||
}
|
||||
return if (rootObjectType.members.containsKey(name)) rootObjectType else null
|
||||
}
|
||||
|
||||
fun getInstanceMember(atPos: Pos, name: String): ObjRecord =
|
||||
getInstanceMemberOrNull(name)
|
||||
?: throw ScriptError(atPos, "symbol doesn't exist: $name")
|
||||
|
||||
/**
|
||||
* Resolve member starting from a specific ancestor class [start], not from this class.
|
||||
* Searches [start] first, then traverses its linearized parents.
|
||||
*/
|
||||
fun getInstanceMemberFromAncestor(start: ObjClass, name: String): ObjRecord? {
|
||||
val order = mro
|
||||
val idx = order.indexOf(start)
|
||||
if (idx < 0) return null
|
||||
for (i in idx until order.size) {
|
||||
val cls = order[i]
|
||||
// Prefer true instance members on the class
|
||||
cls.members[name]?.let { return it }
|
||||
// Fallback to class-scope function registered during class-body execution
|
||||
cls.classScope?.objects?.get(name)?.let { return it }
|
||||
}
|
||||
return rootObjectType.members[name]
|
||||
}
|
||||
|
||||
override suspend fun readField(scope: Scope, name: String): ObjRecord {
|
||||
classScope?.objects?.get(name)?.let {
|
||||
if (it.visibility.isPublic) return it
|
||||
|
||||
@@ -19,6 +19,7 @@ package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.Arguments
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.canAccessMember
|
||||
import net.sergeych.lynon.LynonDecoder
|
||||
import net.sergeych.lynon.LynonEncoder
|
||||
import net.sergeych.lynon.LynonType
|
||||
@@ -28,40 +29,124 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
internal lateinit var instanceScope: Scope
|
||||
|
||||
override suspend fun readField(scope: Scope, name: String): ObjRecord {
|
||||
return instanceScope[name]?.let {
|
||||
if (it.visibility.isPublic)
|
||||
it
|
||||
else
|
||||
scope.raiseError(ObjAccessException(scope, "can't access non-public field $name"))
|
||||
// Direct (unmangled) lookup first
|
||||
instanceScope[name]?.let {
|
||||
val decl = it.declaringClass ?: objClass.findDeclaringClassOf(name)
|
||||
val caller = scope.currentClassCtx ?: instanceScope.currentClassCtx
|
||||
val allowed = if (it.visibility == net.sergeych.lyng.Visibility.Private) (decl === objClass) else canAccessMember(it.visibility, decl, caller)
|
||||
if (!allowed)
|
||||
scope.raiseError(ObjAccessException(scope, "can't access field $name (declared in ${decl?.className ?: "?"})"))
|
||||
return it
|
||||
}
|
||||
?: super.readField(scope, name)
|
||||
// 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 { return it }
|
||||
// ancestors in deterministic C3 order
|
||||
for (p in cls.mroParents) {
|
||||
instanceScope.objects["${p.className}::$name"]?.let { return it }
|
||||
}
|
||||
return null
|
||||
}
|
||||
findMangled()?.let { rec ->
|
||||
val declName = rec.importedFrom?.packageName // unused; use mangled key instead
|
||||
// derive declaring class by mangled prefix: try self then parents
|
||||
val declaring = when {
|
||||
instanceScope.objects.containsKey("${cls.className}::$name") -> cls
|
||||
else -> cls.mroParents.firstOrNull { instanceScope.objects.containsKey("${it.className}::$name") }
|
||||
}
|
||||
val caller = scope.currentClassCtx ?: instanceScope.currentClassCtx
|
||||
val allowed = if (rec.visibility == net.sergeych.lyng.Visibility.Private) (declaring === objClass) else canAccessMember(rec.visibility, declaring, caller)
|
||||
if (!allowed)
|
||||
scope.raiseError(ObjAccessException(scope, "can't access field $name (declared in ${declaring?.className ?: "?"})"))
|
||||
return rec
|
||||
}
|
||||
// Fall back to methods/properties on class
|
||||
return super.readField(scope, name)
|
||||
}
|
||||
|
||||
override suspend fun writeField(scope: Scope, name: String, newValue: Obj) {
|
||||
// Direct (unmangled) first
|
||||
instanceScope[name]?.let { f ->
|
||||
if (!f.visibility.isPublic)
|
||||
ObjIllegalAssignmentException(scope, "can't assign to non-public field $name")
|
||||
val decl = f.declaringClass ?: objClass.findDeclaringClassOf(name)
|
||||
val caller = scope.currentClassCtx ?: instanceScope.currentClassCtx
|
||||
val allowed = if (f.visibility == net.sergeych.lyng.Visibility.Private) (decl === objClass) else canAccessMember(f.visibility, decl, caller)
|
||||
if (!allowed)
|
||||
ObjIllegalAssignmentException(scope, "can't assign to field $name (declared in ${decl?.className ?: "?"})").raise()
|
||||
if (!f.isMutable) ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
|
||||
if (f.value.assign(scope, newValue) == null)
|
||||
f.value = newValue
|
||||
} ?: super.writeField(scope, name, newValue)
|
||||
return
|
||||
}
|
||||
// Try MI-mangled resolution along linearization (C3 MRO)
|
||||
val cls = objClass
|
||||
fun findMangled(): ObjRecord? {
|
||||
instanceScope.objects["${cls.className}::$name"]?.let { return it }
|
||||
for (p in cls.mroParents) {
|
||||
instanceScope.objects["${p.className}::$name"]?.let { return it }
|
||||
}
|
||||
return null
|
||||
}
|
||||
val rec = findMangled()
|
||||
if (rec != null) {
|
||||
val declaring = when {
|
||||
instanceScope.objects.containsKey("${cls.className}::$name") -> cls
|
||||
else -> cls.mroParents.firstOrNull { instanceScope.objects.containsKey("${it.className}::$name") }
|
||||
}
|
||||
val caller = scope.currentClassCtx ?: instanceScope.currentClassCtx
|
||||
val allowed = if (rec.visibility == net.sergeych.lyng.Visibility.Private) (declaring === objClass) else canAccessMember(rec.visibility, declaring, caller)
|
||||
if (!allowed)
|
||||
ObjIllegalAssignmentException(scope, "can't assign to field $name (declared in ${declaring?.className ?: "?"})").raise()
|
||||
if (!rec.isMutable) ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
|
||||
if (rec.value.assign(scope, newValue) == null)
|
||||
rec.value = newValue
|
||||
return
|
||||
}
|
||||
super.writeField(scope, name, newValue)
|
||||
}
|
||||
|
||||
override suspend fun invokeInstanceMethod(scope: Scope, name: String, args: Arguments,
|
||||
onNotFoundResult: (()->Obj?)?): Obj =
|
||||
instanceScope[name]?.let {
|
||||
if (it.visibility.isPublic)
|
||||
it.value.invoke(
|
||||
instanceScope[name]?.let { rec ->
|
||||
val decl = rec.declaringClass ?: objClass.findDeclaringClassOf(name)
|
||||
val caller = scope.currentClassCtx ?: instanceScope.currentClassCtx
|
||||
val allowed = if (rec.visibility == net.sergeych.lyng.Visibility.Private) (decl === objClass) else canAccessMember(rec.visibility, decl, caller)
|
||||
if (!allowed)
|
||||
scope.raiseError(ObjAccessException(scope, "can't invoke method $name (declared in ${decl?.className ?: "?"})"))
|
||||
// execute with lexical class context propagated to declaring class
|
||||
val saved = instanceScope.currentClassCtx
|
||||
instanceScope.currentClassCtx = decl
|
||||
try {
|
||||
rec.value.invoke(
|
||||
instanceScope,
|
||||
this,
|
||||
args)
|
||||
else
|
||||
scope.raiseError(ObjAccessException(scope, "can't invoke non-public method $name"))
|
||||
} finally {
|
||||
instanceScope.currentClassCtx = saved
|
||||
}
|
||||
}
|
||||
?: run {
|
||||
// fallback: class-scope function (registered during class body execution)
|
||||
objClass.classScope?.objects?.get(name)?.let { rec ->
|
||||
val decl = rec.declaringClass ?: objClass.findDeclaringClassOf(name)
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(rec.visibility, decl, caller))
|
||||
scope.raiseError(ObjAccessException(scope, "can't invoke method $name (declared in ${decl?.className ?: "?"})"))
|
||||
val saved = instanceScope.currentClassCtx
|
||||
instanceScope.currentClassCtx = decl
|
||||
try {
|
||||
rec.value.invoke(instanceScope, this, args)
|
||||
} finally {
|
||||
instanceScope.currentClassCtx = saved
|
||||
}
|
||||
}
|
||||
}
|
||||
?: super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
|
||||
|
||||
private val publicFields: Map<String, ObjRecord>
|
||||
get() = instanceScope.objects.filter { it.value.visibility.isPublic }
|
||||
get() = instanceScope.objects.filter { it.value.visibility.isPublic && it.value.type.serializable }
|
||||
|
||||
override fun toString(): String {
|
||||
val fields = publicFields.map { "${it.key}=${it.value.value}" }.joinToString(",")
|
||||
@@ -120,4 +205,117 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A qualified view over an [ObjInstance] that resolves members starting from a specific ancestor class.
|
||||
* It does not change identity; it only affects lookup precedence for fields and methods.
|
||||
*/
|
||||
class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjClass) : Obj() {
|
||||
override val objClass: ObjClass get() = instance.objClass
|
||||
|
||||
private fun memberFromAncestor(name: String): ObjRecord? =
|
||||
instance.objClass.getInstanceMemberFromAncestor(startClass, name)
|
||||
|
||||
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.instanceScope.objects[mangled]?.let { rec ->
|
||||
// Visibility: declaring class is the qualified ancestor for mangled storage
|
||||
val decl = rec.declaringClass ?: startClass
|
||||
val caller = scope.currentClassCtx
|
||||
if (!net.sergeych.lyng.canAccessMember(rec.visibility, decl, caller))
|
||||
scope.raiseError(ObjAccessException(scope, "can't access field $name (declared in ${decl.className})"))
|
||||
return rec
|
||||
}
|
||||
// Then try instance locals (unmangled) only if startClass is the dynamic class itself
|
||||
if (startClass === instance.objClass) {
|
||||
instance.instanceScope[name]?.let { rec ->
|
||||
val decl = rec.declaringClass ?: instance.objClass.findDeclaringClassOf(name)
|
||||
val caller = scope.currentClassCtx
|
||||
if (!net.sergeych.lyng.canAccessMember(rec.visibility, decl, caller))
|
||||
scope.raiseError(ObjAccessException(scope, "can't access field $name (declared in ${decl?.className ?: "?"})"))
|
||||
return rec
|
||||
}
|
||||
}
|
||||
// Finally try methods/properties starting from ancestor
|
||||
val r = memberFromAncestor(name) ?: scope.raiseError("no such field: $name")
|
||||
val decl = r.declaringClass ?: startClass
|
||||
val caller = scope.currentClassCtx
|
||||
if (!net.sergeych.lyng.canAccessMember(r.visibility, decl, caller))
|
||||
scope.raiseError(ObjAccessException(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
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun writeField(scope: Scope, name: String, newValue: Obj) {
|
||||
// Qualified write: target mangled storage for the ancestor
|
||||
val mangled = "${startClass.className}::$name"
|
||||
instance.instanceScope.objects[mangled]?.let { f ->
|
||||
val decl = f.declaringClass ?: startClass
|
||||
val caller = scope.currentClassCtx
|
||||
if (!net.sergeych.lyng.canAccessMember(f.visibility, decl, caller))
|
||||
ObjIllegalAssignmentException(scope, "can't assign to field $name (declared in ${decl.className})").raise()
|
||||
if (!f.isMutable) ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
|
||||
if (f.value.assign(scope, newValue) == null) f.value = newValue
|
||||
return
|
||||
}
|
||||
// If start is dynamic class, allow unmangled
|
||||
if (startClass === instance.objClass) {
|
||||
instance.instanceScope[name]?.let { f ->
|
||||
val decl = f.declaringClass ?: instance.objClass.findDeclaringClassOf(name)
|
||||
val caller = scope.currentClassCtx
|
||||
if (!net.sergeych.lyng.canAccessMember(f.visibility, decl, caller))
|
||||
ObjIllegalAssignmentException(scope, "can't assign to field $name (declared in ${decl?.className ?: "?"})").raise()
|
||||
if (!f.isMutable) ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
|
||||
if (f.value.assign(scope, newValue) == null) f.value = newValue
|
||||
return
|
||||
}
|
||||
}
|
||||
val r = memberFromAncestor(name) ?: scope.raiseError("no such field: $name")
|
||||
val decl = r.declaringClass ?: startClass
|
||||
val caller = scope.currentClassCtx
|
||||
if (!net.sergeych.lyng.canAccessMember(r.visibility, decl, caller))
|
||||
ObjIllegalAssignmentException(scope, "can't assign to field $name (declared in ${decl.className})").raise()
|
||||
if (!r.isMutable) scope.raiseError("can't assign to read-only field: $name")
|
||||
if (r.value.assign(scope, newValue) == null) r.value = newValue
|
||||
}
|
||||
|
||||
override suspend fun invokeInstanceMethod(scope: Scope, name: String, args: Arguments, onNotFoundResult: (() -> Obj?)?): Obj {
|
||||
// Qualified method dispatch must start from the specified ancestor, not from the instance scope.
|
||||
memberFromAncestor(name)?.let { rec ->
|
||||
val decl = rec.declaringClass ?: startClass
|
||||
val caller = scope.currentClassCtx
|
||||
if (!net.sergeych.lyng.canAccessMember(rec.visibility, decl, caller))
|
||||
scope.raiseError(ObjAccessException(scope, "can't invoke method $name (declared in ${decl.className})"))
|
||||
val saved = instance.instanceScope.currentClassCtx
|
||||
instance.instanceScope.currentClassCtx = decl
|
||||
try {
|
||||
return rec.value.invoke(instance.instanceScope, instance, args)
|
||||
} finally {
|
||||
instance.instanceScope.currentClassCtx = saved
|
||||
}
|
||||
}
|
||||
// If the qualifier is the dynamic class itself, allow instance-scope methods as a fallback
|
||||
if (startClass === instance.objClass) {
|
||||
instance.instanceScope[name]?.let { rec ->
|
||||
val decl = rec.declaringClass ?: instance.objClass.findDeclaringClassOf(name)
|
||||
val caller = scope.currentClassCtx
|
||||
if (!net.sergeych.lyng.canAccessMember(rec.visibility, decl, caller))
|
||||
scope.raiseError(ObjAccessException(scope, "can't invoke method $name (declared in ${decl?.className ?: "?"})"))
|
||||
val saved = instance.instanceScope.currentClassCtx
|
||||
instance.instanceScope.currentClassCtx = decl
|
||||
try {
|
||||
return rec.value.invoke(instance.instanceScope, instance, args)
|
||||
} finally {
|
||||
instance.instanceScope.currentClassCtx = saved
|
||||
}
|
||||
}
|
||||
}
|
||||
return onNotFoundResult?.invoke() ?: scope.raiseSymbolNotFound(name)
|
||||
}
|
||||
|
||||
override fun toString(): String = instance.toString()
|
||||
}
|
||||
@@ -25,7 +25,7 @@ import net.sergeych.lynon.LynonType
|
||||
/**
|
||||
* Special variant of [ObjClass] to be used in [ObjInstance], e.g. for Lyng compiled classes
|
||||
*/
|
||||
class ObjInstanceClass(val name: String) : ObjClass(name) {
|
||||
class ObjInstanceClass(val name: String, vararg parents: ObjClass) : ObjClass(name, *parents) {
|
||||
|
||||
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {
|
||||
val args = decoder.decodeAnyList(scope)
|
||||
@@ -41,7 +41,6 @@ class ObjInstanceClass(val name: String) : ObjClass(name) {
|
||||
|
||||
init {
|
||||
addFn("toString", true) {
|
||||
println("-------------- tos! --------------")
|
||||
ObjString(thisObj.toString())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,9 @@ class ObjMutex(val mutex: Mutex): Obj() {
|
||||
}.apply {
|
||||
addFn("withLock") {
|
||||
val f = requiredArg<Statement>(0)
|
||||
// Execute user lambda directly in the current scope to preserve the active scope
|
||||
// ancestry across suspension points. The lambda still constructs a ClosureScope
|
||||
// on top of this frame, and parseLambdaExpression sets skipScopeCreation for its body.
|
||||
thisAs<ObjMutex>().mutex.withLock { f.execute(this) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,8 @@ data class ObjRecord(
|
||||
var value: Obj,
|
||||
val isMutable: Boolean,
|
||||
val visibility: Visibility = Visibility.Public,
|
||||
/** If non-null, denotes the class that declared this member (field/method). */
|
||||
val declaringClass: ObjClass? = null,
|
||||
var importedFrom: Scope? = null,
|
||||
val isTransient: Boolean = false,
|
||||
val type: Type = Type.Other
|
||||
|
||||
@@ -253,6 +253,49 @@ class BinaryOpRef(private val op: BinOp, private val left: ObjRef, private val r
|
||||
}
|
||||
}
|
||||
|
||||
/** Cast operator reference: left `as` rightType or `as?` (nullable). */
|
||||
class CastRef(
|
||||
private val valueRef: ObjRef,
|
||||
private val typeRef: ObjRef,
|
||||
private val isNullable: Boolean,
|
||||
private val atPos: Pos,
|
||||
) : ObjRef {
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
val v0 = if (net.sergeych.lyng.PerfFlags.RVAL_FASTPATH) valueRef.evalValue(scope) else valueRef.get(scope).value
|
||||
val t = if (net.sergeych.lyng.PerfFlags.RVAL_FASTPATH) typeRef.evalValue(scope) else typeRef.get(scope).value
|
||||
val target = (t as? ObjClass) ?: scope.raiseClassCastError("${'$'}t is not the class instance")
|
||||
// unwrap qualified views
|
||||
val v = when (v0) {
|
||||
is ObjQualifiedView -> v0.instance
|
||||
else -> v0
|
||||
}
|
||||
return if (v.isInstanceOf(target)) {
|
||||
// For instances, return a qualified view to enforce ancestor-start dispatch
|
||||
if (v is ObjInstance) ObjQualifiedView(v, target).asReadonly else v.asReadonly
|
||||
} else {
|
||||
if (isNullable) ObjNull.asReadonly else scope.raiseClassCastError(
|
||||
"Cannot cast ${'$'}{(v as? Obj)?.objClass?.className ?: v::class.simpleName} to ${'$'}{target.className}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 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 {
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
val thisObj = scope.thisObj
|
||||
val t = scope[typeName]?.value as? ObjClass
|
||||
?: scope.raiseError("unknown type $typeName")
|
||||
val inst = (thisObj as? ObjInstance)
|
||||
?: scope.raiseClassCastError("this is not an instance")
|
||||
if (!inst.objClass.allParentsSet.contains(t) && inst.objClass !== t)
|
||||
scope.raiseClassCastError(
|
||||
"Qualifier ${'$'}{t.className} is not an ancestor of ${'$'}{inst.objClass.className} (order: ${'$'}{inst.objClass.renderLinearization(true)})"
|
||||
)
|
||||
return ObjQualifiedView(inst, t).asReadonly
|
||||
}
|
||||
}
|
||||
|
||||
/** Assignment compound op: target op= value */
|
||||
class AssignOpRef(
|
||||
private val op: BinOp,
|
||||
@@ -1168,13 +1211,20 @@ class LocalVarRef(private val name: String, private val atPos: Pos) : ObjRef {
|
||||
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
scope.pos = atPos
|
||||
// 1) Try fast slot/local
|
||||
if (!PerfFlags.LOCAL_SLOT_PIC) {
|
||||
scope.getSlotIndexOf(name)?.let {
|
||||
scope.getSlotIndexOf(name)?.let {
|
||||
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.localVarPicHit++
|
||||
return scope.getSlotRecord(it)
|
||||
return scope.getSlotRecord(it)
|
||||
}
|
||||
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.localVarPicMiss++
|
||||
return scope[name] ?: scope.raiseError("symbol not defined: '$name'")
|
||||
// 2) Fallback to current-scope object or field on `this`
|
||||
scope[name]?.let { return it }
|
||||
val th = scope.thisObj
|
||||
return when (th) {
|
||||
is Obj -> th.readField(scope, name)
|
||||
else -> scope.raiseError("symbol not defined: '$name'")
|
||||
}
|
||||
}
|
||||
val hit = (cachedFrameId == scope.frameId && cachedSlot >= 0 && cachedSlot < scope.slotCount())
|
||||
val slot = if (hit) cachedSlot else resolveSlot(scope)
|
||||
@@ -1185,19 +1235,37 @@ class LocalVarRef(private val name: String, private val atPos: Pos) : ObjRef {
|
||||
return scope.getSlotRecord(slot)
|
||||
}
|
||||
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) net.sergeych.lyng.PerfStats.localVarPicMiss++
|
||||
return scope[name] ?: scope.raiseError("symbol not defined: '$name'")
|
||||
// 2) Fallback name in scope or field on `this`
|
||||
scope[name]?.let { return it }
|
||||
val th = scope.thisObj
|
||||
return when (th) {
|
||||
is Obj -> th.readField(scope, name)
|
||||
else -> scope.raiseError("symbol not defined: '$name'")
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun evalValue(scope: Scope): Obj {
|
||||
scope.pos = atPos
|
||||
if (!PerfFlags.LOCAL_SLOT_PIC) {
|
||||
scope.getSlotIndexOf(name)?.let { return scope.getSlotRecord(it).value }
|
||||
return (scope[name] ?: scope.raiseError("symbol not defined: '$name'")).value
|
||||
// fallback to current-scope object or field on `this`
|
||||
scope[name]?.let { return it.value }
|
||||
val th = scope.thisObj
|
||||
return when (th) {
|
||||
is Obj -> th.readField(scope, name).value
|
||||
else -> scope.raiseError("symbol not defined: '$name'")
|
||||
}
|
||||
}
|
||||
val hit = (cachedFrameId == scope.frameId && cachedSlot >= 0 && cachedSlot < scope.slotCount())
|
||||
val slot = if (hit) cachedSlot else resolveSlot(scope)
|
||||
if (slot >= 0) return scope.getSlotRecord(slot).value
|
||||
return (scope[name] ?: scope.raiseError("symbol not defined: '$name'")).value
|
||||
// Fallback name in scope or field on `this`
|
||||
scope[name]?.let { return it.value }
|
||||
val th = scope.thisObj
|
||||
return when (th) {
|
||||
is Obj -> th.readField(scope, name).value
|
||||
else -> scope.raiseError("symbol not defined: '$name'")
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
||||
@@ -1209,10 +1277,18 @@ class LocalVarRef(private val name: String, private val atPos: Pos) : ObjRef {
|
||||
rec.value = newValue
|
||||
return
|
||||
}
|
||||
val stored = scope[name] ?: scope.raiseError("symbol not defined: '$name'")
|
||||
if (stored.isMutable) stored.value = newValue
|
||||
else scope.raiseError("Cannot assign to immutable value")
|
||||
return
|
||||
scope[name]?.let { stored ->
|
||||
if (stored.isMutable) stored.value = newValue
|
||||
else scope.raiseError("Cannot assign to immutable value")
|
||||
return
|
||||
}
|
||||
// Fallback: write to field on `this`
|
||||
val th = scope.thisObj
|
||||
if (th is Obj) {
|
||||
th.writeField(scope, name, newValue)
|
||||
return
|
||||
}
|
||||
scope.raiseError("symbol not defined: '$name'")
|
||||
}
|
||||
val slot = if (cachedFrameId == scope.frameId && cachedSlot >= 0 && cachedSlot < scope.slotCount()) cachedSlot else resolveSlot(scope)
|
||||
if (slot >= 0) {
|
||||
@@ -1221,9 +1297,17 @@ class LocalVarRef(private val name: String, private val atPos: Pos) : ObjRef {
|
||||
rec.value = newValue
|
||||
return
|
||||
}
|
||||
val stored = scope[name] ?: scope.raiseError("symbol not defined: '$name'")
|
||||
if (stored.isMutable) stored.value = newValue
|
||||
else scope.raiseError("Cannot assign to immutable value")
|
||||
scope[name]?.let { stored ->
|
||||
if (stored.isMutable) stored.value = newValue
|
||||
else scope.raiseError("Cannot assign to immutable value")
|
||||
return
|
||||
}
|
||||
val th = scope.thisObj
|
||||
if (th is Obj) {
|
||||
th.writeField(scope, name, newValue)
|
||||
return
|
||||
}
|
||||
scope.raiseError("symbol not defined: '$name'")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1298,11 +1382,32 @@ class FastLocalVarRef(
|
||||
val ownerValid = isOwnerValidFor(scope)
|
||||
val slot = if (ownerValid && cachedSlot >= 0) cachedSlot else resolveSlotInAncestry(scope)
|
||||
val actualOwner = cachedOwnerScope
|
||||
if (slot < 0 || actualOwner == null) scope.raiseError("local '$name' is not available in this scope")
|
||||
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) {
|
||||
if (ownerValid) net.sergeych.lyng.PerfStats.fastLocalHit++ else net.sergeych.lyng.PerfStats.fastLocalMiss++
|
||||
if (slot >= 0 && actualOwner != null) {
|
||||
if (net.sergeych.lyng.PerfFlags.PIC_DEBUG_COUNTERS) {
|
||||
if (ownerValid) net.sergeych.lyng.PerfStats.fastLocalHit++ else net.sergeych.lyng.PerfStats.fastLocalMiss++
|
||||
}
|
||||
return actualOwner.getSlotRecord(slot)
|
||||
}
|
||||
return actualOwner.getSlotRecord(slot)
|
||||
// Try per-frame local binding maps in the ancestry first (locals declared in frames)
|
||||
run {
|
||||
var s: Scope? = scope
|
||||
while (s != null) {
|
||||
s.localBindings[name]?.let { return it }
|
||||
s = s.parent
|
||||
}
|
||||
}
|
||||
// Try to find a direct local binding in the current ancestry (without invoking name resolution that may prefer fields)
|
||||
var s: Scope? = scope
|
||||
while (s != null) {
|
||||
s.objects[name]?.let { return it }
|
||||
s = s.parent
|
||||
}
|
||||
// Fallback to standard name lookup (locals or closure chain) if the slot owner changed across suspension
|
||||
scope[name]?.let { return it }
|
||||
// As a last resort, treat as field on `this`
|
||||
val th = scope.thisObj
|
||||
if (th is Obj) return th.readField(scope, name)
|
||||
scope.raiseError("local '$name' is not available in this scope")
|
||||
}
|
||||
|
||||
override suspend fun evalValue(scope: Scope): Obj {
|
||||
@@ -1310,8 +1415,26 @@ class FastLocalVarRef(
|
||||
val ownerValid = isOwnerValidFor(scope)
|
||||
val slot = if (ownerValid && cachedSlot >= 0) cachedSlot else resolveSlotInAncestry(scope)
|
||||
val actualOwner = cachedOwnerScope
|
||||
if (slot < 0 || actualOwner == null) scope.raiseError("local '$name' is not available in this scope")
|
||||
return actualOwner.getSlotRecord(slot).value
|
||||
if (slot >= 0 && actualOwner != null) return actualOwner.getSlotRecord(slot).value
|
||||
// Try per-frame local binding maps in the ancestry first
|
||||
run {
|
||||
var s: Scope? = scope
|
||||
while (s != null) {
|
||||
s.localBindings[name]?.let { return it.value }
|
||||
s = s.parent
|
||||
}
|
||||
}
|
||||
// Try to find a direct local binding in the current ancestry first
|
||||
var s: Scope? = scope
|
||||
while (s != null) {
|
||||
s.objects[name]?.let { return it.value }
|
||||
s = s.parent
|
||||
}
|
||||
// Fallback to standard name lookup (locals or closure chain)
|
||||
scope[name]?.let { return it.value }
|
||||
val th = scope.thisObj
|
||||
if (th is Obj) return th.readField(scope, name).value
|
||||
scope.raiseError("local '$name' is not available in this scope")
|
||||
}
|
||||
|
||||
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
||||
@@ -1319,10 +1442,37 @@ class FastLocalVarRef(
|
||||
val owner = if (isOwnerValidFor(scope)) cachedOwnerScope else null
|
||||
val slot = if (owner != null && cachedSlot >= 0) cachedSlot else resolveSlotInAncestry(scope)
|
||||
val actualOwner = cachedOwnerScope
|
||||
if (slot < 0 || actualOwner == null) scope.raiseError("local '$name' is not available in this scope")
|
||||
val rec = actualOwner.getSlotRecord(slot)
|
||||
if (!rec.isMutable) scope.raiseError("Cannot assign to immutable value")
|
||||
rec.value = newValue
|
||||
if (slot >= 0 && actualOwner != null) {
|
||||
val rec = actualOwner.getSlotRecord(slot)
|
||||
if (!rec.isMutable) scope.raiseError("Cannot assign to immutable value")
|
||||
rec.value = newValue
|
||||
return
|
||||
}
|
||||
// Try per-frame local binding maps in the ancestry first
|
||||
run {
|
||||
var s: Scope? = scope
|
||||
while (s != null) {
|
||||
val rec = s.localBindings[name]
|
||||
if (rec != null) {
|
||||
if (!rec.isMutable) scope.raiseError("Cannot assign to immutable value")
|
||||
rec.value = newValue
|
||||
return
|
||||
}
|
||||
s = s.parent
|
||||
}
|
||||
}
|
||||
// Fallback to standard name lookup
|
||||
scope[name]?.let { stored ->
|
||||
if (stored.isMutable) stored.value = newValue
|
||||
else scope.raiseError("Cannot assign to immutable value")
|
||||
return
|
||||
}
|
||||
val th = scope.thisObj
|
||||
if (th is Obj) {
|
||||
th.writeField(scope, name, newValue)
|
||||
return
|
||||
}
|
||||
scope.raiseError("local '$name' is not available in this scope")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1385,3 +1535,5 @@ class AssignRef(
|
||||
return v.asReadonly
|
||||
}
|
||||
}
|
||||
|
||||
// (duplicate LocalVarRef removed; the canonical implementation is defined earlier in this file)
|
||||
Reference in new issue
Block a user