From 4b613fda7c77f22de310c20b74d37080b02ea3b5 Mon Sep 17 00:00:00 2001 From: sergeych Date: Fri, 22 Aug 2025 00:01:59 +0300 Subject: [PATCH] fix #58 improper toString overload processing in Lyng fix #57 Exception#getStackTrace() ref #56 StackTraceEntry is serializable now --- docs/List.md | 2 +- docs/Range.md | 2 +- docs/declaring_arguments.md | 6 +- docs/parallelism.md | 4 +- docs/tutorial.md | 12 +- .../kotlin/net/sergeych/lyng/Arguments.kt | 2 +- .../kotlin/net/sergeych/lyng/Compiler.kt | 8 +- .../kotlin/net/sergeych/lyng/Script.kt | 14 +-- .../kotlin/net/sergeych/lyng/Source.kt | 4 + .../kotlin/net/sergeych/lyng/obj/Obj.kt | 81 ++++++++++--- .../kotlin/net/sergeych/lyng/obj/ObjArray.kt | 1 - .../kotlin/net/sergeych/lyng/obj/ObjBool.kt | 1 - .../kotlin/net/sergeych/lyng/obj/ObjBuffer.kt | 8 +- .../kotlin/net/sergeych/lyng/obj/ObjChar.kt | 2 +- .../kotlin/net/sergeych/lyng/obj/ObjClass.kt | 25 ++-- .../net/sergeych/lyng/obj/ObjCollection.kt | 2 +- .../net/sergeych/lyng/obj/ObjDuration.kt | 4 +- .../net/sergeych/lyng/obj/ObjInstance.kt | 2 +- .../net/sergeych/lyng/obj/ObjInstanceClass.kt | 13 ++- .../net/sergeych/lyng/obj/ObjInstant.kt | 2 +- .../kotlin/net/sergeych/lyng/obj/ObjInt.kt | 1 - .../sergeych/lyng/obj/ObjKotlinIterator.kt | 8 +- .../kotlin/net/sergeych/lyng/obj/ObjList.kt | 6 +- .../net/sergeych/lyng/obj/ObjMutableBuffer.kt | 6 +- .../kotlin/net/sergeych/lyng/obj/ObjRange.kt | 12 +- .../kotlin/net/sergeych/lyng/obj/ObjReal.kt | 1 - .../kotlin/net/sergeych/lyng/obj/ObjString.kt | 9 +- .../lyng/stdlib_included/root_lyng.kt | 15 +++ lynglib/src/commonTest/kotlin/ScriptTest.kt | 110 +++++++++++++----- lynglib/src/jvmTest/kotlin/BookTest.kt | 6 +- 30 files changed, 250 insertions(+), 119 deletions(-) diff --git a/docs/List.md b/docs/List.md index e8f0c8b..36dbdd9 100644 --- a/docs/List.md +++ b/docs/List.md @@ -24,7 +24,7 @@ There is a shortcut for the last: val list = [10, 20, 30] [list.last, list.lastIndex] - >>> [30, 2] + >>> [30,2] __Important__ negative indexes works wherever indexes are used, e.g. in insertion and removal methods too. diff --git a/docs/Range.md b/docs/Range.md index 2d1a610..cf92d20 100644 --- a/docs/Range.md +++ b/docs/Range.md @@ -87,7 +87,7 @@ You can use Char as both ends of the closed range: Exclusive end char ranges are supported too: ('a'..<'c').toList - >>> ['a', 'b'] + >>> [a,b] # Instance members diff --git a/docs/declaring_arguments.md b/docs/declaring_arguments.md index f769be4..f467a6a 100644 --- a/docs/declaring_arguments.md +++ b/docs/declaring_arguments.md @@ -71,7 +71,7 @@ destructuring arrays when calling functions and lambdas: [ first, last ] } getFirstAndLast( ...(1..10) ) // see "splats" section below - >>> [1, 10] + >>> [1,10] # Splats @@ -83,7 +83,7 @@ or whatever implementing [Iterable], is called _splats_. Here is how we use it: } val array = [1,2,3] testSplat("start", ...array, "end") - >>> ["start", 1, 2, 3, "end"] + >>> [start,1,2,3,end] >>> void 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 testSplat("start", ...range, "end") - >>> ["start", 1, 2, 3, "end"] + >>> [start,1,2,3,end] >>> void diff --git a/docs/parallelism.md b/docs/parallelism.md index 8b4e245..7a86de2 100644 --- a/docs/parallelism.md +++ b/docs/parallelism.md @@ -183,8 +183,8 @@ Important difference from the channels or like, every time you collect the flow, // and again: 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 Notice that flow's lambda is not called until actual collection is started. Cold flows are diff --git a/docs/tutorial.md b/docs/tutorial.md index 84f74aa..380a146 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -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: [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): val a = ["one", "two"] val b = [10.1, 20.2] ["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: val a = [1, 2, 3] a[1] = 200 a - >>> [1, 200, 3] + >>> [1,200,3] 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] x.insertAt( 1, ...[0,100,0]) 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: val x = [1,2,3] x.insertAt(3, 10) x - >>> [1, 2, 3, 10] + >>> [1,2,3,10] but it is much simpler, and we recommend to use '+=' val x = [1,2,3] x += 10 - >>> [1, 2, 3, 10] + >>> [1,2,3,10] ## Removing list items diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Arguments.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Arguments.kt index 9849464..5e8a30c 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Arguments.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Arguments.kt @@ -63,7 +63,7 @@ data class Arguments(val list: List, val tailBlockMode: Boolean = false) : 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 { val EMPTY = Arguments(emptyList()) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index 622d0d7..88ba683 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -1363,8 +1363,8 @@ class Compiler( } - return statement(body.pos) { ctx -> - val forContext = ctx.copy(start) + return statement(body.pos) { cxt -> + val forContext = cxt.copy(start) // loop var: StoredObject val loopSO = forContext.addItem(tVar.value, true, ObjNull) @@ -1393,7 +1393,7 @@ class Compiler( .getOrElse { throw ScriptError( tOp.pos, - "object is not enumerable: no index access for ${sourceObj.inspect()}", + "object is not enumerable: no index access for ${sourceObj.inspect(cxt)}", it ) } @@ -1420,7 +1420,7 @@ class Compiler( } } if (!breakCaught && elseStatement != null) { - result = elseStatement.execute(ctx) + result = elseStatement.execute(cxt) } result } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt index a676a51..fb41b38 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt @@ -57,15 +57,15 @@ class Script( ObjException.addExceptionsToContext(this) addFn("print") { for ((i, a) in args.withIndex()) { - if (i > 0) print(' ' + a.asStr.value) - else print(a.asStr.value) + if (i > 0) print(' ' + a.toString(this).value) + else print(a.toString(this).value) } ObjVoid } addFn("println") { for ((i, a) in args.withIndex()) { - if (i > 0) print(' ' + a.asStr.value) - else print(a.asStr.value) + if (i > 0) print(' ' + a.toString(this).value) + else print(a.toString(this).value) } println() ObjVoid @@ -165,13 +165,13 @@ class Script( val a = requiredArg(0) val b = requiredArg(1) 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") { val a = requiredArg(0) val b = requiredArg(1) 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") { val code = requireOnlyArg() @@ -287,7 +287,7 @@ class Script( is ObjInt -> delay(a.value * 1000) is ObjReal -> delay((a.value * 1000).roundToLong()) 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 } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Source.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Source.kt index a08ffa8..5885f1c 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Source.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Source.kt @@ -17,10 +17,14 @@ package net.sergeych.lyng +import net.sergeych.lyng.obj.ObjString + class Source(val fileName: String, text: String) { val lines = text.lines().map { it.trimEnd() } + val objSourceName by lazy { ObjString(fileName) } + companion object { val builtIn: Source by lazy { Source("built-in", "") } val UNKNOWN: Source by lazy { Source("UNKNOWN", "") } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt index e816c44..0df90bc 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt @@ -26,6 +26,7 @@ import net.sergeych.lyng.* import net.sergeych.lynon.LynonDecoder import net.sergeych.lynon.LynonEncoder import net.sergeych.lynon.LynonType +import net.sergeych.mptools.CachedExpression import net.sergeych.synctools.ProtectedOp import net.sergeych.synctools.withLock import kotlin.contracts.ExperimentalContracts @@ -49,7 +50,7 @@ open class Obj { 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. @@ -84,14 +85,14 @@ open class Obj { scope: Scope, name: String, args: Arguments = Arguments.EMPTY, - onNotFoundResult: Obj?=null + onNotFoundResult: (()->Obj?)? = null ): Obj { return objClass.getInstanceMemberOrNull(name)?.value?.invoke( scope, this, args ) - ?: onNotFoundResult + ?: onNotFoundResult?.invoke() ?: scope.raiseSymbolNotFound(name) } @@ -115,8 +116,11 @@ open class Obj { return invokeInstanceMethod(scope, "contains", other).toBool() } - open val asStr: ObjString by lazy { - if (this is ObjString) this else ObjString(this.toString()) + suspend open fun toString(scope: Scope): ObjString { + return if (this is ObjString) this + else invokeInstanceMethod(scope, "toString") { + ObjString(this.toString()) + } as ObjString } /** @@ -276,9 +280,9 @@ open class Obj { scope.raiseNotImplemented() } - fun autoInstanceScope(parent: Scope): Scope { - val scope = parent.copy(newThisObj = this, args = parent.args) - for( m in objClass.members) { + fun autoInstanceScope(parent: Scope): Scope { + val scope = parent.copy(newThisObj = this, args = parent.args) + for (m in objClass.members) { scope.objects[m.key] = m.value } return scope @@ -287,11 +291,11 @@ open class Obj { companion object { val rootObjectType = ObjClass("Obj").apply { - addFn("toString") { - thisObj.asStr + addFn("toString", true) { + ObjString(thisObj.toString()) } addFn("inspect", true) { - thisObj.inspect().toObj() + thisObj.inspect(this).toObj() } addFn("contains") { ObjBool(thisObj.contains(this, args.firstAndOnly())) @@ -392,9 +396,14 @@ object ObjNull : Obj() { scope.raiseNPE() } - override suspend fun invokeInstanceMethod(scope: Scope, name: String, args: Arguments, onNotFoundResult: Obj?): Obj { - scope.raiseNPE() - } +// override suspend fun invokeInstanceMethod( +// scope: Scope, +// name: String, +// args: Arguments, +// onNotFoundResult: (()->Obj?)? +// ): Obj { +// scope.raiseNPE() +// } override suspend fun getAt(scope: Scope, index: Obj): Obj { scope.raiseNPE() @@ -469,20 +478,51 @@ fun Obj.toBool(): Boolean = data class ObjNamespace(val name: String) : Obj() { 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 { 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( getOrCreateExceptionClass(name), scope, message ) + private val cachedStackTrace = CachedExpression() + + 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) 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()}" } + override suspend fun serialize(scope: Scope, encoder: LynonEncoder, lynonType: LynonType?) { + encoder.encodeAny(scope, ObjString(exceptionClass.name)) + encoder.encodeAny(scope, ObjString(message)) + } + + companion object { 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 { (thisObj as ObjException).message.toObj() }) + addFn("getStackTrace") { + (thisObj as ObjException).getStackTrace() + } } private val op = ProtectedOp() diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjArray.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjArray.kt index 47bf747..324bb23 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjArray.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjArray.kt @@ -50,6 +50,5 @@ val ObjArray by lazy { addFn("indices") { ObjRange(0.toObj(), thisObj.invokeInstanceMethod(this, "size"), false) } - } } \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBool.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBool.kt index 77d37e6..8f76e5c 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBool.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBool.kt @@ -23,7 +23,6 @@ import net.sergeych.lynon.LynonEncoder import net.sergeych.lynon.LynonType data class ObjBool(val value: Boolean) : Obj() { - override val asStr by lazy { ObjString(value.toString()) } override suspend fun compareTo(scope: Scope, other: Obj): Int { if (other !is ObjBool) return -2 diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBuffer.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBuffer.kt index ffa37fc..9dfb67e 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBuffer.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBuffer.kt @@ -87,7 +87,7 @@ open class ObjBuffer(val byteArray: UByteArray) : Obj() { byteArray + other.toFlow(scope).map { it.toLong().toUByte() }.toList().toTypedArray() .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 @@ -107,7 +107,7 @@ open class ObjBuffer(val byteArray: UByteArray) : Obj() { encoder.encodeCachedBytes(byteArray.asByteArray()) } - override fun inspect(): String = "Buf($base64)" + override suspend fun inspect(scope: Scope): String = "Buf($base64)" companion object { private suspend fun createBufferFrom(scope: Scope, obj: Obj): ObjBuffer = @@ -129,7 +129,7 @@ open class ObjBuffer(val byteArray: UByteArray) : Obj() { ) } else 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 ObjInt -> b.value.toUByte() 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 diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjChar.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjChar.kt index 20cf472..1c90000 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjChar.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjChar.kt @@ -28,7 +28,7 @@ class ObjChar(val value: Char): Obj() { override fun toString(): String = value.toString() - override fun inspect(): String = "'$value'" + override suspend fun inspect(scope: Scope): String = "'$value'" override fun hashCode(): Int { return value.hashCode() diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt index 1cd432e..f2b2e50 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt @@ -61,13 +61,17 @@ open class ObjClass( override suspend fun callOn(scope: Scope): Obj { 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) { instanceConstructor!!.execute(instance.instanceScope) } return instance } + suspend fun callWithArgs(scope: Scope, vararg plainArgs: Obj): Obj { + return callOn(scope.copy(Arguments(*plainArgs))) + } + fun createField( name: String, @@ -77,13 +81,13 @@ open class ObjClass( pos: Pos = Pos.builtIn ) { 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") members[name] = ObjRecord(initialValue, isMutable, visibility) } private fun initClassScope(): Scope { - if( classScope == null ) classScope = Scope() + if (classScope == null) classScope = Scope() return classScope!! } @@ -96,7 +100,7 @@ open class ObjClass( ) { initClassScope() 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") classScope!!.addItem(name, isMutable, initialValue, visibility) } @@ -127,26 +131,29 @@ open class ObjClass( override suspend fun readField(scope: Scope, name: String): ObjRecord { classScope?.objects?.get(name)?.let { - if( it.visibility.isPublic ) return it + if (it.visibility.isPublic) return it } return super.readField(scope, name) } override suspend fun writeField(scope: Scope, name: String, newValue: Obj) { 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") } ?: super.writeField(scope, name, newValue) } - override suspend fun invokeInstanceMethod(scope: Scope, name: String, args: Arguments, - onNotFoundResult: Obj?): Obj { + override suspend fun invokeInstanceMethod( + scope: Scope, name: String, args: Arguments, + onNotFoundResult: (() -> Obj?)? + ): Obj { return classScope?.objects?.get(name)?.value?.invoke(scope, this, args) ?: 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() } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjCollection.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjCollection.kt index 6cf9511..a12a238 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjCollection.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjCollection.kt @@ -22,4 +22,4 @@ package net.sergeych.lyng.obj */ val ObjCollection = ObjClass("Collection", ObjIterable).apply { - } \ No newline at end of file +} \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDuration.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDuration.kt index 4d71549..ec21bc3 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDuration.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDuration.kt @@ -57,7 +57,7 @@ class ObjDuration(val duration: Duration) : Obj() { override suspend fun callOn(scope: Scope): Obj { val args = scope.args 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) return ObjDuration( @@ -66,7 +66,7 @@ class ObjDuration(val duration: Duration) : Obj() { is ObjInt -> a0.value.seconds is ObjReal -> a0.value.seconds else -> { - scope.raiseIllegalArgument("can't construct Instant(${args.inspect()})") + scope.raiseIllegalArgument("can't construct Instant(${args.inspect(scope)})") } } ) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstance.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstance.kt index 62b10b6..90015e6 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstance.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstance.kt @@ -48,7 +48,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { } override suspend fun invokeInstanceMethod(scope: Scope, name: String, args: Arguments, - onNotFoundResult: Obj?): Obj = + onNotFoundResult: (()->Obj?)?): Obj = instanceScope[name]?.let { if (it.visibility.isPublic) it.value.invoke( diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstanceClass.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstanceClass.kt index 2760bc5..098d73e 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstanceClass.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstanceClass.kt @@ -30,12 +30,19 @@ class ObjInstanceClass(val name: String) : ObjClass(name) { override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj { val args = decoder.decodeAnyList(scope) 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}") val newScope = scope.copy(args = Arguments(args)) return (callOn(newScope) as ObjInstance).apply { - deserializeStateVars(scope,decoder) - invokeInstanceMethod(scope, "onDeserialized", onNotFoundResult = ObjVoid) + deserializeStateVars(scope, decoder) + invokeInstanceMethod(scope, "onDeserialized") { ObjVoid } + } + } + + init { + addFn("toString", true) { + println("-------------- tos! --------------") + ObjString(thisObj.toString()) } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstant.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstant.kt index 5583745..11a091a 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstant.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstant.kt @@ -117,7 +117,7 @@ class ObjInstant(val instant: Instant,val truncateMode: LynonSettings.InstantTru is ObjInstant -> a0.instant else -> { - scope.raiseIllegalArgument("can't construct Instant(${args.inspect()})") + scope.raiseIllegalArgument("can't construct Instant(${args.inspect(scope)})") } } ) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInt.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInt.kt index 2664de6..7a02be0 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInt.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInt.kt @@ -23,7 +23,6 @@ import net.sergeych.lynon.LynonEncoder import net.sergeych.lynon.LynonType 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 doubleValue get() = value.toDouble() override val toObjInt get() = this diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjKotlinIterator.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjKotlinIterator.kt index 258caaa..5634080 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjKotlinIterator.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjKotlinIterator.kt @@ -82,18 +82,18 @@ fun Obj.toFlow(scope: Scope): Flow = flow { * * 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 hasNext = iterator.getInstanceMethod(scope, "hasNext") val next = iterator.getInstanceMethod(scope, "next") var closeIt = false while (hasNext.invoke(scope, iterator).toBool()) { val nextValue = next.invoke(scope, iterator) - if( !callback(nextValue) ) { + if (!callback(nextValue)) { closeIt = true break } } - if( closeIt ) - iterator.invokeInstanceMethod(scope, "cancelIteration", onNotFoundResult = ObjVoid) + if (closeIt) + iterator.invokeInstanceMethod(scope, "cancelIteration") { ObjVoid } } \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjList.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjList.kt index fe05970..185fa91 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjList.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjList.kt @@ -25,10 +25,6 @@ import net.sergeych.lynon.LynonType class ObjList(val list: MutableList = mutableListOf()) : Obj() { - override fun toString(): String = "[${ - list.joinToString(separator = ", ") { it.inspect() } - }]" - override suspend fun getAt(scope: Scope, index: Obj): Obj { return when (index) { is ObjInt -> { @@ -65,7 +61,7 @@ class ObjList(val list: MutableList = 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)}") } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMutableBuffer.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMutableBuffer.kt index 69ead0a..5677d3e 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMutableBuffer.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMutableBuffer.kt @@ -28,7 +28,7 @@ class ObjMutableBuffer(byteArray: UByteArray) : ObjBuffer(byteArray) { is ObjInt -> newValue.value.toUByte() is ObjChar -> newValue.value.code.toUByte() 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 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 ObjInt -> b.value.toUByte() 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 diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRange.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRange.kt index 9cdce7d..d80dc21 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRange.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRange.kt @@ -26,12 +26,12 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob override val objClass: ObjClass = type - override fun toString(): String { + override suspend fun toString(scope: Scope): ObjString { val result = StringBuilder() - result.append("${start?.inspect() ?: '∞'} ..") + result.append("${start?.inspect(scope) ?: '∞'} ..") if (!isEndInclusive) result.append('<') - result.append(" ${end?.inspect() ?: '∞'}") - return result.toString() + result.append(" ${end?.inspect(scope) ?: '∞'}") + 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] * otherwise returns start.value.toInt() */ - fun startInt(scope: Scope): Int = + suspend fun startInt(scope: Scope): Int = if( start == null || start is ObjNull) 0 else { 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 { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjReal.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjReal.kt index d1a94ae..b5cce43 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjReal.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjReal.kt @@ -27,7 +27,6 @@ import kotlin.math.floor import kotlin.math.roundToLong 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 doubleValue: Double by lazy { value } override val toObjInt: ObjInt by lazy { ObjInt(longValue) } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjString.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjString.kt index 011d01b..7277714 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjString.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjString.kt @@ -44,9 +44,7 @@ data class ObjString(val value: String) : Obj() { override fun toString(): String = value - override val asStr: ObjString by lazy { this } - - override fun inspect(): String { + override suspend fun inspect(scope: Scope): String { return "\"$value\"" } @@ -54,7 +52,7 @@ data class ObjString(val value: String) : Obj() { get() = type 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 { @@ -159,6 +157,9 @@ data class ObjString(val value: String) : Obj() { addFn("toReal") { ObjReal(thisAs().value.toDouble()) } + addFn("trim") { + thisAs().value.trim().let(::ObjString) + } } } } \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/stdlib_included/root_lyng.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/stdlib_included/root_lyng.kt index 54c2338..b683020 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/stdlib_included/root_lyng.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/stdlib_included/root_lyng.kt @@ -103,6 +103,21 @@ fun Iterable.any(predicate): Bool { fun Iterable.all(predicate): Bool { !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() diff --git a/lynglib/src/commonTest/kotlin/ScriptTest.kt b/lynglib/src/commonTest/kotlin/ScriptTest.kt index 6a20147..d7ce805 100644 --- a/lynglib/src/commonTest/kotlin/ScriptTest.kt +++ b/lynglib/src/commonTest/kotlin/ScriptTest.kt @@ -1789,7 +1789,7 @@ class ScriptTest { "e="+e+"f="+f() } 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() ) @@ -1824,7 +1824,7 @@ class ScriptTest { } val f = Foo() 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() ) @@ -2661,7 +2661,8 @@ class ScriptTest { @Test fun testBufferEncodings() = runTest { - eval(""" + eval( + """ import lyng.buffer val b = Buffer("hello") @@ -2676,7 +2677,8 @@ class ScriptTest { println(b.inspect()) - """.trimIndent()) + """.trimIndent() + ) } @Test @@ -2900,7 +2902,8 @@ class ScriptTest { @Test fun tesFunAnnotation() = runTest { - eval(""" + eval( + """ val exportedSymbols = Map() @@ -2919,7 +2922,8 @@ class ScriptTest { assert( exportedSymbols["getBalance"] != null ) assertEquals(122, getBalance(1)) - """.trimIndent()) + """.trimIndent() + ) } @Test @@ -2939,12 +2943,14 @@ class ScriptTest { assertEquals( Color.valueOf("GREEN"), Color.GREEN ) - """.trimIndent()) + """.trimIndent() + ) } @Test fun enumSerializationTest() = runTest { - eval(""" + eval( + """ import lyng.serialization enum Color { @@ -2960,12 +2966,14 @@ class ScriptTest { assert( e1.size / 1000.0 < 6) println(Lynon.encode( (1..100).map { "RED" } ).toDump() ) - """.trimIndent()) + """.trimIndent() + ) } @Test fun cachedTest() = runTest { - eval( """ + eval( + """ var counter = 0 var value = cached { @@ -2978,66 +2986,82 @@ class ScriptTest { assertEquals(1, counter) assertEquals("ok", value()) assertEquals(1, counter) - """.trimIndent()) + """.trimIndent() + ) } @Test fun testJoinToString() = runTest { - eval(""" + eval( + """ assertEquals( (1..3).joinToString(), "1 2 3") assertEquals( (1..3).joinToString(":"), "1:2:3") assertEquals( (1..3).joinToString { it * 10 }, "10 20 30") - """.trimIndent()) + """.trimIndent() + ) } @Test fun testElvisAndThrow() = runTest { - eval(""" + eval( + """ val x = assertThrows { null ?: throw "test" + "x" } assertEquals( "testx", x.message) - """.trimIndent()) + """.trimIndent() + ) } @Test fun testElvisAndThrow2() = runTest { - eval(""" + eval( + """ val t = "112" val x = t ?: run { throw "testx" } } assertEquals( "112", x) - """.trimIndent()) + """.trimIndent() + ) } @Test fun testElvisAndRunThrow() = runTest { - eval(""" + eval( + """ val x = assertThrows { null ?: run { throw "testx" } } assertEquals( "testx", x.message) - """.trimIndent()) + """.trimIndent() + ) } @Test fun testNewlinesAnsCommentsInExpressions() = runTest { - assertEquals( 2, (Scope().eval(""" + assertEquals( + 2, (Scope().eval( + """ val e = 1 + 4 - 3 - """.trimIndent())).toInt()) + """.trimIndent() + )).toInt() + ) - eval(""" + eval( + """ val x = [1,2,3] .map { it * 10 } .map { it + 1 } assertEquals( [11,21,31], x) - """.trimIndent()) + """.trimIndent() + ) } @Test fun testNotExpressionWithoutWs() = runTest { - eval(""" + eval( + """ fun test() { false } class T(value) assert( !false ) @@ -3046,12 +3070,14 @@ class ScriptTest { val t = T(false) assert( !t.value ) assert( !if( true ) false else true ) - """.trimIndent()) + """.trimIndent() + ) } @Test fun testMultilineFnDeclaration() = runTest { - eval(""" + eval( + """ fun test( x = 1, y = 2 @@ -3063,8 +3089,38 @@ class ScriptTest { 6, 7 ) ) + """.trimIndent() + ) + } + + @Test + fun testOverridenListToString() = runTest { + eval(""" + val x = [1,2,3] + assertEquals( "[1,2,3]", x.toString() ) """.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() + ) + } } \ No newline at end of file diff --git a/lynglib/src/jvmTest/kotlin/BookTest.kt b/lynglib/src/jvmTest/kotlin/BookTest.kt index 9eb991c..65f7eb4 100644 --- a/lynglib/src/jvmTest/kotlin/BookTest.kt +++ b/lynglib/src/jvmTest/kotlin/BookTest.kt @@ -177,11 +177,11 @@ suspend fun DocTest.test(_scope: Scope? = null) { scope.apply { addFn("println") { 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 { 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') } } @@ -194,7 +194,7 @@ suspend fun DocTest.test(_scope: Scope? = null) { } catch (e: Throwable) { error = e null - }?.inspect()?.replace(Regex("@\\d+"), "@...") + }?.inspect(scope)?.replace(Regex("@\\d+"), "@...") if (bookMode) { if (error != null) {