fix #58 improper toString overload processing in Lyng

fix #57 Exception#getStackTrace()
ref #56 StackTraceEntry is serializable now
This commit is contained in:
Sergey Chernov 2025-08-22 00:01:59 +03:00
parent 8d1cafae80
commit 4b613fda7c
30 changed files with 250 additions and 119 deletions

View File

@ -24,7 +24,7 @@ There is a shortcut for the last:
val list = [10, 20, 30] val list = [10, 20, 30]
[list.last, list.lastIndex] [list.last, list.lastIndex]
>>> [30, 2] >>> [30,2]
__Important__ negative indexes works wherever indexes are used, e.g. in insertion and removal methods too. __Important__ negative indexes works wherever indexes are used, e.g. in insertion and removal methods too.

View File

@ -87,7 +87,7 @@ You can use Char as both ends of the closed range:
Exclusive end char ranges are supported too: Exclusive end char ranges are supported too:
('a'..<'c').toList ('a'..<'c').toList
>>> ['a', 'b'] >>> [a,b]
# Instance members # Instance members

View File

@ -71,7 +71,7 @@ destructuring arrays when calling functions and lambdas:
[ first, last ] [ first, last ]
} }
getFirstAndLast( ...(1..10) ) // see "splats" section below getFirstAndLast( ...(1..10) ) // see "splats" section below
>>> [1, 10] >>> [1,10]
# Splats # Splats
@ -83,7 +83,7 @@ or whatever implementing [Iterable], is called _splats_. Here is how we use it:
} }
val array = [1,2,3] val array = [1,2,3]
testSplat("start", ...array, "end") testSplat("start", ...array, "end")
>>> ["start", 1, 2, 3, "end"] >>> [start,1,2,3,end]
>>> void >>> void
There could be any number of splats at any positions. You can splat any other [Iterable] type: There could be any number of splats at any positions. You can splat any other [Iterable] type:
@ -93,7 +93,7 @@ There could be any number of splats at any positions. You can splat any other [I
} }
val range = 1..3 val range = 1..3
testSplat("start", ...range, "end") testSplat("start", ...range, "end")
>>> ["start", 1, 2, 3, "end"] >>> [start,1,2,3,end]
>>> void >>> void

View File

@ -183,8 +183,8 @@ Important difference from the channels or like, every time you collect the flow,
// and again: // and again:
assertEquals( result, f.toList() ) assertEquals( result, f.toList() )
>>> ["start", 1, 2, 3, 4] >>> [start,1,2,3,4]
>>> ["start", 1, 2, 3, 4] >>> [start,1,2,3,4]
>>> void >>> void
Notice that flow's lambda is not called until actual collection is started. Cold flows are Notice that flow's lambda is not called until actual collection is started. Cold flows are

View File

@ -505,21 +505,21 @@ Notice usage of indexing. You can use negative indexes to offset from the end of
When you want to "flatten" it to single array, you can use splat syntax: When you want to "flatten" it to single array, you can use splat syntax:
[1, ...[2,3], 4] [1, ...[2,3], 4]
>>> [1, 2, 3, 4] >>> [1,2,3,4]
Of course, you can splat from anything that is List (or list-like, but it will be defined later): Of course, you can splat from anything that is List (or list-like, but it will be defined later):
val a = ["one", "two"] val a = ["one", "two"]
val b = [10.1, 20.2] val b = [10.1, 20.2]
["start", ...b, ...a, "end"] ["start", ...b, ...a, "end"]
>>> ["start", 10.1, 20.2, "one", "two", "end"] >>> [start,10.1,20.2,one,two,end]
Of course, you can set any list element: Of course, you can set any list element:
val a = [1, 2, 3] val a = [1, 2, 3]
a[1] = 200 a[1] = 200
a a
>>> [1, 200, 3] >>> [1,200,3]
Lists are comparable, and it works well as long as their respective elements are: Lists are comparable, and it works well as long as their respective elements are:
@ -609,20 +609,20 @@ Using splat arguments can simplify inserting list in list:
val x = [1, 2, 3] val x = [1, 2, 3]
x.insertAt( 1, ...[0,100,0]) x.insertAt( 1, ...[0,100,0])
x x
>>> [1, 0, 100, 0, 2, 3] >>> [1,0,100,0,2,3]
Note that to add to the end you still need to use `add` or positive index of the after-last element: 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] val x = [1,2,3]
x.insertAt(3, 10) x.insertAt(3, 10)
x x
>>> [1, 2, 3, 10] >>> [1,2,3,10]
but it is much simpler, and we recommend to use '+=' but it is much simpler, and we recommend to use '+='
val x = [1,2,3] val x = [1,2,3]
x += 10 x += 10
>>> [1, 2, 3, 10] >>> [1,2,3,10]
## Removing list items ## Removing list items

View File

