diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index 5eaac5a..131279c 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -223,6 +223,11 @@ class Compiler( } private fun resolveIdentifierRef(name: String, pos: Pos): ObjRef { + if (name == "__PACKAGE__") { + resolutionSink?.reference(name, pos) + val value = ObjString(packageName ?: "unknown").asReadonly + return ConstRef(value) + } if (name == "this") { resolutionSink?.reference(name, pos) return LocalVarRef(name, pos) @@ -3176,10 +3181,13 @@ class Compiler( val label = getLabel()?.also { cc.labels += it } val loopSlotPlan = SlotPlan(mutableMapOf(), 0, nextScopeId++) slotPlanStack.add(loopSlotPlan) + var conditionSlotPlan: SlotPlan = loopSlotPlan val (canBreak, parsedBody) = try { cc.parseLoop { if (cc.current().type == Token.Type.LBRACE) { - parseLoopBlock() + val (blockStmt, blockPlan) = parseLoopBlockWithPlan() + conditionSlotPlan = blockPlan + blockStmt } else { parseStatement() ?: throw ScriptError(cc.currentPos(), "Bad do-while statement: expected body statement") } @@ -3196,7 +3204,7 @@ class Compiler( throw ScriptError(tWhile.pos, "Expected 'while' after do body") ensureLparen() - slotPlanStack.add(loopSlotPlan) + slotPlanStack.add(conditionSlotPlan) val condition = try { parseExpression() ?: throw ScriptError(cc.currentPos(), "Expected condition after 'while'") } finally { @@ -3212,7 +3220,7 @@ class Compiler( cc.previous() null } - val loopPlanSnapshot = slotPlanIndices(loopSlotPlan) + val loopPlanSnapshot = slotPlanIndices(conditionSlotPlan) return DoWhileStatement(body, condition, elseStatement, label, loopPlanSnapshot, body.pos) } @@ -3819,6 +3827,35 @@ class Compiler( } } + private suspend fun parseLoopBlockWithPlan(): Pair { + val startPos = cc.currentPos() + val t = cc.next() + if (t.type != Token.Type.LBRACE) + throw ScriptError(t.pos, "Expected block body start: {") + resolutionSink?.enterScope(ScopeKind.BLOCK, startPos, null) + val blockSlotPlan = SlotPlan(mutableMapOf(), 0, nextScopeId++) + slotPlanStack.add(blockSlotPlan) + val capturePlan = CapturePlan(blockSlotPlan) + capturePlanStack.add(capturePlan) + val block = try { + parseScript() + } finally { + capturePlanStack.removeLast() + slotPlanStack.removeLast() + } + val planSnapshot = slotPlanIndices(blockSlotPlan) + val stmt = BlockStatement(block, planSnapshot, capturePlan.captures.toList(), startPos) + val wrapped = wrapBytecode(stmt) + val t1 = cc.next() + if (t1.type != Token.Type.RBRACE) + throw ScriptError(t1.pos, "unbalanced braces: expected block body end: }") + val range = MiniRange(startPos, t1.pos) + lastParsedBlockRange = range + miniSink?.onBlock(MiniBlock(range)) + resolutionSink?.exitScope(t1.pos) + return wrapped to blockSlotPlan + } + private suspend fun parseVarDeclaration( isMutable: Boolean, visibility: Visibility, diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt index ba2a7a6..ae337e6 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt @@ -347,6 +347,13 @@ open class Scope( open operator fun get(name: String): ObjRecord? { if (name == "this") return thisObj.asReadonly + if (name == "__PACKAGE__") { + var s: Scope? = this + while (s != null) { + if (s is ModuleScope) return s.packageNameObj + s = s.parent + } + } // 1. Prefer direct locals/bindings declared in this frame tryGetLocalRecord(this, name, currentClassCtx)?.let { return it } 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 ac8db97..468c901 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt @@ -175,6 +175,9 @@ class BytecodeCompiler( return when (ref) { is ConstRef -> compileConst(ref.constValue) is LocalSlotRef -> { + if (ref.name == "__PACKAGE__") { + return compileNameLookup(ref.name) + } if (!allowLocalSlots) return null if (ref.isDelegated) return null if (ref.name.isEmpty()) return null @@ -201,6 +204,9 @@ class BytecodeCompiler( CompiledValue(mapped, resolved) } is LocalVarRef -> { + if (ref.name == "__PACKAGE__") { + return compileNameLookup(ref.name) + } if (allowLocalSlots) { if (!forceScopeSlots) { scopeSlotIndexByName[ref.name]?.let { slot -> @@ -2775,18 +2781,34 @@ class BytecodeCompiler( val loopLabel = builder.label() val continueLabel = builder.label() val endLabel = builder.label() + val useLoopScope = stmt.loopSlotPlan.isNotEmpty() + val breakLabel = if (useLoopScope) builder.label() else endLabel + val planId = if (useLoopScope) { + builder.addConst(BytecodeConst.SlotPlan(stmt.loopSlotPlan, emptyList())) + } else { + -1 + } builder.mark(loopLabel) + if (useLoopScope) { + builder.emit(Opcode.PUSH_SCOPE, planId) + resetAddrCache() + } loopStack.addLast( LoopContext( stmt.label, - endLabel, + breakLabel, continueLabel, breakFlagSlot, if (wantResult) resultSlot else null, hasIterator = false ) ) - val bodyValue = compileStatementValueOrFallback(stmt.body, wantResult) ?: return null + val bodyTarget = if (stmt.body is BytecodeStatement) stmt.body.original else stmt.body + val bodyValue = if (useLoopScope && bodyTarget is BlockStatement) { + emitInlineBlock(bodyTarget, wantResult) + } else { + compileStatementValueOrFallback(stmt.body, wantResult) + } ?: return null loopStack.removeLast() if (wantResult) { val bodyObj = ensureObjSlot(bodyValue) @@ -2795,10 +2817,20 @@ class BytecodeCompiler( builder.mark(continueLabel) val condition = compileCondition(stmt.condition, stmt.pos) ?: return null if (condition.type != SlotType.BOOL) return null + if (useLoopScope) { + builder.emit(Opcode.POP_SCOPE) + resetAddrCache() + } builder.emit( Opcode.JMP_IF_TRUE, listOf(CmdBuilder.Operand.IntVal(condition.slot), CmdBuilder.Operand.LabelRef(loopLabel)) ) + if (useLoopScope) { + builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel))) + builder.mark(breakLabel) + builder.emit(Opcode.POP_SCOPE) + resetAddrCache() + } builder.mark(endLabel) if (stmt.elseStatement != null) { diff --git a/lynglib/src/commonTest/kotlin/ScriptTest.kt b/lynglib/src/commonTest/kotlin/ScriptTest.kt index d1da602..a2a0d1f 100644 --- a/lynglib/src/commonTest/kotlin/ScriptTest.kt +++ b/lynglib/src/commonTest/kotlin/ScriptTest.kt @@ -2008,7 +2008,6 @@ class ScriptTest { } - @Ignore("incremental enable") @Test fun testMethodCallLastBlockWithEllipsis() = runTest { eval( @@ -2027,7 +2026,6 @@ class ScriptTest { } - @Ignore("incremental enable") @Test fun nationalCharsTest() = runTest { eval( @@ -2051,7 +2049,6 @@ class ScriptTest { ) } - @Ignore("incremental enable") @Test fun doWhileSimpleTest() = runTest { eval( @@ -2066,7 +2063,6 @@ class ScriptTest { ) } - @Ignore("incremental enable") @Test fun testFailDoWhileSample1() = runTest { eval( @@ -2081,7 +2077,6 @@ class ScriptTest { ) } - @Ignore("incremental enable") @Test fun testForContinue() = runTest { eval(