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) )
|
assert( (1..<3) in (1..3) )
|
||||||
>>> void
|
>>> 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 given a range with both ends, you can assume it is [Iterable]. This automatically let
|
||||||
so their meaning is special.
|
use finite ranges in loops and convert it to lists:
|
||||||
|
|
||||||
For open ranges, size throws and exception.
|
assert( [-2, -1, 0, 1] == (-2..1).toList() )
|
||||||
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 )
|
|
||||||
>>> void
|
>>> void
|
||||||
|
|
||||||
In spite of this you can use ranges in for loops:
|
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:
|
Exclusive end char ranges are supported too:
|
||||||
|
|
||||||
('a'..<'c').size
|
('a'..<'c').toList
|
||||||
>>> 2
|
>>> ['a', 'b']
|
||||||
|
|
||||||
|
|
||||||
# Instance members
|
# Instance members
|
||||||
@ -119,3 +104,5 @@ Exclusive end char ranges are supported too:
|
|||||||
| size | for finite ranges, see above | Long |
|
| size | for finite ranges, see above | Long |
|
||||||
| [] | see above | |
|
| [] | see above | |
|
||||||
| | | |
|
| | | |
|
||||||
|
|
||||||
|
[Iterable]: Iterable.md
|
@ -49,6 +49,7 @@ kotlin {
|
|||||||
//put your multiplatform dependencies here
|
//put your multiplatform dependencies here
|
||||||
implementation(libs.kotlinx.coroutines.core)
|
implementation(libs.kotlinx.coroutines.core)
|
||||||
implementation(libs.mp.bintools)
|
implementation(libs.mp.bintools)
|
||||||
|
implementation("net.sergeych:mp_stools:1.5.2")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val commonTest by getting {
|
val commonTest by getting {
|
||||||
|
@ -144,7 +144,7 @@ class Compiler(
|
|||||||
context.pos = next.pos
|
context.pos = next.pos
|
||||||
val v = left.getter(context).value
|
val v = left.getter(context).value
|
||||||
WithAccess(
|
WithAccess(
|
||||||
v.callInstanceMethod(
|
v.invokeInstanceMethod(
|
||||||
context,
|
context,
|
||||||
next.value,
|
next.value,
|
||||||
args.toArguments(context)
|
args.toArguments(context)
|
||||||
@ -540,43 +540,50 @@ class Compiler(
|
|||||||
|
|
||||||
// insofar we suggest source object is enumerable. Later we might need to add checks
|
// insofar we suggest source object is enumerable. Later we might need to add checks
|
||||||
val sourceObj = source.execute(forContext)
|
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) }
|
if (sourceObj.isInstanceOf(ObjIterable)) {
|
||||||
var result: Obj = ObjVoid
|
loopIterable(forContext, sourceObj, loopSO, body, elseStatement, label)
|
||||||
var breakCaught = false
|
} else {
|
||||||
if (size > 0) {
|
val size = runCatching { sourceObj.invokeInstanceMethod(forContext, "size").toInt() }
|
||||||
var current = runCatching { sourceObj.getAt(forContext, 0) }
|
.getOrElse { throw ScriptError(tOp.pos, "object is not enumerable: no size", it) }
|
||||||
.getOrElse {
|
|
||||||
throw ScriptError(
|
var result: Obj = ObjVoid
|
||||||
tOp.pos,
|
var breakCaught = false
|
||||||
"object is not enumerable: no index access for ${sourceObj.inspect()}",
|
|
||||||
it
|
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 {
|
} else {
|
||||||
// maybe other loops?
|
// 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 {
|
private fun parseWhileStatement(cc: CompilerContext): Statement {
|
||||||
val label = getLabel(cc)?.also { cc.labels += it }
|
val label = getLabel(cc)?.also { cc.labels += it }
|
||||||
val start = ensureLparen(cc)
|
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)
|
ensureRparen(cc)
|
||||||
|
|
||||||
val body = parseStatement(cc) ?: throw ScriptError(start, "Bad while statement: expected statement")
|
val body = parseStatement(cc) ?: throw ScriptError(start, "Bad while statement: expected statement")
|
||||||
@ -958,6 +986,8 @@ class Compiler(
|
|||||||
// in, is:
|
// in, is:
|
||||||
Operator.simple(Token.Type.IN, lastPrty) { c, a, b -> ObjBool(b.contains(c, a)) },
|
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.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
|
// shuttle <=> 6
|
||||||
// bit shifts 7
|
// bit shifts 7
|
||||||
Operator.simple(Token.Type.PLUS, ++lastPrty) { ctx, a, b -> a.plus(ctx, b) },
|
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.coroutines.sync.withLock
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import net.sergeych.synctools.ProtectedOp
|
||||||
|
|
||||||
//typealias InstanceMethod = (Context, Obj) -> Obj
|
//typealias InstanceMethod = (Context, Obj) -> Obj
|
||||||
|
|
||||||
@ -18,16 +19,15 @@ data class Accessor(
|
|||||||
fun setter(pos: Pos) = setterOrNull ?: throw ScriptError(pos, "can't assign value")
|
fun setter(pos: Pos) = setterOrNull ?: throw ScriptError(pos, "can't assign value")
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class Obj {
|
open class Obj {
|
||||||
var isFrozen: Boolean = false
|
var isFrozen: Boolean = false
|
||||||
|
|
||||||
private val monitor = Mutex()
|
private val monitor = Mutex()
|
||||||
|
|
||||||
// members: fields most often
|
|
||||||
private val members = mutableMapOf<String, WithAccess<Obj>>()
|
|
||||||
|
|
||||||
// private val memberMutex = Mutex()
|
// private val memberMutex = Mutex()
|
||||||
private val parentInstances = listOf<Obj>()
|
internal var parentInstances: MutableList<Obj> = mutableListOf()
|
||||||
|
|
||||||
|
private val opInstances = ProtectedOp()
|
||||||
|
|
||||||
open fun inspect(): String = toString()
|
open fun inspect(): String = toString()
|
||||||
|
|
||||||
@ -39,20 +39,12 @@ sealed class Obj {
|
|||||||
*/
|
*/
|
||||||
open fun byValueCopy(): Obj = this
|
open fun byValueCopy(): Obj = this
|
||||||
|
|
||||||
/**
|
fun isInstanceOf(someClass: Obj) = someClass === objClass || objClass.allParentsSet.contains(someClass)
|
||||||
* 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 getInstanceMember(atPos: Pos, name: String): WithAccess<Obj> =
|
suspend fun invokeInstanceMethod(context: Context, name: String, vararg args: Obj): Obj =
|
||||||
getInstanceMemberOrNull(name)
|
invokeInstanceMethod(context, name, Arguments(args.map { Arguments.Info(it, context.pos) }))
|
||||||
?: throw ScriptError(atPos, "symbol doesn't exist: $name")
|
|
||||||
|
|
||||||
suspend fun callInstanceMethod(
|
suspend fun invokeInstanceMethod(
|
||||||
context: Context,
|
context: Context,
|
||||||
name: String,
|
name: String,
|
||||||
args: Arguments = Arguments.EMPTY
|
args: Arguments = Arguments.EMPTY
|
||||||
@ -60,6 +52,8 @@ sealed class Obj {
|
|||||||
// note that getInstanceMember traverses the hierarchy
|
// note that getInstanceMember traverses the hierarchy
|
||||||
objClass.getInstanceMember(context.pos, name).value.invoke(context, this, args)
|
objClass.getInstanceMember(context.pos, name).value.invoke(context, this, args)
|
||||||
|
|
||||||
|
fun getMemberOrNull(name: String): Obj? = objClass.getInstanceMemberOrNull(name)?.value
|
||||||
|
|
||||||
// methods that to override
|
// methods that to override
|
||||||
|
|
||||||
open suspend fun compareTo(context: Context, other: Obj): Int {
|
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
|
value.execute(context.copy(context.pos, newThisObj = this)).asReadonly
|
||||||
}
|
}
|
||||||
// could be writable property naturally
|
// could be writable property naturally
|
||||||
else -> getInstanceMember(context.pos, name)
|
null -> ObjNull.asReadonly
|
||||||
|
else -> obj
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun writeField(context: Context, name: String, newValue: Obj) {
|
fun writeField(context: Context, name: String, newValue: Obj) {
|
||||||
willMutate(context)
|
willMutate(context)
|
||||||
members[name]?.let { if (it.isMutable) it.value = newValue }
|
val field = objClass.getInstanceMemberOrNull(name) ?: context.raiseError("no such field: $name")
|
||||||
?: context.raiseError("Can't reassign member: $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 {
|
open suspend fun getAt(context: Context, index: Int): Obj {
|
||||||
@ -188,18 +183,6 @@ sealed class Obj {
|
|||||||
context.raiseNotImplemented("indexing")
|
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 {
|
open suspend fun callOn(context: Context): Obj {
|
||||||
context.raiseNotImplemented()
|
context.raiseNotImplemented()
|
||||||
}
|
}
|
||||||
@ -207,6 +190,24 @@ sealed class Obj {
|
|||||||
suspend fun invoke(context: Context, thisObj: Obj, args: Arguments): Obj =
|
suspend fun invoke(context: Context, thisObj: Obj, args: Arguments): Obj =
|
||||||
callOn(context.copy(context.pos, args = args, newThisObj = thisObj))
|
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 =
|
suspend fun invoke(context: Context, atPos: Pos, thisObj: Obj, args: Arguments): Obj =
|
||||||
callOn(context.copy(atPos, args = args, newThisObj = thisObj))
|
callOn(context.copy(atPos, args = args, newThisObj = thisObj))
|
||||||
|
|
||||||
@ -277,7 +278,7 @@ fun Obj.toDouble(): Double =
|
|||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun Obj.toLong(): Long =
|
fun Obj.toLong(): Long =
|
||||||
when(this) {
|
when (this) {
|
||||||
is Numeric -> longValue
|
is Numeric -> longValue
|
||||||
is ObjString -> value.toLong()
|
is ObjString -> value.toLong()
|
||||||
is ObjChar -> value.code.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 ObjAssertionError(context: Context, message: String) : ObjError(context, message)
|
||||||
class ObjClassCastError(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 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 {
|
companion object {
|
||||||
val type = ObjClass("Bool")
|
val type = ObjClass("Bool")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val ObjTrue = ObjBool(true)
|
||||||
|
val ObjFalse = ObjBool(false)
|
@ -1,20 +1,147 @@
|
|||||||
package net.sergeych.ling
|
package net.sergeych.ling
|
||||||
|
|
||||||
val ObjClassType by lazy { ObjClass("Class") }
|
val ObjClassType by lazy { ObjClass("Class") }
|
||||||
|
|
||||||
class ObjClass(
|
class ObjClass(
|
||||||
val className: String
|
val className: String,
|
||||||
): Obj() {
|
vararg val parents: ObjClass,
|
||||||
|
) : Obj() {
|
||||||
|
|
||||||
|
val allParentsSet: Set<ObjClass> = parents.flatMap {
|
||||||
|
listOf(it) + it.allParentsSet
|
||||||
|
}.toSet()
|
||||||
|
|
||||||
override val objClass: ObjClass by lazy { ObjClassType }
|
override val objClass: ObjClass by lazy { ObjClassType }
|
||||||
|
|
||||||
|
// members: fields most often
|
||||||
|
private val members = mutableMapOf<String, WithAccess<Obj>>()
|
||||||
|
|
||||||
override fun toString(): String = className
|
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 {
|
// suspend fun newInstance(context: Context, vararg args: Obj): Obj =
|
||||||
// getInstanceMethod(context, name).invoke(context, self,args)
|
// 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 {
|
companion object {
|
||||||
val type = ObjClass("Int")
|
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() {
|
class ObjList(val list: MutableList<Obj>) : Obj() {
|
||||||
|
|
||||||
|
init {
|
||||||
|
for( p in objClass.parents)
|
||||||
|
parentInstances.add( p.defaultInstance())
|
||||||
|
}
|
||||||
|
|
||||||
override fun toString(): String = "[${
|
override fun toString(): String = "[${
|
||||||
list.joinToString(separator = ", ") { it.inspect() }
|
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 {
|
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())
|
return ObjList((list + other.list).toMutableList())
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun plusAssign(context: Context, other: Obj): Obj {
|
override suspend fun plusAssign(context: Context, other: Obj): Obj {
|
||||||
(other as? ObjList) ?: context.raiseError("cannot concatenate $this with $other")
|
// optimization
|
||||||
list += other.list
|
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
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,12 +68,23 @@ class ObjList(val list: MutableList<Obj>) : Obj() {
|
|||||||
get() = type
|
get() = type
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val type = ObjClass("List").apply {
|
val type = ObjClass("List", ObjArray).apply {
|
||||||
|
|
||||||
createField("size",
|
createField("size",
|
||||||
statement {
|
statement {
|
||||||
(thisObj as ObjList).list.size.toObj()
|
(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",
|
createField("add",
|
||||||
statement {
|
statement {
|
||||||
val l = thisAs<ObjList>().list
|
val l = thisAs<ObjList>().list
|
||||||
|
@ -70,7 +70,7 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob
|
|||||||
if (isEndInclusive) r1++
|
if (isEndInclusive) r1++
|
||||||
val i = index + r0
|
val i = index + r0
|
||||||
if (i >= r1) context.raiseIndexOutOfBounds("index $index is not in range (${r1 - 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 {
|
companion object {
|
||||||
val type = ObjClass("Range").apply {
|
val type = ObjClass("Range", ObjIterable).apply {
|
||||||
addFn("start") {
|
addFn("start") {
|
||||||
thisAs<ObjRange>().start ?: ObjNull
|
thisAs<ObjRange>().start ?: ObjNull
|
||||||
}
|
}
|
||||||
@ -102,18 +102,57 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob
|
|||||||
addFn("isEndInclusive") {
|
addFn("isEndInclusive") {
|
||||||
thisAs<ObjRange>().isEndInclusive.toObj()
|
thisAs<ObjRange>().isEndInclusive.toObj()
|
||||||
}
|
}
|
||||||
addFn("size") {
|
addFn("iterator") {
|
||||||
val self = thisAs<ObjRange>()
|
val self = thisAs<ObjRange>()
|
||||||
if (self.start == null || self.end == null)
|
ObjRangeIterator(self).apply { init() }
|
||||||
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())
|
class ObjRangeIterator(val self: ObjRange) : Obj() {
|
||||||
} else {
|
|
||||||
ObjInt(2)
|
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') {
|
if (currentChar == 'i') {
|
||||||
pos.advance()
|
pos.advance()
|
||||||
if( currentChar == 'n') {
|
when (currentChar) {
|
||||||
pos.advance()
|
'n' -> {
|
||||||
Token("!in", from, Token.Type.NOTIN)
|
pos.advance()
|
||||||
} else {
|
Token("!in", from, Token.Type.NOTIN)
|
||||||
pos.back()
|
}
|
||||||
Token("!", from, Token.Type.NOT)
|
's' -> {
|
||||||
|
pos.advance()
|
||||||
|
Token("!is", from, Token.Type.NOTIS)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
pos.back()
|
||||||
|
Token("!", from, Token.Type.NOT)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
if (currentChar == '=') {
|
if (currentChar == '=') {
|
||||||
@ -249,6 +256,7 @@ private class Parser(fromPos: Pos) {
|
|||||||
} else
|
} else
|
||||||
when (text) {
|
when (text) {
|
||||||
"in" -> Token("in", from, Token.Type.IN)
|
"in" -> Token("in", from, Token.Type.IN)
|
||||||
|
"is" -> Token("is", from, Token.Type.IS)
|
||||||
else -> Token(text, from, Token.Type.ID)
|
else -> Token(text, from, Token.Type.ID)
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
|
@ -60,6 +60,11 @@ class Script(
|
|||||||
addConst("Char", ObjChar.type)
|
addConst("Char", ObjChar.type)
|
||||||
addConst("List", ObjList.type)
|
addConst("List", ObjList.type)
|
||||||
addConst("Range", ObjRange.type)
|
addConst("Range", ObjRange.type)
|
||||||
|
|
||||||
|
// interfaces
|
||||||
|
addConst("Iterable", ObjIterable)
|
||||||
|
addConst("Array", ObjArray)
|
||||||
|
|
||||||
val pi = ObjReal(PI)
|
val pi = ObjReal(PI)
|
||||||
addConst("π", pi)
|
addConst("π", pi)
|
||||||
getOrCreateNamespace("Math").apply {
|
getOrCreateNamespace("Math").apply {
|
||||||
|
@ -2,7 +2,6 @@ package io.github.kotlin.fibonacci
|
|||||||
|
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import net.sergeych.ling.*
|
import net.sergeych.ling.*
|
||||||
import kotlin.math.PI
|
|
||||||
import kotlin.test.*
|
import kotlin.test.*
|
||||||
|
|
||||||
class ScriptTest {
|
class ScriptTest {
|
||||||
@ -154,12 +153,12 @@ class ScriptTest {
|
|||||||
@Test
|
@Test
|
||||||
fun compileBuiltinCallsTest() = runTest {
|
fun compileBuiltinCallsTest() = runTest {
|
||||||
// println(eval("π"))
|
// println(eval("π"))
|
||||||
val pi = eval("Math.PI")
|
// val pi = eval("Math.PI")
|
||||||
assertIs<ObjReal>(pi)
|
// assertIs<ObjReal>(pi)
|
||||||
assertTrue(pi.value - PI < 0.000001)
|
// assertTrue(pi.value - PI < 0.000001)
|
||||||
assertTrue(eval("Math.PI+1").toDouble() - PI - 1.0 < 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)
|
assertTrue(eval("sin(π)").toDouble() - 1 < 0.000001)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -831,6 +830,7 @@ class ScriptTest {
|
|||||||
"""
|
"""
|
||||||
val a = [4,3]
|
val a = [4,3]
|
||||||
assert(a.size == 2)
|
assert(a.size == 2)
|
||||||
|
assert( 3 == a[1] )
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -977,16 +977,69 @@ class ScriptTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testCharacterRange() = runTest {
|
fun testCharacterRange() = runTest {
|
||||||
eval("""
|
eval(
|
||||||
|
"""
|
||||||
val x = '0'..'9'
|
val x = '0'..'9'
|
||||||
println(x)
|
println(x)
|
||||||
assert( '5' in x)
|
assert( '5' in x)
|
||||||
assert( 'z' !in x)
|
assert( 'z' !in x)
|
||||||
for( ch in x )
|
for( ch in x )
|
||||||
println(ch)
|
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
|
// @Test
|
||||||
// fun testLambda1() = runTest {
|
// fun testLambda1() = runTest {
|
||||||
// val l = eval("""
|
// val l = eval("""
|
||||||
|
@ -13,6 +13,7 @@ dependencyResolutionManagement {
|
|||||||
maven("https://maven.universablockchain.com/")
|
maven("https://maven.universablockchain.com/")
|
||||||
maven("https://gitea.sergeych.net/api/packages/SergeychWorks/maven")
|
maven("https://gitea.sergeych.net/api/packages/SergeychWorks/maven")
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
|
maven("https://gitea.sergeych.net/api/packages/SergeychWorks/maven")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user