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]
[list.last, list.lastIndex]
>>> [30, 2]
>>> [30,2]
__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:
('a'..<'c').toList
>>> ['a', 'b']
>>> [a,b]
# Instance members

View File

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

View File

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

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:
[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

View File

@ -63,7 +63,7 @@ data class Arguments(val list: List<Obj>, 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())

View File

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

View File

@ -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<Obj>(0)
val b = requiredArg<Obj>(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<Obj>(0)
val b = requiredArg<Obj>(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<Statement>()
@ -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
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -22,4 +22,4 @@ package net.sergeych.lyng.obj
*/
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 {
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)})")
}
}
)

View File

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

View File

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

View File

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

View File

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

View File

@ -82,18 +82,18 @@ fun Obj.toFlow(scope: Scope): Flow<Obj> = 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 }
}

View File

@ -25,10 +25,6 @@ import net.sergeych.lynon.LynonType
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 {
return when (index) {
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 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

View File

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

View File

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

View File

@ -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<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 {
!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()

View File

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

View File

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