@ -63,7 +63,7 @@ data class Arguments(val list: List<Obj>, val tailBlockMode: Boolean = false) :
return list.map { it.toKotlin(scope) } return list.map { it.toKotlin(scope) }
} }
fun inspect(): String = list.joinToString(", ") { it.inspect() } suspend fun inspect(scope: Scope): String = list.map{ it.inspect(scope)}.joinToString(",")
companion object { companion object {
val EMPTY = Arguments(emptyList()) val EMPTY = Arguments(emptyList())

View File

@ -1363,8 +1363,8 @@ class Compiler(
} }
return statement(body.pos) { ctx -> return statement(body.pos) { cxt ->
val forContext = ctx.copy(start) val forContext = cxt.copy(start)
// loop var: StoredObject // loop var: StoredObject
val loopSO = forContext.addItem(tVar.value, true, ObjNull) val loopSO = forContext.addItem(tVar.value, true, ObjNull)
@ -1393,7 +1393,7 @@ class Compiler(
.getOrElse { .getOrElse {
throw ScriptError( throw ScriptError(
tOp.pos, tOp.pos,
"object is not enumerable: no index access for ${sourceObj.inspect()}", "object is not enumerable: no index access for ${sourceObj.inspect(cxt)}",
it it
) )
} }
@ -1420,7 +1420,7 @@ class Compiler(
} }
} }
if (!breakCaught && elseStatement != null) { if (!breakCaught && elseStatement != null) {
result = elseStatement.execute(ctx) result = elseStatement.execute(cxt)
} }
result result
} }

View File

