published to our maven
This commit is contained in:
1 parent
185aa4e0cf
commit
8c6a1979ed
39 files changed
+89
-104
No files matched your search
@@ -0,0 +1,8 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
enum class AccessType(val isMutable: Boolean) {
|
||||
Val(false), Var(true),
|
||||
|
||||
@Suppress("unused")
|
||||
Initialization(false)
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
/**
|
||||
* List of argument declarations in the __definition__ of the lambda, class constructor,
|
||||
* function, etc. It is created by [Compiler.parseArgsDeclaration]
|
||||
*/
|
||||
data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type) {
|
||||
init {
|
||||
val i = params.count { it.isEllipsis }
|
||||
if (i > 1) throw ScriptError(params[i].pos, "there can be only one argument")
|
||||
val start = params.indexOfFirst { it.defaultValue != null }
|
||||
if (start >= 0)
|
||||
for (j in start + 1 until params.size)
|
||||
// last non-default could be lambda:
|
||||
if (params[j].defaultValue == null && j != params.size - 1) throw ScriptError(
|
||||
params[j].pos,
|
||||
"required argument can't follow default one"
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* parse args and create local vars in a given context
|
||||
*/
|
||||
suspend fun assignToContext(
|
||||
context: Context,
|
||||
arguments: Arguments = context.args,
|
||||
defaultAccessType: AccessType = AccessType.Var,
|
||||
defaultVisibility: Visibility = Visibility.Public
|
||||
) {
|
||||
fun assign(a: Item, value: Obj) {
|
||||
context.addItem(a.name, (a.accessType ?: defaultAccessType).isMutable, value,
|
||||
a.visibility ?: defaultVisibility)
|
||||
}
|
||||
|
||||
// will be used with last lambda arg fix
|
||||
val callArgs: List<Obj>
|
||||
val paramsSize: Int
|
||||
|
||||
if( arguments.tailBlockMode ) {
|
||||
paramsSize = params.size - 1
|
||||
assign(params.last(), arguments.list.last())
|
||||
callArgs = arguments.list.dropLast(1)
|
||||
} else {
|
||||
paramsSize = params.size
|
||||
callArgs = arguments.list
|
||||
}
|
||||
|
||||
suspend fun processHead(index: Int): Int {
|
||||
var i = index
|
||||
while (i != paramsSize) {
|
||||
val a = params[i]
|
||||
if (a.isEllipsis) break
|
||||
val value = when {
|
||||
i < callArgs.size -> callArgs[i]
|
||||
a.defaultValue != null -> a.defaultValue.execute(context)
|
||||
else -> {
|
||||
println("callArgs: ${callArgs.joinToString()}")
|
||||
println("tailBlockMode: ${arguments.tailBlockMode}")
|
||||
context.raiseArgumentError("too few arguments for the call")
|
||||
}
|
||||
}
|
||||
assign(a, value)
|
||||
i++
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
suspend fun processTail(index: Int): Int {
|
||||
var i = paramsSize - 1
|
||||
var j = callArgs.size - 1
|
||||
while (i > index) {
|
||||
val a = params[i]
|
||||
if (a.isEllipsis) break
|
||||
val value = when {
|
||||
j >= index -> {
|
||||
callArgs[j--]
|
||||
}
|
||||
|
||||
a.defaultValue != null -> a.defaultValue.execute(context)
|
||||
else -> context.raiseArgumentError("too few arguments for the call")
|
||||
}
|
||||
assign(a, value)
|
||||
i--
|
||||
}
|
||||
return j
|
||||
}
|
||||
|
||||
fun processEllipsis(index: Int, toFromIndex: Int) {
|
||||
val a = params[index]
|
||||
val l = if (index > toFromIndex) ObjList()
|
||||
else ObjList(callArgs.subList(index, toFromIndex + 1).toMutableList())
|
||||
assign(a, l)
|
||||
}
|
||||
|
||||
val leftIndex = processHead(0)
|
||||
if (leftIndex < paramsSize) {
|
||||
val end = processTail(leftIndex)
|
||||
processEllipsis(leftIndex, end)
|
||||
} else {
|
||||
if (leftIndex < callArgs.size)
|
||||
context.raiseArgumentError("too many arguments for the call")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Single argument declaration descriptor.
|
||||
*
|
||||
* @param defaultValue default value, if set, can't be an [Obj] as it can depend on the call site, call args, etc.
|
||||
* If not null, could be executed on __caller context__ only.
|
||||
*/
|
||||
data class Item(
|
||||
val name: String,
|
||||
val type: TypeDecl = TypeDecl.Obj,
|
||||
val pos: Pos = Pos.builtIn,
|
||||
val isEllipsis: Boolean = false,
|
||||
/**
|
||||
* Default value, if set, can't be an [Obj] as it can depend on the call site, call args, etc.
|
||||
* So it is a [Statement] that must be executed on __caller context__.
|
||||
*/
|
||||
val defaultValue: Statement? = null,
|
||||
val accessType: AccessType? = null,
|
||||
val visibility: Visibility? = null,
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
data class ParsedArgument(val value: Statement, val pos: Pos, val isSplat: Boolean = false)
|
||||
|
||||
suspend fun Collection<ParsedArgument>.toArguments(context: Context,tailBlockMode: Boolean): Arguments {
|
||||
val list = mutableListOf<Obj>()
|
||||
|
||||
for (x in this) {
|
||||
val value = x.value.execute(context)
|
||||
if (x.isSplat) {
|
||||
when {
|
||||
value is ObjList -> {
|
||||
for (subitem in value.list) list.add(subitem)
|
||||
}
|
||||
|
||||
value.isInstanceOf(ObjIterable) -> {
|
||||
val i = (value.invokeInstanceMethod(context, "toList") as ObjList).list
|
||||
i.forEach { list.add(it) }
|
||||
}
|
||||
|
||||
else -> context.raiseClassCastError("expected list of objects for splat argument")
|
||||
}
|
||||
} else
|
||||
list.add(value)
|
||||
}
|
||||
return Arguments(list,tailBlockMode)
|
||||
}
|
||||
|
||||
data class Arguments(val list: List<Obj>,val tailBlockMode: Boolean = false) : List<Obj> by list {
|
||||
|
||||
fun firstAndOnly(pos: Pos = Pos.UNKNOWN): Obj {
|
||||
if (list.size != 1) throw ScriptError(pos, "expected one argument, got ${list.size}")
|
||||
return list.first()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val EMPTY = Arguments(emptyList())
|
||||
fun from(values: Collection<Obj>) = Arguments(values.toList())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
//fun buildDoubleFromParts(
|
||||
// integerPart: Long,
|
||||
// decimalPart: Long,
|
||||
// exponent: Int
|
||||
//): Double {
|
||||
// // Handle zero decimal case efficiently
|
||||
// val numDecimalDigits = if (decimalPart == 0L) 0 else decimalPart.toString().length
|
||||
//
|
||||
// // Calculate decimal multiplier (10^-digits)
|
||||
// val decimalMultiplier = 10.0.pow(-numDecimalDigits)
|
||||
//
|
||||
// // Combine integer and decimal parts
|
||||
// val baseValue = integerPart.toDouble() + decimalPart.toDouble() * decimalMultiplier
|
||||
//
|
||||
// // Apply exponent
|
||||
// return baseValue * 10.0.pow(exponent)
|
||||
//}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,163 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
internal class CompilerContext(val tokens: List<Token>) {
|
||||
val labels = mutableSetOf<String>()
|
||||
|
||||
var breakFound = false
|
||||
private set
|
||||
|
||||
var loopLevel = 0
|
||||
private set
|
||||
|
||||
inline fun <T> parseLoop(f: () -> T): Pair<Boolean, T> {
|
||||
if (++loopLevel == 0) breakFound = false
|
||||
val result = f()
|
||||
return Pair(breakFound, result).also {
|
||||
--loopLevel
|
||||
}
|
||||
}
|
||||
|
||||
var currentIndex = 0
|
||||
|
||||
fun hasNext() = currentIndex < tokens.size
|
||||
fun hasPrevious() = currentIndex > 0
|
||||
fun next() =
|
||||
if( currentIndex < tokens.size ) tokens[currentIndex++]
|
||||
else Token("", tokens.last().pos, Token.Type.EOF)
|
||||
// throw IllegalStateException("No more tokens")
|
||||
|
||||
fun previous() = if (!hasPrevious()) throw IllegalStateException("No previous token") else tokens[--currentIndex]
|
||||
|
||||
fun savePos() = currentIndex
|
||||
fun restorePos(pos: Int) {
|
||||
currentIndex = pos
|
||||
}
|
||||
|
||||
fun ensureLabelIsValid(pos: Pos, label: String) {
|
||||
if (label !in labels)
|
||||
throw ScriptError(pos, "Undefined label '$label'")
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun requireId() = requireToken(Token.Type.ID, "identifier is required")
|
||||
|
||||
fun requireToken(type: Token.Type, message: String = "required ${type.name}"): Token =
|
||||
next().also {
|
||||
if (type != it.type) throw ScriptError(it.pos, message)
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun syntaxError(at: Pos, message: String = "Syntax error"): Nothing {
|
||||
throw ScriptError(at, message)
|
||||
}
|
||||
|
||||
fun currentPos(): Pos = tokens[currentIndex].pos
|
||||
|
||||
/**
|
||||
* Skips next token if its type is `tokenType`, returns `true` if so.
|
||||
* @param errorMessage message to throw if next token is not `tokenType`
|
||||
* @param isOptional if `true` and token is not of `tokenType`, just return `false` and does not skip it
|
||||
* @return `true` if the token was skipped
|
||||
* @throws ScriptError if [isOptional] is `false` and next token is not of [tokenType]
|
||||
*/
|
||||
fun skipTokenOfType(
|
||||
tokenType: Token.Type,
|
||||
errorMessage: String = "expected ${tokenType.name}",
|
||||
isOptional: Boolean = false
|
||||
): Boolean {
|
||||
val t = next()
|
||||
return if (t.type != tokenType) {
|
||||
if (!isOptional) {
|
||||
println("unexpected: $t (needed $tokenType)")
|
||||
throw ScriptError(t.pos, errorMessage)
|
||||
} else {
|
||||
previous()
|
||||
false
|
||||
}
|
||||
} else true
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun skipTokens(vararg tokenTypes: Token.Type) {
|
||||
while (next().type in tokenTypes) { /**/
|
||||
}
|
||||
previous()
|
||||
}
|
||||
|
||||
|
||||
fun ifNextIs(typeId: Token.Type, f: (Token) -> Unit): Boolean {
|
||||
val t = next()
|
||||
return if (t.type == typeId) {
|
||||
f(t)
|
||||
true
|
||||
} else {
|
||||
previous()
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
inline fun addBreak() {
|
||||
breakFound = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Return value of the next token if it is an identifier, null otherwise.
|
||||
* Does not change position.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
fun nextIdValue(): String? {
|
||||
return if (hasNext()) {
|
||||
val nt = tokens[currentIndex]
|
||||
if (nt.type == Token.Type.ID)
|
||||
nt.value
|
||||
else null
|
||||
} else null
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun current(): Token = tokens[currentIndex]
|
||||
|
||||
/**
|
||||
* If the token at current position plus offset (could be negative) exists, returns it, otherwise returns null.
|
||||
*/
|
||||
@Suppress("unused")
|
||||
fun atOffset(offset: Int): Token? =
|
||||
if (currentIndex + offset in tokens.indices) tokens[currentIndex + offset] else null
|
||||
|
||||
fun matchQualifiers(keyword: String, vararg qualifiers: String): Boolean {
|
||||
val pos = savePos()
|
||||
var count = 0
|
||||
while( count < qualifiers.size) {
|
||||
val t = next()
|
||||
when(t.type) {
|
||||
Token.Type.ID -> {
|
||||
if( t.value in qualifiers ) count++
|
||||
else { restorePos(pos); return false }
|
||||
}
|
||||
Token.Type.MULTILINE_COMMENT, Token.Type.SINLGE_LINE_COMMENT, Token.Type.NEWLINE -> {}
|
||||
else -> { restorePos(pos); return false }
|
||||
}
|
||||
}
|
||||
val t = next()
|
||||
if( t.type == Token.Type.ID && t.value == keyword ) {
|
||||
return true
|
||||
} else {
|
||||
restorePos(pos)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip newlines and comments. Returns (and reads) first non-whitespace token.
|
||||
* Note that [Token.Type.EOF] is not considered a whitespace token.
|
||||
*/
|
||||
fun skipWsTokens(): Token {
|
||||
while( current().type in wstokens ) next()
|
||||
return next()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val wstokens = setOf(Token.Type.NEWLINE, Token.Type.MULTILINE_COMMENT, Token.Type.SINLGE_LINE_COMMENT)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
class Context(
|
||||
val parent: Context?,
|
||||
val args: Arguments = Arguments.EMPTY,
|
||||
var pos: Pos = Pos.builtIn,
|
||||
val thisObj: Obj = ObjVoid,
|
||||
var skipContextCreation: Boolean = false,
|
||||
) {
|
||||
constructor(
|
||||
args: Arguments = Arguments.EMPTY,
|
||||
pos: Pos = Pos.builtIn,
|
||||
)
|
||||
: this(Script.defaultContext, args, pos)
|
||||
|
||||
fun raiseNotImplemented(what: String = "operation"): Nothing = raiseError("$what is not implemented")
|
||||
|
||||
@Suppress("unused")
|
||||
fun raiseNPE(): Nothing = raiseError(ObjNullPointerException(this))
|
||||
|
||||
@Suppress("unused")
|
||||
fun raiseIndexOutOfBounds(message: String = "Index out of bounds"): Nothing =
|
||||
raiseError(ObjIndexOutOfBoundsException(this, message))
|
||||
|
||||
@Suppress("unused")
|
||||
fun raiseArgumentError(message: String = "Illegal argument error"): Nothing =
|
||||
raiseError(ObjIllegalArgumentException(this, message))
|
||||
|
||||
fun raiseClassCastError(msg: String): Nothing = raiseError(ObjClassCastException(this, msg))
|
||||
|
||||
@Suppress("unused")
|
||||
fun raiseSymbolNotFound(name: String): Nothing =
|
||||
raiseError(ObjSymbolNotDefinedException(this, "symbol is not defined: $name"))
|
||||
|
||||
fun raiseError(message: String): Nothing {
|
||||
throw ExecutionError(ObjException(this, message))
|
||||
}
|
||||
|
||||
fun raiseError(obj: ObjException): Nothing {
|
||||
throw ExecutionError(obj)
|
||||
}
|
||||
|
||||
inline fun <reified T : Obj> requiredArg(index: Int): T {
|
||||
if (args.list.size <= index) raiseError("Expected at least ${index + 1} argument, got ${args.list.size}")
|
||||
return (args.list[index] as? T)
|
||||
?: raiseClassCastError("Expected type ${T::class.simpleName}, got ${args.list[index]::class.simpleName}")
|
||||
}
|
||||
|
||||
inline fun <reified T : Obj> requireOnlyArg(): T {
|
||||
if (args.list.size != 1) raiseError("Expected exactly 1 argument, got ${args.list.size}")
|
||||
return requiredArg(0)
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun requireExactCount(count: Int) {
|
||||
if (args.list.size != count) {
|
||||
raiseError("Expected exactly $count arguments, got ${args.list.size}")
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T : Obj> thisAs(): T = (thisObj as? T)
|
||||
?: raiseClassCastError("Cannot cast ${thisObj.objClass.className} to ${T::class.simpleName}")
|
||||
|
||||
internal val objects = mutableMapOf<String, ObjRecord>()
|
||||
|
||||
operator fun get(name: String): ObjRecord? =
|
||||
objects[name]
|
||||
?: parent?.get(name)
|
||||
|
||||
fun copy(pos: Pos, args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Context =
|
||||
Context(this, args, pos, newThisObj ?: thisObj)
|
||||
|
||||
fun copy(args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Context =
|
||||
Context(this, args, pos, newThisObj ?: thisObj)
|
||||
|
||||
fun copy() = Context(this, args, pos, thisObj)
|
||||
|
||||
fun addItem(
|
||||
name: String,
|
||||
isMutable: Boolean,
|
||||
value: Obj,
|
||||
visibility: Visibility = Visibility.Public
|
||||
): ObjRecord {
|
||||
return ObjRecord(value, isMutable, visibility).also { objects[name] = it }
|
||||
}
|
||||
|
||||
fun getOrCreateNamespace(name: String): ObjClass {
|
||||
val ns = objects.getOrPut(name) { ObjRecord(ObjNamespace(name), isMutable = false) }.value
|
||||
return ns.objClass
|
||||
}
|
||||
|
||||
inline fun addVoidFn(vararg names: String, crossinline fn: suspend Context.() -> Unit) {
|
||||
addFn<ObjVoid>(*names) {
|
||||
fn(this)
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T : Obj> addFn(vararg names: String, crossinline fn: suspend Context.() -> T) {
|
||||
val newFn = object : Statement() {
|
||||
override val pos: Pos = Pos.builtIn
|
||||
|
||||
override suspend fun execute(context: Context): Obj = context.fn()
|
||||
|
||||
}
|
||||
for (name in names) {
|
||||
addItem(
|
||||
name,
|
||||
false,
|
||||
newFn
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun addConst(name: String, value: Obj) = addItem(name, false, value)
|
||||
|
||||
suspend fun eval(code: String): Obj =
|
||||
Compiler().compile(code.toSource()).execute(this)
|
||||
|
||||
fun containsLocal(name: String): Boolean = name in objects
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
sealed class ListEntry {
|
||||
data class Element(val accessor: Accessor) : ListEntry()
|
||||
|
||||
data class Spread(val accessor: Accessor) : ListEntry()
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
class LoopBreakContinueException(
|
||||
val doContinue: Boolean,
|
||||
val result: Obj = ObjVoid,
|
||||
val label: String? = null
|
||||
) : RuntimeException()
|
||||
@@ -0,0 +1,428 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.sergeych.bintools.encodeToHex
|
||||
import net.sergeych.synctools.ProtectedOp
|
||||
import net.sergeych.synctools.withLock
|
||||
import kotlin.contracts.ExperimentalContracts
|
||||
|
||||
/**
|
||||
* Record to store object with access rules, e.g. [isMutable] and access level [visibility].
|
||||
*/
|
||||
data class ObjRecord(
|
||||
var value: Obj,
|
||||
val isMutable: Boolean,
|
||||
val visibility: Visibility = Visibility.Public
|
||||
)
|
||||
|
||||
/**
|
||||
* When we need read-write access to an object in some abstract storage, we need Accessor,
|
||||
* as in-site assigning is not always sufficient, in general case we need to replace the object
|
||||
* in the storage.
|
||||
*
|
||||
* Note that assigning new value is more complex than just replacing the object, see how assignment
|
||||
* operator is implemented in [Compiler.allOps].
|
||||
*/
|
||||
data class Accessor(
|
||||
val getter: suspend (Context) -> ObjRecord,
|
||||
val setterOrNull: (suspend (Context, Obj) -> Unit)?
|
||||
) {
|
||||
/**
|
||||
* Simplified constructor for immutable stores.
|
||||
*/
|
||||
constructor(getter: suspend (Context) -> ObjRecord) : this(getter, null)
|
||||
|
||||
/**
|
||||
* Get the setter or throw.
|
||||
*/
|
||||
fun setter(pos: Pos) = setterOrNull ?: throw ScriptError(pos, "can't assign value")
|
||||
}
|
||||
|
||||
open class Obj {
|
||||
var isFrozen: Boolean = false
|
||||
|
||||
private val monitor = Mutex()
|
||||
|
||||
// private val memberMutex = Mutex()
|
||||
internal var parentInstances: MutableList<Obj> = mutableListOf()
|
||||
|
||||
private val opInstances = ProtectedOp()
|
||||
|
||||
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
|
||||
|
||||
fun isInstanceOf(someClass: Obj) = someClass === objClass || objClass.allParentsSet.contains(someClass)
|
||||
|
||||
suspend fun invokeInstanceMethod(context: Context, name: String, vararg args: Obj): Obj =
|
||||
invokeInstanceMethod(context, name, Arguments(args.toList()))
|
||||
|
||||
suspend inline fun <reified T : Obj> callMethod(
|
||||
context: Context,
|
||||
name: String,
|
||||
args: Arguments = Arguments.EMPTY
|
||||
): T = invokeInstanceMethod(context, name, args) as T
|
||||
|
||||
open suspend fun invokeInstanceMethod(
|
||||
context: Context,
|
||||
name: String,
|
||||
args: Arguments = Arguments.EMPTY
|
||||
): 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 {
|
||||
context.raiseNotImplemented()
|
||||
}
|
||||
|
||||
open suspend fun contains(context: Context, other: Obj): Boolean {
|
||||
return invokeInstanceMethod(context, "contains", other).toBool()
|
||||
}
|
||||
|
||||
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").apply {
|
||||
addFn("toString") {
|
||||
thisObj.asStr
|
||||
}
|
||||
addFn("contains") {
|
||||
ObjBool(thisObj.contains(this, args.firstAndOnly()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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() }
|
||||
|
||||
open suspend fun readField(context: Context, name: String): ObjRecord {
|
||||
// could be property or class field:
|
||||
val obj = objClass.getInstanceMemberOrNull(name) ?: context.raiseError("no such field: $name")
|
||||
return when (val value = obj.value) {
|
||||
is Statement -> {
|
||||
ObjRecord(value.execute(context.copy(context.pos, newThisObj = this)), obj.isMutable)
|
||||
}
|
||||
// could be writable property naturally
|
||||
// null -> ObjNull.asReadonly
|
||||
else -> obj
|
||||
}
|
||||
}
|
||||
|
||||
open suspend fun writeField(context: Context, name: String, newValue: Obj) {
|
||||
willMutate(context)
|
||||
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 {
|
||||
context.raiseNotImplemented("indexing")
|
||||
}
|
||||
|
||||
open suspend fun putAt(context: Context, index: Int, newValue: Obj) {
|
||||
context.raiseNotImplemented("indexing")
|
||||
}
|
||||
|
||||
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, thisObj: Obj, vararg args: Obj): Obj =
|
||||
callOn(
|
||||
context.copy(
|
||||
context.pos,
|
||||
args = Arguments(args.toList()),
|
||||
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))
|
||||
|
||||
|
||||
val asReadonly: ObjRecord by lazy { ObjRecord(this, false) }
|
||||
val asMutable: ObjRecord by lazy { ObjRecord(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
|
||||
}
|
||||
|
||||
override fun toString(): String = "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 =
|
||||
when (this) {
|
||||
is Numeric -> longValue
|
||||
is ObjString -> value.toLong()
|
||||
is ObjChar -> value.code.toLong()
|
||||
else -> 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 ObjNamespace(val name: String) : Obj() {
|
||||
override val objClass by lazy { ObjClass(name) }
|
||||
|
||||
override fun inspect(): String = "Ns[$name]"
|
||||
|
||||
override fun toString(): String {
|
||||
return "package $name"
|
||||
}
|
||||
}
|
||||
|
||||
open class ObjException(exceptionClass: ExceptionClass, val context: Context, val message: String) : Obj() {
|
||||
constructor(name: String,context: Context, message: String) : this(getOrCreateExceptionClass(name), context, message)
|
||||
constructor(context: Context, message: String) : this(Root, context, message)
|
||||
|
||||
fun raise(): Nothing {
|
||||
throw ExecutionError(this)
|
||||
}
|
||||
|
||||
override val objClass: ObjClass = exceptionClass
|
||||
|
||||
override fun toString(): String {
|
||||
return "ObjException:${objClass.className}:${context.pos}@${hashCode().encodeToHex()}"
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
class ExceptionClass(val name: String,vararg parents: ObjClass) : ObjClass(name, *parents) {
|
||||
override suspend fun callOn(context: Context): Obj {
|
||||
val message = context.args.getOrNull(0)?.toString() ?: name
|
||||
return ObjException(this, context, message)
|
||||
}
|
||||
override fun toString(): String = "ExceptionClass[$name]@${hashCode().encodeToHex()}"
|
||||
}
|
||||
val Root = ExceptionClass("Throwable").apply {
|
||||
addConst("message", statement {
|
||||
(thisObj as ObjException).message.toObj()
|
||||
})
|
||||
}
|
||||
|
||||
private val op = ProtectedOp()
|
||||
private val existingErrorClasses = mutableMapOf<String, ExceptionClass>()
|
||||
|
||||
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
protected fun getOrCreateExceptionClass(name: String): ExceptionClass {
|
||||
return op.withLock {
|
||||
existingErrorClasses.getOrPut(name) {
|
||||
ExceptionClass(name, Root)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get [ObjClass] for error class by name if exists.
|
||||
*/
|
||||
@OptIn(ExperimentalContracts::class)
|
||||
fun getErrorClass(name: String): ObjClass? = op.withLock {
|
||||
existingErrorClasses[name]
|
||||
}
|
||||
|
||||
fun addExceptionsToContext(context: Context) {
|
||||
context.addConst("Exception", Root)
|
||||
existingErrorClasses["Exception"] = Root
|
||||
for (name in listOf(
|
||||
"NullPointerException",
|
||||
"AssertionFailedException",
|
||||
"ClassCastException",
|
||||
"IndexOutOfBoundsException",
|
||||
"IllegalArgumentException",
|
||||
"IllegalAssignmentException",
|
||||
"SymbolNotDefinedException",
|
||||
"IterationEndException",
|
||||
"AccessException",
|
||||
"UnknownException",
|
||||
)) {
|
||||
context.addConst(name, getOrCreateExceptionClass(name))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ObjNullPointerException(context: Context) : ObjException("NullPointerException", context, "object is null")
|
||||
|
||||
class ObjAssertionFailedException(context: Context, message: String) :
|
||||
ObjException("AssertionFailedException", context, message)
|
||||
|
||||
class ObjClassCastException(context: Context, message: String) : ObjException("ClassCastException", context, message)
|
||||
class ObjIndexOutOfBoundsException(context: Context, message: String = "index out of bounds") :
|
||||
ObjException("IndexOutOfBoundsException", context, message)
|
||||
|
||||
class ObjIllegalArgumentException(context: Context, message: String = "illegal argument") :
|
||||
ObjException("IllegalArgumentException", context, message)
|
||||
|
||||
class ObjIllegalAssignmentException(context: Context, message: String = "illegal assignment") :
|
||||
ObjException("IllegalAssignmentException", context, message)
|
||||
|
||||
class ObjSymbolNotDefinedException(context: Context, message: String = "symbol is not defined") :
|
||||
ObjException("SymbolNotDefinedException", context, message)
|
||||
|
||||
class ObjIterationFinishedException(context: Context) :
|
||||
ObjException("IterationEndException", context, "iteration finished")
|
||||
|
||||
class ObjAccessException(context: Context, message: String = "access not allowed error") :
|
||||
ObjException("AccessException", context, message)
|
||||
|
||||
class ObjUnknownException(context: Context, message: String = "access not allowed error") :
|
||||
ObjException("UnknownException", context, message)
|
||||
@@ -0,0 +1,27 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
val ObjTrue = ObjBool(true)
|
||||
val ObjFalse = ObjBool(false)
|
||||
@@ -0,0 +1,19 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
class ObjChar(val value: Char): Obj() {
|
||||
|
||||
override val objClass: ObjClass = type
|
||||
|
||||
override suspend fun compareTo(context: Context, other: Obj): Int =
|
||||
(other as? ObjChar)?.let { value.compareTo(it.value) } ?: -1
|
||||
|
||||
override fun toString(): String = value.toString()
|
||||
|
||||
override fun inspect(): String = "'$value'"
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("Char").apply {
|
||||
addFn("code") { ObjInt(thisAs<ObjChar>().value.code.toLong()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
val ObjClassType by lazy { ObjClass("Class") }
|
||||
|
||||
open class ObjClass(
|
||||
val className: String,
|
||||
vararg val parents: ObjClass,
|
||||
) : Obj() {
|
||||
|
||||
var instanceConstructor: Statement? = null
|
||||
|
||||
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, ObjRecord>()
|
||||
|
||||
override fun toString(): String = className
|
||||
|
||||
override suspend fun compareTo(context: Context, other: Obj): Int = if (other === this) 0 else -1
|
||||
|
||||
override suspend fun callOn(context: Context): Obj {
|
||||
val instance = ObjInstance(this)
|
||||
instance.instanceContext = context.copy(newThisObj = instance,args = context.args)
|
||||
if (instanceConstructor != null) {
|
||||
instanceConstructor!!.execute(instance.instanceContext)
|
||||
}
|
||||
return instance
|
||||
}
|
||||
|
||||
fun defaultInstance(): Obj = object : Obj() {
|
||||
override val objClass: ObjClass = this@ObjClass
|
||||
}
|
||||
|
||||
fun createField(
|
||||
name: String,
|
||||
initialValue: Obj,
|
||||
isMutable: Boolean = false,
|
||||
visibility: Visibility = Visibility.Public,
|
||||
pos: Pos = Pos.builtIn
|
||||
) {
|
||||
val existing = members[name] ?: allParentsSet.firstNotNullOfOrNull { it.members[name] }
|
||||
if( existing?.isMutable == false)
|
||||
throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
|
||||
members[name] = ObjRecord(initialValue, isMutable, visibility)
|
||||
}
|
||||
|
||||
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): ObjRecord? {
|
||||
members[name]?.let { return it }
|
||||
allParentsSet.forEach { parent -> parent.getInstanceMemberOrNull(name)?.let { return it } }
|
||||
return null
|
||||
}
|
||||
|
||||
fun getInstanceMember(atPos: Pos, name: String): ObjRecord =
|
||||
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).apply {
|
||||
// it is not effective, but it is open:
|
||||
addFn("contains", isOpen = true) {
|
||||
val obj = args.firstAndOnly()
|
||||
val it = thisObj.invokeInstanceMethod(this, "iterator")
|
||||
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
|
||||
if( obj.compareTo(this, it.invokeInstanceMethod(this, "next")) == 0 )
|
||||
return@addFn ObjTrue
|
||||
}
|
||||
ObjFalse
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
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(ObjIterationFinishedException(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("contains", isOpen = true) {
|
||||
val obj = args.firstAndOnly()
|
||||
for( i in 0..< thisObj.invokeInstanceMethod(this, "size").toInt()) {
|
||||
if( thisObj.getAt(this, i).compareTo(this, obj) == 0 ) return@addFn ObjTrue
|
||||
}
|
||||
ObjFalse
|
||||
}
|
||||
|
||||
addFn("isample") { "ok".toObj() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
|
||||
internal lateinit var instanceContext: Context
|
||||
|
||||
override suspend fun readField(context: Context, name: String): ObjRecord {
|
||||
return instanceContext[name]?.let {
|
||||
if (it.visibility.isPublic)
|
||||
it
|
||||
else
|
||||
context.raiseError(ObjAccessException(context, "can't access non-public field $name"))
|
||||
}
|
||||
?: super.readField(context, name)
|
||||
}
|
||||
|
||||
override suspend fun writeField(context: Context, name: String, newValue: Obj) {
|
||||
instanceContext[name]?.let { f ->
|
||||
if (!f.visibility.isPublic)
|
||||
ObjIllegalAssignmentException(context, "can't assign to non-public field $name")
|
||||
if (!f.isMutable) ObjIllegalAssignmentException(context, "can't reassign val $name").raise()
|
||||
if (f.value.assign(context, newValue) == null)
|
||||
f.value = newValue
|
||||
} ?: super.writeField(context, name, newValue)
|
||||
}
|
||||
|
||||
override suspend fun invokeInstanceMethod(context: Context, name: String, args: Arguments): Obj =
|
||||
instanceContext[name]?.let {
|
||||
if (it.visibility.isPublic)
|
||||
it.value.invoke(context, this, args)
|
||||
else
|
||||
context.raiseError(ObjAccessException(context, "can't invoke non-public method $name"))
|
||||
}
|
||||
?: super.invokeInstanceMethod(context, name, args)
|
||||
|
||||
private val publicFields: Map<String, ObjRecord>
|
||||
get() = instanceContext.objects.filter { it.value.visibility.isPublic }
|
||||
|
||||
override fun toString(): String {
|
||||
val fields = publicFields.map { "${it.key}=${it.value.value}" }.joinToString(",")
|
||||
return "${objClass.className}($fields)"
|
||||
}
|
||||
|
||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
||||
if (other !is ObjInstance) return -1
|
||||
if (other.objClass != objClass) return -1
|
||||
for (f in publicFields) {
|
||||
val a = f.value.value
|
||||
val b = other.instanceContext[f.key]!!.value
|
||||
val d = a.compareTo(context, b)
|
||||
if (d != 0) return d
|
||||
}
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
fun Int.toObj() = ObjInt(this.toLong())
|
||||
@@ -0,0 +1,141 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
||||
|
||||
init {
|
||||
for (p in objClass.parents)
|
||||
parentInstances.add(p.defaultInstance())
|
||||
}
|
||||
|
||||
override fun toString(): String = "[${
|
||||
list.joinToString(separator = ", ") { it.inspect() }
|
||||
}]"
|
||||
|
||||
fun normalize(context: Context, index: Int, allowisEndInclusive: Boolean = false): Int {
|
||||
val i = if (index < 0) list.size + index else index
|
||||
if (allowisEndInclusive && i == list.size) return i
|
||||
if (i !in list.indices) context.raiseError("index $index out of bounds for size ${list.size}")
|
||||
return i
|
||||
}
|
||||
|
||||
override suspend fun getAt(context: Context, index: Int): Obj {
|
||||
val i = normalize(context, index)
|
||||
return list[i]
|
||||
}
|
||||
|
||||
override suspend fun putAt(context: Context, index: Int, newValue: Obj) {
|
||||
val i = normalize(context, index)
|
||||
list[i] = newValue
|
||||
}
|
||||
|
||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
||||
if (other !is ObjList) return -2
|
||||
val mySize = list.size
|
||||
val otherSize = other.list.size
|
||||
val commonSize = minOf(mySize, otherSize)
|
||||
for (i in 0..<commonSize) {
|
||||
if (list[i].compareTo(context, other.list[i]) != 0) {
|
||||
return list[i].compareTo(context, other.list[i])
|
||||
}
|
||||
}
|
||||
// equal so far, longer is greater:
|
||||
return when {
|
||||
mySize < otherSize -> -1
|
||||
mySize > otherSize -> 1
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun plus(context: Context, other: Obj): Obj =
|
||||
when {
|
||||
other is ObjList ->
|
||||
ObjList((list + other.list).toMutableList())
|
||||
|
||||
other.isInstanceOf(ObjIterable) -> {
|
||||
val l = other.callMethod<ObjList>(context, "toList")
|
||||
ObjList((list + l.list).toMutableList())
|
||||
}
|
||||
|
||||
else ->
|
||||
context.raiseError("'+': can't concatenate $this with $other")
|
||||
}
|
||||
|
||||
|
||||
override suspend fun plusAssign(context: Context, other: Obj): Obj {
|
||||
// optimization
|
||||
if (other is ObjList) {
|
||||
list += other.list
|
||||
return this
|
||||
}
|
||||
if (other.isInstanceOf(ObjIterable)) {
|
||||
val otherList = other.invokeInstanceMethod(context, "toList") as ObjList
|
||||
list += otherList.list
|
||||
} else
|
||||
list += other
|
||||
return this
|
||||
}
|
||||
|
||||
override suspend fun contains(context: Context, other: Obj): Boolean {
|
||||
return list.contains(other)
|
||||
}
|
||||
|
||||
override val objClass: ObjClass
|
||||
get() = type
|
||||
|
||||
companion object {
|
||||
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
|
||||
for (a in args) l.add(a)
|
||||
ObjVoid
|
||||
}
|
||||
)
|
||||
createField("addAt",
|
||||
statement {
|
||||
if (args.size < 2) raiseError("addAt takes 2+ arguments")
|
||||
val l = thisAs<ObjList>()
|
||||
var index = l.normalize(
|
||||
this, requiredArg<ObjInt>(0).value.toInt(),
|
||||
allowisEndInclusive = true
|
||||
)
|
||||
for (i in 1..<args.size) l.list.add(index++, args[i])
|
||||
ObjVoid
|
||||
}
|
||||
)
|
||||
addFn("removeAt") {
|
||||
val self = thisAs<ObjList>()
|
||||
val start = self.normalize(this, requiredArg<ObjInt>(0).value.toInt())
|
||||
if (args.size == 2) {
|
||||
val end = requireOnlyArg<ObjInt>().value.toInt()
|
||||
self.list.subList(start, self.normalize(this, end)).clear()
|
||||
} else
|
||||
self.list.removeAt(start)
|
||||
self
|
||||
}
|
||||
addFn("removeRangeInclusive") {
|
||||
val self = thisAs<ObjList>()
|
||||
val start = self.normalize(this, requiredArg<ObjInt>(0).value.toInt())
|
||||
val end = self.normalize(this, requiredArg<ObjInt>(1).value.toInt()) + 1
|
||||
self.list.subList(start, end).clear()
|
||||
self
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Obj() {
|
||||
|
||||
override val objClass: ObjClass = type
|
||||
|
||||
override fun toString(): String {
|
||||
val result = StringBuilder()
|
||||
result.append("${start?.inspect() ?: '∞'} ..")
|
||||
if (!isEndInclusive) result.append('<')
|
||||
result.append(" ${end?.inspect() ?: '∞'}")
|
||||
return result.toString()
|
||||
}
|
||||
|
||||
suspend fun containsRange(context: Context, other: ObjRange): Boolean {
|
||||
if (start != null) {
|
||||
// our start is not -∞ so other start should be GTE or is not contained:
|
||||
if (other.start != null && start.compareTo(context, other.start) > 0) return false
|
||||
}
|
||||
if (end != null) {
|
||||
// same with the end: if it is open, it can't be contained in ours:
|
||||
if (other.end == null) return false
|
||||
// both exists, now there could be 4 cases:
|
||||
return when {
|
||||
other.isEndInclusive && isEndInclusive ->
|
||||
end.compareTo(context, other.end) >= 0
|
||||
|
||||
!other.isEndInclusive && !isEndInclusive ->
|
||||
end.compareTo(context, other.end) >= 0
|
||||
|
||||
other.isEndInclusive && !isEndInclusive ->
|
||||
end.compareTo(context, other.end) > 0
|
||||
|
||||
!other.isEndInclusive && isEndInclusive ->
|
||||
end.compareTo(context, other.end) >= 0
|
||||
|
||||
else -> throw IllegalStateException("unknown comparison")
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override suspend fun contains(context: Context, other: Obj): Boolean {
|
||||
|
||||
if (other is ObjRange)
|
||||
return containsRange(context, other)
|
||||
|
||||
if (start == null && end == null) return true
|
||||
if (start != null) {
|
||||
if (start.compareTo(context, other) > 0) return false
|
||||
}
|
||||
if (end != null) {
|
||||
val cmp = end.compareTo(context, other)
|
||||
if (isEndInclusive && cmp < 0 || !isEndInclusive && cmp <= 0) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
val isIntRange: Boolean by lazy {
|
||||
start is ObjInt && end is ObjInt
|
||||
}
|
||||
|
||||
val isCharRange: Boolean by lazy {
|
||||
start is ObjChar && end is ObjChar
|
||||
}
|
||||
|
||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
||||
return (other as? ObjRange)?.let {
|
||||
if( start == other.start && end == other.end ) 0 else -1
|
||||
}
|
||||
?: -1
|
||||
}
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("Range", ObjIterable).apply {
|
||||
addFn("start") {
|
||||
thisAs<ObjRange>().start ?: ObjNull
|
||||
}
|
||||
addFn("end") {
|
||||
thisAs<ObjRange>().end ?: ObjNull
|
||||
}
|
||||
addFn("isOpen") {
|
||||
thisAs<ObjRange>().let { it.start == null || it.end == null }.toObj()
|
||||
}
|
||||
addFn("isIntRange") {
|
||||
thisAs<ObjRange>().isIntRange.toObj()
|
||||
}
|
||||
addFn("isCharRange") {
|
||||
thisAs<ObjRange>().isCharRange.toObj()
|
||||
}
|
||||
addFn("isEndInclusive") {
|
||||
thisAs<ObjRange>().isEndInclusive.toObj()
|
||||
}
|
||||
addFn("iterator") {
|
||||
val self = thisAs<ObjRange>()
|
||||
ObjRangeIterator(self).apply { init() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
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(ObjIterationFinishedException(context))
|
||||
}
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("RangeIterator", ObjIterable).apply {
|
||||
addFn("hasNext") {
|
||||
thisAs<ObjRangeIterator>().hasNext().toObj()
|
||||
}
|
||||
addFn("next") {
|
||||
thisAs<ObjRangeIterator>().next(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.roundToLong
|
||||
|
||||
data class ObjReal(val value: Double) : Obj(), Numeric {
|
||||
override val asStr by lazy { ObjString(value.toString()) }
|
||||
override val longValue: Long by lazy { floor(value).toLong() }
|
||||
override val doubleValue: Double by lazy { value }
|
||||
override val toObjInt: ObjInt by lazy { ObjInt(longValue) }
|
||||
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()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
@SerialName("string")
|
||||
data class ObjString(val value: String) : Obj() {
|
||||
|
||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
||||
if (other !is ObjString) return -2
|
||||
return this.value.compareTo(other.value)
|
||||
}
|
||||
|
||||
override fun toString(): String = value
|
||||
|
||||
override val asStr: ObjString by lazy { this }
|
||||
|
||||
override fun inspect(): String {
|
||||
return "\"$value\""
|
||||
}
|
||||
|
||||
override val objClass: ObjClass
|
||||
get() = type
|
||||
|
||||
override suspend fun plus(context: Context, other: Obj): Obj {
|
||||
return ObjString(value + other.asStr.value)
|
||||
}
|
||||
|
||||
override suspend fun getAt(context: Context, index: Int): Obj {
|
||||
return ObjChar(value[index])
|
||||
}
|
||||
|
||||
override suspend fun contains(context: Context, other: Obj): Boolean {
|
||||
return if (other is ObjString)
|
||||
value.contains(other.value)
|
||||
else if (other is ObjChar)
|
||||
value.contains(other.value)
|
||||
else context.raiseArgumentError("String.contains can't take $other")
|
||||
}
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("String").apply {
|
||||
addConst("startsWith",
|
||||
statement {
|
||||
ObjBool(thisAs<ObjString>().value.startsWith(requiredArg<ObjString>(0).value))
|
||||
})
|
||||
addConst("length",
|
||||
statement { ObjInt(thisAs<ObjString>().value.length.toLong()) }
|
||||
)
|
||||
addFn("takeLast") {
|
||||
thisAs<ObjString>().value.takeLast(
|
||||
requiredArg<ObjInt>(0).toInt()
|
||||
).let(::ObjString)
|
||||
}
|
||||
addFn("take") {
|
||||
thisAs<ObjString>().value.take(
|
||||
requiredArg<ObjInt>(0).toInt()
|
||||
).let(::ObjString)
|
||||
}
|
||||
addFn("drop") {
|
||||
thisAs<ObjString>().value.drop(
|
||||
requiredArg<ObjInt>(0).toInt()
|
||||
).let(::ObjString)
|
||||
}
|
||||
addFn("dropLast") {
|
||||
thisAs<ObjString>().value.dropLast(
|
||||
requiredArg<ObjInt>(0).toInt()
|
||||
).let(::ObjString)
|
||||
}
|
||||
addFn("lower") {
|
||||
thisAs<ObjString>().value.lowercase().let(::ObjString)
|
||||
}
|
||||
addFn("upper") {
|
||||
thisAs<ObjString>().value.uppercase().let(::ObjString)
|
||||
}
|
||||
addFn("size") { ObjInt(thisAs<ObjString>().value.length.toLong()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,461 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
val digitsSet = ('0'..'9').toSet()
|
||||
val digits = { d: Char -> d in digitsSet }
|
||||
val hexDigits = digitsSet + ('a'..'f') + ('A'..'F')
|
||||
val idNextChars = { d: Char -> d.isLetter() || d == '_' || d.isDigit() }
|
||||
|
||||
@Suppress("unused")
|
||||
val idFirstChars = { d: Char -> d.isLetter() || d == '_' }
|
||||
|
||||
fun parseLyng(source: Source): List<Token> {
|
||||
val p = Parser(fromPos = source.startPos)
|
||||
val tokens = mutableListOf<Token>()
|
||||
do {
|
||||
val t = p.nextToken()
|
||||
tokens += t
|
||||
} while (t.type != Token.Type.EOF)
|
||||
return tokens
|
||||
}
|
||||
|
||||
private class Parser(fromPos: Pos) {
|
||||
|
||||
private val pos = MutablePos(fromPos)
|
||||
|
||||
/**
|
||||
* Immutable copy of current position
|
||||
*/
|
||||
private val currentPos: Pos get() = pos.toPos()
|
||||
|
||||
private fun raise(msg: String): Nothing = throw ScriptError(currentPos, msg)
|
||||
|
||||
fun nextToken(): Token {
|
||||
skipws()
|
||||
if (pos.end) return Token("", currentPos, Token.Type.EOF)
|
||||
val from = currentPos
|
||||
return when (val ch = pos.currentChar.also { pos.advance() }) {
|
||||
'(' -> Token("(", from, Token.Type.LPAREN)
|
||||
')' -> Token(")", from, Token.Type.RPAREN)
|
||||
'{' -> Token("{", from, Token.Type.LBRACE)
|
||||
'}' -> Token("}", from, Token.Type.RBRACE)
|
||||
'[' -> Token("[", from, Token.Type.LBRACKET)
|
||||
']' -> Token("]", from, Token.Type.RBRACKET)
|
||||
',' -> Token(",", from, Token.Type.COMMA)
|
||||
';' -> Token(";", from, Token.Type.SEMICOLON)
|
||||
'=' -> {
|
||||
if (pos.currentChar == '=') {
|
||||
pos.advance()
|
||||
if (currentChar == '=') {
|
||||
pos.advance()
|
||||
Token("===", from, Token.Type.REF_EQ)
|
||||
} else
|
||||
Token("==", from, Token.Type.EQ)
|
||||
} else
|
||||
Token("=", from, Token.Type.ASSIGN)
|
||||
}
|
||||
|
||||
'+' -> {
|
||||
when (currentChar) {
|
||||
'+' -> {
|
||||
pos.advance()
|
||||
Token("+", from, Token.Type.PLUS2)
|
||||
}
|
||||
|
||||
'=' -> {
|
||||
pos.advance()
|
||||
Token("+", from, Token.Type.PLUSASSIGN)
|
||||
}
|
||||
|
||||
else ->
|
||||
Token("+", from, Token.Type.PLUS)
|
||||
}
|
||||
}
|
||||
|
||||
'-' -> {
|
||||
when (currentChar) {
|
||||
'-' -> {
|
||||
pos.advance()
|
||||
Token("--", from, Token.Type.MINUS2)
|
||||
}
|
||||
|
||||
'=' -> {
|
||||
pos.advance()
|
||||
Token("-", from, Token.Type.MINUSASSIGN)
|
||||
}
|
||||
|
||||
'>' -> {
|
||||
pos.advance()
|
||||
Token("->", from, Token.Type.ARROW)
|
||||
}
|
||||
|
||||
else -> Token("-", from, Token.Type.MINUS)
|
||||
}
|
||||
}
|
||||
|
||||
'*' -> {
|
||||
if (currentChar == '=') {
|
||||
pos.advance()
|
||||
Token("*=", from, Token.Type.STARASSIGN)
|
||||
} else
|
||||
Token("*", from, Token.Type.STAR)
|
||||
}
|
||||
|
||||
'/' -> when (currentChar) {
|
||||
'/' -> {
|
||||
pos.advance()
|
||||
Token(loadToEnd().trim(), from, Token.Type.SINLGE_LINE_COMMENT)
|
||||
}
|
||||
|
||||
'*' -> {
|
||||
pos.advance()
|
||||
Token(
|
||||
loadTo("*/")?.trim()
|
||||
?: throw ScriptError(from, "Unterminated multiline comment"),
|
||||
from,
|
||||
Token.Type.MULTILINE_COMMENT
|
||||
)
|
||||
}
|
||||
|
||||
'=' -> {
|
||||
pos.advance()
|
||||
Token("/=", from, Token.Type.SLASHASSIGN)
|
||||
}
|
||||
|
||||
else -> Token("/", from, Token.Type.SLASH)
|
||||
}
|
||||
|
||||
'%' -> when (currentChar) {
|
||||
'=' -> {
|
||||
pos.advance(); Token("%=", from, Token.Type.PERCENTASSIGN)
|
||||
}
|
||||
|
||||
else -> Token("%", from, Token.Type.PERCENT)
|
||||
}
|
||||
|
||||
'.' -> {
|
||||
// could be: dot, range .. or ..<, or ellipsis ...:
|
||||
if (currentChar == '.') {
|
||||
pos.advance()
|
||||
// .. already parsed:
|
||||
when (currentChar) {
|
||||
'.' -> {
|
||||
pos.advance()
|
||||
Token("...", from, Token.Type.ELLIPSIS)
|
||||
}
|
||||
|
||||
'<' -> {
|
||||
pos.advance()
|
||||
Token("..<", from, Token.Type.DOTDOTLT)
|
||||
}
|
||||
|
||||
else -> {
|
||||
Token("..", from, Token.Type.DOTDOT)
|
||||
}
|
||||
}
|
||||
} else
|
||||
Token(".", from, Token.Type.DOT)
|
||||
}
|
||||
|
||||
'<' -> {
|
||||
if (currentChar == '=') {
|
||||
pos.advance()
|
||||
if (currentChar == '>') {
|
||||
pos.advance()
|
||||
Token("<=>", from, Token.Type.SHUTTLE)
|
||||
} else {
|
||||
Token("<=", from, Token.Type.LTE)
|
||||
}
|
||||
} else
|
||||
Token("<", from, Token.Type.LT)
|
||||
}
|
||||
|
||||
'>' -> {
|
||||
if (currentChar == '=') {
|
||||
pos.advance()
|
||||
Token(">=", from, Token.Type.GTE)
|
||||
} else
|
||||
Token(">", from, Token.Type.GT)
|
||||
}
|
||||
|
||||
'!' -> {
|
||||
if (currentChar == 'i') {
|
||||
pos.advance()
|
||||
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 == '=') {
|
||||
pos.advance()
|
||||
if (currentChar == '=') {
|
||||
pos.advance()
|
||||
Token("!==", from, Token.Type.REF_NEQ)
|
||||
} else
|
||||
Token("!=", from, Token.Type.NEQ)
|
||||
} else
|
||||
Token("!", from, Token.Type.NOT)
|
||||
}
|
||||
|
||||
'|' -> {
|
||||
if (currentChar == '|') {
|
||||
pos.advance()
|
||||
Token("||", from, Token.Type.OR)
|
||||
} else
|
||||
Token("|", from, Token.Type.BITOR)
|
||||
}
|
||||
|
||||
'&' -> {
|
||||
if (currentChar == '&') {
|
||||
pos.advance()
|
||||
Token("&&", from, Token.Type.AND)
|
||||
} else
|
||||
Token("&", from, Token.Type.BITAND)
|
||||
}
|
||||
|
||||
'@' -> {
|
||||
val label = loadChars(idNextChars)
|
||||
if (label.isNotEmpty()) Token(label, from, Token.Type.ATLABEL)
|
||||
else raise("unexpected @ character")
|
||||
}
|
||||
|
||||
'\n' -> Token("\n", from, Token.Type.NEWLINE)
|
||||
|
||||
':' -> {
|
||||
if (currentChar == ':') {
|
||||
pos.advance()
|
||||
Token("::", from, Token.Type.COLONCOLON)
|
||||
} else
|
||||
Token(":", from, Token.Type.COLON)
|
||||
}
|
||||
|
||||
'"' -> loadStringToken()
|
||||
|
||||
in digitsSet -> {
|
||||
pos.back()
|
||||
decodeNumber(loadChars(digits), from)
|
||||
}
|
||||
|
||||
'\'' -> {
|
||||
val start = pos.toPos()
|
||||
var value = currentChar
|
||||
pos.advance()
|
||||
if (currentChar == '\\') {
|
||||
value = currentChar
|
||||
pos.advance()
|
||||
value = when (value) {
|
||||
'n' -> '\n'
|
||||
'r' -> '\r'
|
||||
't' -> '\t'
|
||||
'\'', '\\' -> value
|
||||
else -> throw ScriptError(currentPos, "unsupported escape character: $value")
|
||||
}
|
||||
}
|
||||
if (currentChar != '\'') throw ScriptError(currentPos, "expected end of character literal: '")
|
||||
pos.advance()
|
||||
Token(value.toString(), start, Token.Type.CHAR)
|
||||
}
|
||||
|
||||
else -> {
|
||||
// text infix operators:
|
||||
// Labels processing is complicated!
|
||||
// some@ statement: label 'some', ID 'statement'
|
||||
// statement@some: ID 'statement', LABEL 'some'!
|
||||
if (ch.isLetter() || ch == '_') {
|
||||
val text = ch + loadChars(idNextChars)
|
||||
if (currentChar == '@') {
|
||||
pos.advance()
|
||||
if (currentChar.isLetter()) {
|
||||
// break@label or like
|
||||
pos.back()
|
||||
Token(text, from, Token.Type.ID)
|
||||
} else
|
||||
Token(text, from, Token.Type.LABEL)
|
||||
} 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
|
||||
raise("can't parse token")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun decodeNumber(p1: String, start: Pos): Token =
|
||||
if (pos.end)
|
||||
Token(p1, start, Token.Type.INT)
|
||||
else if (currentChar == 'e' || currentChar == 'E') {
|
||||
pos.advance()
|
||||
var negative = false
|
||||
if (currentChar == '+')
|
||||
pos.advance()
|
||||
else if (currentChar == '-') {
|
||||
negative = true
|
||||
pos.advance()
|
||||
}
|
||||
var p3 = loadChars(digits)
|
||||
if (negative) p3 = "-$p3"
|
||||
Token("${p1}e$p3", start, Token.Type.REAL)
|
||||
} else if (currentChar == '.') {
|
||||
// could be decimal
|
||||
pos.advance()
|
||||
if (currentChar in digitsSet) {
|
||||
// decimal part
|
||||
val p2 = loadChars(digits)
|
||||
// with exponent?
|
||||
if (currentChar == 'e' || currentChar == 'E') {
|
||||
pos.advance()
|
||||
var negative = false
|
||||
if (currentChar == '+')
|
||||
pos.advance()
|
||||
else if (currentChar == '-') {
|
||||
negative = true
|
||||
pos.advance()
|
||||
}
|
||||
var p3 = loadChars(digits)
|
||||
if (negative) p3 = "-$p3"
|
||||
Token("$p1.${p2}e$p3", start, Token.Type.REAL)
|
||||
} else {
|
||||
// no exponent
|
||||
Token("$p1.$p2", start, Token.Type.REAL)
|
||||
}
|
||||
} else {
|
||||
// not decimal
|
||||
// something like 10.times, method call on integer number
|
||||
pos.back()
|
||||
Token(p1, start, Token.Type.INT)
|
||||
}
|
||||
} else {
|
||||
// could be integer, also hex:
|
||||
if (currentChar == 'x' && p1 == "0") {
|
||||
pos.advance()
|
||||
Token(loadChars { it in hexDigits }, start, Token.Type.HEX).also {
|
||||
if (currentChar.isLetter())
|
||||
raise("invalid hex literal")
|
||||
}
|
||||
} else {
|
||||
Token(p1, start, Token.Type.INT)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private val currentChar: Char get() = pos.currentChar
|
||||
|
||||
private fun loadStringToken(): Token {
|
||||
val start = currentPos
|
||||
|
||||
// if (currentChar == '"') pos.advance()
|
||||
// else start = start.back()
|
||||
// start = start.back()
|
||||
|
||||
val sb = StringBuilder()
|
||||
while (currentChar != '"') {
|
||||
if (pos.end) throw ScriptError(start, "unterminated string started there")
|
||||
when (currentChar) {
|
||||
'\\' -> {
|
||||
pos.advance() ?: raise("unterminated string")
|
||||
when (currentChar) {
|
||||
'n' -> sb.append('\n')
|
||||
'r' -> sb.append('\r')
|
||||
't' -> sb.append('\t')
|
||||
'"' -> sb.append('"')
|
||||
else -> sb.append('\\').append(currentChar)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
sb.append(currentChar)
|
||||
pos.advance()
|
||||
}
|
||||
}
|
||||
}
|
||||
pos.advance()
|
||||
return Token(sb.toString(), start, Token.Type.STRING)
|
||||
}
|
||||
|
||||
/**
|
||||
* Load characters from the set until it reaches EOF or invalid character found.
|
||||
* stop at EOF on character filtered by [isValidChar].
|
||||
*
|
||||
* Note this function loads only on one string. Multiline texts are not supported by
|
||||
* this method.
|
||||
*
|
||||
* @return the string of valid characters, could be empty
|
||||
*/
|
||||
private fun loadChars(isValidChar: (Char) -> Boolean): String {
|
||||
val startLine = pos.line
|
||||
val result = StringBuilder()
|
||||
while (!pos.end && pos.line == startLine) {
|
||||
val ch = pos.currentChar
|
||||
if (isValidChar(ch)) {
|
||||
result.append(ch)
|
||||
pos.advance()
|
||||
} else
|
||||
break
|
||||
}
|
||||
return result.toString()
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
private fun loadUntil(endChars: Set<Char>): String {
|
||||
return if (pos.end) ""
|
||||
else {
|
||||
val result = StringBuilder()
|
||||
while (!pos.end) {
|
||||
val ch = pos.currentChar
|
||||
if (ch in endChars) break
|
||||
result.append(ch)
|
||||
pos.advance()
|
||||
}
|
||||
result.toString()
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadToEnd(): String {
|
||||
val result = StringBuilder()
|
||||
val l = pos.line
|
||||
do {
|
||||
result.append(pos.currentChar)
|
||||
pos.advance()
|
||||
} while (pos.line == l)
|
||||
return result.toString()
|
||||
}
|
||||
|
||||
private fun loadTo(str: String): String? {
|
||||
val result = StringBuilder()
|
||||
while (!pos.readFragment(str)) {
|
||||
if (pos.end) return null
|
||||
result.append(pos.currentChar); pos.advance()
|
||||
}
|
||||
return result.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* next non-whitespace char (newline are skipped too) or null if EOF
|
||||
*/
|
||||
private fun skipws(): Char? {
|
||||
while (!pos.end) {
|
||||
val ch = pos.currentChar
|
||||
if (ch == '\n') break
|
||||
if (ch.isWhitespace())
|
||||
pos.advance()
|
||||
else
|
||||
return ch
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
data class Pos(val source: Source, val line: Int, val column: Int) {
|
||||
override fun toString(): String {
|
||||
return "${source.fileName}:${line+1}:${column}"
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun back(): Pos =
|
||||
if( column > 0) Pos(source, line, column-1)
|
||||
else if( line > 0) Pos(source, line-1, source.lines[line-1].length - 1)
|
||||
else throw IllegalStateException("can't go back from line 0, column 0")
|
||||
|
||||
val currentLine: String get() = if( end ) "EOF" else source.lines[line]
|
||||
|
||||
val end: Boolean get() = line >= source.lines.size
|
||||
|
||||
companion object {
|
||||
val builtIn = Pos(Source.builtIn, 0, 0)
|
||||
val UNKNOWN = Pos(Source.UNKNOWN, -1, -1)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MutablePos(private val from: Pos) {
|
||||
|
||||
val lines = from.source.lines
|
||||
var line = from.line
|
||||
private set
|
||||
var column = from.column
|
||||
private set
|
||||
|
||||
val end: Boolean get() =
|
||||
line == lines.size
|
||||
|
||||
fun toPos(): Pos = Pos(from.source, line, column)
|
||||
|
||||
fun advance(): Char? {
|
||||
if (end) return null
|
||||
val current = lines[line]
|
||||
return if (column < current.length) {
|
||||
column++
|
||||
currentChar
|
||||
} else {
|
||||
column = 0
|
||||
if(++line >= lines.size) null
|
||||
else currentChar
|
||||
}
|
||||
}
|
||||
|
||||
fun resetTo(pos: Pos) { line = pos.line; column = pos.column }
|
||||
|
||||
fun back() {
|
||||
if (column > 0) column--
|
||||
else if (line > 0)
|
||||
column = lines[--line].length - 1
|
||||
else throw IllegalStateException("can't go back from line 0, column 0")
|
||||
}
|
||||
|
||||
val currentChar: Char
|
||||
get() {
|
||||
if (end) return 0.toChar()
|
||||
val current = lines[line]
|
||||
return if (column >= current.length) '\n'
|
||||
else current[column]
|
||||
}
|
||||
|
||||
/**
|
||||
* If the next characters are equal to the fragment, advances the position and returns true.
|
||||
* Otherwise, does nothing and returns false.
|
||||
*/
|
||||
fun readFragment(fragment: String): Boolean {
|
||||
val mark = toPos()
|
||||
for(ch in fragment)
|
||||
if( currentChar != ch ) { resetTo(mark); return false }
|
||||
else advance()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun toString() = "($line:$column)"
|
||||
|
||||
init {
|
||||
if( lines[0].isEmpty()) advance()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlin.math.*
|
||||
|
||||
class Script(
|
||||
override val pos: Pos,
|
||||
private val statements: List<Statement> = emptyList(),
|
||||
// private val catchReturn: Boolean = false,
|
||||
) : Statement() {
|
||||
|
||||
override suspend fun execute(context: Context): Obj {
|
||||
var lastResult: Obj = ObjVoid
|
||||
for (s in statements) {
|
||||
lastResult = s.execute(context)
|
||||
}
|
||||
return lastResult
|
||||
}
|
||||
|
||||
suspend fun execute() = execute(defaultContext.copy(pos = pos))
|
||||
|
||||
companion object {
|
||||
val defaultContext: Context = Context().apply {
|
||||
ObjException.addExceptionsToContext(this)
|
||||
addFn("println") {
|
||||
for ((i, a) in args.withIndex()) {
|
||||
if (i > 0) print(' ' + a.asStr.value)
|
||||
else print(a.asStr.value)
|
||||
}
|
||||
println()
|
||||
ObjVoid
|
||||
}
|
||||
addFn("floor") {
|
||||
val x = args.firstAndOnly()
|
||||
(if (x is ObjInt) x
|
||||
else ObjReal(floor(x.toDouble())))
|
||||
}
|
||||
addFn("ceil") {
|
||||
val x = args.firstAndOnly()
|
||||
(if (x is ObjInt) x
|
||||
else ObjReal(ceil(x.toDouble())))
|
||||
}
|
||||
addFn("round") {
|
||||
val x = args.firstAndOnly()
|
||||
(if (x is ObjInt) x
|
||||
else ObjReal(round(x.toDouble())))
|
||||
}
|
||||
|
||||
addFn("sin") {
|
||||
ObjReal(sin(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("cos") {
|
||||
ObjReal(cos(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("tan") {
|
||||
ObjReal(tan(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("asin") {
|
||||
ObjReal(asin(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("acos") {
|
||||
ObjReal(acos(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("atan") {
|
||||
ObjReal(atan(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
|
||||
addFn("sinh") {
|
||||
ObjReal(sinh(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("cosh") {
|
||||
ObjReal(cosh(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("tanh") {
|
||||
ObjReal(tanh(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("asinh") {
|
||||
ObjReal(asinh(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("acosh") {
|
||||
ObjReal(acosh(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("atanh") {
|
||||
ObjReal(atanh(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
|
||||
addFn("exp") {
|
||||
ObjReal(exp(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("ln") {
|
||||
ObjReal(ln(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
|
||||
addFn("log10") {
|
||||
ObjReal(log10(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
|
||||
addFn("log2") {
|
||||
ObjReal(log2(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
|
||||
addFn("pow") {
|
||||
requireExactCount(2)
|
||||
ObjReal(
|
||||
(args[0].toDouble()).pow(args[1].toDouble())
|
||||
)
|
||||
}
|
||||
addFn("sqrt") {
|
||||
ObjReal(
|
||||
sqrt(args.firstAndOnly().toDouble())
|
||||
)
|
||||
}
|
||||
addFn( "abs" ) {
|
||||
val x = args.firstAndOnly()
|
||||
if( x is ObjInt ) ObjInt( x.value.absoluteValue ) else ObjReal( x.toDouble().absoluteValue )
|
||||
}
|
||||
|
||||
addVoidFn("assert") {
|
||||
val cond = requiredArg<ObjBool>(0)
|
||||
if( !cond.value == true )
|
||||
raiseError(ObjAssertionFailedException(this,"Assertion failed"))
|
||||
}
|
||||
|
||||
addVoidFn("assertEquals") {
|
||||
val a = requiredArg<Obj>(0)
|
||||
val b = requiredArg<Obj>(1)
|
||||
if( a.compareTo(this, b) != 0 )
|
||||
raiseError(ObjAssertionFailedException(this,"Assertion failed: ${a.inspect()} == ${b.inspect()}"))
|
||||
}
|
||||
addFn("assertThrows") {
|
||||
val code = requireOnlyArg<Statement>()
|
||||
val result =try {
|
||||
code.execute(this)
|
||||
null
|
||||
}
|
||||
catch( e: ExecutionError ) {
|
||||
e.errorObject
|
||||
}
|
||||
catch (e: ScriptError) {
|
||||
ObjNull
|
||||
}
|
||||
result ?: raiseError(ObjAssertionFailedException(this,"Expected exception but nothing was thrown"))
|
||||
}
|
||||
|
||||
addVoidFn("delay") {
|
||||
delay((this.args.firstAndOnly().toDouble()/1000.0).roundToLong())
|
||||
}
|
||||
|
||||
addConst("Real", ObjReal.type)
|
||||
addConst("String", ObjString.type)
|
||||
addConst("Int", ObjInt.type)
|
||||
addConst("Bool", ObjBool.type)
|
||||
addConst("Char", ObjChar.type)
|
||||
addConst("List", ObjList.type)
|
||||
addConst("Range", ObjRange.type)
|
||||
@Suppress("RemoveRedundantQualifierName")
|
||||
addConst("Callable", Statement.type)
|
||||
// interfaces
|
||||
addConst("Iterable", ObjIterable)
|
||||
addConst("Array", ObjArray)
|
||||
addConst("Class", ObjClassType)
|
||||
addConst("Object", Obj().objClass)
|
||||
|
||||
val pi = ObjReal(PI)
|
||||
addConst("π", pi)
|
||||
getOrCreateNamespace("Math").apply {
|
||||
addConst("PI", pi)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
@file:Suppress("CanBeParameter")
|
||||
|
||||
package net.sergeych.lyng
|
||||
|
||||
open class ScriptError(val pos: Pos, val errorMessage: String,cause: Throwable?=null) : Exception(
|
||||
"""
|
||||
$pos: Error: $errorMessage
|
||||
|
||||
${pos.currentLine}
|
||||
${"-".repeat(pos.column)}^
|
||||
""".trimIndent(),
|
||||
cause
|
||||
)
|
||||
|
||||
class ExecutionError(val errorObject: ObjException) : ScriptError(errorObject.context.pos, errorObject.message)
|
||||
@@ -0,0 +1,15 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
class Source(val fileName: String, text: String) {
|
||||
|
||||
val lines = text.lines().map { it.trimEnd() }
|
||||
|
||||
companion object {
|
||||
val builtIn: Source by lazy { Source("built-in", "") }
|
||||
val UNKNOWN: Source by lazy { Source("UNKNOWN", "") }
|
||||
}
|
||||
|
||||
val startPos: Pos = Pos(this, 0, 0)
|
||||
|
||||
fun posAt(line: Int, column: Int): Pos = Pos(this, line, column)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
data class Token(val value: String, val pos: Pos, val type: Type) {
|
||||
val isComment: Boolean by lazy { type == Type.SINLGE_LINE_COMMENT || type == Type.MULTILINE_COMMENT }
|
||||
|
||||
@Suppress("unused")
|
||||
enum class Type {
|
||||
ID, INT, REAL, HEX, STRING, CHAR,
|
||||
LPAREN, RPAREN, LBRACE, RBRACE, LBRACKET, RBRACKET, COMMA,
|
||||
SEMICOLON, COLON,
|
||||
PLUS, MINUS, STAR, SLASH, PERCENT,
|
||||
ASSIGN, PLUSASSIGN, MINUSASSIGN, STARASSIGN, SLASHASSIGN, PERCENTASSIGN,
|
||||
PLUS2, MINUS2,
|
||||
IN, NOTIN, IS, NOTIS,
|
||||
EQ, NEQ, LT, LTE, GT, GTE, REF_EQ, REF_NEQ,
|
||||
SHUTTLE,
|
||||
AND, BITAND, OR, BITOR, NOT, BITNOT, DOT, ARROW, QUESTION, COLONCOLON,
|
||||
SINLGE_LINE_COMMENT, MULTILINE_COMMENT,
|
||||
LABEL, ATLABEL, // label@ at@label
|
||||
//PUBLIC, PROTECTED, INTERNAL, EXPORT, OPEN, INLINE, OVERRIDE, ABSTRACT, SEALED, EXTERNAL, VAL, VAR, CONST, TYPE, FUN, CLASS, INTERFACE, ENUM, OBJECT, TRAIT, THIS,
|
||||
ELLIPSIS, DOTDOT, DOTDOTLT,
|
||||
NEWLINE,
|
||||
EOF,
|
||||
}
|
||||
|
||||
companion object {
|
||||
// fun eof(parser: Parser) = Token("", parser.currentPos, Type.EOF)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
// this is highly experimental and subject to complete redesign
|
||||
// very soon
|
||||
sealed class TypeDecl {
|
||||
// ??
|
||||
// data class Fn(val argTypes: List<ArgsDeclaration.Item>, val retType: TypeDecl) : TypeDecl()
|
||||
object Obj : TypeDecl()
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
enum class Visibility {
|
||||
Public, Private, Protected;//, Internal
|
||||
val isPublic by lazy { this == Public }
|
||||
@Suppress("unused")
|
||||
val isProtected by lazy { this == Protected }
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
|
||||
val LyngVersion = BuildKonfig.version
|
||||
@@ -0,0 +1,60 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
fun String.toSource(name: String = "eval"): Source = Source(name, this)
|
||||
|
||||
sealed class ObjType {
|
||||
object Any : ObjType()
|
||||
object Int : ObjType()
|
||||
}
|
||||
|
||||
|
||||
@Suppress("unused")
|
||||
abstract class Statement(
|
||||
val isStaticConst: Boolean = false,
|
||||
val isConst: Boolean = false,
|
||||
val returnType: ObjType = ObjType.Any
|
||||
) : Obj() {
|
||||
|
||||
override val objClass: ObjClass = type
|
||||
|
||||
abstract val pos: Pos
|
||||
abstract suspend fun execute(context: Context): Obj
|
||||
|
||||
override suspend fun compareTo(context: Context,other: Obj): Int {
|
||||
throw UnsupportedOperationException("not comparable")
|
||||
}
|
||||
|
||||
override suspend fun callOn(context: Context): Obj {
|
||||
return execute(context)
|
||||
}
|
||||
|
||||
override fun toString(): String = "Callable@${this.hashCode()}"
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("Callable")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun Statement.raise(text: String): Nothing {
|
||||
throw ScriptError(pos, text)
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun Statement.require(cond: Boolean, message: () -> String) {
|
||||
if (!cond) raise(message())
|
||||
}
|
||||
|
||||
fun statement(pos: Pos, isStaticConst: Boolean = false, isConst: Boolean = false, f: suspend (Context) -> Obj): Statement =
|
||||
object : Statement(isStaticConst, isConst) {
|
||||
override val pos: Pos = pos
|
||||
override suspend fun execute(context: Context): Obj = f(context)
|
||||
}
|
||||
|
||||
fun statement(isStaticConst: Boolean = false, isConst: Boolean = false, f: suspend Context.() -> Obj): Statement =
|
||||
object : Statement(isStaticConst, isConst) {
|
||||
override val pos: Pos = Pos.builtIn
|
||||
override suspend fun execute(context: Context): Obj = f(context)
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user