440 lines
14 KiB
Kotlin
440 lines
14 KiB
Kotlin
package net.sergeych.ling
|
|
|
|
import kotlinx.coroutines.sync.Mutex
|
|
import kotlinx.coroutines.sync.withLock
|
|
import kotlinx.serialization.SerialName
|
|
import kotlinx.serialization.Serializable
|
|
import kotlin.math.floor
|
|
import kotlin.math.roundToLong
|
|
|
|
//typealias InstanceMethod = (Context, Obj) -> Obj
|
|
|
|
data class WithAccess<T>(var value: T, val isMutable: Boolean)
|
|
|
|
data class Accessor(
|
|
val getter: suspend (Context) -> WithAccess<Obj>,
|
|
val setterOrNull: (suspend (Context, Obj) -> Unit)?
|
|
) {
|
|
constructor(getter: suspend (Context) -> WithAccess<Obj>) : this(getter, null)
|
|
|
|
fun setter(pos: Pos) = setterOrNull ?: throw ScriptError(pos, "can't assign value")
|
|
}
|
|
|
|
sealed 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>()
|
|
|
|
open fun inspect(): String = toString()
|
|
|
|
/**
|
|
* Some objects are by-value, historically [ObjInt] and [ObjReal] are usually treated as such.
|
|
* When initializing a var with it, by value objects must be copied. By-reference ones aren't.
|
|
*
|
|
* Almost all objects are by-reference.
|
|
*/
|
|
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 getInstanceMember(atPos: Pos, name: String): WithAccess<Obj> =
|
|
getInstanceMemberOrNull(name)
|
|
?: throw ScriptError(atPos, "symbol doesn't exist: $name")
|
|
|
|
suspend fun callInstanceMethod(context: Context, name: String, args: Arguments): Obj =
|
|
// note that getInstanceMember traverses the hierarchy
|
|
objClass.getInstanceMember(context.pos, name).value.invoke(context, this, args)
|
|
|
|
// methods that to override
|
|
|
|
open suspend fun compareTo(context: Context, other: Obj): Int {
|
|
context.raiseNotImplemented()
|
|
}
|
|
|
|
open val asStr: ObjString by lazy {
|
|
if (this is ObjString) this else ObjString(this.toString())
|
|
}
|
|
|
|
/**
|
|
* Class of the object: definition of member functions (top-level), etc.
|
|
* Note that using lazy allows to avoid endless recursion here
|
|
*/
|
|
open val objClass: ObjClass by lazy { ObjClass("Obj") }
|
|
|
|
open suspend fun plus(context: Context, other: Obj): Obj {
|
|
context.raiseNotImplemented()
|
|
}
|
|
|
|
open suspend fun minus(context: Context, other: Obj): Obj {
|
|
context.raiseNotImplemented()
|
|
}
|
|
|
|
open suspend fun mul(context: Context, other: Obj): Obj {
|
|
context.raiseNotImplemented()
|
|
}
|
|
|
|
open suspend fun div(context: Context, other: Obj): Obj {
|
|
context.raiseNotImplemented()
|
|
}
|
|
|
|
open suspend fun mod(context: Context, other: Obj): Obj {
|
|
context.raiseNotImplemented()
|
|
}
|
|
|
|
open suspend fun logicalNot(context: Context): Obj {
|
|
context.raiseNotImplemented()
|
|
}
|
|
|
|
open suspend fun logicalAnd(context: Context, other: Obj): Obj {
|
|
context.raiseNotImplemented()
|
|
}
|
|
|
|
open suspend fun logicalOr(context: Context, other: Obj): Obj {
|
|
context.raiseNotImplemented()
|
|
}
|
|
|
|
open suspend fun assign(context: Context, other: Obj): Obj? = null
|
|
|
|
/**
|
|
* a += b
|
|
* if( the operation is not defined, it returns null and the compiler would try
|
|
* to generate it as 'this = this + other', reassigning its variable
|
|
*/
|
|
open suspend fun plusAssign(context: Context, other: Obj): Obj? = null
|
|
|
|
/**
|
|
* `-=` operations, see [plusAssign]
|
|
*/
|
|
open suspend fun minusAssign(context: Context, other: Obj): Obj? = null
|
|
open suspend fun mulAssign(context: Context, other: Obj): Obj? = null
|
|
open suspend fun divAssign(context: Context, other: Obj): Obj? = null
|
|
open suspend fun modAssign(context: Context, other: Obj): Obj? = null
|
|
|
|
open suspend fun getAndIncrement(context: Context): Obj {
|
|
context.raiseNotImplemented()
|
|
}
|
|
|
|
open suspend fun incrementAndGet(context: Context): Obj {
|
|
context.raiseNotImplemented()
|
|
}
|
|
|
|
open suspend fun decrementAndGet(context: Context): Obj {
|
|
context.raiseNotImplemented()
|
|
}
|
|
|
|
open suspend fun getAndDecrement(context: Context): Obj {
|
|
context.raiseNotImplemented()
|
|
}
|
|
|
|
fun willMutate(context: Context) {
|
|
if (isFrozen) context.raiseError("attempt to mutate frozen object")
|
|
}
|
|
|
|
suspend fun <T> sync(block: () -> T): T = monitor.withLock { block() }
|
|
|
|
suspend fun readField(context: Context, name: String): WithAccess<Obj> {
|
|
// could be property or class field:
|
|
val obj = objClass.getInstanceMemberOrNull(name)
|
|
val value = obj?.value
|
|
return when (value) {
|
|
is Statement -> {
|
|
// readonly property, important: call it on this
|
|
value.execute(context.copy(context.pos, newThisObj = this)).asReadonly
|
|
}
|
|
// could be writable property naturally
|
|
else -> getInstanceMember(context.pos, name)
|
|
}
|
|
}
|
|
|
|
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")
|
|
}
|
|
|
|
open suspend fun getAt(context: Context, index: Int): Obj {
|
|
context.raiseNotImplemented("indexing")
|
|
}
|
|
|
|
open suspend fun putAt(context: Context, index: Int, newValue: 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()
|
|
}
|
|
|
|
suspend fun invoke(context: Context, thisObj: Obj, args: Arguments): Obj =
|
|
callOn(context.copy(context.pos, args = args, newThisObj = thisObj))
|
|
|
|
suspend fun invoke(context: Context, atPos: Pos, thisObj: Obj, args: Arguments): Obj =
|
|
callOn(context.copy(atPos, args = args, newThisObj = thisObj))
|
|
|
|
val asReadonly: WithAccess<Obj> by lazy { WithAccess(this, false) }
|
|
val asMutable: WithAccess<Obj> by lazy { WithAccess(this, true) }
|
|
|
|
|
|
companion object {
|
|
inline fun <reified T> from(obj: T): Obj {
|
|
return when (obj) {
|
|
is Obj -> obj
|
|
is Double -> ObjReal(obj)
|
|
is Float -> ObjReal(obj.toDouble())
|
|
is Int -> ObjInt(obj.toLong())
|
|
is Long -> ObjInt(obj)
|
|
is String -> ObjString(obj)
|
|
is CharSequence -> ObjString(obj.toString())
|
|
is Boolean -> ObjBool(obj)
|
|
Unit -> ObjVoid
|
|
null -> ObjNull
|
|
else -> throw IllegalArgumentException("cannot convert to Obj: $obj")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Suppress("unused")
|
|
inline fun <reified T> T.toObj(): Obj = Obj.from(this)
|
|
|
|
@Serializable
|
|
@SerialName("void")
|
|
object ObjVoid : Obj() {
|
|
override fun equals(other: Any?): Boolean {
|
|
return other is ObjVoid || other is Unit
|
|
}
|
|
|
|
override suspend fun compareTo(context: Context, other: Obj): Int {
|
|
return if (other === this) 0 else -1
|
|
}
|
|
|
|
override fun toString(): String = "void"
|
|
}
|
|
|
|
@Serializable
|
|
@SerialName("null")
|
|
object ObjNull : Obj() {
|
|
override suspend fun compareTo(context: Context, other: Obj): Int {
|
|
return if (other === this) 0 else -1
|
|
}
|
|
|
|
override fun equals(other: Any?): Boolean {
|
|
return other is ObjNull || other == null
|
|
}
|
|
}
|
|
|
|
interface Numeric {
|
|
val longValue: Long
|
|
val doubleValue: Double
|
|
val toObjInt: ObjInt
|
|
val toObjReal: ObjReal
|
|
}
|
|
|
|
fun Obj.toDouble(): Double =
|
|
(this as? Numeric)?.doubleValue
|
|
?: (this as? ObjString)?.value?.toDouble()
|
|
?: throw IllegalArgumentException("cannot convert to double $this")
|
|
|
|
@Suppress("unused")
|
|
fun Obj.toLong(): Long =
|
|
(this as? Numeric)?.longValue
|
|
?: (this as? ObjString)?.value?.toLong()
|
|
?: throw IllegalArgumentException("cannot convert to double $this")
|
|
|
|
fun Obj.toInt(): Int = toLong().toInt()
|
|
|
|
fun Obj.toBool(): Boolean =
|
|
(this as? ObjBool)?.value ?: throw IllegalArgumentException("cannot convert to boolean $this")
|
|
|
|
|
|
data class ObjReal(val value: Double) : Obj(), Numeric {
|
|
override val asStr by lazy { ObjString(value.toString()) }
|
|
override val longValue: Long by lazy { floor(value).toLong() }
|
|
override val doubleValue: Double by lazy { value }
|
|
override val toObjInt: ObjInt by lazy { ObjInt(longValue) }
|
|
override val toObjReal: ObjReal by lazy { ObjReal(value) }
|
|
|
|
override fun byValueCopy(): Obj = ObjReal(value)
|
|
|
|
override suspend fun compareTo(context: Context, other: Obj): Int {
|
|
if (other !is Numeric) return -2
|
|
return value.compareTo(other.doubleValue)
|
|
}
|
|
|
|
override fun toString(): String = value.toString()
|
|
|
|
override val objClass: ObjClass = type
|
|
|
|
override suspend fun plus(context: Context, other: Obj): Obj =
|
|
ObjReal(this.value + other.toDouble())
|
|
|
|
override suspend fun minus(context: Context, other: Obj): Obj =
|
|
ObjReal(this.value - other.toDouble())
|
|
|
|
override suspend fun mul(context: Context, other: Obj): Obj =
|
|
ObjReal(this.value * other.toDouble())
|
|
|
|
override suspend fun div(context: Context, other: Obj): Obj =
|
|
ObjReal(this.value / other.toDouble())
|
|
|
|
override suspend fun mod(context: Context, other: Obj): Obj =
|
|
ObjReal(this.value % other.toDouble())
|
|
|
|
companion object {
|
|
val type: ObjClass = ObjClass("Real").apply {
|
|
createField(
|
|
"roundToInt",
|
|
statement(Pos.builtIn) {
|
|
(it.thisObj as ObjReal).value.roundToLong().toObj()
|
|
},
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
data class ObjInt(var value: Long) : Obj(), Numeric {
|
|
override val asStr get() = ObjString(value.toString())
|
|
override val longValue get() = value
|
|
override val doubleValue get() = value.toDouble()
|
|
override val toObjInt get() = this
|
|
override val toObjReal = ObjReal(doubleValue)
|
|
|
|
override fun byValueCopy(): Obj = ObjInt(value)
|
|
|
|
override suspend fun getAndIncrement(context: Context): Obj {
|
|
return ObjInt(value).also { value++ }
|
|
}
|
|
|
|
override suspend fun getAndDecrement(context: Context): Obj {
|
|
return ObjInt(value).also { value-- }
|
|
}
|
|
|
|
override suspend fun incrementAndGet(context: Context): Obj {
|
|
return ObjInt(++value)
|
|
}
|
|
|
|
override suspend fun decrementAndGet(context: Context): Obj {
|
|
return ObjInt(--value)
|
|
}
|
|
|
|
override suspend fun compareTo(context: Context, other: Obj): Int {
|
|
if (other !is Numeric) return -2
|
|
return value.compareTo(other.doubleValue)
|
|
}
|
|
|
|
override fun toString(): String = value.toString()
|
|
|
|
override val objClass: ObjClass = type
|
|
|
|
override suspend fun plus(context: Context, other: Obj): Obj =
|
|
if (other is ObjInt)
|
|
ObjInt(this.value + other.value)
|
|
else
|
|
ObjReal(this.doubleValue + other.toDouble())
|
|
|
|
override suspend fun minus(context: Context, other: Obj): Obj =
|
|
if (other is ObjInt)
|
|
ObjInt(this.value - other.value)
|
|
else
|
|
ObjReal(this.doubleValue - other.toDouble())
|
|
|
|
override suspend fun mul(context: Context, other: Obj): Obj =
|
|
if (other is ObjInt) {
|
|
ObjInt(this.value * other.value)
|
|
} else ObjReal(this.value * other.toDouble())
|
|
|
|
override suspend fun div(context: Context, other: Obj): Obj =
|
|
if (other is ObjInt)
|
|
ObjInt(this.value / other.value)
|
|
else ObjReal(this.value / other.toDouble())
|
|
|
|
override suspend fun mod(context: Context, other: Obj): Obj =
|
|
if (other is ObjInt)
|
|
ObjInt(this.value % other.value)
|
|
else ObjReal(this.value.toDouble() % other.toDouble())
|
|
|
|
/**
|
|
* We are by-value type ([byValueCopy] is implemented) so we can do in-place
|
|
* assignment
|
|
*/
|
|
override suspend fun assign(context: Context, other: Obj): Obj? {
|
|
return if (other is ObjInt) {
|
|
value = other.value
|
|
this
|
|
} else null
|
|
}
|
|
|
|
companion object {
|
|
val type = ObjClass("Int")
|
|
}
|
|
}
|
|
|
|
data class ObjBool(val value: Boolean) : Obj() {
|
|
override val asStr by lazy { ObjString(value.toString()) }
|
|
|
|
override suspend fun compareTo(context: Context, other: Obj): Int {
|
|
if (other !is ObjBool) return -2
|
|
return value.compareTo(other.value)
|
|
}
|
|
|
|
override fun toString(): String = value.toString()
|
|
|
|
override val objClass: ObjClass = type
|
|
|
|
override suspend fun logicalNot(context: Context): Obj = ObjBool(!value)
|
|
|
|
override suspend fun logicalAnd(context: Context, other: Obj): Obj = ObjBool(value && other.toBool())
|
|
|
|
override suspend fun logicalOr(context: Context, other: Obj): Obj = ObjBool(value || other.toBool())
|
|
|
|
companion object {
|
|
val type = ObjClass("Bool")
|
|
}
|
|
}
|
|
|
|
//open class ObjProperty(var value: Obj =ObjVoid) {
|
|
// open suspend fun get(context: Context): Obj = value
|
|
// open suspend fun set(context: Context,newValue: Obj): Obj {
|
|
// return value.also { value = newValue }
|
|
// }
|
|
//}
|
|
|
|
data class ObjNamespace(val name: String) : Obj() {
|
|
override fun toString(): String {
|
|
return "namespace ${name}"
|
|
}
|
|
}
|
|
|
|
open class ObjError(val context: Context, val message: String) : Obj() {
|
|
override val asStr: ObjString by lazy { ObjString("Error: $message") }
|
|
}
|
|
|
|
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)
|