while-else loop and string.startsWith
This commit is contained in:
parent
f3b3ebbb52
commit
f7b35c7576
12
docs/List.md
12
docs/List.md
@ -47,10 +47,18 @@ __Important__ negative indexes works wherever indexes are used, e.g. in insertio
|
||||
## Members
|
||||
|
||||
| name | meaning | type |
|
||||
|----------------------------|----------------------------------------------|----------|
|
||||
|-----------------------------------|-------------------------------------|----------|
|
||||
| `size` | current size | Int |
|
||||
| `add(elements...)` | add one or more elements to the end | Any |
|
||||
| `addAt(index,elements...)` | insert elements at position | Int, Any |
|
||||
| `removeAt(index)` | remove element at position | Int |
|
||||
| `removeAt(start,end)` | remove range, start inclusive, end exclusive | Int, Int |
|
||||
| `removeRangeInclusive(start,end)` | remove range, inclusive (1) | Int, Int |
|
||||
| | | |
|
||||
|
||||
(1)
|
||||
: end-inclisiveness allows to use negative indexes to, for exampe, remove several last elements, like `list.removeRangeInclusive(-2, -1)` will remove two last elements.
|
||||
|
||||
|
||||
# Notes
|
||||
|
||||
Could be rewritten using array as a class but List as the interface
|
||||
|
@ -378,7 +378,7 @@ Note that to add to the end you still need to use `add` or positive index of the
|
||||
x.removeAt(2)
|
||||
assert( x == [1, 2, 4, 5])
|
||||
// or remove range (start inclusive, end exclusive):
|
||||
x.removeAt(1,3)
|
||||
x.removeRangeInclusive(1,2)
|
||||
assert( x == [1, 5])
|
||||
>>> void
|
||||
|
||||
@ -390,9 +390,9 @@ Again, you can use negative indexes. For example, removing last elements like:
|
||||
x.removeAt(-1)
|
||||
assert( x == [1, 2, 3, 4])
|
||||
|
||||
// remove 3 last:
|
||||
x.removeAt(-3,1)
|
||||
assert( x == [1])
|
||||
// remove 2 last:
|
||||
x.removeRangeInclusive(-2,-1)
|
||||
assert( x == [1, 2])
|
||||
>>> void
|
||||
|
||||
# Flow control operators
|
||||
@ -514,6 +514,28 @@ occasional clash. Labels are also scoped to their context and do not exist outsi
|
||||
|
||||
Right now labels are implemented only for the while loop. It is intended to be implemented for all loops and returns.
|
||||
|
||||
## while - else statement
|
||||
|
||||
The while loop can be followed by the else block, which is executed when the loop
|
||||
ends normally, without breaks. It allows override loop result value, for example,
|
||||
to not calculate it in every iteration. Here is the sample:
|
||||
|
||||
### Else, labels, and break practical sample
|
||||
|
||||
// Get a first word that starts with a given previx and return it:
|
||||
fun findPrefix(prefix,words) {
|
||||
var index = 0
|
||||
while( index < words.size ) {
|
||||
val w = words[index++]
|
||||
if( w.startsWith(prefix) ) break w
|
||||
}
|
||||
else null
|
||||
}
|
||||
val words = ["hello", "world", "foobar", "end"]
|
||||
assert( findPrefix("bad", words) == null )
|
||||
findPrefix("foo", words )
|
||||
>>> "foobar"
|
||||
|
||||
# Self-assignments in expression
|
||||
|
||||
There are auto-increments and auto-decrements:
|
||||
|
@ -209,6 +209,7 @@ class Compiler(
|
||||
is ListEntry.Element -> {
|
||||
list += e.accessor.getter(cxt).value
|
||||
}
|
||||
|
||||
is ListEntry.Spread -> {
|
||||
val elements = e.accessor.getter(cxt).value
|
||||
when {
|
||||
@ -321,10 +322,12 @@ class Compiler(
|
||||
Token.Type.COMMA -> {
|
||||
// todo: check commas sequences like [,] [,,] before, after or instead of expressions
|
||||
}
|
||||
|
||||
Token.Type.RBRACKET -> return entries
|
||||
Token.Type.ELLIPSIS -> {
|
||||
parseExpressionLevel(cc)?.let { entries += ListEntry.Spread(it) }
|
||||
}
|
||||
|
||||
else -> {
|
||||
cc.previous()
|
||||
parseExpressionLevel(cc)?.let { entries += ListEntry.Element(it) }
|
||||
@ -358,6 +361,7 @@ class Compiler(
|
||||
parseStatement(cc)?.let { args += ParsedArgument(it, t.pos, isSplat = true) }
|
||||
?: throw ScriptError(t.pos, "Expecting arguments list")
|
||||
}
|
||||
|
||||
else -> {
|
||||
cc.previous()
|
||||
parseStatement(cc)?.let { args += ParsedArgument(it, t.pos) }
|
||||
@ -375,7 +379,8 @@ class Compiler(
|
||||
|
||||
return Accessor { context ->
|
||||
val v = left.getter(context)
|
||||
v.value.callOn(context.copy(
|
||||
v.value.callOn(
|
||||
context.copy(
|
||||
context.pos,
|
||||
args.toArguments(context)
|
||||
// Arguments(
|
||||
@ -496,6 +501,13 @@ class Compiler(
|
||||
val body = parseStatement(cc) ?: throw ScriptError(start, "Bad while statement: expected statement")
|
||||
label?.also { cc.labels -= it }
|
||||
|
||||
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 result: Obj = ObjVoid
|
||||
while (condition.execute(it).toBool()) {
|
||||
@ -503,6 +515,7 @@ class Compiler(
|
||||
// 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
|
||||
|
@ -33,6 +33,17 @@ class Context(
|
||||
?: raiseClassCastError("Expected type ${T::class.simpleName}, got ${args.list[index].value::class.simpleName}")
|
||||
}
|
||||
|
||||
inline fun <reified T: Obj>requireOnlyArg(): T {
|
||||
if( args.list.size != 1 ) raiseError("Expected exactly 1 argument, got ${args.list.size}")
|
||||
return requiredArg(0)
|
||||
}
|
||||
|
||||
fun requireExactCount(count: Int) {
|
||||
if( args.list.size != count ) {
|
||||
raiseError("Expected exactly $count arguments, got ${args.list.size}")
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T: Obj>thisAs(): T = (thisObj as? T)
|
||||
?: raiseClassCastError("Cannot cast ${thisObj.objClass.className} to ${T::class.simpleName}")
|
||||
|
||||
|
@ -179,6 +179,10 @@ sealed class Obj {
|
||||
members[name] = WithAccess(initialValue, isMutable)
|
||||
}
|
||||
|
||||
fun addFn(name: String, isOpen: Boolean = false, code: suspend Context.()->Obj) {
|
||||
createField(name, statement { code() }, isOpen)
|
||||
}
|
||||
|
||||
fun addConst(name: String, value: Obj) = createField(name, value, isMutable = false)
|
||||
|
||||
open suspend fun callOn(context: Context): Obj {
|
||||
@ -243,35 +247,6 @@ object ObjNull : Obj() {
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
@SerialName("string")
|
||||
data class ObjString(val value: String) : Obj() {
|
||||
|
||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
||||
if (other !is ObjString) return -2
|
||||
return this.value.compareTo(other.value)
|
||||
}
|
||||
|
||||
override fun toString(): String = value
|
||||
|
||||
override val asStr: ObjString by lazy { this }
|
||||
|
||||
override fun inspect(): String {
|
||||
return "\"$value\""
|
||||
}
|
||||
|
||||
override val objClass: ObjClass
|
||||
get() = type
|
||||
|
||||
override suspend fun plus(context: Context, other: Obj): Obj {
|
||||
return ObjString(value + other.asStr.value)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("String")
|
||||
}
|
||||
}
|
||||
|
||||
interface Numeric {
|
||||
val longValue: Long
|
||||
val doubleValue: Double
|
||||
|
@ -73,23 +73,31 @@ class ObjList(val list: MutableList<Obj>) : Obj() {
|
||||
statement {
|
||||
if (args.size < 2) raiseError("addAt takes 2+ arguments")
|
||||
val l = thisAs<ObjList>()
|
||||
var index = l.normalize(this, requiredArg<ObjInt>(0).value.toInt(),
|
||||
allowInclusiveEnd = true)
|
||||
var index = l.normalize(
|
||||
this, requiredArg<ObjInt>(0).value.toInt(),
|
||||
allowInclusiveEnd = true
|
||||
)
|
||||
for (i in 1..<args.size) l.list.add(index++, args[i])
|
||||
ObjVoid
|
||||
}
|
||||
)
|
||||
createField("removeAt",
|
||||
statement {
|
||||
addFn("removeAt") {
|
||||
val self = thisAs<ObjList>()
|
||||
val start = self.normalize(this, requiredArg<ObjInt>(0).value.toInt())
|
||||
if (args.size == 2) {
|
||||
val end = requiredArg<ObjInt>(1).value.toInt()
|
||||
val end = requireOnlyArg<ObjInt>().value.toInt()
|
||||
self.list.subList(start, self.normalize(this, end)).clear()
|
||||
} else
|
||||
self.list.removeAt(start)
|
||||
self
|
||||
})
|
||||
}
|
||||
addFn("removeRangeInclusive") {
|
||||
val self = thisAs<ObjList>()
|
||||
val start = self.normalize(this, requiredArg<ObjInt>(0).value.toInt())
|
||||
val end = self.normalize(this, requiredArg<ObjInt>(1).value.toInt()) + 1
|
||||
self.list.subList(start, end).clear()
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
41
library/src/commonMain/kotlin/net/sergeych/ling/ObjString.kt
Normal file
41
library/src/commonMain/kotlin/net/sergeych/ling/ObjString.kt
Normal file
@ -0,0 +1,41 @@
|
||||
package net.sergeych.ling
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@SerialName("string")
|
||||
data class ObjString(val value: String) : Obj() {
|
||||
|
||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
||||
if (other !is ObjString) return -2
|
||||
return this.value.compareTo(other.value)
|
||||
}
|
||||
|
||||
override fun toString(): String = value
|
||||
|
||||
override val asStr: ObjString by lazy { this }
|
||||
|
||||
override fun inspect(): String {
|
||||
return "\"$value\""
|
||||
}
|
||||
|
||||
override val objClass: ObjClass
|
||||
get() = type
|
||||
|
||||
override suspend fun plus(context: Context, other: Obj): Obj {
|
||||
return ObjString(value + other.asStr.value)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("String").apply {
|
||||
addConst("startsWith",
|
||||
statement {
|
||||
ObjBool(thisAs<ObjString>().value.startsWith(requiredArg<ObjString>(0).value))
|
||||
})
|
||||
addConst("length",
|
||||
statement { ObjInt(thisAs<ObjString>().value.length.toLong()) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user