fix #51 ref #48 flows started. bad closure-based bug fixed

This commit is contained in:
sergeych committed 2025-08-08 19:23:43 +03:00
1 parent f7f020f4d6
commit 1a90b25b1e
20 files changed
+533 -66

No files matched your search

+1
View File
@@ -65,6 +65,7 @@ kotlin {
languageSettings.optIn("kotlin.contracts.ExperimentalContracts")
languageSettings.optIn("kotlin.ExperimentalUnsignedTypes")
languageSettings.optIn("kotlin.coroutines.DelicateCoroutinesApi")
languageSettings.optIn("kotlinx.coroutines.flow.DelicateCoroutinesApi")
}
val commonMain by getting {
@@ -1,19 +0,0 @@
package net.sergeych.lyng
import net.sergeych.lyng.obj.ObjRecord
/**
* Special version of the [Scope] used to `apply` new this object to
* _parent context property.
*
* @param _parent context to apply to
* @param args arguments for the new context
* @param appliedScope the new context to apply, it will have lower priority except for `this` which
* will be reset by appliedContext's `this`.
*/
class AppliedScope(_parent: Scope, args: Arguments, val appliedScope: Scope)
: Scope(_parent, args, appliedScope.pos, appliedScope.thisObj) {
override fun get(name: String): ObjRecord? =
if (name == "this") thisObj.asReadonly
else super.get(name) ?: appliedScope[name]
}
@@ -34,7 +34,8 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
defaultRecordType: ObjRecord.Type = ObjRecord.Type.ConstructorField
) {
fun assign(a: Item, value: Obj) {
scope.addItem(a.name, (a.accessType ?: defaultAccessType).isMutable, value,
scope.addItem(a.name, (a.accessType ?: defaultAccessType).isMutable,
value.byValueCopy(),
a.visibility ?: defaultVisibility,
recordType = defaultRecordType)
}
@@ -36,7 +36,7 @@ data class Arguments(val list: List<Obj>, val tailBlockMode: Boolean = false) :
fun firstAndOnly(pos: Pos = Pos.UNKNOWN): Obj {
if (list.size != 1) throw ScriptError(pos, "expected one argument, got ${list.size}")
return list.first()
return list.first().byValueCopy()
}
/**
@@ -0,0 +1,16 @@
package net.sergeych.lyng
import net.sergeych.lyng.obj.ObjRecord
/**
* Scope that adds a "closure" to caller; most often it is used to apply class instance to caller scope.
* Inherits [Scope.args] and [Scope.thisObj] from [callScope] and adds lookup for symbols
* from [closureScope] with proper precedence
*/
class ClosureScope(val callScope: Scope,val closureScope: Scope) : Scope(callScope, callScope.args, thisObj = callScope.thisObj) {
override fun get(name: String): ObjRecord? {
// closure should be treated below callScope
return super.get(name) ?: closureScope.get(name)
}
}
@@ -470,7 +470,7 @@ class Compiler(
val callStatement = statement {
// and the source closure of the lambda which might have other thisObj.
val context = AppliedScope(closure!!, args, this)
val context = ClosureScope(this, closure!!) //AppliedScope(closure!!, args, this)
if (argsDeclaration == null) {
// no args: automatic var 'it'
val l = args.list
@@ -1540,7 +1540,8 @@ class Compiler(
}
}
private suspend fun parseFunctionDeclaration(
private suspend fun
parseFunctionDeclaration(
visibility: Visibility = Visibility.Public,
@Suppress("UNUSED_PARAMETER") isOpen: Boolean = false,
isExtern: Boolean = false,
@@ -1586,9 +1587,11 @@ class Compiler(
val fnBody = statement(t.pos) { callerContext ->
callerContext.pos = start
// restore closure where the function was defined, and making a copy of it
// for local space (otherwise it will write local stuff to closure!)
val context = closure?.copy() ?: callerContext.raiseError("bug: closure not set")
val context = closure?.let { ClosureScope(callerContext, it) }
?: callerContext.raiseError("bug: closure not set")
// load params from caller context
argsDeclaration.assignToContext(context, callerContext.args, defaultAccessType = AccessType.Val)
@@ -1597,7 +1600,7 @@ class Compiler(
}
fnStatements.execute(context)
}
val fnCreatestatement = statement(start) { context ->
val fnCreateStatement = statement(start) { context ->
// we added fn in the context. now we must save closure
// for the function
closure = context
@@ -1606,7 +1609,12 @@ class Compiler(
val type = context[typeName]?.value ?: context.raiseSymbolNotFound("class $typeName not found")
if (type !is ObjClass) context.raiseClassCastError("$typeName is not the class instance")
type.addFn(name, isOpen = true) {
fnBody.execute(this)
// ObjInstance has a fixed instance scope, so we need to build a closure
(thisObj as? ObjInstance)?.let { i ->
fnBody.execute(ClosureScope(this, i.instanceScope))
}
// other classes can create one-time scope for this rare case:
?: fnBody.execute(thisObj.autoInstanceScope(this))
}
}
// regular function/method
@@ -1616,10 +1624,10 @@ class Compiler(
fnBody
}
return if (isStatic) {
currentInitScope += fnCreatestatement
currentInitScope += fnCreateStatement
NopStatement
} else
fnCreatestatement
fnCreateStatement
}
private suspend fun parseBlock(skipLeadingBrace: Boolean = false): Statement {
@@ -16,7 +16,7 @@ import net.sergeych.lyng.pacman.ImportProvider
*
* There are special types of scopes:
*
* - [AppliedScope] - scope used to apply a closure to some thisObj scope
* - [ClosureScope] - scope used to apply a closure to some thisObj scope
*/
open class Scope(
val parent: Scope?,
@@ -73,7 +73,7 @@ open class Scope(
inline fun <reified T : Obj> requiredArg(index: Int): T {
if (args.list.size <= index) raiseError("Expected at least ${index + 1} argument, got ${args.list.size}")
return (args.list[index] as? T)
return (args.list[index].byValueCopy() as? T)
?: raiseClassCastError("Expected type ${T::class.simpleName}, got ${args.list[index]::class.simpleName}")
}
@@ -94,8 +94,15 @@ open class Scope(
raiseError("This function does not accept any arguments")
}
inline fun <reified T : Obj> thisAs(): T = (thisObj as? T)
?: raiseClassCastError("Cannot cast ${thisObj.objClass.className} to ${T::class.simpleName}")
inline fun <reified T : Obj> thisAs(): T {
var s: Scope? = this
do {
val t = s!!.thisObj
if (t is T) return t
s = s.parent
} while(s != null)
raiseClassCastError("Cannot cast ${thisObj.objClass.className} to ${T::class.simpleName}")
}
internal val objects = mutableMapOf<String, ObjRecord>()
@@ -193,6 +200,11 @@ open class Scope(
val importManager by lazy { (currentImportProvider as? ImportManager)
?: throw IllegalStateException("this scope has no manager in the chain (provided $currentImportProvider") }
override fun toString(): String {
val contents = objects.entries.joinToString { "${if( it.value.isMutable ) "var" else "val" } ${it.key}=${it.value.value}" }
return "S[this=$thisObj $contents]"
}
companion object {
fun new(): Scope =
@@ -153,6 +153,16 @@ class Script(
}
result ?: raiseError(ObjAssertionFailedException(this,"Expected exception but nothing was thrown"))
}
addFn("traceScope") {
println("trace Scope: $this")
var p = this.parent
var level = 0
while (p != null) {
println(" parent#${++level}: $p")
p = p.parent
}
ObjVoid
}
addVoidFn("delay") {
delay((this.args.firstAndOnly().toDouble()/1000.0).roundToLong())
@@ -182,7 +192,7 @@ class Script(
addConst("Mutex", ObjMutex.type)
addFn("launch") {
val callable = args.firstAndOnly() as Statement
val callable = requireOnlyArg<Statement>()
ObjDeferred(globalDefer {
callable.execute(this@addFn)
})
@@ -193,6 +203,9 @@ class Script(
ObjVoid
}
addFn("flow") {
ObjFlow(requireOnlyArg<Statement>())
}
val pi = ObjReal(PI)
addConst("π", pi)
@@ -14,6 +14,8 @@ open class ScriptError(val pos: Pos, val errorMessage: String, cause: Throwable?
cause
)
class ScriptFlowIsNoMoreCollected: Exception()
class ExecutionError(val errorObject: ObjException) : ScriptError(errorObject.scope.pos, errorObject.message)
class ImportException(pos: Pos, message: String) : ScriptError(pos, message)
@@ -55,7 +55,8 @@ open class Obj {
scope: Scope,
name: String,
args: Arguments = Arguments.EMPTY
): T = invokeInstanceMethod(scope, name, args) as T
): T =
invokeInstanceMethod(scope, name, args) as T
/**
* Invoke a method of the object if exists
@@ -68,7 +69,10 @@ open class Obj {
args: Arguments = Arguments.EMPTY,
onNotFoundResult: Obj?=null
): Obj =
objClass.getInstanceMemberOrNull(name)?.value?.invoke(scope, this, args)
objClass.getInstanceMemberOrNull(name)?.value?.invoke(
scope,
this,
args)
?: onNotFoundResult
?: scope.raiseSymbolNotFound(name)
@@ -251,6 +255,14 @@ open class Obj {
scope.raiseNotImplemented()
}
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
}
companion object {
val rootObjectType = ObjClass("Obj").apply {
@@ -36,7 +36,7 @@ open class ObjClass(
/**
* members: fields most often. These are called with [ObjInstance] withs ths [ObjInstance.objClass]
*/
private val members = mutableMapOf<String, ObjRecord>()
internal val members = mutableMapOf<String, ObjRecord>()
override fun toString(): String = className
@@ -0,0 +1,141 @@
package net.sergeych.lyng.obj
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.ChannelResult
import kotlinx.coroutines.channels.ReceiveChannel
import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScriptFlowIsNoMoreCollected
import net.sergeych.lyng.Statement
import net.sergeych.mp_tools.globalLaunch
import kotlin.coroutines.cancellation.CancellationException
class ObjFlowBuilder(val output: SendChannel<Obj>) : Obj() {
override val objClass = type
companion object {
@OptIn(DelicateCoroutinesApi::class)
val type = object : ObjClass("FlowBuilder") {}.apply {
addFn("emit") {
val data = requireOnlyArg<Obj>()
println("well well $data")
try {
println("builder ${thisAs<ObjFlowBuilder>().hashCode()}")
println("channel ${thisAs<ObjFlowBuilder>().output.hashCode()}")
val channel = thisAs<ObjFlowBuilder>().output
if( !channel.isClosedForSend )
channel.send(data)
else
throw ScriptFlowIsNoMoreCollected()
} catch (x: Exception) {
if( x !is CancellationException )
x.printStackTrace()
throw ScriptFlowIsNoMoreCollected()
}
ObjVoid
}
}
}
}
private fun createLyngFlowInput(scope: Scope, producer: Statement): ReceiveChannel<Obj> {
val channel = Channel<Obj>(Channel.RENDEZVOUS)
val builder = ObjFlowBuilder(channel)
val builderScope = scope.copy(newThisObj = builder)
globalLaunch {
try {
producer.execute(builderScope)
}
catch(x: ScriptFlowIsNoMoreCollected) {
x.printStackTrace()
// premature flow closing, OK
}
catch(x: Exception) {
x.printStackTrace()
}
channel.close()
}
return channel
}
class ObjFlow(val producer: Statement) : Obj() {
override val objClass = type
companion object {
val type = object : ObjClass("Flow", ObjIterable) {
override suspend fun callOn(scope: Scope): Obj {
scope.raiseError("Flow constructor is not available")
}
}.apply {
addFn("iterator") {
println("called iterator!")
ObjFlowIterator(thisAs<ObjFlow>().producer)
}
}
}
}
class ObjFlowIterator(val producer: Statement) : Obj() {
override val objClass: ObjClass = type
private var channel: ReceiveChannel<Obj>? = null
private var nextItem: ChannelResult<Obj>? = null
private var isCancelled = false
private fun checkNotCancelled(scope: Scope) {
if( isCancelled )
scope.raiseIllegalState("iteration is cancelled")
}
suspend fun hasNext(scope: Scope): ObjBool {
checkNotCancelled(scope)
// cold start:
if (channel == null) channel = createLyngFlowInput(scope, producer)
if (nextItem == null) nextItem = channel!!.receiveCatching()
return ObjBool(nextItem!!.isSuccess)
}
suspend fun next(scope: Scope): Obj {
checkNotCancelled(scope)
if (hasNext(scope).value == false) scope.raiseIllegalState("iteration is done")
return nextItem!!.getOrThrow().also { nextItem = null }
}
private val access = Mutex()
suspend fun cancel() {
access.withLock {
if (!isCancelled) {
isCancelled = true
channel?.cancel()
}
}
}
companion object {
val type = object : ObjClass("FlowIterator", ObjIterator) {
}.apply {
addFn("hasNext") {
thisAs<ObjFlowIterator>().hasNext(this).toObj()
}
addFn("next") {
val x = thisAs<ObjFlowIterator>()
x.next(this)
}
addFn("cancelIteration") {
val x = thisAs<ObjFlowIterator>()
x.cancel()
ObjVoid
}
}
}
}
@@ -34,7 +34,10 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
onNotFoundResult: Obj?): Obj =
instanceScope[name]?.let {
if (it.visibility.isPublic)
it.value.invoke(scope, this, args)
it.value.invoke(
instanceScope,
this,
args)
else
scope.raiseError(ObjAccessException(scope, "can't invoke non-public method $name"))
}
@@ -53,7 +53,7 @@ val ObjIterable by lazy {
addFn("toMap") {
val result = ObjMap()
thisObj.toFlow(this).collect { pair ->
result.map[pair.getAt(this,0)] = pair.getAt(this, 1)
result.map[pair.getAt(this, 0)] = pair.getAt(this, 1)
}
result
}
@@ -86,6 +86,24 @@ val ObjIterable by lazy {
ObjList(result)
}
addFn("take") {
var n = requireOnlyArg<ObjInt>().value.toInt()
val result = mutableListOf<Obj>()
if (n > 0) {
thisObj.enumerate(this) {
result += it
--n > 0
}
}
ObjList(result)
}
// addFn("drop" ) {
// var n = requireOnlyArg<ObjInt>().value.toInt()
// if( n < 0 ) raiseIllegalArgument("drop($n): should be positive")
// val it = callMethod<>()
// }
addFn("isEmpty") {
ObjBool(
thisObj.invokeInstanceMethod(this, "iterator")
@@ -1,3 +1,4 @@
package net.sergeych.lyng.obj
val ObjIterator by lazy { ObjClass("Iterator") }
val ObjIterator by lazy { ObjClass("Iterator") }
@@ -55,4 +55,26 @@ fun Obj.toFlow(scope: Scope): Flow<Obj> = flow {
while (hasNext.invoke(scope, iterator).toBool()) {
emit(next.invoke(scope, iterator))
}
}
/**
* Call [callback] for each element of this obj considering it provides [Iterator]
* methods `hasNext` and `next`.
*
* IF callback returns false, iteration is stopped.
*/
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) ) {
closeIt = true
break
}
}
if( closeIt )
iterator.invokeInstanceMethod(scope, "cancelIteration", onNotFoundResult = ObjVoid)
}
@@ -93,7 +93,9 @@ data class ObjString(val value: String) : Obj() {
ObjString(decoder.unpackBinaryData().decodeToString())
}.apply {
addFn("toInt") {
ObjInt(thisAs<ObjString>().value.toLong())
ObjInt(thisAs<ObjString>().value.toLongOrNull()
?: raiseIllegalArgument("can't convert to int: $thisObj")
)
}
addFn("startsWith") {
ObjBool(thisAs<ObjString>().value.startsWith(requiredArg<ObjString>(0).value))
@@ -137,7 +139,9 @@ data class ObjString(val value: String) : Obj() {
}
addFn("encodeUtf8") { ObjBuffer(thisAs<ObjString>().value.encodeToByteArray().asUByteArray()) }
addFn("size") { ObjInt(thisAs<ObjString>().value.length.toLong()) }
addFn("toReal") { ObjReal(thisAs<ObjString>().value.toDouble()) }
addFn("toReal") {
ObjReal(thisAs<ObjString>().value.toDouble())
}
}
}
}
@@ -63,4 +63,55 @@ class TestCoroutines {
""".trimIndent()
)
}
@Test
fun testFlows() = runTest {
eval("""
val f = flow {
println("Starting generator")
var n1 = 0
var n2 = 1
emit(n1)
emit(n2)
while(true) {
val n = n1 + n2
emit(n)
n1 = n2
n2 = n
}
}
val correctFibs = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765]
assertEquals( correctFibs, f.take(correctFibs.size))
""".trimIndent())
}
@Test
fun testFlow2() = runTest {
eval("""
val f = flow {
println("Starting generator")
emit("start")
emit("start2")
println("Emitting")
(1..4).forEach {
// println("you hoo "+it)
emit(it)
}
println("Done emitting")
}
// let's collect flow:
val result = []
// for( x in f ) result += x
println(result)
// let's collect it once again:
println(f.toList())
println(f.toList())
// for( x in f ) println(x)
// for( x in f ) println(x)
//assertEquals( result, f.toList() )
""".trimIndent())
}
}
+154 -25
View File
@@ -1274,6 +1274,106 @@ class ScriptTest {
)
}
@Test
fun testCaptureLocals() = runTest {
eval(
"""
fun outer(prefix) {
val p1 = "0" + prefix
{
p1 + "2" + it
}
}
fun outer2(prefix) {
val p1 = "*" + prefix
{
p1 + "2" + it
}
}
val x = outer("1")
val y = outer2("1")
println(x("!"))
assertEquals( "0123", x("3") )
assertEquals( "*123", y("3") )
""".trimIndent()
)
}
@Test
fun testInstanceCallScopeIsCorrect() = runTest {
eval(
"""
val prefix = ":"
class T(text) {
fun getText() {
println(text)
prefix + text + "!"
}
}
val text = "invalid"
val t1 = T("foo")
val t2 = T("bar")
// get inside the block
for( i in 1..3 ) {
assertEquals( "foo", t1.text )
assertEquals( ":foo!", t1.getText() )
assertEquals( "bar", t2.text )
assertEquals( ":bar!", t2.getText() )
}
""".trimIndent()
)
}
@Test
fun testAppliedScopes() = runTest {
eval(
"""
class T(text) {
fun getText() {
println(text)
text + "!"
}
}
val prefix = ":"
val lambda = {
prefix + getText() + "!"
}
val text = "invalid"
val t1 = T("foo")
val t2 = T("bar")
t1.apply {
// it must take "text" from class t1:
assertEquals("foo", text)
assertEquals( "foo!", getText() )
assertEquals( ":foo!!", lambda() )
}
t2.apply {
assertEquals("bar", text)
assertEquals( "bar!", getText() )
assertEquals( ":bar!!", lambda() )
}
// worst case: names clash
fun badOne() {
val prefix = "&"
t1.apply {
assertEquals( ":foo!!", lambda() )
}
}
badOne()
""".trimIndent()
)
}
@Test
fun testLambdaWithArgsEllipsis() = runTest {
eval(
@@ -2244,10 +2344,12 @@ class ScriptTest {
@Test
fun testSet2() = runTest {
eval("""
eval(
"""
assertEquals( Set( ...[1,2,3]), Set(1,2,3) )
assertEquals( Set( ...[1,false,"ok"]), Set("ok", 1, false) )
""".trimIndent())
""".trimIndent()
)
}
@Test
@@ -2284,9 +2386,11 @@ class ScriptTest {
eval(
"""
class Point(x,y)
// see the difference: apply changes this to newly created Point:
val p = Point(1,2).apply {
this.x++; this.y++
this.x++
y++
}
assertEquals(p, Point(2,3))
>>> void
@@ -2305,6 +2409,10 @@ class ScriptTest {
}
fun Object.isInteger() {
println(this)
println(this is Int)
println(this is Real)
println(this is String)
when(this) {
is Int -> true
is Real -> toInt() == this
@@ -2319,7 +2427,7 @@ class ScriptTest {
assert( 12.isInteger() == true )
assert( 12.1.isInteger() == false )
assert( "5".isInteger() )
assert( ! "5.2".isInteger() )
assert( !"5.2".isInteger() )
""".trimIndent()
)
}
@@ -2446,20 +2554,26 @@ class ScriptTest {
fun testDefaultImportManager() = runTest {
val scope = Scope.new()
assertFails {
scope.eval("""
scope.eval(
"""
import foo
foo()
""".trimIndent())
""".trimIndent()
)
}
scope.importManager.addTextPackages("""
scope.importManager.addTextPackages(
"""
package foo
fun foo() { "bar" }
""".trimIndent())
scope.eval("""
""".trimIndent()
)
scope.eval(
"""
import foo
assertEquals( "bar", foo())
""".trimIndent())
""".trimIndent()
)
}
@Test
@@ -2478,7 +2592,8 @@ class ScriptTest {
@Test
fun testBuffer() = runTest {
eval("""
eval(
"""
import lyng.buffer
assertEquals( 0, Buffer().size )
@@ -2497,12 +2612,14 @@ class ScriptTest {
assertEquals(101, buffer[2])
assertEquals("Heelo", buffer.decodeUtf8())
""".trimIndent())
""".trimIndent()
)
}
@Test
fun testBufferCompare() = runTest {
eval("""
eval(
"""
import lyng.buffer
println("Hello".characters())
@@ -2520,7 +2637,8 @@ class ScriptTest {
assertEquals("foo", map[b2])
assertEquals(null, map[b3])
""".trimIndent())
""".trimIndent()
)
}
@Test
@@ -2548,6 +2666,7 @@ class ScriptTest {
)
delay(1000)
}
@Test
fun testTimeStatics() = runTest {
eval(
@@ -2589,7 +2708,8 @@ class ScriptTest {
println(Script.defaultImportManager.packageNames)
println(s.importManager.packageNames)
s.importManager.addTextPackages("""
s.importManager.addTextPackages(
"""
package foo
import lyng.time
@@ -2597,8 +2717,10 @@ class ScriptTest {
fun foo() {
println("foo: %s"(Instant()))
}
""".trimIndent())
s.importManager.addTextPackages("""
""".trimIndent()
)
s.importManager.addTextPackages(
"""
package bar
import lyng.time
@@ -2606,24 +2728,28 @@ class ScriptTest {
fun bar() {
println("bar: %s"(Instant()))
}
""".trimIndent())
""".trimIndent()
)
println(s.importManager.packageNames)
s.eval("""
s.eval(
"""
import foo
import bar
foo()
bar()
""".trimIndent())
""".trimIndent()
)
}
@Test
fun testIndexIntIncrements() = runTest {
eval("""
eval(
"""
val x = [1,2,3]
x[1]++
++x[0]
@@ -2636,12 +2762,14 @@ class ScriptTest {
assert( b == Buffer(1,3,3) )
++b[0]
assertEquals( b, Buffer(2,3,3) )
""".trimIndent())
""".trimIndent()
)
}
@Test
fun testIndexIntDecrements() = runTest {
eval("""
eval(
"""
val x = [1,2,3]
x[1]--
--x[0]
@@ -2654,13 +2782,14 @@ class ScriptTest {
assert( b == Buffer(1,1,3) )
--b[0]
assertEquals( b, Buffer(0,1,3) )
""".trimIndent())
""".trimIndent()
)
}
@Test
fun testRangeToList() = runTest {
val x = eval("""(1..10).toList()""") as ObjList
assertEquals(listOf(1,2,3,4,5,6,7,8,9,10), x.list.map { it.toInt() })
assertEquals(listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), x.list.map { it.toInt() })
val y = eval("""(-2..3).toList()""") as ObjList
println(y.list)
}