From 3cd2786ef075f140a437ec7da48b3c1693b727fb Mon Sep 17 00:00:00 2001 From: sergeych Date: Mon, 2 Jun 2025 18:08:54 +0400 Subject: [PATCH] while/else practical sample !fixed else-while not calling on never run loop bug --- docs/tutorial.md | 22 +++++-- .../kotlin/net/sergeych/lyng/Compiler.kt | 4 +- library/src/commonTest/kotlin/ScriptTest.kt | 62 ++++++++++++++++--- 3 files changed, 72 insertions(+), 16 deletions(-) diff --git a/docs/tutorial.md b/docs/tutorial.md index 8991328..dee1c30 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -572,17 +572,15 @@ We can skip the rest of the loop and restart it, as usual, with `continue` opera "found even numbers: " + countEven >>> "found even numbers: 5" -`continue` can't "return" anything: it just restarts the loop. It can use labeled loops to restart outer ones: +`continue` can't "return" anything: it just restarts the loop. It can use labeled loops to restart outer ones (we intentionally avoid using for loops here): var count = 0 var total = 0 // notice the label: - outerLoop@ while( count < 5 ) { - count = count + 1 + outerLoop@ while( count++ < 5 ) { var innerCount = 0 while( innerCount < 10 ) { - innerCount = innerCount + 1 - if( innerCount == 10 ) + if( ++innerCount == 10 ) continue@outerLoop } // we don't reach it because continue above restarts our loop @@ -600,6 +598,20 @@ The while and for loops can be followed by the else block, which is executed whe ends normally, without breaks. It allows override loop result value, for example, to not calculate it in every iteration. See for loop example just below. + fun naive_is_prime(candidate) { + val x = if( candidate !is Int) candidate.toInt() else candidate + var divisor = 1 + while( ++divisor < x/2 || divisor == 2 ) { + if( x % divisor == 0 ) break false + } + else true + } + assert( !naive_is_prime(16) ) + assert( naive_is_prime(17) ) + assert( naive_is_prime(3) ) + assert( !naive_is_prime(4) ) + >>> void + ## Loop return value diagram ```mermaid diff --git a/library/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/library/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index c36d6df..3e9c69a 100644 --- a/library/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/library/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -753,23 +753,25 @@ class Compiler( } return statement(body.pos) { var result: Obj = ObjVoid + var wasBroken = false while (condition.execute(it).toBool()) { try { // we don't need to create new context here: if body is a block, // parse block will do it, otherwise single statement doesn't need it: result = body.execute(it) - elseStatement?.let { s -> result = s.execute(it) } } catch (lbe: LoopBreakContinueException) { if (lbe.label == label || lbe.label == null) { if (lbe.doContinue) continue else { result = lbe.result + wasBroken = true break } } else throw lbe } } + if( !wasBroken ) elseStatement?.let { s -> result = s.execute(it) } result } } diff --git a/library/src/commonTest/kotlin/ScriptTest.kt b/library/src/commonTest/kotlin/ScriptTest.kt index 9e02996..7acfdde 100644 --- a/library/src/commonTest/kotlin/ScriptTest.kt +++ b/library/src/commonTest/kotlin/ScriptTest.kt @@ -1041,7 +1041,8 @@ class ScriptTest { @Test fun testLambdaWithIt1() = runTest { - eval(""" + eval( + """ val x = { it + "!" } @@ -1050,43 +1051,51 @@ class ScriptTest { assert( x is Callable) assert(y == "OK") assert( x("hello") == "hello!") - """.trimIndent()) + """.trimIndent() + ) } @Test fun testLambdaWithIt2() = runTest { - eval(""" + eval( + """ val x = { assert(it == void) } assert( x() == void) - """.trimIndent()) + """.trimIndent() + ) } @Test fun testLambdaWithIt3() = runTest { - eval(""" + eval( + """ val x = { assert( it == [1,2,"end"]) } println("0----") assert( x(1, 2, "end") == void) - """.trimIndent()) + """.trimIndent() + ) } @Test fun testLambdaWithArgs() = runTest { - eval(""" + eval( + """ val x = { x, y, z -> assert( [x, y, z] == [1,2,"end"]) } assert( x(1, 2, "end") == void) - """.trimIndent()) + """.trimIndent() + ) } @Test fun testLambdaWithArgsEllipsis() = runTest { - eval(""" + eval( + """ val x = { x, y... -> println("-- y=",y) println(":: "+y::class) @@ -1094,7 +1103,8 @@ class ScriptTest { } assert( x(1, 2, "end") == void) assert( x(1, ...[2, "end"]) == void) - """.trimIndent()) + """.trimIndent() + ) } @Test @@ -1111,4 +1121,36 @@ class ScriptTest { ) } } + + @Test + fun testWhileExecuteElseIfNotExecuted() = runTest { + assertEquals( + "ok", + eval( + """ + while( 5 < 1 ) { + "bad" + } else "ok" + """.trimIndent() + ).toString() + ) + } + + @Test + fun testIsPrimeSampleBug() = runTest { + eval(""" + fun naive_is_prime(candidate) { + val x = if( candidate !is Int) candidate.toInt() else candidate + var divisor = 1 + println("start with ",x) + while( ++divisor < x/2 && divisor != 2 ) { + println("x=", x, " // ", divisor, " :: ", x % divisor) + if( x % divisor == 0 ) break false + } + else true + } + naive_is_prime(4) + + """.trimIndent()) + } } \ No newline at end of file