while-else loop and string.startsWith

This commit is contained in:
sergeych committed 2025-05-30 13:50:37 +04:00
1 parent f3b3ebbb52
commit f7b35c7576
7 files changed
+142 -64

No files matched your search

@@ -205,12 +205,13 @@ class Compiler(
operand = Accessor { cxt ->
val list = mutableListOf<Obj>()
for (e in entries) {
when(e) {
when (e) {
is ListEntry.Element -> {
list += e.accessor.getter(cxt).value
}
is ListEntry.Spread -> {
val elements=e.accessor.getter(cxt).value
val elements = e.accessor.getter(cxt).value
when {
elements is ObjList -> list.addAll(elements.list)
else -> cxt.raiseError("Spread element must be list")
@@ -315,16 +316,18 @@ class Compiler(
private fun parseArrayLiteral(cc: CompilerContext): List<ListEntry> {
// it should be called after LBRACKET is consumed
val entries = mutableListOf<ListEntry>()
while(true) {
while (true) {
val t = cc.next()
when(t.type) {
when (t.type) {
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,13 +379,14 @@ class Compiler(
return Accessor { context ->
val v = left.getter(context)
v.value.callOn(context.copy(
context.pos,
args.toArguments(context)
v.value.callOn(
context.copy(
context.pos,
args.toArguments(context)
// Arguments(
// args.map { Arguments.Info((it.value as Statement).execute(context), it.pos) }
// ),
)
)
).asReadonly
}
}
@@ -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
@@ -6,9 +6,9 @@ class ObjList(val list: MutableList<Obj>) : Obj() {
list.joinToString(separator = ", ") { it.inspect() }
}]"
fun normalize(context: Context, index: Int,allowInclusiveEnd: Boolean = false): Int {
fun normalize(context: Context, index: Int, allowInclusiveEnd: Boolean = false): Int {
val i = if (index < 0) list.size + index else index
if( allowInclusiveEnd && i == list.size ) return i
if (allowInclusiveEnd && i == list.size) return i
if (i !in list.indices) context.raiseError("index $index out of bounds for size ${list.size}")
return i
}
@@ -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 {
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()
self.list.subList(start, self.normalize(this, end)).clear()
} else
self.list.removeAt(start)
self
})
addFn("removeAt") {
val self = thisAs<ObjList>()
val start = self.normalize(this, requiredArg<ObjInt>(0).value.toInt())
if (args.size == 2) {
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
}
}
}
}
@@ -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()) }
)
}
}
}