Fix apply captures, class forward refs, and when bytecode

This commit is contained in:
Sergey Chernov 2026-01-30 17:41:04 +03:00
parent 4b66454bf3
commit 615dc026f7
6 changed files with 129 additions and 19 deletions

View File

@ -30,9 +30,15 @@ class BlockStatement(
val target = if (scope.skipScopeCreation) scope else scope.createChildScope(startPos)
if (slotPlan.isNotEmpty()) target.applySlotPlan(slotPlan)
if (captureSlots.isNotEmpty()) {
val applyScope = scope as? ApplyScope
for (capture in captureSlots) {
val rec = scope.resolveCaptureRecord(capture.name)
?: scope.raiseSymbolNotFound("symbol ${capture.name} not found")
val rec = if (applyScope != null) {
applyScope.resolveCaptureRecord(capture.name)
?: applyScope.callScope.resolveCaptureRecord(capture.name)
} else {
scope.resolveCaptureRecord(capture.name)
} ?: (applyScope?.callScope ?: scope)
.raiseSymbolNotFound("symbol ${capture.name} not found")
target.updateSlotFor(capture.name, rec)
}
}

View File

@ -71,7 +71,8 @@ class ClosureScope(val callScope: Scope, val closureScope: Scope) :
}
}
class ApplyScope(callScope: Scope, val applied: Scope) : Scope(callScope.parent?.parent ?: callScope.parent ?: callScope, thisObj = applied.thisObj) {
class ApplyScope(val callScope: Scope, val applied: Scope) :
Scope(callScope.parent?.parent ?: callScope.parent ?: callScope, thisObj = applied.thisObj) {
override fun get(name: String): ObjRecord? {
return applied.get(name) ?: super.get(name)

View File

@ -179,6 +179,63 @@ class Compiler(
}
}
private fun predeclareClassMembers(target: MutableSet<String>) {
val saved = cc.savePos()
var depth = 0
val modifiers = setOf(
"public", "private", "protected", "internal",
"override", "abstract", "extern", "static", "transient"
)
fun nextNonWs(): Token {
var t = cc.next()
while (t.type == Token.Type.NEWLINE || t.type == Token.Type.SINGLE_LINE_COMMENT || t.type == Token.Type.MULTILINE_COMMENT) {
t = cc.next()
}
return t
}
try {
while (cc.hasNext()) {
var t = cc.next()
when (t.type) {
Token.Type.LBRACE -> depth++
Token.Type.RBRACE -> if (depth == 0) break else depth--
Token.Type.ID -> if (depth == 0) {
while (t.type == Token.Type.ID && t.value in modifiers) {
t = nextNonWs()
}
when (t.value) {
"fun", "fn", "val", "var" -> {
val nameToken = nextNonWs()
if (nameToken.type == Token.Type.ID) {
val afterName = cc.peekNextNonWhitespace()
if (afterName.type != Token.Type.DOT) {
target.add(nameToken.value)
}
}
}
"class", "object" -> {
val nameToken = nextNonWs()
if (nameToken.type == Token.Type.ID) {
target.add(nameToken.value)
}
}
"enum" -> {
val next = nextNonWs()
val nameToken = if (next.type == Token.Type.ID && next.value == "class") nextNonWs() else next
if (nameToken.type == Token.Type.ID) {
target.add(nameToken.value)
}
}
}
}
else -> {}
}
}
} finally {
cc.restorePos(saved)
}
}
private fun buildParamSlotPlan(names: List<String>): SlotPlan {
val map = mutableMapOf<String, Int>()
var idx = 0
@ -228,6 +285,10 @@ class Compiler(
val value = ObjString(packageName ?: "unknown").asReadonly
return ConstRef(value)
}
if (name == "$~") {
resolutionSink?.reference(name, pos)
return LocalVarRef(name, pos)
}
if (name == "this") {
resolutionSink?.reference(name, pos)
return LocalVarRef(name, pos)
@ -1415,8 +1476,20 @@ class Compiler(
// and the source closure of the lambda which might have other thisObj.
val context = scope.applyClosure(closureScope)
if (paramSlotPlanSnapshot.isNotEmpty()) context.applySlotPlan(paramSlotPlanSnapshot)
if (captureSlots.isNotEmpty() && context !is ApplyScope) {
if (captureSlots.isNotEmpty()) {
val moduleScope = if (context is ApplyScope) {
var s: Scope? = closureScope
while (s != null && s !is ModuleScope) {
s = s.parent
}
s as? ModuleScope
} else {
null
}
for (capture in captureSlots) {
if (moduleScope != null && moduleScope.getLocalRecordDirect(capture.name) != null) {
continue
}
val rec = closureScope.resolveCaptureRecord(capture.name)
?: closureScope.raiseSymbolNotFound("symbol ${capture.name} not found")
context.updateSlotFor(capture.name, rec)
@ -2530,6 +2603,11 @@ class Compiler(
}
return BlockStatement(stmt.block, newPlan, stmt.captureSlots, stmt.pos)
}
fun stripCatchCaptures(block: Statement): Statement {
val stmt = block as? BlockStatement ?: return block
if (stmt.captureSlots.isEmpty()) return stmt
return BlockStatement(stmt.block, stmt.slotPlan, emptyList(), stmt.pos)
}
val body = unwrapBytecodeDeep(parseBlock())
val catches = mutableListOf<CatchBlockData>()
@ -2572,7 +2650,12 @@ class Compiler(
val block = try {
resolutionSink?.enterScope(ScopeKind.BLOCK, catchVar.pos, null)
resolutionSink?.declareSymbol(catchVar.value, SymbolKind.LOCAL, isMutable = false, pos = catchVar.pos)
withCatchSlot(unwrapBytecodeDeep(parseBlockWithPredeclared(listOf(catchVar.value to false))), catchVar.value)
stripCatchCaptures(
withCatchSlot(
unwrapBytecodeDeep(parseBlockWithPredeclared(listOf(catchVar.value to false))),
catchVar.value
)
)
} finally {
resolutionSink?.exitScope(cc.currentPos())
}
@ -2586,9 +2669,11 @@ class Compiler(
val block = try {
resolutionSink?.enterScope(ScopeKind.BLOCK, itToken.pos, null)
resolutionSink?.declareSymbol(itToken.value, SymbolKind.LOCAL, isMutable = false, pos = itToken.pos)
withCatchSlot(
unwrapBytecodeDeep(parseBlockWithPredeclared(listOf(itToken.value to false), skipLeadingBrace = true)),
itToken.value
stripCatchCaptures(
withCatchSlot(
unwrapBytecodeDeep(parseBlockWithPredeclared(listOf(itToken.value to false), skipLeadingBrace = true)),
itToken.value
)
)
} finally {
resolutionSink?.exitScope(cc.currentPos())
@ -2860,6 +2945,7 @@ class Compiler(
pendingDeclStart = null
resolutionSink?.declareSymbol(nameToken.value, SymbolKind.CLASS, isMutable = false, pos = nameToken.pos)
return inCodeContext(CodeContext.ClassBody(nameToken.value, isExtern = isExtern)) {
val classCtx = codeContexts.lastOrNull() as? CodeContext.ClassBody
val constructorArgsDeclaration =
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true))
parseArgsDeclaration(isClassDeclaration = true)
@ -2892,6 +2978,11 @@ class Compiler(
cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
pushInitScope()
constructorArgsDeclaration?.params?.forEach { param ->
if (param.accessType != null) {
classCtx?.declaredMembers?.add(param.name)
}
}
// Robust body detection: peek next non-whitespace token; if it's '{', consume and parse the body
var classBodyRange: MiniRange? = null
@ -2945,6 +3036,7 @@ class Compiler(
resolutionSink?.declareSymbol(param.name, kind, mutable, param.pos)
}
val st = try {
classCtx?.let { predeclareClassMembers(it.declaredMembers) }
withLocalNames(constructorArgsDeclaration?.params?.map { it.name }?.toSet() ?: emptySet()) {
parseScript()
}

View File

@ -407,6 +407,12 @@ open class Scope(
fun updateSlotFor(name: String, record: ObjRecord) {
nameToSlot[name]?.let { slots[it] = record }
if (objects[name] == null) {
objects[name] = record
}
if (localBindings[name] == null) {
localBindings[name] = record
}
}
/**

View File

@ -80,6 +80,22 @@ class BytecodeCompiler(
is net.sergeych.lyng.ForInStatement -> compileForIn(name, stmt)
is net.sergeych.lyng.DoWhileStatement -> compileDoWhile(name, stmt)
is net.sergeych.lyng.WhileStatement -> compileWhile(name, stmt)
is net.sergeych.lyng.WhenStatement -> {
val value = compileWhen(stmt, true) ?: return null
builder.emit(Opcode.RET, value.slot)
val localCount = maxOf(nextSlot, value.slot + 1) - scopeSlotCount
builder.build(
name,
localCount,
addrCount = nextAddrSlot,
returnLabels = returnLabels,
scopeSlotIndices,
scopeSlotNames,
scopeSlotIsModule,
localSlotNames,
localSlotMutables
)
}
is BlockStatement -> compileBlock(name, stmt)
is VarDeclStatement -> compileVarDecl(name, stmt)
is net.sergeych.lyng.ThrowStatement -> compileThrowStatement(name, stmt)

View File

@ -2184,7 +2184,6 @@ class ScriptTest {
)
}
@Ignore("incremental enable")
@Test
fun testAccessEHData() = runTest {
eval(
@ -2207,7 +2206,6 @@ class ScriptTest {
)
}
@Ignore("incremental enable")
@Test
fun testTryFinally() = runTest {
val c = Scope()
@ -2231,7 +2229,6 @@ class ScriptTest {
)
}
@Ignore("incremental enable")
@Test
fun testThrowFromKotlin() = runTest {
val c = Script.newScope()
@ -2256,7 +2253,6 @@ class ScriptTest {
)
}
@Ignore("incremental enable")
@Test
fun testReturnValue1() = runTest {
val r = eval(
@ -2278,7 +2274,6 @@ class ScriptTest {
assertEquals("111", r.toString())
}
@Ignore("incremental enable")
@Test
fun doWhileValuesTest() = runTest {
eval(
@ -2323,7 +2318,6 @@ class ScriptTest {
)
}
@Ignore("incremental enable")
@Test
fun doWhileValuesLabelTest() = runTest {
withTimeout(5.seconds) {
@ -2357,7 +2351,6 @@ class ScriptTest {
}
}
@Ignore("incremental enable")
@Test
fun testSimpleWhen() = runTest {
eval(
@ -2382,7 +2375,6 @@ class ScriptTest {
)
}
@Ignore("incremental enable")
@Test
fun testWhenIs() = runTest {
eval(
@ -2413,7 +2405,6 @@ class ScriptTest {
)
}
@Ignore("incremental enable")
@Test
fun testWhenIn() = runTest {
eval(
@ -2453,7 +2444,6 @@ class ScriptTest {
)
}
@Ignore("incremental enable")
@Test
fun testParseSpecialVars() {
val l = parseLyng("$~".toSource("test$~"))
@ -2462,7 +2452,6 @@ class ScriptTest {
assertEquals("$~", l[0].value)
}
@Ignore("incremental enable")
@Test
fun testMatchOperator() = runTest {
eval(