Fix exception class lookup and add lazy delegate
This commit is contained in:
parent
ffb22d0875
commit
d363501081
@ -458,11 +458,15 @@ class Compiler(
|
||||
|
||||
private fun seedResolutionFromScope(scope: Scope, pos: Pos) {
|
||||
val sink = resolutionSink ?: return
|
||||
for ((name, record) in scope.objects) {
|
||||
var current: Scope? = scope
|
||||
while (current != null) {
|
||||
for ((name, record) in current.objects) {
|
||||
if (!record.visibility.isPublic) continue
|
||||
if (!resolutionPredeclared.add(name)) continue
|
||||
sink.declareSymbol(name, SymbolKind.LOCAL, record.isMutable, pos)
|
||||
}
|
||||
current = current.parent
|
||||
}
|
||||
}
|
||||
|
||||
private var anonCounter = 0
|
||||
@ -2635,6 +2639,13 @@ class Compiler(
|
||||
if (stmt.captureSlots.isEmpty()) return stmt
|
||||
return BlockStatement(stmt.block, stmt.slotPlan, emptyList(), stmt.pos)
|
||||
}
|
||||
fun resolveExceptionClass(scope: Scope, name: String): ObjClass {
|
||||
val rec = scope[name]
|
||||
val cls = rec?.value as? ObjClass
|
||||
if (cls != null) return cls
|
||||
if (name == "Exception") return ObjException.Root
|
||||
scope.raiseSymbolNotFound("error class does not exist or is not a class: $name")
|
||||
}
|
||||
|
||||
val body = unwrapBytecodeDeep(parseBlock())
|
||||
val catches = mutableListOf<CatchBlockData>()
|
||||
@ -2742,8 +2753,7 @@ class Compiler(
|
||||
for (cdata in catches) {
|
||||
var match: Obj? = null
|
||||
for (exceptionClassName in cdata.classNames) {
|
||||
val exObj = scope[exceptionClassName]?.value as? ObjClass
|
||||
?: scope.raiseSymbolNotFound("error class does not exist or is not a class: $exceptionClassName")
|
||||
val exObj = resolveExceptionClass(scope, exceptionClassName)
|
||||
if (caughtObj.isInstanceOf(exObj)) {
|
||||
match = caughtObj
|
||||
break
|
||||
@ -3131,9 +3141,12 @@ class Compiler(
|
||||
// accessors, constructor registration, etc.
|
||||
// Resolve parent classes by name at execution time
|
||||
val parentClasses = baseSpecs.map { baseSpec ->
|
||||
val rec =
|
||||
scope[baseSpec.name] ?: throw ScriptError(nameToken.pos, "unknown base class: ${baseSpec.name}")
|
||||
(rec.value as? ObjClass) ?: throw ScriptError(nameToken.pos, "${baseSpec.name} is not a class")
|
||||
val rec = scope[baseSpec.name]
|
||||
val cls = rec?.value as? ObjClass
|
||||
if (cls != null) return@map cls
|
||||
if (baseSpec.name == "Exception") return@map ObjException.Root
|
||||
if (rec == null) throw ScriptError(nameToken.pos, "unknown base class: ${baseSpec.name}")
|
||||
throw ScriptError(nameToken.pos, "${baseSpec.name} is not a class")
|
||||
}
|
||||
|
||||
val newClass = ObjInstanceClass(className, *parentClasses.toTypedArray()).also {
|
||||
|
||||
@ -126,6 +126,14 @@ open class Scope(
|
||||
}
|
||||
s.getSlotIndexOf(name)?.let { idx ->
|
||||
val rec = s.getSlotRecord(idx)
|
||||
val hasDirectBinding =
|
||||
s.objects.containsKey(name) ||
|
||||
s.localBindings.containsKey(name) ||
|
||||
(caller?.let { ctx ->
|
||||
s.objects.containsKey(ctx.mangledName(name)) ||
|
||||
s.localBindings.containsKey(ctx.mangledName(name))
|
||||
} ?: false)
|
||||
if (!hasDirectBinding && rec.value === ObjUnset) return null
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, caller, name)) return rec
|
||||
}
|
||||
return null
|
||||
|
||||
@ -55,8 +55,34 @@ class Script(
|
||||
scope.updateSlotFor(name, scope.objects[name]!!)
|
||||
continue
|
||||
}
|
||||
parent.get(name)?.let { scope.updateSlotFor(name, it) }
|
||||
val seed = findSeedRecord(parent, name)
|
||||
if (seed != null) {
|
||||
if (name == "Exception" && seed.value !is ObjClass) {
|
||||
scope.updateSlotFor(name, ObjRecord(ObjException.Root, isMutable = false))
|
||||
} else {
|
||||
scope.updateSlotFor(name, seed)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if (name == "Exception") {
|
||||
scope.updateSlotFor(name, ObjRecord(ObjException.Root, isMutable = false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun findSeedRecord(scope: Scope?, name: String): ObjRecord? {
|
||||
var s = scope
|
||||
var hops = 0
|
||||
while (s != null && hops++ < 1024) {
|
||||
s.objects[name]?.let { return it }
|
||||
s.localBindings[name]?.let { return it }
|
||||
s.getSlotIndexOf(name)?.let { idx ->
|
||||
val rec = s.getSlotRecord(idx)
|
||||
if (rec.value !== ObjUnset) return rec
|
||||
}
|
||||
s = s.parent
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
internal fun debugStatements(): List<Statement> = statements
|
||||
@ -363,6 +389,10 @@ class Script(
|
||||
}
|
||||
thunk
|
||||
}
|
||||
addFn("lazy") {
|
||||
val builder = requireOnlyArg<Statement>()
|
||||
ObjLazyDelegate(builder, this)
|
||||
}
|
||||
|
||||
addVoidFn("delay") {
|
||||
val a = args.firstAndOnly()
|
||||
|
||||
@ -36,6 +36,7 @@ class BytecodeStatement private constructor(
|
||||
override val pos: Pos = original.pos
|
||||
|
||||
override suspend fun execute(scope: Scope): Obj {
|
||||
scope.pos = pos
|
||||
return CmdVm().execute(function, scope, scope.args.list)
|
||||
}
|
||||
|
||||
|
||||
@ -344,7 +344,11 @@ fun Obj.isLyngException(): Boolean = isInstanceOf("Exception")
|
||||
*/
|
||||
suspend fun Obj.getLyngExceptionMessage(scope: Scope? = null): String {
|
||||
require(this.isLyngException())
|
||||
val s = scope ?: Script.newScope()
|
||||
val s = scope ?: when (this) {
|
||||
is ObjException -> this.scope
|
||||
is ObjInstance -> this.instanceScope
|
||||
else -> Script.newScope()
|
||||
}
|
||||
return invokeInstanceMethod(s, "message").toString(s).value
|
||||
}
|
||||
|
||||
@ -361,16 +365,25 @@ suspend fun Obj.getLyngExceptionMessage(scope: Scope? = null): String {
|
||||
*/
|
||||
suspend fun Obj.getLyngExceptionMessageWithStackTrace(scope: Scope? = null,showDetails:Boolean=true): String {
|
||||
require(this.isLyngException())
|
||||
val s = scope ?: Script.newScope()
|
||||
val s = scope ?: when (this) {
|
||||
is ObjException -> this.scope
|
||||
is ObjInstance -> this.instanceScope
|
||||
else -> Script.newScope()
|
||||
}
|
||||
val msg = getLyngExceptionMessage(s)
|
||||
val trace = getLyngExceptionStackTrace(s)
|
||||
var at = "unknown"
|
||||
// var firstLine = true
|
||||
val stack = if (!trace.list.isEmpty()) {
|
||||
val first = trace.list[0]
|
||||
at = (first.readField(s, "at").value as ObjString).value
|
||||
"\n" + trace.list.map { " at " + it.toString(s).value }.joinToString("\n")
|
||||
} else ""
|
||||
} else {
|
||||
val pos = s.pos
|
||||
if (pos.source.fileName.isNotEmpty() && pos.currentLine.isNotEmpty()) {
|
||||
at = "${pos.source.fileName}:${pos.line + 1}:${pos.column + 1}"
|
||||
}
|
||||
""
|
||||
}
|
||||
return "$at: $msg$stack"
|
||||
}
|
||||
|
||||
@ -396,9 +409,16 @@ suspend fun Obj.getLyngExceptionString(scope: Scope): String =
|
||||
* Rethrow this object as a Kotlin [ExecutionError] if it's an exception.
|
||||
*/
|
||||
suspend fun Obj.raiseAsExecutionError(scope: Scope? = null): Nothing {
|
||||
if (this is ObjException) raise()
|
||||
val sc = scope ?: Script.newScope()
|
||||
val msg = getLyngExceptionMessage(sc)
|
||||
val pos = (this as? ObjInstance)?.instanceScope?.pos ?: Pos.builtIn
|
||||
val sc = scope ?: when (this) {
|
||||
is ObjException -> this.scope
|
||||
is ObjInstance -> this.instanceScope
|
||||
else -> Script.newScope()
|
||||
}
|
||||
val msg = getLyngExceptionMessageWithStackTrace(sc)
|
||||
val pos = when (this) {
|
||||
is ObjException -> this.scope.pos
|
||||
is ObjInstance -> this.instanceScope.pos
|
||||
else -> Pos.builtIn
|
||||
}
|
||||
throw ExecutionError(this, pos, msg)
|
||||
}
|
||||
|
||||
@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov
|
||||
*
|
||||
* 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.obj
|
||||
|
||||
import net.sergeych.lyng.Arguments
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.Statement
|
||||
|
||||
/**
|
||||
* Lazy delegate used by `val x by lazy { ... }`.
|
||||
*/
|
||||
class ObjLazyDelegate(
|
||||
private val builder: Statement,
|
||||
private val capturedScope: Scope,
|
||||
) : Obj() {
|
||||
override val objClass: ObjClass = type
|
||||
|
||||
private var calculated = false
|
||||
private var cachedValue: Obj = ObjVoid
|
||||
|
||||
override suspend fun invokeInstanceMethod(
|
||||
scope: Scope,
|
||||
name: String,
|
||||
args: Arguments,
|
||||
onNotFoundResult: (suspend () -> Obj?)?,
|
||||
): Obj {
|
||||
return when (name) {
|
||||
"getValue" -> {
|
||||
if (!calculated) {
|
||||
cachedValue = builder.execute(capturedScope)
|
||||
calculated = true
|
||||
}
|
||||
cachedValue
|
||||
}
|
||||
"setValue" -> scope.raiseIllegalAssignment("lazy delegate is read-only")
|
||||
else -> super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("LazyDelegate")
|
||||
}
|
||||
}
|
||||
@ -3696,7 +3696,6 @@ class ScriptTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Ignore("incremental enable: stackTrace member not resolved in bytecode path")
|
||||
@Test
|
||||
fun testExceptionSerialization() = runTest {
|
||||
eval(
|
||||
@ -3854,7 +3853,6 @@ class ScriptTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Ignore("incremental enable: extension resolution for toList in chained call")
|
||||
@Test
|
||||
fun binarySearchTest2() = runTest {
|
||||
eval(
|
||||
@ -4829,7 +4827,6 @@ class ScriptTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Ignore("incremental enable: raiseAsExecutionError missing source name in trace")
|
||||
@Test
|
||||
fun testRaiseAsError() = runTest {
|
||||
var x = evalNamed(
|
||||
@ -4891,7 +4888,6 @@ class ScriptTest {
|
||||
}
|
||||
|
||||
|
||||
@Ignore("incremental enable: exception helper missing source info")
|
||||
@Test
|
||||
fun testLyngToKotlinExceptionHelpers() = runTest {
|
||||
var x = evalNamed(
|
||||
@ -4949,7 +4945,6 @@ class ScriptTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Ignore("incremental enable: lazy delegate not resolved in new compiler")
|
||||
@Test
|
||||
fun testLazyLocals() = runTest() {
|
||||
eval(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user