while-else loop and string.startsWith

This commit is contained in:
Sergey Chernov 2025-05-30 13:50:37 +04:00
parent f3b3ebbb52
commit f7b35c7576
7 changed files with 142 additions and 64 deletions

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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}")

View File

@ -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

View File

@ -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
}
}
}
}

View 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()) }
)
}
}
}