@ -57,15 +57,15 @@ class Script(
ObjException.addExceptionsToContext(this) ObjException.addExceptionsToContext(this)
addFn("print") { addFn("print") {
for ((i, a) in args.withIndex()) { for ((i, a) in args.withIndex()) {
if (i > 0) print(' ' + a.asStr.value) if (i > 0) print(' ' + a.toString(this).value)
else print(a.asStr.value) else print(a.toString(this).value)
} }
ObjVoid ObjVoid
} }
addFn("println") { addFn("println") {
for ((i, a) in args.withIndex()) { for ((i, a) in args.withIndex()) {
if (i > 0) print(' ' + a.asStr.value) if (i > 0) print(' ' + a.toString(this).value)
else print(a.asStr.value) else print(a.toString(this).value)
} }
println() println()
ObjVoid ObjVoid
@ -165,13 +165,13 @@ class Script(
val a = requiredArg<Obj>(0) val a = requiredArg<Obj>(0)
val b = requiredArg<Obj>(1) val b = requiredArg<Obj>(1)
if( a.compareTo(this, b) != 0 ) if( a.compareTo(this, b) != 0 )
raiseError(ObjAssertionFailedException(this,"Assertion failed: ${a.inspect()} == ${b.inspect()}")) raiseError(ObjAssertionFailedException(this,"Assertion failed: ${a.inspect(this)} == ${b.inspect(this)}"))
} }
addVoidFn("assertNotEquals") { addVoidFn("assertNotEquals") {
val a = requiredArg<Obj>(0) val a = requiredArg<Obj>(0)
val b = requiredArg<Obj>(1) val b = requiredArg<Obj>(1)
if( a.compareTo(this, b) == 0 ) if( a.compareTo(this, b) == 0 )
raiseError(ObjAssertionFailedException(this,"Assertion failed: ${a.inspect()} != ${b.inspect()}")) raiseError(ObjAssertionFailedException(this,"Assertion failed: ${a.inspect(this)} != ${b.inspect(this)}"))
} }
addFn("assertThrows") { addFn("assertThrows") {
val code = requireOnlyArg<Statement>() val code = requireOnlyArg<Statement>()
@ -287,7 +287,7 @@ class Script(
is ObjInt -> delay(a.value * 1000) is ObjInt -> delay(a.value * 1000)
is ObjReal -> delay((a.value * 1000).roundToLong()) is ObjReal -> delay((a.value * 1000).roundToLong())
is ObjDuration -> delay(a.duration) is ObjDuration -> delay(a.duration)
else -> raiseIllegalArgument("Expected Duration, Int or Real, got ${a.inspect()}") else -> raiseIllegalArgument("Expected Duration, Int or Real, got ${a.inspect(this)}")
} }
ObjVoid ObjVoid
} }

View File

@ -17,10 +17,14 @@
package net.sergeych.lyng package net.sergeych.lyng
import net.sergeych.lyng.obj.ObjString
class Source(val fileName: String, text: String) { class Source(val fileName: String, text: String) {
val lines = text.lines().map { it.trimEnd() } val lines = text.lines().map { it.trimEnd() }
val objSourceName by lazy { ObjString(fileName) }
companion object { companion object {
val builtIn: Source by lazy { Source("built-in", "") } val builtIn: Source by lazy { Source("built-in", "") }
val UNKNOWN: Source by lazy { Source("UNKNOWN", "") } val UNKNOWN: Source by lazy { Source("UNKNOWN", "") }

View File

@ -26,6 +26,7 @@ import net.sergeych.lyng.*
import net.sergeych.lynon.LynonDecoder import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonEncoder import net.sergeych.lynon.LynonEncoder
import net.sergeych.lynon.LynonType import net.sergeych.lynon.LynonType
import net.sergeych.mptools.CachedExpression
import net.sergeych.synctools.ProtectedOp import net.sergeych.synctools.ProtectedOp
import net.sergeych.synctools.withLock import net.sergeych.synctools.withLock
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
@ -49,7 +50,7 @@ open class Obj {
private val opInstances = ProtectedOp() private val opInstances = ProtectedOp()
open fun inspect(): String = toString() open suspend fun inspect(scope: Scope): String = toString(scope).value
/** /**
* Some objects are by-value, historically [ObjInt] and [ObjReal] are usually treated as such. * Some objects are by-value, historically [ObjInt] and [ObjReal] are usually treated as such.
@ -84,14 +85,14 @@ open class Obj {
scope: Scope, scope: Scope,
name: String, name: String,
args: Arguments = Arguments.EMPTY, args: Arguments = Arguments.EMPTY,
onNotFoundResult: Obj?=null onNotFoundResult: (()->Obj?)? = null
): Obj { ): Obj {
return objClass.getInstanceMemberOrNull(name)?.value?.invoke( return objClass.getInstanceMemberOrNull(name)?.value?.invoke(
scope, scope,
this, this,
args args
) )
?: onNotFoundResult ?: onNotFoundResult?.invoke()
?: scope.raiseSymbolNotFound(name) ?: scope.raiseSymbolNotFound(name)
} }
@ -115,8 +116,11 @@ open class Obj {
return invokeInstanceMethod(scope, "contains", other).toBool() return invokeInstanceMethod(scope, "contains", other).toBool()
} }
open val asStr: ObjString by lazy { suspend open fun toString(scope: Scope): ObjString {
if (this is ObjString) this else ObjString(this.toString()) return if (this is ObjString) this
else invokeInstanceMethod(scope, "toString") {
ObjString(this.toString())
} as ObjString
} }
/** /**
@ -276,9 +280,9 @@ open class Obj {
scope.raiseNotImplemented() scope.raiseNotImplemented()
} }
fun autoInstanceScope(parent: Scope): Scope { fun autoInstanceScope(parent: Scope): Scope {
val scope = parent.copy(newThisObj = this, args = parent.args) val scope = parent.copy(newThisObj = this, args = parent.args)
for( m in objClass.members) { for (m in objClass.members) {
scope.objects[m.key] = m.value scope.objects[m.key] = m.value
} }
return scope return scope
@ -287,11 +291,11 @@ open class Obj {
companion object { companion object {
val rootObjectType = ObjClass("Obj").apply { val rootObjectType = ObjClass("Obj").apply {
addFn("toString") { addFn("toString", true) {
thisObj.asStr ObjString(thisObj.toString())
} }
addFn("inspect", true) { addFn("inspect", true) {
thisObj.inspect().toObj() thisObj.inspect(this).toObj()
} }
addFn("contains") { addFn("contains") {
ObjBool(thisObj.contains(this, args.firstAndOnly())) ObjBool(thisObj.contains(this, args.firstAndOnly()))
@ -392,9 +396,14 @@ object ObjNull : Obj() {
scope.raiseNPE() scope.raiseNPE()
} }
override suspend fun invokeInstanceMethod(scope: Scope, name: String, args: Arguments, onNotFoundResult: Obj?): Obj { // override suspend fun invokeInstanceMethod(
scope.raiseNPE() // scope: Scope,
} // name: String,
// args: Arguments,
// onNotFoundResult: (()->Obj?)?
// ): Obj {
// scope.raiseNPE()
// }
override suspend fun getAt(scope: Scope, index: Obj): Obj { override suspend fun getAt(scope: Scope, index: Obj): Obj {
scope.raiseNPE() scope.raiseNPE()
@ -469,20 +478,51 @@ fun Obj.toBool(): Boolean =
data class ObjNamespace(val name: String) : Obj() { data class ObjNamespace(val name: String) : Obj() {
override val objClass by lazy { ObjClass(name) } override val objClass by lazy { ObjClass(name) }
override fun inspect(): String = "Ns[$name]" override suspend fun inspect(scope: Scope): String = "Ns[$name]"
override fun toString(): String { override fun toString(): String {
return "package $name" return "package $name"
} }
} }
open class ObjException(exceptionClass: ExceptionClass, val scope: Scope, val message: String) : Obj() { open class ObjException(
val exceptionClass: ExceptionClass,
val scope: Scope,
val message: String,
@Suppress("unused") val extraData: Obj = ObjNull
) : Obj() {
constructor(name: String, scope: Scope, message: String) : this( constructor(name: String, scope: Scope, message: String) : this(
getOrCreateExceptionClass(name), getOrCreateExceptionClass(name),
scope, scope,
message message
) )
private val cachedStackTrace = CachedExpression<ObjList>()
suspend fun getStackTrace(): ObjList {
return cachedStackTrace.get {
val result = ObjList()
val cls = scope.get("StackTraceEntry")!!.value as ObjClass
var s: Scope? = scope
var lastPos: Pos? = null
while( s != null ) {
val pos = s.pos
if( pos != lastPos && !pos.currentLine.isEmpty() ) {
result.list += cls.callWithArgs(
scope,
pos.source.objSourceName,
ObjInt(pos.line.toLong()),
ObjInt(pos.column.toLong()),
ObjString(pos.currentLine)
)
}
s = s.parent
lastPos = pos
}
result
}
}
constructor(scope: Scope, message: String) : this(Root, scope, message) constructor(scope: Scope, message: String) : this(Root, scope, message)
fun raise(): Nothing { fun raise(): Nothing {
@ -495,6 +535,12 @@ open class ObjException(exceptionClass: ExceptionClass, val scope: Scope, val me
return "ObjException:${objClass.className}:${scope.pos}@${hashCode().encodeToHex()}" return "ObjException:${objClass.className}:${scope.pos}@${hashCode().encodeToHex()}"
} }
override suspend fun serialize(scope: Scope, encoder: LynonEncoder, lynonType: LynonType?) {
encoder.encodeAny(scope, ObjString(exceptionClass.name))
encoder.encodeAny(scope, ObjString(message))
}
companion object { companion object {
class ExceptionClass(val name: String, vararg parents: ObjClass) : ObjClass(name, *parents) { class ExceptionClass(val name: String, vararg parents: ObjClass) : ObjClass(name, *parents) {
@ -510,6 +556,9 @@ open class ObjException(exceptionClass: ExceptionClass, val scope: Scope, val me
addConst("message", statement { addConst("message", statement {
(thisObj as ObjException).message.toObj() (thisObj as ObjException).message.toObj()
}) })
addFn("getStackTrace") {
(thisObj as ObjException).getStackTrace()
}
} }
private val op = ProtectedOp() private val op = ProtectedOp()

View File

@ -50,6 +50,5 @@ val ObjArray by lazy {
addFn("indices") { addFn("indices") {
ObjRange(0.toObj(), thisObj.invokeInstanceMethod(this, "size"), false) ObjRange(0.toObj(), thisObj.invokeInstanceMethod(this, "size"), false)
} }
} }
} }

View File

@ -23,7 +23,6 @@ import net.sergeych.lynon.LynonEncoder
import net.sergeych.lynon.LynonType import net.sergeych.lynon.LynonType
data class ObjBool(val value: Boolean) : Obj() { data class ObjBool(val value: Boolean) : Obj() {
override val asStr by lazy { ObjString(value.toString()) }
override suspend fun compareTo(scope: Scope, other: Obj): Int { override suspend fun compareTo(scope: Scope, other: Obj): Int {
if (other !is ObjBool) return -2 if (other !is ObjBool) return -2

View File

@ -87,7 +87,7 @@ open class ObjBuffer(val byteArray: UByteArray) : Obj() {
byteArray + other.toFlow(scope).map { it.toLong().toUByte() }.toList().toTypedArray() byteArray + other.toFlow(scope).map { it.toLong().toUByte() }.toList().toTypedArray()
.toUByteArray() .toUByteArray()
) )
} else scope.raiseIllegalArgument("can't concatenate buffer with ${other.inspect()}") } else scope.raiseIllegalArgument("can't concatenate buffer with ${other.inspect(scope)}")
} }
override fun toString(): String = base64 override fun toString(): String = base64
@ -107,7 +107,7 @@ open class ObjBuffer(val byteArray: UByteArray) : Obj() {
encoder.encodeCachedBytes(byteArray.asByteArray()) encoder.encodeCachedBytes(byteArray.asByteArray())
} }
override fun inspect(): String = "Buf($base64)" override suspend fun inspect(scope: Scope): String = "Buf($base64)"
companion object { companion object {
private suspend fun createBufferFrom(scope: Scope, obj: Obj): ObjBuffer = private suspend fun createBufferFrom(scope: Scope, obj: Obj): ObjBuffer =
@ -129,7 +129,7 @@ open class ObjBuffer(val byteArray: UByteArray) : Obj() {
) )
} else } else
scope.raiseIllegalArgument( scope.raiseIllegalArgument(
"can't construct buffer from ${obj.inspect()}" "can't construct buffer from ${obj.inspect(scope)}"
) )
} }
} }
@ -149,7 +149,7 @@ open class ObjBuffer(val byteArray: UByteArray) : Obj() {
is ObjChar -> b.value.code.toUByte() is ObjChar -> b.value.code.toUByte()
is ObjInt -> b.value.toUByte() is ObjInt -> b.value.toUByte()
else -> scope.raiseIllegalArgument( else -> scope.raiseIllegalArgument(
"invalid byte value for buffer constructor at index $i: ${b.inspect()}" "invalid byte value for buffer constructor at index $i: ${b.inspect(scope)}"
) )
} }
data[i] = code data[i] = code

View File

@ -28,7 +28,7 @@ class ObjChar(val value: Char): Obj() {
override fun toString(): String = value.toString() override fun toString(): String = value.toString()
override fun inspect(): String = "'$value'" override suspend fun inspect(scope: Scope): String = "'$value'"
override fun hashCode(): Int { override fun hashCode(): Int {
return value.hashCode() return value.hashCode()

View File

@ -61,13 +61,17 @@ open class ObjClass(
override suspend fun callOn(scope: Scope): Obj { override suspend fun callOn(scope: Scope): Obj {
val instance = ObjInstance(this) val instance = ObjInstance(this)
instance.instanceScope = scope.copy(newThisObj = instance,args = scope.args) instance.instanceScope = scope.copy(newThisObj = instance, args = scope.args)
if (instanceConstructor != null) { if (instanceConstructor != null) {
instanceConstructor!!.execute(instance.instanceScope) instanceConstructor!!.execute(instance.instanceScope)
} }
return instance return instance
} }
suspend fun callWithArgs(scope: Scope, vararg plainArgs: Obj): Obj {
return callOn(scope.copy(Arguments(*plainArgs)))
}
fun createField( fun createField(
name: String, name: String,
@ -77,13 +81,13 @@ open class ObjClass(
pos: Pos = Pos.builtIn pos: Pos = Pos.builtIn
) { ) {
val existing = members[name] ?: allParentsSet.firstNotNullOfOrNull { it.members[name] } val existing = members[name] ?: allParentsSet.firstNotNullOfOrNull { it.members[name] }
if( existing?.isMutable == false) if (existing?.isMutable == false)
throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes") throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
members[name] = ObjRecord(initialValue, isMutable, visibility) members[name] = ObjRecord(initialValue, isMutable, visibility)
} }
private fun initClassScope(): Scope { private fun initClassScope(): Scope {
if( classScope == null ) classScope = Scope() if (classScope == null) classScope = Scope()
return classScope!! return classScope!!
} }
@ -96,7 +100,7 @@ open class ObjClass(
) { ) {
initClassScope() initClassScope()
val existing = classScope!!.objects[name] val existing = classScope!!.objects[name]
if( existing != null) if (existing != null)
throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes") throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
classScope!!.addItem(name, isMutable, initialValue, visibility) classScope!!.addItem(name, isMutable, initialValue, visibility)
} }
@ -127,26 +131,29 @@ open class ObjClass(
override suspend fun readField(scope: Scope, name: String): ObjRecord { override suspend fun readField(scope: Scope, name: String): ObjRecord {
classScope?.objects?.get(name)?.let { classScope?.objects?.get(name)?.let {
if( it.visibility.isPublic ) return it if (it.visibility.isPublic) return it
} }
return super.readField(scope, name) return super.readField(scope, name)
} }
override suspend fun writeField(scope: Scope, name: String, newValue: Obj) { override suspend fun writeField(scope: Scope, name: String, newValue: Obj) {
initClassScope().objects[name]?.let { initClassScope().objects[name]?.let {
if( it.isMutable) it.value = newValue if (it.isMutable) it.value = newValue
else scope.raiseIllegalAssignment("can't assign $name is not mutable") else scope.raiseIllegalAssignment("can't assign $name is not mutable")
} }
?: super.writeField(scope, name, newValue) ?: super.writeField(scope, name, newValue)
} }
override suspend fun invokeInstanceMethod(scope: Scope, name: String, args: Arguments, override suspend fun invokeInstanceMethod(
onNotFoundResult: Obj?): Obj { scope: Scope, name: String, args: Arguments,
onNotFoundResult: (() -> Obj?)?
): Obj {
return classScope?.objects?.get(name)?.value?.invoke(scope, this, args) return classScope?.objects?.get(name)?.value?.invoke(scope, this, args)
?: super.invokeInstanceMethod(scope, name, args, onNotFoundResult) ?: super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
} }
open suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj = scope.raiseNotImplemented() open suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj =
scope.raiseNotImplemented()
} }

View File

@ -22,4 +22,4 @@ package net.sergeych.lyng.obj
*/ */
val ObjCollection = ObjClass("Collection", ObjIterable).apply { val ObjCollection = ObjClass("Collection", ObjIterable).apply {
} }

View File

@ -57,7 +57,7 @@ class ObjDuration(val duration: Duration) : Obj() {
override suspend fun callOn(scope: Scope): Obj { override suspend fun callOn(scope: Scope): Obj {
val args = scope.args val args = scope.args
if( args.list.size > 1 ) if( args.list.size > 1 )
scope.raiseIllegalArgument("can't construct Duration(${args.inspect()})") scope.raiseIllegalArgument("can't construct Duration(${args.inspect(scope)})")
val a0 = args.list.getOrNull(0) val a0 = args.list.getOrNull(0)
return ObjDuration( return ObjDuration(
@ -66,7 +66,7 @@ class ObjDuration(val duration: Duration) : Obj() {
is ObjInt -> a0.value.seconds is ObjInt -> a0.value.seconds
is ObjReal -> a0.value.seconds is ObjReal -> a0.value.seconds
else -> { else -> {
scope.raiseIllegalArgument("can't construct Instant(${args.inspect()})") scope.raiseIllegalArgument("can't construct Instant(${args.inspect(scope)})")
} }
} }
) )

View File

@ -48,7 +48,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
} }
override suspend fun invokeInstanceMethod(scope: Scope, name: String, args: Arguments, override suspend fun invokeInstanceMethod(scope: Scope, name: String, args: Arguments,
onNotFoundResult: Obj?): Obj = onNotFoundResult: (()->Obj?)?): Obj =
instanceScope[name]?.let { instanceScope[name]?.let {
if (it.visibility.isPublic) if (it.visibility.isPublic)
it.value.invoke( it.value.invoke(

View File

@ -30,12 +30,19 @@ class ObjInstanceClass(val name: String) : ObjClass(name) {
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj { override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {
val args = decoder.decodeAnyList(scope) val args = decoder.decodeAnyList(scope)
val actualSize = constructorMeta?.params?.size ?: 0 val actualSize = constructorMeta?.params?.size ?: 0
if( args.size > actualSize ) if (args.size > actualSize)
scope.raiseIllegalArgument("constructor $name has only $actualSize but serialized version has ${args.size}") scope.raiseIllegalArgument("constructor $name has only $actualSize but serialized version has ${args.size}")
val newScope = scope.copy(args = Arguments(args)) val newScope = scope.copy(args = Arguments(args))
return (callOn(newScope) as ObjInstance).apply { return (callOn(newScope) as ObjInstance).apply {
deserializeStateVars(scope,decoder) deserializeStateVars(scope, decoder)
invokeInstanceMethod(scope, "onDeserialized", onNotFoundResult = ObjVoid) invokeInstanceMethod(scope, "onDeserialized") { ObjVoid }
}
}
init {
addFn("toString", true) {
println("-------------- tos! --------------")
ObjString(thisObj.toString())
} }
} }

View File

@ -117,7 +117,7 @@ class ObjInstant(val instant: Instant,val truncateMode: LynonSettings.InstantTru
is ObjInstant -> a0.instant is ObjInstant -> a0.instant
else -> { else -> {
scope.raiseIllegalArgument("can't construct Instant(${args.inspect()})") scope.raiseIllegalArgument("can't construct Instant(${args.inspect(scope)})")
} }
} }
) )

View File

@ -23,7 +23,6 @@ import net.sergeych.lynon.LynonEncoder
import net.sergeych.lynon.LynonType import net.sergeych.lynon.LynonType
class ObjInt(var value: Long, override val isConst: Boolean = false) : Obj(), Numeric { class ObjInt(var value: Long, override val isConst: Boolean = false) : Obj(), Numeric {
override val asStr get() = ObjString(value.toString())
override val longValue get() = value override val longValue get() = value
override val doubleValue get() = value.toDouble() override val doubleValue get() = value.toDouble()
override val toObjInt get() = this override val toObjInt get() = this

View File

@ -82,18 +82,18 @@ fun Obj.toFlow(scope: Scope): Flow<Obj> = flow {
* *
* IF callback returns false, iteration is stopped. * IF callback returns false, iteration is stopped.
*/ */
suspend fun Obj.enumerate(scope: Scope,callback: suspend (Obj)->Boolean) { suspend fun Obj.enumerate(scope: Scope, callback: suspend (Obj) -> Boolean) {
val iterator = invokeInstanceMethod(scope, "iterator") val iterator = invokeInstanceMethod(scope, "iterator")
val hasNext = iterator.getInstanceMethod(scope, "hasNext") val hasNext = iterator.getInstanceMethod(scope, "hasNext")
val next = iterator.getInstanceMethod(scope, "next") val next = iterator.getInstanceMethod(scope, "next")
var closeIt = false var closeIt = false
while (hasNext.invoke(scope, iterator).toBool()) { while (hasNext.invoke(scope, iterator).toBool()) {
val nextValue = next.invoke(scope, iterator) val nextValue = next.invoke(scope, iterator)
if( !callback(nextValue) ) { if (!callback(nextValue)) {
closeIt = true closeIt = true
break break
} }
} }
if( closeIt ) if (closeIt)
iterator.invokeInstanceMethod(scope, "cancelIteration", onNotFoundResult = ObjVoid) iterator.invokeInstanceMethod(scope, "cancelIteration") { ObjVoid }
} }

View File

@ -25,10 +25,6 @@ import net.sergeych.lynon.LynonType
class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() { class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
override fun toString(): String = "[${
list.joinToString(separator = ", ") { it.inspect() }
}]"
override suspend fun getAt(scope: Scope, index: Obj): Obj { override suspend fun getAt(scope: Scope, index: Obj): Obj {
return when (index) { return when (index) {
is ObjInt -> { is ObjInt -> {
@ -65,7 +61,7 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
} }
} }
else -> scope.raiseIllegalArgument("Illegal index object for a list: ${index.inspect()}") else -> scope.raiseIllegalArgument("Illegal index object for a list: ${index.inspect(scope)}")
} }
} }

View File

@ -28,7 +28,7 @@ class ObjMutableBuffer(byteArray: UByteArray) : ObjBuffer(byteArray) {
is ObjInt -> newValue.value.toUByte() is ObjInt -> newValue.value.toUByte()
is ObjChar -> newValue.value.code.toUByte() is ObjChar -> newValue.value.code.toUByte()
else -> scope.raiseIllegalArgument( else -> scope.raiseIllegalArgument(
"invalid byte value for buffer at index ${index.inspect()}: ${newValue.inspect()}" "invalid byte value for buffer at index ${index.inspect(scope)}: ${newValue.inspect(scope)}"
) )
} }
} }
@ -54,7 +54,7 @@ class ObjMutableBuffer(byteArray: UByteArray) : ObjBuffer(byteArray) {
) )
} else } else
scope.raiseIllegalArgument( scope.raiseIllegalArgument(
"can't construct buffer from ${obj.inspect()}" "can't construct buffer from ${obj.inspect(scope)}"
) )
} }
} }
@ -74,7 +74,7 @@ class ObjMutableBuffer(byteArray: UByteArray) : ObjBuffer(byteArray) {
is ObjChar -> b.value.code.toUByte() is ObjChar -> b.value.code.toUByte()
is ObjInt -> b.value.toUByte() is ObjInt -> b.value.toUByte()
else -> scope.raiseIllegalArgument( else -> scope.raiseIllegalArgument(
"invalid byte value for buffer constructor at index $i: ${b.inspect()}" "invalid byte value for buffer constructor at index $i: ${b.inspect(scope)}"
) )
} }
data[i] = code data[i] = code

