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