fix #17 do-while loops
+mode string functions +val/var cold be initialized with statement and return assigned values
This commit is contained in:
		
							parent
							
								
									55fd3ea716
								
							
						
					
					
						commit
						a1267c4395
					
				@ -554,16 +554,16 @@ Or, more neat:
 | 
			
		||||
 | 
			
		||||
## while
 | 
			
		||||
 | 
			
		||||
Regular pre-condition while loop, as expression, loop returns it's last line result:
 | 
			
		||||
Regular pre-condition while loop, as expression, loop returns the last expression as everything else:
 | 
			
		||||
 | 
			
		||||
    var count = 0
 | 
			
		||||
    while( count < 5 ) {
 | 
			
		||||
        count++
 | 
			
		||||
        count * 10
 | 
			
		||||
    }
 | 
			
		||||
    >>> 50
 | 
			
		||||
    val result = while( count < 5 ) count++
 | 
			
		||||
    result
 | 
			
		||||
    >>> 4
 | 
			
		||||
 | 
			
		||||
We can break as usual:
 | 
			
		||||
Notice it _is 4 because when count became 5, the loop body was not executed!_.
 | 
			
		||||
 | 
			
		||||
We can break while as usual:
 | 
			
		||||
 | 
			
		||||
    var count = 0
 | 
			
		||||
    while( count < 5 ) {
 | 
			
		||||
@ -682,6 +682,36 @@ So the returned value, as seen from diagram could be one of:
 | 
			
		||||
- value returned from the `else` clause, of the loop was not broken
 | 
			
		||||
- value returned from the last execution of loop body, if there was no `break` and no `else` clause.
 | 
			
		||||
 | 
			
		||||
## do-while loops
 | 
			
		||||
 | 
			
		||||
There works exactly as while loops but the body is executed prior to checking the while condition:
 | 
			
		||||
 | 
			
		||||
    var i = 0
 | 
			
		||||
    do { i++ } while( i < 1 )
 | 
			
		||||
    i
 | 
			
		||||
    >>> 1
 | 
			
		||||
 | 
			
		||||
The important feature of the do-while loop is that the condition expression is
 | 
			
		||||
evaluated on the body scope, e.g., variables, intruduced in the loop body are
 | 
			
		||||
available in the condition:
 | 
			
		||||
 | 
			
		||||
    do {
 | 
			
		||||
        var continueLoop = false
 | 
			
		||||
        "OK"
 | 
			
		||||
    } while( continueLoop )
 | 
			
		||||
    >>> "OK"
 | 
			
		||||
 | 
			
		||||
This is sometimes convenient when condition is complex and has to be calculated inside the loop body. Notice the value returning by the loop:
 | 
			
		||||
 | 
			
		||||
    fun readLine() { "done: result" }
 | 
			
		||||
    val result = do {
 | 
			
		||||
        val line = readLine()
 | 
			
		||||
    } while( !line.startsWith("done:") )
 | 
			
		||||
    result.drop(6)
 | 
			
		||||
    >>> "result"
 | 
			
		||||
 | 
			
		||||
Suppose readLine() here reads some stream of lines.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
## For loops
 | 
			
		||||
 | 
			
		||||
@ -791,7 +821,7 @@ See [Ranges](Range.md) for detailed documentation on it.
 | 
			
		||||
 | 
			
		||||
    // single line comment
 | 
			
		||||
    var result = null // here we will store the result
 | 
			
		||||
    >>> void
 | 
			
		||||
    >>> null
 | 
			
		||||
 | 
			
		||||
# Integral data types
 | 
			
		||||
 | 
			
		||||
@ -837,10 +867,36 @@ Are the same as in string literals with little difference:
 | 
			
		||||
 | 
			
		||||
## String details
 | 
			
		||||
 | 
			
		||||
Strings are much like Kotlin ones:
 | 
			
		||||
 | 
			
		||||
    "Hello".length
 | 
			
		||||
    >>> 5
 | 
			
		||||
And supports growing set of kotlin-borrowed operations, see below, for example:
 | 
			
		||||
 | 
			
		||||
    assertEquals("Hell", "Hello".dropLast(1))
 | 
			
		||||
    >>> void
 | 
			
		||||
 | 
			
		||||
### String operations
 | 
			
		||||
 | 
			
		||||
Concatenation is a `+`: `"hello " + name` works as expected. No confusion.
 | 
			
		||||
 | 
			
		||||
Typical set of String functions includes:
 | 
			
		||||
 | 
			
		||||
| fun/prop         | description / notes                                        |
 | 
			
		||||
|------------------|------------------------------------------------------------|
 | 
			
		||||
| lower()          | change case to unicode upper                               |
 | 
			
		||||
| upper()          | change case to unicode lower                               |
 | 
			
		||||
| startsWith(prefix) | true if starts with a prefix                               |
 | 
			
		||||
| take(n)          | get a new string from up to n first characters             |
 | 
			
		||||
| takeLast(n)      | get a new string from up to n last characters              |
 | 
			
		||||
| drop(n)          | get a new string dropping n first chars, or empty string   |
 | 
			
		||||
| dropLast(n)      | get a new string dropping n last chars, or empty string    |
 | 
			
		||||
| size             | size in characters like `length` because String is [Array] |
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Literals
 | 
			
		||||
 | 
			
		||||
String literal could be multiline:
 | 
			
		||||
 | 
			
		||||
@ -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 {
 | 
			
		||||
 | 
			
		||||
@ -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())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user