diff --git a/docs/List.md b/docs/List.md index 8272f4f..7482c4b 100644 --- a/docs/List.md +++ b/docs/List.md @@ -46,11 +46,19 @@ __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 | -| | | | +| 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 | +| `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 diff --git a/docs/tutorial.md b/docs/tutorial.md index dcbc934..202d34e 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -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: diff --git a/library/src/commonMain/kotlin/net/sergeych/ling/Compiler.kt b/library/src/commonMain/kotlin/net/sergeych/ling/Compiler.kt index 918621b..0d6d03c 100644 --- a/library/src/commonMain/kotlin/net/sergeych/ling/Compiler.kt +++ b/library/src/commonMain/kotlin/net/sergeych/ling/Compiler.kt @@ -205,12 +205,13 @@ class Compiler( operand = Accessor { cxt -> val list = mutableListOf() 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 { // it should be called after LBRACKET is consumed val entries = mutableListOf() - 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 diff --git a/library/src/commonMain/kotlin/net/sergeych/ling/Context.kt b/library/src/commonMain/kotlin/net/sergeych/ling/Context.kt index a1ec5df..1d42d4c 100644 --- a/library/src/commonMain/kotlin/net/sergeych/ling/Context.kt +++ b/library/src/commonMain/kotlin/net/sergeych/ling/Context.kt @@ -33,6 +33,17 @@ class Context( ?: raiseClassCastError("Expected type ${T::class.simpleName}, got ${args.list[index].value::class.simpleName}") } + inline fun 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 thisAs(): T = (thisObj as? T) ?: raiseClassCastError("Cannot cast ${thisObj.objClass.className} to ${T::class.simpleName}") diff --git a/library/src/commonMain/kotlin/net/sergeych/ling/Obj.kt b/library/src/commonMain/kotlin/net/sergeych/ling/Obj.kt index d0eb181..dfff099 100644 --- a/library/src/commonMain/kotlin/net/sergeych/ling/Obj.kt +++ b/library/src/commonMain/kotlin/net/sergeych/ling/Obj.kt @@ -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 diff --git a/library/src/commonMain/kotlin/net/sergeych/ling/ObjList.kt b/library/src/commonMain/kotlin/net/sergeych/ling/ObjList.kt index cfa09bb..23f992f 100644 --- a/library/src/commonMain/kotlin/net/sergeych/ling/ObjList.kt +++ b/library/src/commonMain/kotlin/net/sergeych/ling/ObjList.kt @@ -6,9 +6,9 @@ class ObjList(val list: MutableList) : 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() { statement { if (args.size < 2) raiseError("addAt takes 2+ arguments") val l = thisAs() - var index = l.normalize(this, requiredArg(0).value.toInt(), - allowInclusiveEnd = true) + var index = l.normalize( + this, requiredArg(0).value.toInt(), + allowInclusiveEnd = true + ) for (i in 1..() - val start = self.normalize(this, requiredArg(0).value.toInt()) - if (args.size == 2) { - val end = requiredArg(1).value.toInt() - self.list.subList(start, self.normalize(this, end)).clear() - } else - self.list.removeAt(start) - self - }) + addFn("removeAt") { + val self = thisAs() + val start = self.normalize(this, requiredArg(0).value.toInt()) + if (args.size == 2) { + val end = requireOnlyArg().value.toInt() + self.list.subList(start, self.normalize(this, end)).clear() + } else + self.list.removeAt(start) + self + } + addFn("removeRangeInclusive") { + val self = thisAs() + val start = self.normalize(this, requiredArg(0).value.toInt()) + val end = self.normalize(this, requiredArg(1).value.toInt()) + 1 + self.list.subList(start, end).clear() + self + } } } } \ No newline at end of file diff --git a/library/src/commonMain/kotlin/net/sergeych/ling/ObjString.kt b/library/src/commonMain/kotlin/net/sergeych/ling/ObjString.kt new file mode 100644 index 0000000..3709c1a --- /dev/null +++ b/library/src/commonMain/kotlin/net/sergeych/ling/ObjString.kt @@ -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().value.startsWith(requiredArg(0).value)) + }) + addConst("length", + statement { ObjInt(thisAs().value.length.toLong()) } + ) + } + } +} \ No newline at end of file