diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index d564e5e..abce1ed 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -1981,6 +1981,39 @@ class Compiler( } } + private suspend fun parseDestructuringPattern(): List { + // it should be called after Token.Type.LBRACKET is consumed + val entries = mutableListOf() + while (true) { + val t = cc.next() + when (t.type) { + Token.Type.COMMA -> { + // allow trailing/extra commas + } + Token.Type.RBRACKET -> return entries + Token.Type.LBRACKET -> { + val nested = parseDestructuringPattern() + entries += ListEntry.Element(ListLiteralRef(nested)) + } + Token.Type.ELLIPSIS -> { + val id = cc.requireToken(Token.Type.ID, "Expected identifier after ...") + val ref = LocalVarRef(id.value, id.pos) + entries += ListEntry.Spread(ref) + } + Token.Type.ID -> { + val ref = LocalVarRef(t.value, t.pos) + if (cc.peekNextNonWhitespace().type == Token.Type.ELLIPSIS) { + cc.next() + entries += ListEntry.Spread(ref) + } else { + entries += ListEntry.Element(ref) + } + } + else -> throw ScriptError(t.pos, "invalid destructuring pattern: expected identifier") + } + } + } + private fun parseScopeOperator(operand: ObjRef?): ObjRef { // implement global scope maybe? if (operand == null) throw ScriptError(cc.next().pos, "Expecting expression before ::") @@ -4406,6 +4439,55 @@ class Compiler( return parseBlockWithPredeclared(emptyList(), skipLeadingBrace) } + private suspend fun parseExpressionWithPredeclared( + predeclared: List> + ): Statement { + val startPos = cc.currentPos() + resolutionSink?.enterScope(ScopeKind.BLOCK, startPos, null) + val exprSlotPlan = SlotPlan(mutableMapOf(), 0, nextScopeId++) + for ((name, isMutable) in predeclared) { + declareSlotNameIn(exprSlotPlan, name, isMutable, isDelegated = false) + resolutionSink?.declareSymbol(name, SymbolKind.LOCAL, isMutable, startPos, isOverride = false) + } + slotPlanStack.add(exprSlotPlan) + val capturePlan = CapturePlan(exprSlotPlan) + capturePlanStack.add(capturePlan) + val expr = try { + parseExpression() ?: throw ScriptError(cc.current().pos, "Expected expression") + } finally { + capturePlanStack.removeLast() + slotPlanStack.removeLast() + } + resolutionSink?.exitScope(cc.currentPos()) + return expr + } + + private suspend fun parseExpressionBlockWithPredeclared( + predeclared: List> + ): Statement { + val startPos = cc.currentPos() + resolutionSink?.enterScope(ScopeKind.BLOCK, startPos, null) + val blockSlotPlan = SlotPlan(mutableMapOf(), 0, nextScopeId++) + for ((name, isMutable) in predeclared) { + declareSlotNameIn(blockSlotPlan, name, isMutable, isDelegated = false) + resolutionSink?.declareSymbol(name, SymbolKind.LOCAL, isMutable, startPos, isOverride = false) + } + slotPlanStack.add(blockSlotPlan) + val capturePlan = CapturePlan(blockSlotPlan) + capturePlanStack.add(capturePlan) + val expr = try { + parseExpression() ?: throw ScriptError(cc.current().pos, "Expected expression") + } finally { + capturePlanStack.removeLast() + slotPlanStack.removeLast() + } + val planSnapshot = slotPlanIndices(blockSlotPlan) + val block = Script(startPos, listOf(expr)) + val stmt = BlockStatement(block, planSnapshot, capturePlan.captures.toList(), startPos) + resolutionSink?.exitScope(cc.currentPos()) + return stmt + } + private fun resolveInitializerObjClass(initializer: Statement?): ObjClass? { if (initializer is BytecodeStatement) { val fn = initializer.bytecodeFunction() @@ -4647,7 +4729,7 @@ class Compiler( // Destructuring if (isStatic) throw ScriptError(start, "static destructuring is not supported") - val entries = parseArrayLiteral() + val entries = parseDestructuringPattern() val pattern = ListLiteralRef(entries) // Register all names in the pattern @@ -5043,14 +5125,20 @@ class Compiler( implicitThisTypeName = extTypeName ) ) { - parseBlock() + parseBlockWithPredeclared(listOf(setArgName to true)) } object : Statement() { override val pos: Pos = body.pos override suspend fun execute(scope: Scope): Obj { val value = scope.args.list.firstOrNull() ?: ObjNull scope.addItem(setArgName, true, value, recordType = ObjRecord.Type.Argument) - return body.execute(scope) + val prev = scope.skipScopeCreation + scope.skipScopeCreation = true + return try { + body.execute(scope) + } finally { + scope.skipScopeCreation = prev + } } } } else if (cc.peekNextNonWhitespace().type == Token.Type.ASSIGN) { @@ -5063,8 +5151,7 @@ class Compiler( implicitThisTypeName = extTypeName ) ) { - parseExpression() - ?: throw ScriptError(cc.current().pos, "Expected setter expression") + parseExpressionBlockWithPredeclared(listOf(setArgName to true)) } val st = expr object : Statement() { @@ -5072,7 +5159,13 @@ class Compiler( override suspend fun execute(scope: Scope): Obj { val value = scope.args.list.firstOrNull() ?: ObjNull scope.addItem(setArgName, true, value, recordType = ObjRecord.Type.Argument) - return st.execute(scope) + val prev = scope.skipScopeCreation + scope.skipScopeCreation = true + return try { + st.execute(scope) + } finally { + scope.skipScopeCreation = prev + } } } } else { @@ -5100,14 +5193,20 @@ class Compiler( implicitThisTypeName = extTypeName ) ) { - parseBlock() + parseBlockWithPredeclared(listOf(setArg.value to true)) } object : Statement() { override val pos: Pos = body.pos override suspend fun execute(scope: Scope): Obj { val value = scope.args.list.firstOrNull() ?: ObjNull scope.addItem(setArg.value, true, value, recordType = ObjRecord.Type.Argument) - return body.execute(scope) + val prev = scope.skipScopeCreation + scope.skipScopeCreation = true + return try { + body.execute(scope) + } finally { + scope.skipScopeCreation = prev + } } } } else if (cc.peekNextNonWhitespace().type == Token.Type.ASSIGN) { @@ -5120,17 +5219,20 @@ class Compiler( implicitThisTypeName = extTypeName ) ) { - parseExpression() ?: throw ScriptError( - cc.current().pos, - "Expected setter expression" - ) + parseExpressionBlockWithPredeclared(listOf(setArg.value to true)) } object : Statement() { override val pos: Pos = st.pos override suspend fun execute(scope: Scope): Obj { val value = scope.args.list.firstOrNull() ?: ObjNull scope.addItem(setArg.value, true, value, recordType = ObjRecord.Type.Argument) - return st.execute(scope) + val prev = scope.skipScopeCreation + scope.skipScopeCreation = true + return try { + st.execute(scope) + } finally { + scope.skipScopeCreation = prev + } } } } else { diff --git a/lynglib/src/commonTest/kotlin/ScriptTest.kt b/lynglib/src/commonTest/kotlin/ScriptTest.kt index 95a3640..0104f50 100644 --- a/lynglib/src/commonTest/kotlin/ScriptTest.kt +++ b/lynglib/src/commonTest/kotlin/ScriptTest.kt @@ -926,7 +926,6 @@ class ScriptTest { ) } - @Ignore("incremental enable") @Test fun bookTest0() = runTest { assertEquals( @@ -1041,7 +1040,6 @@ class ScriptTest { // assertEquals( "4", c.eval("x+0").toString()) } - @Ignore("incremental enable") @Test fun bookTest2() = runTest { val src = """ @@ -4609,7 +4607,6 @@ class ScriptTest { } - @Ignore("incremental enable: destructuring assignments not implemented in bytecode compiler") @Test fun testDestructuringAssignment() = runTest { eval( diff --git a/lynglib/src/commonTest/kotlin/net/sergeych/lyng/PropsTest.kt b/lynglib/src/commonTest/kotlin/net/sergeych/lyng/PropsTest.kt index e15e460..b58d501 100644 --- a/lynglib/src/commonTest/kotlin/net/sergeych/lyng/PropsTest.kt +++ b/lynglib/src/commonTest/kotlin/net/sergeych/lyng/PropsTest.kt @@ -6,7 +6,6 @@ import kotlin.test.Test class PropsTest { - @Ignore("Setter parameter binding (value) not wired in compile-time resolution yet") @Test fun propsProposal() = runTest { eval("""