fix #17 do-while loops

+mode string functions
+val/var cold be initialized with statement and return assigned values
This commit is contained in:
sergeych committed 2025-06-12 11:22:27 +04:00
1 parent 55fd3ea716
commit a1267c4395
8 files changed
+194 -14

No files matched your search

@@ -710,6 +710,7 @@ class Compiler(
"val" -> parseVarDeclaration(id.value, false, cc)
"var" -> parseVarDeclaration(id.value, true, cc)
"while" -> parseWhileStatement(cc)
"do" -> parseDoWhileStatement(cc)
"for" -> parseForStatement(cc)
"break" -> parseBreakStatement(id.pos, cc)
"continue" -> parseContinueStatement(id.pos, cc)
@@ -944,6 +945,59 @@ class Compiler(
return elseStatement?.execute(forContext) ?: result
}
@Suppress("UNUSED_VARIABLE")
private fun parseDoWhileStatement(cc: CompilerContext): Statement {
val label = getLabel(cc)?.also { cc.labels += it }
val (breakFound, body) = cc.parseLoop {
parseStatement(cc) ?: throw ScriptError(cc.currentPos(), "Bad while statement: expected statement")
}
label?.also { cc.labels -= it }
cc.skipTokens(Token.Type.NEWLINE)
val t = cc.next()
if( t.type != Token.Type.ID && t.value != "while" )
cc.skipTokenOfType(Token.Type.LPAREN, "expected '(' here")
val conditionStart = ensureLparen(cc)
val condition =
parseExpression(cc) ?: throw ScriptError(conditionStart, "Bad while statement: expected expression")
ensureRparen(cc)
cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
val elseStatement = if (cc.next().let { it.type == Token.Type.ID && it.value == "else" }) {
parseStatement(cc)
} else {
cc.previous()
null
}
return statement(body.pos) {
var wasBroken = false
var result: Obj = ObjVoid
lateinit var doContext: Context
do {
doContext = it.copy().apply { skipContextCreation = true }
try {
result = body.execute(doContext)
}
catch( e: LoopBreakContinueException) {
if( e.label == label || e.label == null ) {
if( e.doContinue ) continue
else {
result = e.result
wasBroken = true
break
}
}
}
} while( condition.execute(doContext).toBool() )
if( !wasBroken ) elseStatement?.let { s -> result = s.execute(it) }
result
}
}
private fun parseWhileStatement(cc: CompilerContext): Statement {
val label = getLabel(cc)?.also { cc.labels += it }
val start = ensureLparen(cc)
@@ -1149,7 +1203,7 @@ class Compiler(
val block = parseScript(startPos, cc)
return statement(startPos) {
// block run on inner context:
block.execute(it.copy(startPos))
block.execute(if( it.skipContextCreation ) it else it.copy(startPos))
}.also {
val t1 = cc.next()
if (t1.type != Token.Type.RBRACE)
@@ -1184,7 +1238,7 @@ class Compiler(
}
}
val initialExpression = if (setNull) null else parseExpression(tokens)
val initialExpression = if (setNull) null else parseStatement(tokens, true)
?: throw ScriptError(eqToken.pos, "Expected initializer expression")
return statement(nameToken.pos) { context ->
@@ -1196,7 +1250,7 @@ class Compiler(
val initValue = initialExpression?.execute(context)?.byValueCopy() ?: ObjNull
context.addItem(name, mutable, initValue, visibility)
ObjVoid
initValue
}
}
@@ -134,6 +134,13 @@ internal class CompilerContext(val tokens: List<Token>) {
return default
}
// fun expectKeyword(vararg keyword: String): String {
// val t = next()
// if (t.type != Token.Type.ID && t.value !in keyword) {
// throw ScriptError(t.pos, "expected one of ${keyword.joinToString()}")
//
// }
// data class ReturnScope(val needCatch: Boolean = false)
// private val
@@ -4,7 +4,8 @@ class Context(
val parent: Context?,
val args: Arguments = Arguments.EMPTY,
var pos: Pos = Pos.builtIn,
val thisObj: Obj = ObjVoid
val thisObj: Obj = ObjVoid,
var skipContextCreation: Boolean = false,
) {
constructor(
args: Arguments = Arguments.EMPTY,
@@ -286,6 +286,8 @@ object ObjNull : Obj() {
override fun equals(other: Any?): Boolean {
return other is ObjNull || other == null
}
override fun toString(): String = "null"
}
interface Numeric {
@@ -40,6 +40,32 @@ data class ObjString(val value: String) : Obj() {
addConst("length",
statement { ObjInt(thisAs<ObjString>().value.length.toLong()) }
)
addFn("takeLast") {
thisAs<ObjString>().value.takeLast(
requiredArg<ObjInt>(0).toInt()
).let(::ObjString)
}
addFn("take") {
thisAs<ObjString>().value.take(
requiredArg<ObjInt>(0).toInt()
).let(::ObjString)
}
addFn("drop") {
thisAs<ObjString>().value.drop(
requiredArg<ObjInt>(0).toInt()
).let(::ObjString)
}
addFn("dropLast") {
thisAs<ObjString>().value.dropLast(
requiredArg<ObjInt>(0).toInt()
).let(::ObjString)
}
addFn("lower") {
thisAs<ObjString>().value.lowercase().let(::ObjString)
}
addFn("upper") {
thisAs<ObjString>().value.uppercase().let(::ObjString)
}
addFn("size") { ObjInt(thisAs<ObjString>().value.length.toLong()) }
}
}
@@ -17,7 +17,7 @@ class Script(
return lastResult
}
suspend fun execute() = execute(defaultContext.copy(pos))
suspend fun execute() = execute(defaultContext.copy(pos = pos))
companion object {
val defaultContext: Context = Context().apply {
+35 -1
View File
@@ -174,7 +174,7 @@ class ScriptTest {
fun varsAndConstsTest() = runTest {
val context = Context(pos = Pos.builtIn)
assertEquals(
ObjVoid, context.eval(
ObjInt(3L), context.eval(
"""
val a = 17
var b = 3
@@ -434,6 +434,16 @@ class ScriptTest {
}
@Test
fun whileAssignTest() = runTest {
eval("""
var t = 0
val x = while( t < 5 ) { t++ }
// last returned value is 4 - when t was 5 body was not executed
assertEquals( 4, x )
""".trimIndent())
}
@Test
fun whileTest() = runTest {
assertEquals(
@@ -1662,4 +1672,28 @@ class ScriptTest {
""".trimIndent())
}
@Test
fun doWhileSimpleTest() = runTest {
eval("""
var sum = 0
var x = do {
val s = sum
sum += 1
} while( s < 10 )
assertEquals(11, x)
""".trimIndent())
}
@Test
fun testFailDoWhileSample1() = runTest {
eval("""
fun readLine() { "done: result" }
val result = do {
val line = readLine()
} while( !line.startsWith("done:") )
assertEquals("result", result.drop(6))
result
""".trimIndent())
}
}