diff --git a/docs/List.md b/docs/List.md index f3ab0cd..8272f4f 100644 --- a/docs/List.md +++ b/docs/List.md @@ -39,6 +39,7 @@ __Important__ negative indexes works wherever indexes are used, e.g. in insertio assert( [1, 2, 3] > [1, 2]) assert( [1, 3] > [1, 2, 3]) assert( [1, 2, 3] == [1, 2, 3]) + assert( [1, 2, 3] != [1, 2, "three"]) // note that in the case above objects are referentially different: assert( [1, 2, 3] !== [1, 2, 3]) >>> void diff --git a/docs/tutorial.md b/docs/tutorial.md index e6a1aa1..bf756f0 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -342,6 +342,27 @@ You can insert elements at any position using `addAt`: assert( x == [1, "foo", "bar", 2, 3]) >>> void +Using splat arguments can simplify inserting list in list: + + val x = [1, 2, 3] + x.addAt( 1, ...[0,100,0]) + x + >>> [1, 0, 100, 0, 2, 3] + +Using negative indexes can insert elements as offset from the end, for example: + + val x = [1,2,3] + x.addAt(-1, 10) + x + >>> [1, 2, 10, 3] + +Note that to add to the end you still need to use `add` or positive index of the after-last element: + + val x = [1,2,3] + x.addAt(3, 10) + x + >>> [1, 2, 3, 10] + ## Removing list items val x = [1, 2, 3, 4, 5] @@ -352,6 +373,21 @@ You can insert elements at any position using `addAt`: assert( x == [1, 5]) >>> void +Again, you can use negative indexes. For example, removing last elements like: + + val x = [1, 2, 3, 4, 5] + + // remove last: + x.removeAt(-1) + assert( x == [1, 2, 3, 4]) + + // remove 3 last: + x.removeAt(-3,1) + assert( x == [1]) + >>> void + + + # Flow control operators ## if-then-else diff --git a/library/src/commonMain/kotlin/net/sergeych/ling/Arguments.kt b/library/src/commonMain/kotlin/net/sergeych/ling/Arguments.kt index 5fb79fa..2cc1431 100644 --- a/library/src/commonMain/kotlin/net/sergeych/ling/Arguments.kt +++ b/library/src/commonMain/kotlin/net/sergeych/ling/Arguments.kt @@ -1,15 +1,31 @@ package net.sergeych.ling -data class Arguments(val list: List): Iterable { +data class ParsedArgument(val value: Statement, val pos: Pos, val isSplat: Boolean = false) - data class Info(val value: Obj,val pos: Pos) +suspend fun Collection.toArguments(context: Context): Arguments { + val list = mutableListOf() + + for (x in this) { + val value = x.value.execute(context) + if (x.isSplat) { + (value as? ObjList) ?: context.raiseClassCastError("expected list of objects for splat argument") + for (subitem in value.list) list.add(Arguments.Info(subitem, x.pos)) + } else + list.add(Arguments.Info(value, x.pos)) + } + return Arguments(list) +} + +data class Arguments(val list: List) : Iterable { + + data class Info(val value: Obj, val pos: Pos) val size by list::size operator fun get(index: Int): Obj = list[index].value fun firstAndOnly(): Obj { - if( list.size != 1 ) throw IllegalArgumentException("Expected one argument, got ${list.size}") + if (list.size != 1) throw IllegalArgumentException("Expected one argument, got ${list.size}") return list.first().value } @@ -20,4 +36,6 @@ data class Arguments(val list: List): Iterable { override fun iterator(): Iterator { return list.map { it.value }.iterator() } + } + diff --git a/library/src/commonMain/kotlin/net/sergeych/ling/Compiler.kt b/library/src/commonMain/kotlin/net/sergeych/ling/Compiler.kt index 2068f03..918621b 100644 --- a/library/src/commonMain/kotlin/net/sergeych/ling/Compiler.kt +++ b/library/src/commonMain/kotlin/net/sergeych/ling/Compiler.kt @@ -148,10 +148,7 @@ class Compiler( v.callInstanceMethod( context, next.value, - Arguments(args.map { - val st = it.value as Statement - Arguments.Info(st.execute(context),it.pos) } - ) + args.toArguments(context) ), isMutable = false ) } @@ -275,11 +272,11 @@ class Compiler( } ?: run { // no lvalue means pre-increment, expression to increment follows val next = parseAccessor(cc) ?: throw ScriptError(t.pos, "Expecting expression") - operand = Accessor({ ctx -> + operand = Accessor { ctx -> next.getter(ctx).also { if (!it.isMutable) ctx.raiseError("Cannot increment immutable value") }.value.incrementAndGet(ctx).asReadonly - }) + } } } @@ -351,15 +348,19 @@ class Compiler( } } - private fun parseArgs(cc: CompilerContext): List { - val args = mutableListOf() + private fun parseArgs(cc: CompilerContext): List { + val args = mutableListOf() do { val t = cc.next() when (t.type) { Token.Type.RPAREN, Token.Type.COMMA -> {} + Token.Type.ELLIPSIS -> { + parseStatement(cc)?.let { args += ParsedArgument(it, t.pos, isSplat = true) } + ?: throw ScriptError(t.pos, "Expecting arguments list") + } else -> { cc.previous() - parseStatement(cc)?.let { args += Arguments.Info(it, t.pos) } + parseStatement(cc)?.let { args += ParsedArgument(it, t.pos) } ?: throw ScriptError(t.pos, "Expecting arguments list") } } @@ -376,9 +377,10 @@ class Compiler( val v = left.getter(context) v.value.callOn(context.copy( context.pos, - Arguments( - args.map { Arguments.Info((it.value as Statement).execute(context), it.pos) } - ), + args.toArguments(context) +// Arguments( +// args.map { Arguments.Info((it.value as Statement).execute(context), it.pos) } +// ), ) ).asReadonly } diff --git a/library/src/commonMain/kotlin/net/sergeych/ling/Obj.kt b/library/src/commonMain/kotlin/net/sergeych/ling/Obj.kt index 4bfdadc..d0eb181 100644 --- a/library/src/commonMain/kotlin/net/sergeych/ling/Obj.kt +++ b/library/src/commonMain/kotlin/net/sergeych/ling/Obj.kt @@ -55,11 +55,6 @@ sealed class Obj { ?: throw ScriptError(atPos, "symbol doesn't exist: $name") suspend fun callInstanceMethod(context: Context, name: String, args: Arguments): Obj = - // instance _methods_ are our ObjClass instance: - // note that getInstanceMember traverses the hierarchy - // instance _methods_ are our ObjClass instance: - // note that getInstanceMember traverses the hierarchy -// instance _methods_ are our ObjClass instance: // note that getInstanceMember traverses the hierarchy objClass.getInstanceMember(context.pos, name).value.invoke(context, this, args) @@ -154,7 +149,7 @@ sealed class Obj { // could be property or class field: val obj = objClass.getInstanceMemberOrNull(name) val value = obj?.value - return when(value) { + return when (value) { is Statement -> { // readonly property, important: call it on this value.execute(context.copy(context.pos, newThisObj = this)).asReadonly @@ -253,7 +248,7 @@ object ObjNull : Obj() { data class ObjString(val value: String) : Obj() { override suspend fun compareTo(context: Context, other: Obj): Int { - if (other !is ObjString) context.raiseError("cannot compare string with $other") + if (other !is ObjString) return -2 return this.value.compareTo(other.value) } @@ -311,7 +306,7 @@ data class ObjReal(val value: Double) : Obj(), Numeric { override fun byValueCopy(): Obj = ObjReal(value) override suspend fun compareTo(context: Context, other: Obj): Int { - if (other !is Numeric) context.raiseError("cannot compare $this with $other") + if (other !is Numeric) return -2 return value.compareTo(other.doubleValue) } @@ -372,7 +367,7 @@ data class ObjInt(var value: Long) : Obj(), Numeric { } override suspend fun compareTo(context: Context, other: Obj): Int { - if (other !is Numeric) context.raiseError("cannot compare $this with $other") + if (other !is Numeric) return -2 return value.compareTo(other.doubleValue) } @@ -412,7 +407,7 @@ data class ObjInt(var value: Long) : Obj(), Numeric { * assignment */ override suspend fun assign(context: Context, other: Obj): Obj? { - return if( other is ObjInt) { + return if (other is ObjInt) { value = other.value this } else null @@ -427,7 +422,7 @@ data class ObjBool(val value: Boolean) : Obj() { override val asStr by lazy { ObjString(value.toString()) } override suspend fun compareTo(context: Context, other: Obj): Int { - if (other !is ObjBool) context.raiseError("cannot compare $this with $other") + if (other !is ObjBool) return -2 return value.compareTo(other.value) } diff --git a/library/src/commonMain/kotlin/net/sergeych/ling/ObjList.kt b/library/src/commonMain/kotlin/net/sergeych/ling/ObjList.kt index ce5e0bf..cfa09bb 100644 --- a/library/src/commonMain/kotlin/net/sergeych/ling/ObjList.kt +++ b/library/src/commonMain/kotlin/net/sergeych/ling/ObjList.kt @@ -6,8 +6,9 @@ class ObjList(val list: MutableList) : Obj() { list.joinToString(separator = ", ") { it.inspect() } }]" - fun normalize(context: Context, index: Int): 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 (i !in list.indices) context.raiseError("index $index out of bounds for size ${list.size}") return i } @@ -23,7 +24,7 @@ class ObjList(val list: MutableList) : Obj() { } override suspend fun compareTo(context: Context, other: Obj): Int { - if (other !is ObjList) context.raiseError("cannot compare $this with $other") + if (other !is ObjList) return -2 val mySize = list.size val otherSize = other.list.size val commonSize = minOf(mySize, otherSize) @@ -72,7 +73,8 @@ 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()) + var index = l.normalize(this, requiredArg(0).value.toInt(), + allowInclusiveEnd = true) for (i in 1..