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
|
## 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
|
var count = 0
|
||||||
while( count < 5 ) {
|
val result = while( count < 5 ) count++
|
||||||
count++
|
result
|
||||||
count * 10
|
>>> 4
|
||||||
}
|
|
||||||
>>> 50
|
|
||||||
|
|
||||||
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
|
var count = 0
|
||||||
while( count < 5 ) {
|
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 `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.
|
- 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
|
## For loops
|
||||||
|
|
||||||
@ -791,7 +821,7 @@ See [Ranges](Range.md) for detailed documentation on it.
|
|||||||
|
|
||||||
// single line comment
|
// single line comment
|
||||||
var result = null // here we will store the result
|
var result = null // here we will store the result
|
||||||
>>> void
|
>>> null
|
||||||
|
|
||||||
# Integral data types
|
# Integral data types
|
||||||
|
|
||||||
@ -837,10 +867,36 @@ Are the same as in string literals with little difference:
|
|||||||
|
|
||||||
## String details
|
## 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
|
### String operations
|
||||||
|
|
||||||
Concatenation is a `+`: `"hello " + name` works as expected. No confusion.
|
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
|
### Literals
|
||||||
|
|
||||||
String literal could be multiline:
|
String literal could be multiline:
|
||||||
|
@ -710,6 +710,7 @@ class Compiler(
|
|||||||
"val" -> parseVarDeclaration(id.value, false, cc)
|
"val" -> parseVarDeclaration(id.value, false, cc)
|
||||||
"var" -> parseVarDeclaration(id.value, true, cc)
|
"var" -> parseVarDeclaration(id.value, true, cc)
|
||||||
"while" -> parseWhileStatement(cc)
|
"while" -> parseWhileStatement(cc)
|
||||||
|
"do" -> parseDoWhileStatement(cc)
|
||||||
"for" -> parseForStatement(cc)
|
"for" -> parseForStatement(cc)
|
||||||
"break" -> parseBreakStatement(id.pos, cc)
|
"break" -> parseBreakStatement(id.pos, cc)
|
||||||
"continue" -> parseContinueStatement(id.pos, cc)
|
"continue" -> parseContinueStatement(id.pos, cc)
|
||||||
@ -944,6 +945,59 @@ class Compiler(
|
|||||||
return elseStatement?.execute(forContext) ?: result
|
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 {
|
private fun parseWhileStatement(cc: CompilerContext): Statement {
|
||||||
val label = getLabel(cc)?.also { cc.labels += it }
|
val label = getLabel(cc)?.also { cc.labels += it }
|
||||||
val start = ensureLparen(cc)
|
val start = ensureLparen(cc)
|
||||||
@ -1149,7 +1203,7 @@ class Compiler(
|
|||||||
val block = parseScript(startPos, cc)
|
val block = parseScript(startPos, cc)
|
||||||
return statement(startPos) {
|
return statement(startPos) {
|
||||||
// block run on inner context:
|
// block run on inner context:
|
||||||
block.execute(it.copy(startPos))
|
block.execute(if( it.skipContextCreation ) it else it.copy(startPos))
|
||||||
}.also {
|
}.also {
|
||||||
val t1 = cc.next()
|
val t1 = cc.next()
|
||||||
if (t1.type != Token.Type.RBRACE)
|
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")
|
?: throw ScriptError(eqToken.pos, "Expected initializer expression")
|
||||||
|
|
||||||
return statement(nameToken.pos) { context ->
|
return statement(nameToken.pos) { context ->
|
||||||
@ -1196,7 +1250,7 @@ class Compiler(
|
|||||||
val initValue = initialExpression?.execute(context)?.byValueCopy() ?: ObjNull
|
val initValue = initialExpression?.execute(context)?.byValueCopy() ?: ObjNull
|
||||||
|
|
||||||
context.addItem(name, mutable, initValue, visibility)
|
context.addItem(name, mutable, initValue, visibility)
|
||||||
ObjVoid
|
initValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,6 +134,13 @@ internal class CompilerContext(val tokens: List<Token>) {
|
|||||||
return default
|
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)
|
// data class ReturnScope(val needCatch: Boolean = false)
|
||||||
|
|
||||||
// private val
|
// private val
|
||||||
|
@ -4,7 +4,8 @@ class Context(
|
|||||||
val parent: Context?,
|
val parent: Context?,
|
||||||
val args: Arguments = Arguments.EMPTY,
|
val args: Arguments = Arguments.EMPTY,
|
||||||
var pos: Pos = Pos.builtIn,
|
var pos: Pos = Pos.builtIn,
|
||||||
val thisObj: Obj = ObjVoid
|
val thisObj: Obj = ObjVoid,
|
||||||
|
var skipContextCreation: Boolean = false,
|
||||||
) {
|
) {
|
||||||
constructor(
|
constructor(
|
||||||
args: Arguments = Arguments.EMPTY,
|
args: Arguments = Arguments.EMPTY,
|
||||||
|
@ -286,6 +286,8 @@ object ObjNull : Obj() {
|
|||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
return other is ObjNull || other == null
|
return other is ObjNull || other == null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun toString(): String = "null"
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Numeric {
|
interface Numeric {
|
||||||
|
@ -40,6 +40,32 @@ data class ObjString(val value: String) : Obj() {
|
|||||||
addConst("length",
|
addConst("length",
|
||||||
statement { ObjInt(thisAs<ObjString>().value.length.toLong()) }
|
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()) }
|
addFn("size") { ObjInt(thisAs<ObjString>().value.length.toLong()) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ class Script(
|
|||||||
return lastResult
|
return lastResult
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun execute() = execute(defaultContext.copy(pos))
|
suspend fun execute() = execute(defaultContext.copy(pos = pos))
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val defaultContext: Context = Context().apply {
|
val defaultContext: Context = Context().apply {
|
||||||
|
@ -174,7 +174,7 @@ class ScriptTest {
|
|||||||
fun varsAndConstsTest() = runTest {
|
fun varsAndConstsTest() = runTest {
|
||||||
val context = Context(pos = Pos.builtIn)
|
val context = Context(pos = Pos.builtIn)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
ObjVoid, context.eval(
|
ObjInt(3L), context.eval(
|
||||||
"""
|
"""
|
||||||
val a = 17
|
val a = 17
|
||||||
var b = 3
|
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
|
@Test
|
||||||
fun whileTest() = runTest {
|
fun whileTest() = runTest {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
@ -1662,4 +1672,28 @@ class ScriptTest {
|
|||||||
""".trimIndent())
|
""".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