OO: added inheritance, Collection, Iterator, Iterable, Array. Range is rewritten to be Iterable. Math namespace is temporarily broken (to be updated to new ObjClass model).
This commit is contained in:
		
							parent
							
								
									698d169612
								
							
						
					
					
						commit
						c65f711ee3
					
				
							
								
								
									
										31
									
								
								docs/Iterable.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								docs/Iterable.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
			
		||||
# Iterable interface
 | 
			
		||||
 | 
			
		||||
The inteface which requires iterator to be implemented:
 | 
			
		||||
 | 
			
		||||
    fun iterator(): Iterator
 | 
			
		||||
 | 
			
		||||
Iterator itself is a simple interface that should provide only to method:
 | 
			
		||||
 | 
			
		||||
    interface Iterable {
 | 
			
		||||
        fun hasNext(): Bool
 | 
			
		||||
        fun next(): Obj
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
Just remember at this stage typed declarations are not yet supported.
 | 
			
		||||
 | 
			
		||||
Having `Iterable` in base classes allows to use it in for loop. Also, each `Iterable` has some utility functions available:
 | 
			
		||||
 | 
			
		||||
## toList()
 | 
			
		||||
 | 
			
		||||
Creates a list by iterating to the end. So, the Iterator should be finite to be used with it.
 | 
			
		||||
 | 
			
		||||
## Included in interfaces:
 | 
			
		||||
 | 
			
		||||
- Collection, Array, [List]
 | 
			
		||||
 | 
			
		||||
## Implemented in classes:
 | 
			
		||||
 | 
			
		||||
- [List], [Range]
 | 
			
		||||
 | 
			
		||||
[List]: List.md
 | 
			
		||||
[Range]: Range.md
 | 
			
		||||
@ -45,27 +45,12 @@ are equal or within another, taking into account the end-inclusiveness:
 | 
			
		||||
    assert( (1..<3) in (1..3) )
 | 
			
		||||
    >>> void
 | 
			
		||||
 | 
			
		||||
## Range size and indexed access
 | 
			
		||||
## Finite Ranges are iterable
 | 
			
		||||
 | 
			
		||||
This might be confusing, but the range size and limits are used with for loops
 | 
			
		||||
so their meaning is special.
 | 
			
		||||
So given a range with both ends, you can assume it is [Iterable]. This automatically let
 | 
			
		||||
use finite ranges in loops and convert it to lists:
 | 
			
		||||
 | 
			
		||||
For open ranges, size throws and exception.
 | 
			
		||||
For Int ranges, the `size` is `end` - `start` possibly + 1 for ind-inclusive ranges, and indexing getter returns all values from start to end, probably, inclusive:
 | 
			
		||||
 | 
			
		||||
    val r = 1..3
 | 
			
		||||
    assert( r.size == 3 )
 | 
			
		||||
    assert( r[0] == 1 )
 | 
			
		||||
    assert( r[1] == 2 )
 | 
			
		||||
    assert( r[2] == 3 )
 | 
			
		||||
    >>> void
 | 
			
		||||
 | 
			
		||||
And for end-exclusive range:
 | 
			
		||||
 | 
			
		||||
    val r = 1..<3
 | 
			
		||||
    assert(r.size == 2)
 | 
			
		||||
    assert( r[0] == 1 )
 | 
			
		||||
    assert( r[1] == 2 )
 | 
			
		||||
    assert( [-2, -1, 0, 1] == (-2..1).toList() )
 | 
			
		||||
    >>> void
 | 
			
		||||
 | 
			
		||||
In spite of this you can use ranges in for loops:
 | 
			
		||||
@ -102,8 +87,8 @@ You can use Char as both ends of the closed range:
 | 
			
		||||
 | 
			
		||||
Exclusive end char ranges are supported too:
 | 
			
		||||
 | 
			
		||||
    ('a'..<'c').size
 | 
			
		||||
    >>> 2
 | 
			
		||||
    ('a'..<'c').toList 
 | 
			
		||||
    >>> ['a', 'b']
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Instance members
 | 
			
		||||
@ -119,3 +104,5 @@ Exclusive end char ranges are supported too:
 | 
			
		||||
| size            | for finite ranges, see above | Long          |
 | 
			
		||||
| []              | see above                    |               |
 | 
			
		||||
|                 |                              |               |
 | 
			
		||||
 | 
			
		||||
[Iterable]: Iterable.md
 | 
			
		||||
@ -49,6 +49,7 @@ kotlin {
 | 
			
		||||
                //put your multiplatform dependencies here
 | 
			
		||||
                implementation(libs.kotlinx.coroutines.core)
 | 
			
		||||
                implementation(libs.mp.bintools)
 | 
			
		||||
                implementation("net.sergeych:mp_stools:1.5.2")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        val commonTest by getting {
 | 
			
		||||
 | 
			
		||||
@ -144,7 +144,7 @@ class Compiler(
 | 
			
		||||
                                    context.pos = next.pos
 | 
			
		||||
                                    val v = left.getter(context).value
 | 
			
		||||
                                    WithAccess(
 | 
			
		||||
                                        v.callInstanceMethod(
 | 
			
		||||
                                        v.invokeInstanceMethod(
 | 
			
		||||
                                            context,
 | 
			
		||||
                                            next.value,
 | 
			
		||||
                                            args.toArguments(context)
 | 
			
		||||
@ -540,43 +540,50 @@ class Compiler(
 | 
			
		||||
 | 
			
		||||
                // insofar we suggest source object is enumerable. Later we might need to add checks
 | 
			
		||||
                val sourceObj = source.execute(forContext)
 | 
			
		||||
                val size = runCatching { sourceObj.callInstanceMethod(forContext, "size").toInt() }
 | 
			
		||||
                    .getOrElse { throw ScriptError(tOp.pos, "object is not enumerable: no size", it) }
 | 
			
		||||
                var result: Obj = ObjVoid
 | 
			
		||||
                var breakCaught = false
 | 
			
		||||
                if (size > 0) {
 | 
			
		||||
                    var current = runCatching { sourceObj.getAt(forContext, 0) }
 | 
			
		||||
                        .getOrElse {
 | 
			
		||||
                            throw ScriptError(
 | 
			
		||||
                                tOp.pos,
 | 
			
		||||
                                "object is not enumerable: no index access for ${sourceObj.inspect()}",
 | 
			
		||||
                                it
 | 
			
		||||
                            )
 | 
			
		||||
 | 
			
		||||
                if (sourceObj.isInstanceOf(ObjIterable)) {
 | 
			
		||||
                    loopIterable(forContext, sourceObj, loopSO, body, elseStatement, label)
 | 
			
		||||
                } else {
 | 
			
		||||
                    val size = runCatching { sourceObj.invokeInstanceMethod(forContext, "size").toInt() }
 | 
			
		||||
                        .getOrElse { throw ScriptError(tOp.pos, "object is not enumerable: no size", it) }
 | 
			
		||||
 | 
			
		||||
                    var result: Obj = ObjVoid
 | 
			
		||||
                    var breakCaught = false
 | 
			
		||||
 | 
			
		||||
                    if (size > 0) {
 | 
			
		||||
                        var current = runCatching { sourceObj.getAt(forContext, 0) }
 | 
			
		||||
                            .getOrElse {
 | 
			
		||||
                                throw ScriptError(
 | 
			
		||||
                                    tOp.pos,
 | 
			
		||||
                                    "object is not enumerable: no index access for ${sourceObj.inspect()}",
 | 
			
		||||
                                    it
 | 
			
		||||
                                )
 | 
			
		||||
                            }
 | 
			
		||||
                        var index = 0
 | 
			
		||||
                        while (true) {
 | 
			
		||||
                            loopSO.value = current
 | 
			
		||||
                            try {
 | 
			
		||||
                                result = body.execute(forContext)
 | 
			
		||||
                            } catch (lbe: LoopBreakContinueException) {
 | 
			
		||||
                                if (lbe.label == label || lbe.label == null) {
 | 
			
		||||
                                    breakCaught = true
 | 
			
		||||
                                    if (lbe.doContinue) continue
 | 
			
		||||
                                    else {
 | 
			
		||||
                                        result = lbe.result
 | 
			
		||||
                                        break
 | 
			
		||||
                                    }
 | 
			
		||||
                                } else
 | 
			
		||||
                                    throw lbe
 | 
			
		||||
                            }
 | 
			
		||||
                            if (++index >= size) break
 | 
			
		||||
                            current = sourceObj.getAt(forContext, index)
 | 
			
		||||
                        }
 | 
			
		||||
                    var index = 0
 | 
			
		||||
                    while (true) {
 | 
			
		||||
                        loopSO.value = current
 | 
			
		||||
                        try {
 | 
			
		||||
                            result = body.execute(forContext)
 | 
			
		||||
                        } catch (lbe: LoopBreakContinueException) {
 | 
			
		||||
                            if (lbe.label == label || lbe.label == null) {
 | 
			
		||||
                                breakCaught = true
 | 
			
		||||
                                if (lbe.doContinue) continue
 | 
			
		||||
                                else {
 | 
			
		||||
                                    result = lbe.result
 | 
			
		||||
                                    break
 | 
			
		||||
                                }
 | 
			
		||||
                            } else
 | 
			
		||||
                                throw lbe
 | 
			
		||||
                        }
 | 
			
		||||
                        if (++index >= size) break
 | 
			
		||||
                        current = sourceObj.getAt(forContext, index)
 | 
			
		||||
                    }
 | 
			
		||||
                    if (!breakCaught && elseStatement != null) {
 | 
			
		||||
                        result = elseStatement.execute(it)
 | 
			
		||||
                    }
 | 
			
		||||
                    result
 | 
			
		||||
                }
 | 
			
		||||
                if (!breakCaught && elseStatement != null) {
 | 
			
		||||
                    result = elseStatement.execute(it)
 | 
			
		||||
                }
 | 
			
		||||
                result
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            // maybe other loops?
 | 
			
		||||
@ -584,10 +591,31 @@ class Compiler(
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private suspend fun loopIterable(
 | 
			
		||||
        forContext: Context, sourceObj: Obj, loopVar: StoredObj,
 | 
			
		||||
        body: Statement, elseStatement: Statement?, label: String?
 | 
			
		||||
    ): Obj {
 | 
			
		||||
        val iterObj = sourceObj.invokeInstanceMethod(forContext, "iterator")
 | 
			
		||||
        var result: Obj = ObjVoid
 | 
			
		||||
        while (iterObj.invokeInstanceMethod(forContext, "hasNext").toBool()) {
 | 
			
		||||
            try {
 | 
			
		||||
                loopVar.value = iterObj.invokeInstanceMethod(forContext, "next")
 | 
			
		||||
                result = body.execute(forContext)
 | 
			
		||||
            } catch (lbe: LoopBreakContinueException) {
 | 
			
		||||
                if (lbe.label == label || lbe.label == null) {
 | 
			
		||||
                    if (lbe.doContinue) continue
 | 
			
		||||
                }
 | 
			
		||||
                return lbe.result
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return elseStatement?.execute(forContext) ?: result
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun parseWhileStatement(cc: CompilerContext): Statement {
 | 
			
		||||
        val label = getLabel(cc)?.also { cc.labels += it }
 | 
			
		||||
        val start = ensureLparen(cc)
 | 
			
		||||
        val condition = parseExpression(cc) ?: throw ScriptError(start, "Bad while statement: expected expression")
 | 
			
		||||
        val condition =
 | 
			
		||||
            parseExpression(cc) ?: throw ScriptError(start, "Bad while statement: expected expression")
 | 
			
		||||
        ensureRparen(cc)
 | 
			
		||||
 | 
			
		||||
        val body = parseStatement(cc) ?: throw ScriptError(start, "Bad while statement: expected statement")
 | 
			
		||||
@ -958,6 +986,8 @@ class Compiler(
 | 
			
		||||
            // in, is:
 | 
			
		||||
            Operator.simple(Token.Type.IN, lastPrty) { c, a, b -> ObjBool(b.contains(c, a)) },
 | 
			
		||||
            Operator.simple(Token.Type.NOTIN, lastPrty) { c, a, b -> ObjBool(!b.contains(c, a)) },
 | 
			
		||||
            Operator.simple(Token.Type.IS, lastPrty) { c, a, b -> ObjBool(a.isInstanceOf(b)) },
 | 
			
		||||
            Operator.simple(Token.Type.NOTIS, lastPrty) { c, a, b -> ObjBool(!a.isInstanceOf(b)) },
 | 
			
		||||
            // shuttle <=> 6
 | 
			
		||||
            // bit shifts 7
 | 
			
		||||
            Operator.simple(Token.Type.PLUS, ++lastPrty) { ctx, a, b -> a.plus(ctx, b) },
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,7 @@ import kotlinx.coroutines.sync.Mutex
 | 
			
		||||
import kotlinx.coroutines.sync.withLock
 | 
			
		||||
import kotlinx.serialization.SerialName
 | 
			
		||||
import kotlinx.serialization.Serializable
 | 
			
		||||
import net.sergeych.synctools.ProtectedOp
 | 
			
		||||
 | 
			
		||||
//typealias InstanceMethod = (Context, Obj) -> Obj
 | 
			
		||||
 | 
			
		||||
@ -18,16 +19,15 @@ data class Accessor(
 | 
			
		||||
    fun setter(pos: Pos) = setterOrNull ?: throw ScriptError(pos, "can't assign value")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
sealed class Obj {
 | 
			
		||||
open class Obj {
 | 
			
		||||
    var isFrozen: Boolean = false
 | 
			
		||||
 | 
			
		||||
    private val monitor = Mutex()
 | 
			
		||||
 | 
			
		||||
    // members: fields most often
 | 
			
		||||
    private val members = mutableMapOf<String, WithAccess<Obj>>()
 | 
			
		||||
 | 
			
		||||
    //    private val memberMutex = Mutex()
 | 
			
		||||
    private val parentInstances = listOf<Obj>()
 | 
			
		||||
    internal var parentInstances: MutableList<Obj> = mutableListOf()
 | 
			
		||||
 | 
			
		||||
    private val opInstances = ProtectedOp()
 | 
			
		||||
 | 
			
		||||
    open fun inspect(): String = toString()
 | 
			
		||||
 | 
			
		||||
@ -39,20 +39,12 @@ sealed class Obj {
 | 
			
		||||
     */
 | 
			
		||||
    open fun byValueCopy(): Obj = this
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get instance member traversing the hierarchy if needed. Its meaning is different for different objects.
 | 
			
		||||
     */
 | 
			
		||||
    fun getInstanceMemberOrNull(name: String): WithAccess<Obj>? {
 | 
			
		||||
        members[name]?.let { return it }
 | 
			
		||||
        parentInstances.forEach { parent -> parent.getInstanceMemberOrNull(name)?.let { return it } }
 | 
			
		||||
        return null
 | 
			
		||||
    }
 | 
			
		||||
    fun isInstanceOf(someClass: Obj) = someClass === objClass || objClass.allParentsSet.contains(someClass)
 | 
			
		||||
 | 
			
		||||
    fun getInstanceMember(atPos: Pos, name: String): WithAccess<Obj> =
 | 
			
		||||
        getInstanceMemberOrNull(name)
 | 
			
		||||
            ?: throw ScriptError(atPos, "symbol doesn't exist: $name")
 | 
			
		||||
    suspend fun invokeInstanceMethod(context: Context, name: String, vararg args: Obj): Obj =
 | 
			
		||||
        invokeInstanceMethod(context, name, Arguments(args.map { Arguments.Info(it, context.pos) }))
 | 
			
		||||
 | 
			
		||||
    suspend fun callInstanceMethod(
 | 
			
		||||
    suspend fun invokeInstanceMethod(
 | 
			
		||||
        context: Context,
 | 
			
		||||
        name: String,
 | 
			
		||||
        args: Arguments = Arguments.EMPTY
 | 
			
		||||
@ -60,6 +52,8 @@ sealed class Obj {
 | 
			
		||||
        // note that getInstanceMember traverses the hierarchy
 | 
			
		||||
        objClass.getInstanceMember(context.pos, name).value.invoke(context, this, args)
 | 
			
		||||
 | 
			
		||||
    fun getMemberOrNull(name: String): Obj? = objClass.getInstanceMemberOrNull(name)?.value
 | 
			
		||||
 | 
			
		||||
    // methods that to override
 | 
			
		||||
 | 
			
		||||
    open suspend fun compareTo(context: Context, other: Obj): Int {
 | 
			
		||||
@ -170,14 +164,15 @@ sealed class Obj {
 | 
			
		||||
                value.execute(context.copy(context.pos, newThisObj = this)).asReadonly
 | 
			
		||||
            }
 | 
			
		||||
            // could be writable property naturally
 | 
			
		||||
            else -> getInstanceMember(context.pos, name)
 | 
			
		||||
            null -> ObjNull.asReadonly
 | 
			
		||||
            else -> obj
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun writeField(context: Context, name: String, newValue: Obj) {
 | 
			
		||||
        willMutate(context)
 | 
			
		||||
        members[name]?.let { if (it.isMutable) it.value = newValue }
 | 
			
		||||
            ?: context.raiseError("Can't reassign member: $name")
 | 
			
		||||
        val field = objClass.getInstanceMemberOrNull(name) ?: context.raiseError("no such field: $name")
 | 
			
		||||
        if (field.isMutable) field.value = newValue else context.raiseError("can't assign to read-only field: $name")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    open suspend fun getAt(context: Context, index: Int): Obj {
 | 
			
		||||
@ -188,18 +183,6 @@ sealed class Obj {
 | 
			
		||||
        context.raiseNotImplemented("indexing")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun createField(name: String, initialValue: Obj, isMutable: Boolean = false, pos: Pos = Pos.builtIn) {
 | 
			
		||||
        if (name in members || parentInstances.any { name in it.members })
 | 
			
		||||
            throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
 | 
			
		||||
        members[name] = WithAccess(initialValue, isMutable)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun addFn(name: String, isOpen: Boolean = false, code: suspend Context.() -> Obj) {
 | 
			
		||||
        createField(name, statement { code() }, isOpen)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun addConst(name: String, value: Obj) = createField(name, value, isMutable = false)
 | 
			
		||||
 | 
			
		||||
    open suspend fun callOn(context: Context): Obj {
 | 
			
		||||
        context.raiseNotImplemented()
 | 
			
		||||
    }
 | 
			
		||||
@ -207,6 +190,24 @@ sealed class Obj {
 | 
			
		||||
    suspend fun invoke(context: Context, thisObj: Obj, args: Arguments): Obj =
 | 
			
		||||
        callOn(context.copy(context.pos, args = args, newThisObj = thisObj))
 | 
			
		||||
 | 
			
		||||
    suspend fun invoke(context: Context, thisObj: Obj, vararg args: Obj): Obj =
 | 
			
		||||
        callOn(
 | 
			
		||||
            context.copy(
 | 
			
		||||
                context.pos,
 | 
			
		||||
                args = Arguments(args.map { Arguments.Info(it, context.pos) }),
 | 
			
		||||
                newThisObj = thisObj
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    suspend fun invoke(context: Context, thisObj: Obj): Obj =
 | 
			
		||||
        callOn(
 | 
			
		||||
            context.copy(
 | 
			
		||||
                context.pos,
 | 
			
		||||
                args = Arguments.EMPTY,
 | 
			
		||||
                newThisObj = thisObj
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    suspend fun invoke(context: Context, atPos: Pos, thisObj: Obj, args: Arguments): Obj =
 | 
			
		||||
        callOn(context.copy(atPos, args = args, newThisObj = thisObj))
 | 
			
		||||
 | 
			
		||||
@ -277,7 +278,7 @@ fun Obj.toDouble(): Double =
 | 
			
		||||
 | 
			
		||||
@Suppress("unused")
 | 
			
		||||
fun Obj.toLong(): Long =
 | 
			
		||||
    when(this) {
 | 
			
		||||
    when (this) {
 | 
			
		||||
        is Numeric -> longValue
 | 
			
		||||
        is ObjString -> value.toLong()
 | 
			
		||||
        is ObjChar -> value.code.toLong()
 | 
			
		||||
@ -305,4 +306,6 @@ class ObjNullPointerError(context: Context) : ObjError(context, "object is null"
 | 
			
		||||
class ObjAssertionError(context: Context, message: String) : ObjError(context, message)
 | 
			
		||||
class ObjClassCastError(context: Context, message: String) : ObjError(context, message)
 | 
			
		||||
class ObjIndexOutOfBoundsError(context: Context, message: String = "index out of bounds") : ObjError(context, message)
 | 
			
		||||
class ObjIllegalArgumentError(context: Context, message: String = "illegal argument") : ObjError(context, message)
 | 
			
		||||
class ObjIllegalArgumentError(context: Context, message: String = "illegal argument") : ObjError(context, message)
 | 
			
		||||
 | 
			
		||||
class ObjIterationFinishedError(context: Context) : ObjError(context, "iteration finished")
 | 
			
		||||
@ -21,4 +21,7 @@ data class ObjBool(val value: Boolean) : Obj() {
 | 
			
		||||
    companion object {
 | 
			
		||||
        val type = ObjClass("Bool")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
val ObjTrue = ObjBool(true)
 | 
			
		||||
val ObjFalse = ObjBool(false)
 | 
			
		||||
@ -1,20 +1,147 @@
 | 
			
		||||
package net.sergeych.ling
 | 
			
		||||
 | 
			
		||||
val ObjClassType  by lazy { ObjClass("Class") }
 | 
			
		||||
val ObjClassType by lazy { ObjClass("Class") }
 | 
			
		||||
 | 
			
		||||
class ObjClass(
 | 
			
		||||
    val className: String
 | 
			
		||||
): Obj() {
 | 
			
		||||
    val className: String,
 | 
			
		||||
    vararg val parents: ObjClass,
 | 
			
		||||
) : Obj() {
 | 
			
		||||
 | 
			
		||||
    val allParentsSet: Set<ObjClass> = parents.flatMap {
 | 
			
		||||
        listOf(it) + it.allParentsSet
 | 
			
		||||
    }.toSet()
 | 
			
		||||
 | 
			
		||||
    override val objClass: ObjClass by lazy { ObjClassType }
 | 
			
		||||
 | 
			
		||||
    // members: fields most often
 | 
			
		||||
    private val members = mutableMapOf<String, WithAccess<Obj>>()
 | 
			
		||||
 | 
			
		||||
    override fun toString(): String = className
 | 
			
		||||
 | 
			
		||||
    override suspend fun compareTo(context: Context, other: Obj): Int = if( other === this ) 0 else -1
 | 
			
		||||
    override suspend fun compareTo(context: Context, other: Obj): Int = if (other === this) 0 else -1
 | 
			
		||||
 | 
			
		||||
//    val parents: List<ObjClass> get() = emptyList()
 | 
			
		||||
//    private var initInstanceHandler: (suspend (Context, List<Obj>) -> Obj)? = null
 | 
			
		||||
 | 
			
		||||
//    suspend fun callInstanceMethod(context: Context, name: String, self: Obj,args: Arguments): Obj {
 | 
			
		||||
//         getInstanceMethod(context, name).invoke(context, self,args)
 | 
			
		||||
//    suspend fun newInstance(context: Context, vararg args: Obj): Obj =
 | 
			
		||||
//        initInstanceHandler?.invoke(context, args.toList())
 | 
			
		||||
//            ?: context.raiseError("No initInstance handler for $this")
 | 
			
		||||
//
 | 
			
		||||
//    fun buildInstance(f: suspend Context.(List<Obj>) -> Obj) {
 | 
			
		||||
//        if (initInstanceHandler != null) throw IllegalStateException("initInstance already set")
 | 
			
		||||
//        initInstanceHandler = f
 | 
			
		||||
//    }
 | 
			
		||||
//
 | 
			
		||||
//    fun addParent(context: Context, parent: Obj) {
 | 
			
		||||
//        val self = context.thisObj
 | 
			
		||||
//        self.parentInstances.add(parent)
 | 
			
		||||
//    }
 | 
			
		||||
//
 | 
			
		||||
    fun defaultInstance(): Obj = object : Obj() {
 | 
			
		||||
        override val objClass: ObjClass = this@ObjClass
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun createField(name: String, initialValue: Obj, isMutable: Boolean = false, pos: Pos = Pos.builtIn) {
 | 
			
		||||
        if (name in members || allParentsSet.any { name in it.members } == true)
 | 
			
		||||
            throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
 | 
			
		||||
        members[name] = WithAccess(initialValue, isMutable)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun addFn(name: String, isOpen: Boolean = false, code: suspend Context.() -> Obj) {
 | 
			
		||||
        createField(name, statement { code() }, isOpen)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun addConst(name: String, value: Obj) = createField(name, value, isMutable = false)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get instance member traversing the hierarchy if needed. Its meaning is different for different objects.
 | 
			
		||||
     */
 | 
			
		||||
    fun getInstanceMemberOrNull(name: String): WithAccess<Obj>? {
 | 
			
		||||
        members[name]?.let { return it }
 | 
			
		||||
        allParentsSet.forEach { parent -> parent.getInstanceMemberOrNull(name)?.let { return it } }
 | 
			
		||||
        return null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun getInstanceMember(atPos: Pos, name: String): WithAccess<Obj> =
 | 
			
		||||
        getInstanceMemberOrNull(name)
 | 
			
		||||
            ?: throw ScriptError(atPos, "symbol doesn't exist: $name")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Abstract class that must provide `iterator` method that returns [ObjIterator] instance.
 | 
			
		||||
 */
 | 
			
		||||
val ObjIterable by lazy { ObjClass("Iterable").apply {
 | 
			
		||||
 | 
			
		||||
    addFn("toList") {
 | 
			
		||||
        val result = mutableListOf<Obj>()
 | 
			
		||||
        val iterator = thisObj.invokeInstanceMethod(this, "iterator")
 | 
			
		||||
 | 
			
		||||
        while( iterator.invokeInstanceMethod(this, "hasNext").toBool() )
 | 
			
		||||
            result += iterator.invokeInstanceMethod(this, "next")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//        val next = iterator.getMemberOrNull("next")!!
 | 
			
		||||
//        val hasNext = iterator.getMemberOrNull("hasNext")!!
 | 
			
		||||
//        while( hasNext.invoke(this, iterator).toBool() )
 | 
			
		||||
//            result += next.invoke(this, iterator)
 | 
			
		||||
        ObjList(result)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
} }
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Collection is an iterator with `size`]
 | 
			
		||||
 */
 | 
			
		||||
val ObjCollection by lazy {
 | 
			
		||||
    val i: ObjClass = ObjIterable
 | 
			
		||||
    ObjClass("Collection", i)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
val ObjIterator by lazy { ObjClass("Iterator") }
 | 
			
		||||
 | 
			
		||||
class ObjArrayIterator(val array: Obj) : Obj() {
 | 
			
		||||
 | 
			
		||||
    override val objClass: ObjClass by lazy { type }
 | 
			
		||||
 | 
			
		||||
    private var nextIndex = 0
 | 
			
		||||
    private var lastIndex = 0
 | 
			
		||||
 | 
			
		||||
    suspend fun init(context: Context) {
 | 
			
		||||
        nextIndex = 0
 | 
			
		||||
        lastIndex = array.invokeInstanceMethod(context, "size").toInt()
 | 
			
		||||
        ObjVoid
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        val type by lazy {
 | 
			
		||||
            ObjClass("ArrayIterator", ObjIterator).apply {
 | 
			
		||||
                addFn("next") {
 | 
			
		||||
                    val self = thisAs<ObjArrayIterator>()
 | 
			
		||||
                    if (self.nextIndex < self.lastIndex) {
 | 
			
		||||
                        self.array.invokeInstanceMethod(this, "getAt", (self.nextIndex++).toObj())
 | 
			
		||||
                    } else raiseError(ObjIterationFinishedError(this))
 | 
			
		||||
                }
 | 
			
		||||
                addFn("hasNext") {
 | 
			
		||||
                    val self = thisAs<ObjArrayIterator>()
 | 
			
		||||
                    if (self.nextIndex  < self.lastIndex) ObjTrue else ObjFalse
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
val ObjArray by lazy {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Array abstract class is a [ObjCollection] with `getAt` method.
 | 
			
		||||
     */
 | 
			
		||||
    ObjClass("Array", ObjCollection).apply {
 | 
			
		||||
        // we can create iterators using size/getat:
 | 
			
		||||
 | 
			
		||||
        addFn("iterator") {
 | 
			
		||||
            ObjArrayIterator(thisObj).also { it.init(this) }
 | 
			
		||||
        }
 | 
			
		||||
        addFn("isample") { "ok".toObj()}
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -75,4 +75,6 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
 | 
			
		||||
    companion object {
 | 
			
		||||
        val type = ObjClass("Int")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fun Int.toObj() = ObjInt(this.toLong())
 | 
			
		||||
@ -2,6 +2,11 @@ package net.sergeych.ling
 | 
			
		||||
 | 
			
		||||
class ObjList(val list: MutableList<Obj>) : Obj() {
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        for( p in objClass.parents)
 | 
			
		||||
            parentInstances.add( p.defaultInstance())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun toString(): String = "[${
 | 
			
		||||
        list.joinToString(separator = ", ") { it.inspect() }
 | 
			
		||||
    }]"
 | 
			
		||||
@ -42,13 +47,20 @@ class ObjList(val list: MutableList<Obj>) : Obj() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun plus(context: Context, other: Obj): Obj {
 | 
			
		||||
        (other as? ObjList) ?: context.raiseError("cannot concatenate $this with $other")
 | 
			
		||||
        (other as? ObjList) ?: context.raiseError("'+': can't concatenate $this with $other")
 | 
			
		||||
        return ObjList((list + other.list).toMutableList())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun plusAssign(context: Context, other: Obj): Obj {
 | 
			
		||||
        (other as? ObjList) ?: context.raiseError("cannot concatenate $this with $other")
 | 
			
		||||
        list += other.list
 | 
			
		||||
        // optimization
 | 
			
		||||
        if( other is ObjList) {
 | 
			
		||||
            list += other.list
 | 
			
		||||
            return this
 | 
			
		||||
        }
 | 
			
		||||
        if( other.isInstanceOf(ObjIterable)) {
 | 
			
		||||
            TODO("plusassign for iterable is not yet implemented")
 | 
			
		||||
        }
 | 
			
		||||
        list += other
 | 
			
		||||
        return this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -56,12 +68,23 @@ class ObjList(val list: MutableList<Obj>) : Obj() {
 | 
			
		||||
        get() = type
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        val type = ObjClass("List").apply {
 | 
			
		||||
        val type = ObjClass("List", ObjArray).apply {
 | 
			
		||||
 | 
			
		||||
            createField("size",
 | 
			
		||||
                statement {
 | 
			
		||||
                    (thisObj as ObjList).list.size.toObj()
 | 
			
		||||
                }
 | 
			
		||||
            )
 | 
			
		||||
            addFn("getAt") {
 | 
			
		||||
                requireExactCount(1)
 | 
			
		||||
                thisAs<ObjList>().getAt(this, requiredArg<ObjInt>(0).value.toInt())
 | 
			
		||||
            }
 | 
			
		||||
            addFn("putAt") {
 | 
			
		||||
                requireExactCount(2)
 | 
			
		||||
                val newValue = args[1]
 | 
			
		||||
                thisAs<ObjList>().putAt(this, requiredArg<ObjInt>(0).value.toInt(), newValue)
 | 
			
		||||
                newValue
 | 
			
		||||
            }
 | 
			
		||||
            createField("add",
 | 
			
		||||
                statement {
 | 
			
		||||
                    val l = thisAs<ObjList>().list
 | 
			
		||||
 | 
			
		||||
@ -70,7 +70,7 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob
 | 
			
		||||
        if (isEndInclusive) r1++
 | 
			
		||||
        val i = index + r0
 | 
			
		||||
        if (i >= r1) context.raiseIndexOutOfBounds("index $index is not in range (${r1 - r0})")
 | 
			
		||||
        return if( isIntRange ) ObjInt(i.toLong()) else ObjChar(i.toChar())
 | 
			
		||||
        return if (isIntRange) ObjInt(i.toLong()) else ObjChar(i.toChar())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -83,7 +83,7 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        val type = ObjClass("Range").apply {
 | 
			
		||||
        val type = ObjClass("Range", ObjIterable).apply {
 | 
			
		||||
            addFn("start") {
 | 
			
		||||
                thisAs<ObjRange>().start ?: ObjNull
 | 
			
		||||
            }
 | 
			
		||||
@ -102,18 +102,57 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob
 | 
			
		||||
            addFn("isEndInclusive") {
 | 
			
		||||
                thisAs<ObjRange>().isEndInclusive.toObj()
 | 
			
		||||
            }
 | 
			
		||||
            addFn("size") {
 | 
			
		||||
            addFn("iterator") {
 | 
			
		||||
                val self = thisAs<ObjRange>()
 | 
			
		||||
                if (self.start == null || self.end == null)
 | 
			
		||||
                    raiseError("size is only available for finite ranges")
 | 
			
		||||
                if (self.isIntRange || self.isCharRange) {
 | 
			
		||||
                    if (self.isEndInclusive)
 | 
			
		||||
                        ObjInt(self.end.toLong() - self.start.toLong() + 1)
 | 
			
		||||
                    else
 | 
			
		||||
                        ObjInt(self.end.toLong() - self.start.toLong())
 | 
			
		||||
                } else {
 | 
			
		||||
                    ObjInt(2)
 | 
			
		||||
                }
 | 
			
		||||
                ObjRangeIterator(self).apply { init() }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ObjRangeIterator(val self: ObjRange) : Obj() {
 | 
			
		||||
 | 
			
		||||
    private var nextIndex = 0
 | 
			
		||||
    private var lastIndex = 0
 | 
			
		||||
    private var isCharRange: Boolean = false
 | 
			
		||||
 | 
			
		||||
    override val objClass: ObjClass = type
 | 
			
		||||
 | 
			
		||||
    fun Context.init() {
 | 
			
		||||
        if (self.start == null || self.end == null)
 | 
			
		||||
            raiseError("next is only available for finite ranges")
 | 
			
		||||
        isCharRange = self.isCharRange
 | 
			
		||||
        lastIndex = if (self.isIntRange || self.isCharRange) {
 | 
			
		||||
            if (self.isEndInclusive)
 | 
			
		||||
                self.end.toInt() - self.start.toInt() + 1
 | 
			
		||||
            else
 | 
			
		||||
                self.end.toInt() - self.start.toInt()
 | 
			
		||||
        } else {
 | 
			
		||||
            raiseError("not implemented iterator for range of $this")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun hasNext(): Boolean = nextIndex < lastIndex
 | 
			
		||||
 | 
			
		||||
    fun next(context: Context): Obj =
 | 
			
		||||
        if (nextIndex < lastIndex) {
 | 
			
		||||
            val x = if (self.isEndInclusive)
 | 
			
		||||
                self.start!!.toLong() + nextIndex++
 | 
			
		||||
            else
 | 
			
		||||
                self.start!!.toLong() + nextIndex++
 | 
			
		||||
            if( isCharRange ) ObjChar(x.toInt().toChar()) else ObjInt(x)
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
            context.raiseError(ObjIterationFinishedError(context))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        val type = ObjClass("RangeIterator", ObjIterable).apply {
 | 
			
		||||
            addFn("hasNext") {
 | 
			
		||||
                thisAs<ObjRangeIterator>().hasNext().toObj()
 | 
			
		||||
            }
 | 
			
		||||
            addFn("next") {
 | 
			
		||||
                thisAs<ObjRangeIterator>().next(this)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -154,12 +154,19 @@ private class Parser(fromPos: Pos) {
 | 
			
		||||
            '!' -> {
 | 
			
		||||
                if (currentChar == 'i') {
 | 
			
		||||
                    pos.advance()
 | 
			
		||||
                    if( currentChar == 'n') {
 | 
			
		||||
                        pos.advance()
 | 
			
		||||
                        Token("!in", from, Token.Type.NOTIN)
 | 
			
		||||
                    } else {
 | 
			
		||||
                        pos.back()
 | 
			
		||||
                        Token("!", from, Token.Type.NOT)
 | 
			
		||||
                    when (currentChar) {
 | 
			
		||||
                        'n' -> {
 | 
			
		||||
                            pos.advance()
 | 
			
		||||
                            Token("!in", from, Token.Type.NOTIN)
 | 
			
		||||
                        }
 | 
			
		||||
                        's' -> {
 | 
			
		||||
                            pos.advance()
 | 
			
		||||
                            Token("!is", from, Token.Type.NOTIS)
 | 
			
		||||
                        }
 | 
			
		||||
                        else -> {
 | 
			
		||||
                            pos.back()
 | 
			
		||||
                            Token("!", from, Token.Type.NOT)
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                } else
 | 
			
		||||
                    if (currentChar == '=') {
 | 
			
		||||
@ -249,6 +256,7 @@ private class Parser(fromPos: Pos) {
 | 
			
		||||
                    } else
 | 
			
		||||
                        when (text) {
 | 
			
		||||
                            "in" -> Token("in", from, Token.Type.IN)
 | 
			
		||||
                            "is" -> Token("is", from, Token.Type.IS)
 | 
			
		||||
                            else -> Token(text, from, Token.Type.ID)
 | 
			
		||||
                        }
 | 
			
		||||
                } else
 | 
			
		||||
 | 
			
		||||
@ -60,6 +60,11 @@ class Script(
 | 
			
		||||
            addConst("Char", ObjChar.type)
 | 
			
		||||
            addConst("List", ObjList.type)
 | 
			
		||||
            addConst("Range", ObjRange.type)
 | 
			
		||||
 | 
			
		||||
            // interfaces
 | 
			
		||||
            addConst("Iterable", ObjIterable)
 | 
			
		||||
            addConst("Array", ObjArray)
 | 
			
		||||
 | 
			
		||||
            val pi = ObjReal(PI)
 | 
			
		||||
            addConst("π", pi)
 | 
			
		||||
            getOrCreateNamespace("Math").apply {
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,6 @@ package io.github.kotlin.fibonacci
 | 
			
		||||
 | 
			
		||||
import kotlinx.coroutines.test.runTest
 | 
			
		||||
import net.sergeych.ling.*
 | 
			
		||||
import kotlin.math.PI
 | 
			
		||||
import kotlin.test.*
 | 
			
		||||
 | 
			
		||||
class ScriptTest {
 | 
			
		||||
@ -154,12 +153,12 @@ class ScriptTest {
 | 
			
		||||
    @Test
 | 
			
		||||
    fun compileBuiltinCallsTest() = runTest {
 | 
			
		||||
//        println(eval("π"))
 | 
			
		||||
        val pi = eval("Math.PI")
 | 
			
		||||
        assertIs<ObjReal>(pi)
 | 
			
		||||
        assertTrue(pi.value - PI < 0.000001)
 | 
			
		||||
        assertTrue(eval("Math.PI+1").toDouble() - PI - 1.0 < 0.000001)
 | 
			
		||||
//        val pi = eval("Math.PI")
 | 
			
		||||
//        assertIs<ObjReal>(pi)
 | 
			
		||||
//        assertTrue(pi.value - PI < 0.000001)
 | 
			
		||||
//        assertTrue(eval("Math.PI+1").toDouble() - PI - 1.0 < 0.000001)
 | 
			
		||||
 | 
			
		||||
        assertTrue(eval("sin(Math.PI)").toDouble() - 1 < 0.000001)
 | 
			
		||||
//        assertTrue(eval("sin(Math.PI)").toDouble() - 1 < 0.000001)
 | 
			
		||||
        assertTrue(eval("sin(π)").toDouble() - 1 < 0.000001)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -831,6 +830,7 @@ class ScriptTest {
 | 
			
		||||
            """
 | 
			
		||||
            val a = [4,3]
 | 
			
		||||
            assert(a.size == 2)
 | 
			
		||||
            assert( 3 == a[1] )
 | 
			
		||||
        """.trimIndent()
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
@ -977,16 +977,69 @@ class ScriptTest {
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testCharacterRange() = runTest {
 | 
			
		||||
        eval("""
 | 
			
		||||
        eval(
 | 
			
		||||
            """
 | 
			
		||||
            val x = '0'..'9'
 | 
			
		||||
            println(x)
 | 
			
		||||
            assert( '5' in x)
 | 
			
		||||
            assert( 'z' !in x)
 | 
			
		||||
            for( ch in x ) 
 | 
			
		||||
                println(ch)
 | 
			
		||||
        """.trimIndent())
 | 
			
		||||
        """.trimIndent()
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testIs() = runTest {
 | 
			
		||||
        eval(
 | 
			
		||||
            """
 | 
			
		||||
            val x = 1..10
 | 
			
		||||
            assert( x is Range )
 | 
			
		||||
            assert( x is Iterable )
 | 
			
		||||
            assert( x !is String)
 | 
			
		||||
            assert( "foo" is String)
 | 
			
		||||
            
 | 
			
		||||
            assert( x is Iterable )
 | 
			
		||||
        """.trimIndent()
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testForRange() = runTest {
 | 
			
		||||
        eval(
 | 
			
		||||
            """
 | 
			
		||||
            val x = 1..3
 | 
			
		||||
            val result = []
 | 
			
		||||
            for( i in x ) {
 | 
			
		||||
                println(i)
 | 
			
		||||
                result += (i*10)
 | 
			
		||||
            }
 | 
			
		||||
            assert( result == [10,20,30] )
 | 
			
		||||
            """.trimIndent()
 | 
			
		||||
        )
 | 
			
		||||
        val a = mutableListOf(1, 2)
 | 
			
		||||
        val b = listOf(3, 4)
 | 
			
		||||
        a += 10
 | 
			
		||||
        a += b
 | 
			
		||||
        println(a)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun iterableList() = runTest {
 | 
			
		||||
        // 473
 | 
			
		||||
        eval(
 | 
			
		||||
            """
 | 
			
		||||
            for( i in 0..<1024 ) {    
 | 
			
		||||
            val list = (1..1024).toList()
 | 
			
		||||
            assert(list.size == 1024)
 | 
			
		||||
            assert(list[0] == 1)
 | 
			
		||||
            assert(list[-1] == 1024)
 | 
			
		||||
            }
 | 
			
		||||
        """.trimIndent()
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
//    @Test
 | 
			
		||||
//    fun testLambda1() = runTest {
 | 
			
		||||
//        val l = eval("""
 | 
			
		||||
 | 
			
		||||
@ -13,6 +13,7 @@ dependencyResolutionManagement {
 | 
			
		||||
        maven("https://maven.universablockchain.com/")
 | 
			
		||||
        maven("https://gitea.sergeych.net/api/packages/SergeychWorks/maven")
 | 
			
		||||
        mavenLocal()
 | 
			
		||||
        maven("https://gitea.sergeych.net/api/packages/SergeychWorks/maven")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user