+better comparison

+splat arguments in calls
This commit is contained in:
Sergey Chernov 2025-05-30 12:05:21 +04:00
parent b56b5c521d
commit 1479df4426
6 changed files with 83 additions and 29 deletions

View File

@ -39,6 +39,7 @@ __Important__ negative indexes works wherever indexes are used, e.g. in insertio
assert( [1, 2, 3] > [1, 2]) assert( [1, 2, 3] > [1, 2])
assert( [1, 3] > [1, 2, 3]) assert( [1, 3] > [1, 2, 3])
assert( [1, 2, 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: // note that in the case above objects are referentially different:
assert( [1, 2, 3] !== [1, 2, 3]) assert( [1, 2, 3] !== [1, 2, 3])
>>> void >>> void

View File

@ -342,6 +342,27 @@ You can insert elements at any position using `addAt`:
assert( x == [1, "foo", "bar", 2, 3]) assert( x == [1, "foo", "bar", 2, 3])
>>> void >>> 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 ## Removing list items
val x = [1, 2, 3, 4, 5] val x = [1, 2, 3, 4, 5]
@ -352,6 +373,21 @@ You can insert elements at any position using `addAt`:
assert( x == [1, 5]) assert( x == [1, 5])
>>> void >>> 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 # Flow control operators
## if-then-else ## if-then-else

View File

@ -1,15 +1,31 @@
package net.sergeych.ling package net.sergeych.ling
data class Arguments(val list: List<Info>): Iterable<Obj> { 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<ParsedArgument>.toArguments(context: Context): Arguments {
val list = mutableListOf<Arguments.Info>()
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<Info>) : Iterable<Obj> {
data class Info(val value: Obj, val pos: Pos)
val size by list::size val size by list::size
operator fun get(index: Int): Obj = list[index].value operator fun get(index: Int): Obj = list[index].value
fun firstAndOnly(): Obj { 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 return list.first().value
} }
@ -20,4 +36,6 @@ data class Arguments(val list: List<Info>): Iterable<Obj> {
override fun iterator(): Iterator<Obj> { override fun iterator(): Iterator<Obj> {
return list.map { it.value }.iterator() return list.map { it.value }.iterator()
} }
} }

View File

@ -148,10 +148,7 @@ class Compiler(
v.callInstanceMethod( v.callInstanceMethod(
context, context,
next.value, next.value,
Arguments(args.map { args.toArguments(context)
val st = it.value as Statement
Arguments.Info(st.execute(context),it.pos) }
)
), isMutable = false ), isMutable = false
) )
} }
@ -275,11 +272,11 @@ class Compiler(
} ?: run { } ?: run {
// no lvalue means pre-increment, expression to increment follows // no lvalue means pre-increment, expression to increment follows
val next = parseAccessor(cc) ?: throw ScriptError(t.pos, "Expecting expression") val next = parseAccessor(cc) ?: throw ScriptError(t.pos, "Expecting expression")
operand = Accessor({ ctx -> operand = Accessor { ctx ->
next.getter(ctx).also { next.getter(ctx).also {
if (!it.isMutable) ctx.raiseError("Cannot increment immutable value") if (!it.isMutable) ctx.raiseError("Cannot increment immutable value")
}.value.incrementAndGet(ctx).asReadonly }.value.incrementAndGet(ctx).asReadonly
}) }
} }
} }
@ -351,15 +348,19 @@ class Compiler(
} }
} }
private fun parseArgs(cc: CompilerContext): List<Arguments.Info> { private fun parseArgs(cc: CompilerContext): List<ParsedArgument> {
val args = mutableListOf<Arguments.Info>() val args = mutableListOf<ParsedArgument>()
do { do {
val t = cc.next() val t = cc.next()
when (t.type) { when (t.type) {
Token.Type.RPAREN, Token.Type.COMMA -> {} 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 -> { else -> {
cc.previous() 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") ?: throw ScriptError(t.pos, "Expecting arguments list")
} }
} }
@ -376,9 +377,10 @@ class Compiler(
val v = left.getter(context) val v = left.getter(context)
v.value.callOn(context.copy( v.value.callOn(context.copy(
context.pos, context.pos,
Arguments( args.toArguments(context)
args.map { Arguments.Info((it.value as Statement).execute(context), it.pos) } // Arguments(
), // args.map { Arguments.Info((it.value as Statement).execute(context), it.pos) }
// ),
) )
).asReadonly ).asReadonly
} }

View File

@ -55,11 +55,6 @@ sealed class Obj {
?: throw ScriptError(atPos, "symbol doesn't exist: $name") ?: throw ScriptError(atPos, "symbol doesn't exist: $name")
suspend fun callInstanceMethod(context: Context, name: String, args: Arguments): Obj = 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 // note that getInstanceMember traverses the hierarchy
objClass.getInstanceMember(context.pos, name).value.invoke(context, this, args) objClass.getInstanceMember(context.pos, name).value.invoke(context, this, args)
@ -154,7 +149,7 @@ sealed class Obj {
// could be property or class field: // could be property or class field:
val obj = objClass.getInstanceMemberOrNull(name) val obj = objClass.getInstanceMemberOrNull(name)
val value = obj?.value val value = obj?.value
return when(value) { return when (value) {
is Statement -> { is Statement -> {
// readonly property, important: call it on this // readonly property, important: call it on this
value.execute(context.copy(context.pos, newThisObj = this)).asReadonly value.execute(context.copy(context.pos, newThisObj = this)).asReadonly
@ -253,7 +248,7 @@ object ObjNull : Obj() {
data class ObjString(val value: String) : Obj() { data class ObjString(val value: String) : Obj() {
override suspend fun compareTo(context: Context, other: Obj): Int { 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) 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 fun byValueCopy(): Obj = ObjReal(value)
override suspend fun compareTo(context: Context, other: Obj): Int { 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) 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 { 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) return value.compareTo(other.doubleValue)
} }
@ -412,7 +407,7 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
* assignment * assignment
*/ */
override suspend fun assign(context: Context, other: Obj): Obj? { override suspend fun assign(context: Context, other: Obj): Obj? {
return if( other is ObjInt) { return if (other is ObjInt) {
value = other.value value = other.value
this this
} else null } else null
@ -427,7 +422,7 @@ data class ObjBool(val value: Boolean) : Obj() {
override val asStr by lazy { ObjString(value.toString()) } override val asStr by lazy { ObjString(value.toString()) }
override suspend fun compareTo(context: Context, other: Obj): Int { 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) return value.compareTo(other.value)
} }

View File

@ -6,8 +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): 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 (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
} }
@ -23,7 +24,7 @@ class ObjList(val list: MutableList<Obj>) : Obj() {
} }
override suspend fun compareTo(context: Context, other: Obj): Int { 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 mySize = list.size
val otherSize = other.list.size val otherSize = other.list.size
val commonSize = minOf(mySize, otherSize) val commonSize = minOf(mySize, otherSize)
@ -72,7 +73,8 @@ 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(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
} }