View File

@ -26,12 +26,12 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob
override val objClass: ObjClass = type override val objClass: ObjClass = type
override fun toString(): String { override suspend fun toString(scope: Scope): ObjString {
val result = StringBuilder() val result = StringBuilder()
result.append("${start?.inspect() ?: '∞'} ..") result.append("${start?.inspect(scope) ?: '∞'} ..")
if (!isEndInclusive) result.append('<') if (!isEndInclusive) result.append('<')
result.append(" ${end?.inspect() ?: '∞'}") result.append(" ${end?.inspect(scope) ?: '∞'}")
return result.toString() return ObjString(result.toString())
} }
/** /**
@ -52,11 +52,11 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob
* if start is not ObjInt, raises [ObjIllegalArgumentException] * if start is not ObjInt, raises [ObjIllegalArgumentException]
* otherwise returns start.value.toInt() * otherwise returns start.value.toInt()
*/ */
fun startInt(scope: Scope): Int = suspend fun startInt(scope: Scope): Int =
if( start == null || start is ObjNull) 0 if( start == null || start is ObjNull) 0
else { else {
if( start is ObjInt) start.value.toInt() if( start is ObjInt) start.value.toInt()
else scope.raiseIllegalArgument("start is not Int: ${start.inspect()}") else scope.raiseIllegalArgument("start is not Int: ${start.inspect(scope)}")
} }
suspend fun containsRange(scope: Scope, other: ObjRange): Boolean { suspend fun containsRange(scope: Scope, other: ObjRange): Boolean {

View File

@ -27,7 +27,6 @@ import kotlin.math.floor
import kotlin.math.roundToLong import kotlin.math.roundToLong
data class ObjReal(val value: Double) : Obj(), Numeric { data class ObjReal(val value: Double) : Obj(), Numeric {
override val asStr by lazy { ObjString(value.toString()) }
override val longValue: Long by lazy { floor(value).toLong() } override val longValue: Long by lazy { floor(value).toLong() }
override val doubleValue: Double by lazy { value } override val doubleValue: Double by lazy { value }
override val toObjInt: ObjInt by lazy { ObjInt(longValue) } override val toObjInt: ObjInt by lazy { ObjInt(longValue) }

View File

@ -44,9 +44,7 @@ data class ObjString(val value: String) : Obj() {
override fun toString(): String = value override fun toString(): String = value
override val asStr: ObjString by lazy { this } override suspend fun inspect(scope: Scope): String {
override fun inspect(): String {
return "\"$value\"" return "\"$value\""
} }
@ -54,7 +52,7 @@ data class ObjString(val value: String) : Obj() {
get() = type get() = type
override suspend fun plus(scope: Scope, other: Obj): Obj { override suspend fun plus(scope: Scope, other: Obj): Obj {
return ObjString(value + other.asStr.value) return ObjString(value + other.toString(scope).value)
} }
override suspend fun getAt(scope: Scope, index: Obj): Obj { override suspend fun getAt(scope: Scope, index: Obj): Obj {
@ -159,6 +157,9 @@ data class ObjString(val value: String) : Obj() {
addFn("toReal") { addFn("toReal") {
ObjReal(thisAs<ObjString>().value.toDouble()) ObjReal(thisAs<ObjString>().value.toDouble())
} }
addFn("trim") {
thisAs<ObjString>().value.trim().let(::ObjString)
}
} }
} }
} }

View File

@ -103,6 +103,21 @@ fun Iterable.any(predicate): Bool {
fun Iterable.all(predicate): Bool { fun Iterable.all(predicate): Bool {
!any { !predicate(it) } !any { !predicate(it) }
} }
fun List.toString() {
"[" + joinToString(",") + "]"
}
class StackTraceEntry(
val sourceName: String,
val line: Int,
val column: Int,
val sourceString: String
) {
fun toString() {
"%s:%d:%d: %s"(sourceName, line, column, sourceString.trim())
}
}
""".trimIndent() """.trimIndent()

View File

@ -1789,7 +1789,7 @@ class ScriptTest {
"e="+e+"f="+f() "e="+e+"f="+f()
} }
assertEquals("e=[]f=xx", f { "xx" }) assertEquals("e=[]f=xx", f { "xx" })
assertEquals("e=[1, 2]f=xx", f(1,2) { "xx" }) assertEquals("e=[1,2]f=xx", f(1,2) { "xx" })
""".trimIndent() """.trimIndent()
) )
@ -1824,7 +1824,7 @@ class ScriptTest {
} }
val f = Foo() val f = Foo()
assertEquals("e=[]f=xx", f.f { "xx" }) assertEquals("e=[]f=xx", f.f { "xx" })
assertEquals("e=[1, 2]f=xx", f.f(1,2) { "xx" }) assertEquals("e=[1,2]f=xx", f.f(1,2) { "xx" })
""".trimIndent() """.trimIndent()
) )
@ -2661,7 +2661,8 @@ class ScriptTest {
@Test @Test
fun testBufferEncodings() = runTest { fun testBufferEncodings() = runTest {
eval(""" eval(
"""
import lyng.buffer import lyng.buffer
val b = Buffer("hello") val b = Buffer("hello")
@ -2676,7 +2677,8 @@ class ScriptTest {
println(b.inspect()) println(b.inspect())
""".trimIndent()) """.trimIndent()
)
} }
@Test @Test
@ -2900,7 +2902,8 @@ class ScriptTest {
@Test @Test
fun tesFunAnnotation() = runTest { fun tesFunAnnotation() = runTest {
eval(""" eval(
"""
val exportedSymbols = Map() val exportedSymbols = Map()
@ -2919,7 +2922,8 @@ class ScriptTest {
assert( exportedSymbols["getBalance"] != null ) assert( exportedSymbols["getBalance"] != null )
assertEquals(122, getBalance(1)) assertEquals(122, getBalance(1))
""".trimIndent()) """.trimIndent()
)
} }
@Test @Test
@ -2939,12 +2943,14 @@ class ScriptTest {
assertEquals( Color.valueOf("GREEN"), Color.GREEN ) assertEquals( Color.valueOf("GREEN"), Color.GREEN )
""".trimIndent()) """.trimIndent()
)
} }
@Test @Test
fun enumSerializationTest() = runTest { fun enumSerializationTest() = runTest {
eval(""" eval(
"""
import lyng.serialization import lyng.serialization
enum Color { enum Color {
@ -2960,12 +2966,14 @@ class ScriptTest {
assert( e1.size / 1000.0 < 6) assert( e1.size / 1000.0 < 6)
println(Lynon.encode( (1..100).map { "RED" } ).toDump() ) println(Lynon.encode( (1..100).map { "RED" } ).toDump() )
""".trimIndent()) """.trimIndent()
)
} }
@Test @Test
fun cachedTest() = runTest { fun cachedTest() = runTest {
eval( """ eval(
"""
var counter = 0 var counter = 0
var value = cached { var value = cached {
@ -2978,66 +2986,82 @@ class ScriptTest {
assertEquals(1, counter) assertEquals(1, counter)
assertEquals("ok", value()) assertEquals("ok", value())
assertEquals(1, counter) assertEquals(1, counter)
""".trimIndent()) """.trimIndent()
)
} }
@Test @Test
fun testJoinToString() = runTest { fun testJoinToString() = runTest {
eval(""" eval(
"""
assertEquals( (1..3).joinToString(), "1 2 3") assertEquals( (1..3).joinToString(), "1 2 3")
assertEquals( (1..3).joinToString(":"), "1:2:3") assertEquals( (1..3).joinToString(":"), "1:2:3")
assertEquals( (1..3).joinToString { it * 10 }, "10 20 30") assertEquals( (1..3).joinToString { it * 10 }, "10 20 30")
""".trimIndent()) """.trimIndent()
)
} }
@Test @Test
fun testElvisAndThrow() = runTest { fun testElvisAndThrow() = runTest {
eval(""" eval(
"""
val x = assertThrows { val x = assertThrows {
null ?: throw "test" + "x" null ?: throw "test" + "x"
} }
assertEquals( "testx", x.message) assertEquals( "testx", x.message)
""".trimIndent()) """.trimIndent()
)
} }
@Test @Test
fun testElvisAndThrow2() = runTest { fun testElvisAndThrow2() = runTest {
eval(""" eval(
"""
val t = "112" val t = "112"
val x = t ?: run { throw "testx" } val x = t ?: run { throw "testx" }
} }
assertEquals( "112", x) assertEquals( "112", x)
""".trimIndent()) """.trimIndent()
)
} }
@Test @Test
fun testElvisAndRunThrow() = runTest { fun testElvisAndRunThrow() = runTest {
eval(""" eval(
"""
val x = assertThrows { val x = assertThrows {
null ?: run { throw "testx" } null ?: run { throw "testx" }
} }
assertEquals( "testx", x.message) assertEquals( "testx", x.message)
""".trimIndent()) """.trimIndent()
)
} }
@Test @Test
fun testNewlinesAnsCommentsInExpressions() = runTest { fun testNewlinesAnsCommentsInExpressions() = runTest {
assertEquals( 2, (Scope().eval(""" assertEquals(
2, (Scope().eval(
"""
val e = 1 + 4 - val e = 1 + 4 -
3 3
""".trimIndent())).toInt()) """.trimIndent()
)).toInt()
)
eval(""" eval(
"""
val x = [1,2,3] val x = [1,2,3]
.map { it * 10 } .map { it * 10 }
.map { it + 1 } .map { it + 1 }
assertEquals( [11,21,31], x) assertEquals( [11,21,31], x)
""".trimIndent()) """.trimIndent()
)
} }
@Test @Test
fun testNotExpressionWithoutWs() = runTest { fun testNotExpressionWithoutWs() = runTest {
eval(""" eval(
"""
fun test() { false } fun test() { false }
class T(value) class T(value)
assert( !false ) assert( !false )
@ -3046,12 +3070,14 @@ class ScriptTest {
val t = T(false) val t = T(false)
assert( !t.value ) assert( !t.value )
assert( !if( true ) false else true ) assert( !if( true ) false else true )
""".trimIndent()) """.trimIndent()
)
} }
@Test @Test
fun testMultilineFnDeclaration() = runTest { fun testMultilineFnDeclaration() = runTest {
eval(""" eval(
"""
fun test( fun test(
x = 1, x = 1,
y = 2 y = 2
@ -3063,8 +3089,38 @@ class ScriptTest {
6, 6,
7 7
) ) ) )
""".trimIndent()
)
}
@Test
fun testOverridenListToString() = runTest {
eval("""
val x = [1,2,3]
assertEquals( "[1,2,3]", x.toString() )
""".trimIndent()) """.trimIndent())
} }
@Test
fun testExceptionSerialization() = runTest {
eval(
"""
import lyng.serialization
val x = [1,2,3]
assertEquals( "[1,2,3]", x.toString() )
try {
require(false)
}
catch (e) {
println(e)
println(e.getStackTrace())
for( t in e.getStackTrace() ) {
println(t)
}
// val coded = Lynon.encode(e)
// println(coded.toDump())
}
""".trimIndent()
)
}
} }

View File

@ -177,11 +177,11 @@ suspend fun DocTest.test(_scope: Scope? = null) {
scope.apply { scope.apply {
addFn("println") { addFn("println") {
if( bookMode ) { if( bookMode ) {
println("${currentTest.fileNamePart}:${currentTest.line}> ${args.joinToString(" "){it.asStr.value}}") println("${currentTest.fileNamePart}:${currentTest.line}> ${args.map{it.toString(this).value}.joinToString(" ")}")
} }
else { else {
for ((i, a) in args.withIndex()) { for ((i, a) in args.withIndex()) {
if (i > 0) collectedOutput.append(' '); collectedOutput.append(a.asStr.value) if (i > 0) collectedOutput.append(' '); collectedOutput.append(a.toString(this).value)
collectedOutput.append('\n') collectedOutput.append('\n')
} }
} }
@ -194,7 +194,7 @@ suspend fun DocTest.test(_scope: Scope? = null) {
} catch (e: Throwable) { } catch (e: Throwable) {
error = e error = e
null null
}?.inspect()?.replace(Regex("@\\d+"), "@...") }?.inspect(scope)?.replace(Regex("@\\d+"), "@...")
if (bookMode) { if (bookMode) {
if (error != null) { if (error != null) {