while-else loop and string.startsWith
This commit is contained in:
parent
f3b3ebbb52
commit
f7b35c7576
24
docs/List.md
24
docs/List.md
@ -46,11 +46,19 @@ __Important__ negative indexes works wherever indexes are used, e.g. in insertio
|
|||||||
|
|
||||||
## Members
|
## Members
|
||||||
|
|
||||||
| name | meaning | type |
|
| name | meaning | type |
|
||||||
|----------------------------|----------------------------------------------|----------|
|
|-----------------------------------|-------------------------------------|----------|
|
||||||
| `size` | current size | Int |
|
| `size` | current size | Int |
|
||||||
| `add(elements...)` | add one or more elements to the end | Any |
|
| `add(elements...)` | add one or more elements to the end | Any |
|
||||||
| `addAt(index,elements...)` | insert elements at position | Int, Any |
|
| `addAt(index,elements...)` | insert elements at position | Int, Any |
|
||||||
| `removeAt(index)` | remove element at position | Int |
|
| `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)
|
x.removeAt(2)
|
||||||
assert( x == [1, 2, 4, 5])
|
assert( x == [1, 2, 4, 5])
|
||||||
// or remove range (start inclusive, end exclusive):
|
// or remove range (start inclusive, end exclusive):
|
||||||
x.removeAt(1,3)
|
x.removeRangeInclusive(1,2)
|
||||||
assert( x == [1, 5])
|
assert( x == [1, 5])
|
||||||
>>> void
|
>>> void
|
||||||
|
|
||||||
@ -390,9 +390,9 @@ Again, you can use negative indexes. For example, removing last elements like:
|
|||||||
x.removeAt(-1)
|
x.removeAt(-1)
|
||||||
assert( x == [1, 2, 3, 4])
|
assert( x == [1, 2, 3, 4])
|
||||||
|
|
||||||
// remove 3 last:
|
// remove 2 last:
|
||||||
x.removeAt(-3,1)
|
x.removeRangeInclusive(-2,-1)
|
||||||
assert( x == [1])
|
assert( x == [1, 2])
|
||||||
>>> void
|
>>> void
|
||||||
|
|
||||||
# Flow control operators
|
# 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.
|
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
|
# Self-assignments in expression
|
||||||
|
|
||||||
There are auto-increments and auto-decrements:
|
There are auto-increments and auto-decrements:
|
||||||
|
@ -205,12 +205,13 @@ class Compiler(
|
|||||||
operand = Accessor { cxt ->
|
operand = Accessor { cxt ->
|
||||||
val list = mutableListOf<Obj>()
|
val list = mutableListOf<Obj>()
|
||||||
for (e in entries) {
|
for (e in entries) {
|
||||||
when(e) {
|
when (e) {
|
||||||
is ListEntry.Element -> {
|
is ListEntry.Element -> {
|
||||||
list += e.accessor.getter(cxt).value
|
list += e.accessor.getter(cxt).value
|
||||||
}
|
}
|
||||||
|
|
||||||
is ListEntry.Spread -> {
|
is ListEntry.Spread -> {
|
||||||
val elements=e.accessor.getter(cxt).value
|
val elements = e.accessor.getter(cxt).value
|
||||||
when {
|
when {
|
||||||
elements is ObjList -> list.addAll(elements.list)
|
elements is ObjList -> list.addAll(elements.list)
|
||||||
else -> cxt.raiseError("Spread element must be list")
|
else -> cxt.raiseError("Spread element must be list")
|
||||||
@ -315,16 +316,18 @@ class Compiler(
|
|||||||
private fun parseArrayLiteral(cc: CompilerContext): List<ListEntry> {
|
private fun parseArrayLiteral(cc: CompilerContext): List<ListEntry> {
|
||||||
// it should be called after LBRACKET is consumed
|
// it should be called after LBRACKET is consumed
|
||||||
val entries = mutableListOf<ListEntry>()
|
val entries = mutableListOf<ListEntry>()
|
||||||
while(true) {
|
while (true) {
|
||||||
val t = cc.next()
|
val t = cc.next()
|
||||||
when(t.type) {
|
when (t.type) {
|
||||||
Token.Type.COMMA -> {
|
Token.Type.COMMA -> {
|
||||||
// todo: check commas sequences like [,] [,,] before, after or instead of expressions
|
// todo: check commas sequences like [,] [,,] before, after or instead of expressions
|
||||||
}
|
}
|
||||||
|
|
||||||
Token.Type.RBRACKET -> return entries
|
Token.Type.RBRACKET -> return entries
|
||||||
Token.Type.ELLIPSIS -> {
|
Token.Type.ELLIPSIS -> {
|
||||||
parseExpressionLevel(cc)?.let { entries += ListEntry.Spread(it) }
|
parseExpressionLevel(cc)?.let { entries += ListEntry.Spread(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
cc.previous()
|
cc.previous()
|
||||||
parseExpressionLevel(cc)?.let { entries += ListEntry.Element(it) }
|
parseExpressionLevel(cc)?.let { entries += ListEntry.Element(it) }
|
||||||
@ -358,6 +361,7 @@ class Compiler(
|
|||||||
parseStatement(cc)?.let { args += ParsedArgument(it, t.pos, isSplat = true) }
|
parseStatement(cc)?.let { args += ParsedArgument(it, t.pos, isSplat = true) }
|
||||||
?: throw ScriptError(t.pos, "Expecting arguments list")
|
?: throw ScriptError(t.pos, "Expecting arguments list")
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
cc.previous()
|
cc.previous()
|
||||||
parseStatement(cc)?.let { args += ParsedArgument(it, t.pos) }
|
parseStatement(cc)?.let { args += ParsedArgument(it, t.pos) }
|
||||||
@ -375,13 +379,14 @@ class Compiler(
|
|||||||
|
|
||||||
return Accessor { context ->
|
return Accessor { context ->
|
||||||
val v = left.getter(context)
|
val v = left.getter(context)
|
||||||
v.value.callOn(context.copy(
|
v.value.callOn(
|
||||||
context.pos,
|
context.copy(
|
||||||
args.toArguments(context)
|
context.pos,
|
||||||
|
args.toArguments(context)
|
||||||
// Arguments(
|
// Arguments(
|
||||||
// args.map { Arguments.Info((it.value as Statement).execute(context), it.pos) }
|
// args.map { Arguments.Info((it.value as Statement).execute(context), it.pos) }
|
||||||
// ),
|
// ),
|
||||||
)
|
)
|
||||||
).asReadonly
|
).asReadonly
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -496,6 +501,13 @@ class Compiler(
|
|||||||
val body = parseStatement(cc) ?: throw ScriptError(start, "Bad while statement: expected statement")
|
val body = parseStatement(cc) ?: throw ScriptError(start, "Bad while statement: expected statement")
|
||||||
label?.also { cc.labels -= it }
|
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) {
|
return statement(body.pos) {
|
||||||
var result: Obj = ObjVoid
|
var result: Obj = ObjVoid
|
||||||
while (condition.execute(it).toBool()) {
|
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,
|
// 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:
|
// parse block will do it, otherwise single statement doesn't need it:
|
||||||
result = body.execute(it)
|
result = body.execute(it)
|
||||||
|
elseStatement?.let { s -> result = s.execute(it) }
|
||||||
} catch (lbe: LoopBreakContinueException) {
|
} catch (lbe: LoopBreakContinueException) {
|
||||||
if (lbe.label == label || lbe.label == null) {
|
if (lbe.label == label || lbe.label == null) {
|
||||||
if (lbe.doContinue) continue
|
if (lbe.doContinue) continue
|
||||||
|
@ -33,6 +33,17 @@ class Context(
|
|||||||
?: raiseClassCastError("Expected type ${T::class.simpleName}, got ${args.list[index].value::class.simpleName}")
|
?: 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)
|
inline fun <reified T: Obj>thisAs(): T = (thisObj as? T)
|
||||||
?: raiseClassCastError("Cannot cast ${thisObj.objClass.className} to ${T::class.simpleName}")
|
?: raiseClassCastError("Cannot cast ${thisObj.objClass.className} to ${T::class.simpleName}")
|
||||||
|
|
||||||
|
@ -179,6 +179,10 @@ sealed class Obj {
|
|||||||
members[name] = WithAccess(initialValue, isMutable)
|
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)
|
fun addConst(name: String, value: Obj) = createField(name, value, isMutable = false)
|
||||||
|
|
||||||
open suspend fun callOn(context: Context): Obj {
|
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 {
|
interface Numeric {
|
||||||
val longValue: Long
|
val longValue: Long
|
||||||
val doubleValue: Double
|
val doubleValue: Double
|
||||||
|
@ -6,9 +6,9 @@ class ObjList(val list: MutableList<Obj>) : Obj() {
|
|||||||
list.joinToString(separator = ", ") { it.inspect() }
|
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
|
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}")
|
if (i !in list.indices) context.raiseError("index $index out of bounds for size ${list.size}")
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
@ -73,23 +73,31 @@ class ObjList(val list: MutableList<Obj>) : Obj() {
|
|||||||
statement {
|
statement {
|
||||||
if (args.size < 2) raiseError("addAt takes 2+ arguments")
|
if (args.size < 2) raiseError("addAt takes 2+ arguments")
|
||||||
val l = thisAs<ObjList>()
|
val l = thisAs<ObjList>()
|
||||||
var index = l.normalize(this, requiredArg<ObjInt>(0).value.toInt(),
|
var index = l.normalize(
|
||||||
allowInclusiveEnd = true)
|
this, requiredArg<ObjInt>(0).value.toInt(),
|
||||||
|
allowInclusiveEnd = true
|
||||||
|
)
|
||||||
for (i in 1..<args.size) l.list.add(index++, args[i])
|
for (i in 1..<args.size) l.list.add(index++, args[i])
|
||||||
ObjVoid
|
ObjVoid
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
createField("removeAt",
|
addFn("removeAt") {
|
||||||
statement {
|
val self = thisAs<ObjList>()
|
||||||
val self = thisAs<ObjList>()
|
val start = self.normalize(this, requiredArg<ObjInt>(0).value.toInt())
|
||||||
val start = self.normalize(this, requiredArg<ObjInt>(0).value.toInt())
|
if (args.size == 2) {
|
||||||
if (args.size == 2) {
|
val end = requireOnlyArg<ObjInt>().value.toInt()
|
||||||
val end = requiredArg<ObjInt>(1).value.toInt()
|
self.list.subList(start, self.normalize(this, end)).clear()
|
||||||
self.list.subList(start, self.normalize(this, end)).clear()
|
} else
|
||||||
} else
|
self.list.removeAt(start)
|
||||||
self.list.removeAt(start)
|
self
|
||||||
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