+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, 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

View File

@ -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

View File

@ -1,15 +1,31 @@
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
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<Info>): Iterable<Obj> {
override fun iterator(): Iterator<Obj> {
return list.map { it.value }.iterator()
}
}

View File

@ -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<Arguments.Info> {
val args = mutableListOf<Arguments.Info>()
private fun parseArgs(cc: CompilerContext): List<ParsedArgument> {
val args = mutableListOf<ParsedArgument>()
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
}

View File

@ -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)
}

View File

@ -6,8 +6,9 @@ class ObjList(val list: MutableList<Obj>) : 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>) : 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>) : 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())
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
}