fix #58 improper toString overload processing in Lyng
fix #57 Exception#getStackTrace() ref #56 StackTraceEntry is serializable now
This commit is contained in:
parent
8d1cafae80
commit
4b613fda7c
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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", "") }
|
||||
|
@ -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()
|
||||
|
@ -50,6 +50,5 @@ val ObjArray by lazy {
|
||||
addFn("indices") {
|
||||
ObjRange(0.toObj(), thisObj.invokeInstanceMethod(this, "size"), false)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
||||
}
|
||||
|
||||
|
@ -22,4 +22,4 @@ package net.sergeych.lyng.obj
|
||||
*/
|
||||
val ObjCollection = ObjClass("Collection", ObjIterable).apply {
|
||||
|
||||
}
|
||||
}
|
@ -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)})")
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -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(
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)})")
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -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
|
||||
|
@ -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 }
|
||||
}
|
@ -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)}")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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) }
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -104,5 +104,20 @@ 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()
|
||||
|
||||
|
@ -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()
|
||||
)
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user