diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/BlockStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/BlockStatement.kt index 6b882b4..bac0a79 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/BlockStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/BlockStatement.kt @@ -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) } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClosureScope.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClosureScope.kt index 233ef79..d1ef187 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClosureScope.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ClosureScope.kt @@ -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) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index 6661d76..b352aa9 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -179,6 +179,63 @@ class Compiler( } } + private fun predeclareClassMembers(target: MutableSet) { + 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): SlotPlan { val map = mutableMapOf() 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() @@ -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() } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt index ae337e6..90ce1cf 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt @@ -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 + } } /** diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt index d22e77d..f291908 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt @@ -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) diff --git a/lynglib/src/commonTest/kotlin/ScriptTest.kt b/lynglib/src/commonTest/kotlin/ScriptTest.kt index 9dd6f37..7c58973 100644 --- a/lynglib/src/commonTest/kotlin/ScriptTest.kt +++ b/lynglib/src/commonTest/kotlin/ScriptTest.kt @@ -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(