Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
868379ce22 | ||
|
|
f37354f382 | ||
|
|
ddbcbf9e4e | ||
|
|
6cf99fbd13 | ||
|
|
950e8301c3 | ||
|
|
ea2cf1f373 | ||
|
|
eee6d75587 | ||
|
|
a8067d0a6b | ||
|
|
75a6f20150 | ||
|
|
d969d6d572 |
No files matched your search
@@ -75,17 +75,19 @@ runBlocking {
|
|||||||
|
|
||||||
### Exchanging information
|
### Exchanging information
|
||||||
|
|
||||||
Script is executed over some `Context`. Create instance of the context,
|
Script is executed over some `Scope`. Create instance,
|
||||||
add your specific vars and functions to it, an call over it:
|
add your specific vars and functions to it, and call:
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
|
|
||||||
|
import com.sun.source.tree.Scope
|
||||||
import new.sergeych.lyng.*
|
import new.sergeych.lyng.*
|
||||||
|
|
||||||
// simple function
|
// simple function
|
||||||
val context = Context().apply {
|
val scope = Scope().apply {
|
||||||
addFn("addArgs") {
|
addFn("addArgs") {
|
||||||
var sum = 0.0
|
var sum = 0.0
|
||||||
for( a in args) sum += a.toDouble()
|
for (a in args) sum += a.toDouble()
|
||||||
ObjReal(sum)
|
ObjReal(sum)
|
||||||
}
|
}
|
||||||
addConst("LIGHT_SPEED", ObjReal(299_792_458.0))
|
addConst("LIGHT_SPEED", ObjReal(299_792_458.0))
|
||||||
@@ -99,9 +101,9 @@ val context = Context().apply {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// adding constant:
|
// adding constant:
|
||||||
context.eval("addArgs(1,2,3)") // <- 6
|
scope.eval("addArgs(1,2,3)") // <- 6
|
||||||
```
|
```
|
||||||
Note that the context stores all changes in it so you can make calls on a single context to preserve state between calls.
|
Note that the scope stores all changes in it so you can make calls on a single scope to preserve state between calls.
|
||||||
|
|
||||||
## Why?
|
## Why?
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,20 @@ Having `Iterable` in base classes allows to use it in for loop. Also, each `Iter
|
|||||||
|
|
||||||
## Instance methods
|
## Instance methods
|
||||||
|
|
||||||
|
| fun/method | description |
|
||||||
|
|-----------------|---------------------------------------------------------------------------------|
|
||||||
|
| toList() | create a list from iterable |
|
||||||
|
| toSet() | create a set from iterable |
|
||||||
|
| contains(i) | check that iterable contains `i` |
|
||||||
|
| `i in iterator` | same as `contains(i)` |
|
||||||
|
| isEmpty() | check iterable is empty |
|
||||||
|
| forEach(f) | call f for each element |
|
||||||
|
| toMap() | create a map from list of key-value pairs (arrays of 2 items or like) |
|
||||||
|
| map(f) | create a list of values returned by `f` called for each element of the iterable |
|
||||||
|
| indexOf(i) | return index if the first encounter of i or a negative value if not found |
|
||||||
|
| associateBy(kf) | create a map where keys are returned by kf that will be called for each element |
|
||||||
|
|
||||||
|
|
||||||
fun Iterable.toList(): List
|
fun Iterable.toList(): List
|
||||||
fun Iterable.toSet(): Set
|
fun Iterable.toSet(): Set
|
||||||
fun Iterable.indexOf(element): Int
|
fun Iterable.indexOf(element): Int
|
||||||
@@ -28,6 +42,7 @@ Having `Iterable` in base classes allows to use it in for loop. Also, each `Iter
|
|||||||
fun Iterable.isEmpty(element): Bool
|
fun Iterable.isEmpty(element): Bool
|
||||||
fun Iterable.forEach(block: (Any?)->Void ): Void
|
fun Iterable.forEach(block: (Any?)->Void ): Void
|
||||||
fun Iterable.map(block: (Any?)->Void ): List
|
fun Iterable.map(block: (Any?)->Void ): List
|
||||||
|
fun Iterable.associateBy( keyMaker: (Any?)->Any): Map
|
||||||
|
|
||||||
|
|
||||||
## Abstract methods
|
## Abstract methods
|
||||||
|
|||||||
+34
@@ -160,6 +160,40 @@ For example, for our class Point:
|
|||||||
Point(1,1+1)
|
Point(1,1+1)
|
||||||
>>> Point(x=1,y=2)
|
>>> Point(x=1,y=2)
|
||||||
|
|
||||||
|
# Extending classes
|
||||||
|
|
||||||
|
It sometimes happen that the class is missing some particular functionality that can be _added to it_ without rewriting its inner logic and using its private state. In this case _extension methods_ could be used, for example. we want to create an extension method
|
||||||
|
that would test if some object of unknown type contains something that can be interpreted
|
||||||
|
as an integer. In this case we _extend_ class `Object`, as it is the parent class for any instance of any type:
|
||||||
|
|
||||||
|
fun Object.isInteger() {
|
||||||
|
when(this) {
|
||||||
|
// already Int?
|
||||||
|
is Int -> true
|
||||||
|
|
||||||
|
// real, but with no declimal part?
|
||||||
|
is Real -> toInt() == this
|
||||||
|
|
||||||
|
// string with int or real reuusig code above
|
||||||
|
is String -> toReal().isInteger()
|
||||||
|
|
||||||
|
// otherwise, no:
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Let's test:
|
||||||
|
assert( 12.isInteger() == true )
|
||||||
|
assert( 12.1.isInteger() == false )
|
||||||
|
assert( "5".isInteger() )
|
||||||
|
assert( ! "5.2".isInteger() )
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
__Important note__ as for version 0.6.9, extensions are in __global scope__. It means, that once applied to a global type (Int in our sample), they will be available for _all_ contexts, even new created,
|
||||||
|
as they are modifying the type, not the context.
|
||||||
|
|
||||||
|
Beware of it. We might need to reconsider it later.
|
||||||
|
|
||||||
# Theory
|
# Theory
|
||||||
|
|
||||||
## Basic principles:
|
## Basic principles:
|
||||||
|
|||||||
+8
-8
@@ -15,11 +15,11 @@ you can use it's class to ensure type:
|
|||||||
|
|
||||||
## Member functions
|
## Member functions
|
||||||
|
|
||||||
| name | meaning | type |
|
| name | meaning | type |
|
||||||
|-----------------|------------------------------------|------|
|
|-----------------|-------------------------------------------------------------|------|
|
||||||
| `.roundToInt()` | round to nearest int like round(x) | Int |
|
| `.roundToInt()` | round to nearest int like round(x) | Int |
|
||||||
| | | |
|
| `.toInt()` | convert integer part of real to `Int` dropping decimal part | Int |
|
||||||
| | | |
|
| | | |
|
||||||
| | | |
|
| | | |
|
||||||
| | | |
|
| | | |
|
||||||
| | | |
|
| | | |
|
||||||
@@ -1214,6 +1214,9 @@ Typical set of String functions includes:
|
|||||||
| [Range] | substring at range |
|
| [Range] | substring at range |
|
||||||
| s1 + s2 | concatenation |
|
| s1 + s2 | concatenation |
|
||||||
| s1 += s2 | self-modifying concatenation |
|
| s1 += s2 | self-modifying concatenation |
|
||||||
|
| toReal() | attempts to parse string as a Real value |
|
||||||
|
| toInt() | parse string to Int value |
|
||||||
|
| | |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ data class CommandResult(
|
|||||||
val error: String
|
val error: String
|
||||||
)
|
)
|
||||||
|
|
||||||
val baseContext = Context().apply {
|
val baseScope = Scope().apply {
|
||||||
addFn("exit") {
|
addFn("exit") {
|
||||||
exit(requireOnlyArg<ObjInt>().toInt())
|
exit(requireOnlyArg<ObjInt>().toInt())
|
||||||
ObjVoid
|
ObjVoid
|
||||||
@@ -74,7 +74,7 @@ class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand() {
|
|||||||
val objargs = mutableListOf<String>()
|
val objargs = mutableListOf<String>()
|
||||||
script?.let { objargs += it }
|
script?.let { objargs += it }
|
||||||
objargs += args
|
objargs += args
|
||||||
baseContext.addConst(
|
baseScope.addConst(
|
||||||
"ARGV", ObjList(
|
"ARGV", ObjList(
|
||||||
objargs.map { ObjString(it) }.toMutableList()
|
objargs.map { ObjString(it) }.toMutableList()
|
||||||
)
|
)
|
||||||
@@ -82,7 +82,7 @@ class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand() {
|
|||||||
launcher {
|
launcher {
|
||||||
// there is no script name, it is a first argument instead:
|
// there is no script name, it is a first argument instead:
|
||||||
processErrors {
|
processErrors {
|
||||||
baseContext.eval(execute!!)
|
baseScope.eval(execute!!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,7 +98,7 @@ class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand() {
|
|||||||
)
|
)
|
||||||
echoFormattedHelp()
|
echoFormattedHelp()
|
||||||
} else {
|
} else {
|
||||||
baseContext.addConst("ARGV", ObjList(args.map { ObjString(it) }.toMutableList()))
|
baseScope.addConst("ARGV", ObjList(args.map { ObjString(it) }.toMutableList()))
|
||||||
launcher { executeFile(script!!) }
|
launcher { executeFile(script!!) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,7 +118,7 @@ suspend fun executeFile(fileName: String) {
|
|||||||
text = text.substring(pos + 1)
|
text = text.substring(pos + 1)
|
||||||
}
|
}
|
||||||
processErrors {
|
processErrors {
|
||||||
Compiler().compile(Source(fileName, text)).execute(baseContext)
|
Compiler.compile(Source(fileName, text)).execute(baseScope)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
|||||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
group = "net.sergeych"
|
group = "net.sergeych"
|
||||||
version = "0.6.7-SNAPSHOT"
|
version = "0.7.0-SNAPSHOT"
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Special version of the [Scope] used to `apply` new this object to
|
||||||
|
* _parent context property.
|
||||||
|
*
|
||||||
|
* @param _parent context to apply to
|
||||||
|
* @param args arguments for the new context
|
||||||
|
* @param appliedScope the new context to apply, it will have lower priority except for `this` which
|
||||||
|
* will be reset by appliedContext's `this`.
|
||||||
|
*/
|
||||||
|
class AppliedScope(_parent: Scope, args: Arguments, val appliedScope: Scope)
|
||||||
|
: Scope(_parent, args, appliedScope.pos, appliedScope.thisObj) {
|
||||||
|
override fun get(name: String): ObjRecord? =
|
||||||
|
if (name == "this") thisObj.asReadonly
|
||||||
|
else super.get(name) ?: appliedScope[name]
|
||||||
|
}
|
||||||
@@ -22,13 +22,13 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
|||||||
* parse args and create local vars in a given context
|
* parse args and create local vars in a given context
|
||||||
*/
|
*/
|
||||||
suspend fun assignToContext(
|
suspend fun assignToContext(
|
||||||
context: Context,
|
scope: Scope,
|
||||||
arguments: Arguments = context.args,
|
arguments: Arguments = scope.args,
|
||||||
defaultAccessType: AccessType = AccessType.Var,
|
defaultAccessType: AccessType = AccessType.Var,
|
||||||
defaultVisibility: Visibility = Visibility.Public
|
defaultVisibility: Visibility = Visibility.Public
|
||||||
) {
|
) {
|
||||||
fun assign(a: Item, value: Obj) {
|
fun assign(a: Item, value: Obj) {
|
||||||
context.addItem(a.name, (a.accessType ?: defaultAccessType).isMutable, value,
|
scope.addItem(a.name, (a.accessType ?: defaultAccessType).isMutable, value,
|
||||||
a.visibility ?: defaultVisibility)
|
a.visibility ?: defaultVisibility)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,11 +52,11 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
|||||||
if (a.isEllipsis) break
|
if (a.isEllipsis) break
|
||||||
val value = when {
|
val value = when {
|
||||||
i < callArgs.size -> callArgs[i]
|
i < callArgs.size -> callArgs[i]
|
||||||
a.defaultValue != null -> a.defaultValue.execute(context)
|
a.defaultValue != null -> a.defaultValue.execute(scope)
|
||||||
else -> {
|
else -> {
|
||||||
println("callArgs: ${callArgs.joinToString()}")
|
println("callArgs: ${callArgs.joinToString()}")
|
||||||
println("tailBlockMode: ${arguments.tailBlockMode}")
|
println("tailBlockMode: ${arguments.tailBlockMode}")
|
||||||
context.raiseIllegalArgument("too few arguments for the call")
|
scope.raiseIllegalArgument("too few arguments for the call")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
assign(a, value)
|
assign(a, value)
|
||||||
@@ -76,8 +76,8 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
|||||||
callArgs[j--]
|
callArgs[j--]
|
||||||
}
|
}
|
||||||
|
|
||||||
a.defaultValue != null -> a.defaultValue.execute(context)
|
a.defaultValue != null -> a.defaultValue.execute(scope)
|
||||||
else -> context.raiseIllegalArgument("too few arguments for the call")
|
else -> scope.raiseIllegalArgument("too few arguments for the call")
|
||||||
}
|
}
|
||||||
assign(a, value)
|
assign(a, value)
|
||||||
i--
|
i--
|
||||||
@@ -98,7 +98,7 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
|||||||
processEllipsis(leftIndex, end)
|
processEllipsis(leftIndex, end)
|
||||||
} else {
|
} else {
|
||||||
if (leftIndex < callArgs.size)
|
if (leftIndex < callArgs.size)
|
||||||
context.raiseIllegalArgument("too many arguments for the call")
|
scope.raiseIllegalArgument("too many arguments for the call")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
|||||||
*/
|
*/
|
||||||
data class Item(
|
data class Item(
|
||||||
val name: String,
|
val name: String,
|
||||||
val type: TypeDecl = TypeDecl.Obj,
|
val type: TypeDecl = TypeDecl.TypeAny,
|
||||||
val pos: Pos = Pos.builtIn,
|
val pos: Pos = Pos.builtIn,
|
||||||
val isEllipsis: Boolean = false,
|
val isEllipsis: Boolean = false,
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ package net.sergeych.lyng
|
|||||||
|
|
||||||
data class ParsedArgument(val value: Statement, val pos: Pos, val isSplat: Boolean = false)
|
data class ParsedArgument(val value: Statement, val pos: Pos, val isSplat: Boolean = false)
|
||||||
|
|
||||||
suspend fun Collection<ParsedArgument>.toArguments(context: Context,tailBlockMode: Boolean): Arguments {
|
suspend fun Collection<ParsedArgument>.toArguments(scope: Scope, tailBlockMode: Boolean): Arguments {
|
||||||
val list = mutableListOf<Obj>()
|
val list = mutableListOf<Obj>()
|
||||||
|
|
||||||
for (x in this) {
|
for (x in this) {
|
||||||
val value = x.value.execute(context)
|
val value = x.value.execute(scope)
|
||||||
if (x.isSplat) {
|
if (x.isSplat) {
|
||||||
when {
|
when {
|
||||||
value is ObjList -> {
|
value is ObjList -> {
|
||||||
@@ -14,11 +14,11 @@ suspend fun Collection<ParsedArgument>.toArguments(context: Context,tailBlockMod
|
|||||||
}
|
}
|
||||||
|
|
||||||
value.isInstanceOf(ObjIterable) -> {
|
value.isInstanceOf(ObjIterable) -> {
|
||||||
val i = (value.invokeInstanceMethod(context, "toList") as ObjList).list
|
val i = (value.invokeInstanceMethod(scope, "toList") as ObjList).list
|
||||||
i.forEach { list.add(it) }
|
i.forEach { list.add(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> context.raiseClassCastError("expected list of objects for splat argument")
|
else -> scope.raiseClassCastError("expected list of objects for splat argument")
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
list.add(value)
|
list.add(value)
|
||||||
@@ -38,8 +38,8 @@ data class Arguments(val list: List<Obj>,val tailBlockMode: Boolean = false) : L
|
|||||||
/**
|
/**
|
||||||
* Convert to list of kotlin objects, see [Obj.toKotlin].
|
* Convert to list of kotlin objects, see [Obj.toKotlin].
|
||||||
*/
|
*/
|
||||||
suspend fun toKotlinList(context: Context): List<Any?> {
|
suspend fun toKotlinList(scope: Scope): List<Any?> {
|
||||||
return list.map { it.toKotlin(context) }
|
return list.map { it.toKotlin(scope) }
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -1,13 +1,11 @@
|
|||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
internal class CompilerContext(val tokens: List<Token>) {
|
class CompilerContext(val tokens: List<Token>) {
|
||||||
val labels = mutableSetOf<String>()
|
val labels = mutableSetOf<String>()
|
||||||
|
|
||||||
var breakFound = false
|
var breakFound = false
|
||||||
private set
|
|
||||||
|
|
||||||
var loopLevel = 0
|
var loopLevel = 0
|
||||||
private set
|
|
||||||
|
|
||||||
inline fun <T> parseLoop(f: () -> T): Pair<Boolean, T> {
|
inline fun <T> parseLoop(f: () -> T): Pair<Boolean, T> {
|
||||||
if (++loopLevel == 0) breakFound = false
|
if (++loopLevel == 0) breakFound = false
|
||||||
@@ -53,6 +51,20 @@ internal class CompilerContext(val tokens: List<Token>) {
|
|||||||
|
|
||||||
fun currentPos(): Pos = tokens[currentIndex].pos
|
fun currentPos(): Pos = tokens[currentIndex].pos
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the next token is identifier `name`, skip it and return `true`.
|
||||||
|
* else leave where it is and return `false`
|
||||||
|
*/
|
||||||
|
fun skipId(name: String): Boolean {
|
||||||
|
current().let { t ->
|
||||||
|
if( t.type == Token.Type.ID && t.value == name ) {
|
||||||
|
next()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Skips next token if its type is `tokenType`, returns `true` if so.
|
* Skips next token if its type is `tokenType`, returns `true` if so.
|
||||||
* @param errorMessage message to throw if next token is not `tokenType`
|
* @param errorMessage message to throw if next token is not `tokenType`
|
||||||
@@ -85,7 +97,7 @@ internal class CompilerContext(val tokens: List<Token>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun ifNextIs(typeId: Token.Type, f: (Token) -> Unit): Boolean {
|
inline fun ifNextIs(typeId: Token.Type, f: (Token) -> Unit): Boolean {
|
||||||
val t = next()
|
val t = next()
|
||||||
return if (t.type == typeId) {
|
return if (t.type == typeId) {
|
||||||
f(t)
|
f(t)
|
||||||
@@ -96,7 +108,6 @@ internal class CompilerContext(val tokens: List<Token>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("NOTHING_TO_INLINE")
|
|
||||||
inline fun addBreak() {
|
inline fun addBreak() {
|
||||||
breakFound = true
|
breakFound = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
import kotlinx.coroutines.Deferred
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
|
import net.sergeych.mp_tools.globalDefer
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Package manager. Chained manager, too simple. Override [createModuleScope] to return either
|
||||||
|
* valid [ModuleScope] or call [parent] - or return null.
|
||||||
|
*/
|
||||||
|
abstract class Pacman(
|
||||||
|
val parent: Pacman? = null,
|
||||||
|
val rootScope: Scope = parent!!.rootScope,
|
||||||
|
val securityManager: SecurityManager = parent!!.securityManager
|
||||||
|
) {
|
||||||
|
private val opScopes = Mutex()
|
||||||
|
|
||||||
|
private val cachedScopes = mutableMapOf<String, Scope>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new module scope if this pacman can import the module, return null otherwise so
|
||||||
|
* the manager can decide what to do
|
||||||
|
*/
|
||||||
|
abstract suspend fun createModuleScope(name: String): ModuleScope?
|
||||||
|
|
||||||
|
suspend fun prepareImport(pos: Pos, name: String, symbols: Map<String, String>?) {
|
||||||
|
if (!securityManager.canImportModule(name))
|
||||||
|
throw ImportException(pos, "Module $name is not allowed")
|
||||||
|
symbols?.keys?.forEach {
|
||||||
|
if (!securityManager.canImportSymbol(name, it)) throw ImportException(
|
||||||
|
pos,
|
||||||
|
"Symbol $name.$it is not allowed"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// if we can import the module, cache it, or go further
|
||||||
|
opScopes.withLock {
|
||||||
|
cachedScopes[name] ?: createModuleScope(name)?.let { cachedScopes[name] = it }
|
||||||
|
} ?: parent?.prepareImport(pos, name, symbols) ?: throw ImportException(pos, "Module $name is not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun performImport(scope: Scope, name: String, symbols: Map<String, String>?) {
|
||||||
|
val module = opScopes.withLock { cachedScopes[name] }
|
||||||
|
?: scope.raiseSymbolNotFound("module $name not found")
|
||||||
|
val symbolsToImport = symbols?.keys?.toMutableSet()
|
||||||
|
for ((symbol, record) in module.objects) {
|
||||||
|
if (record.visibility.isPublic) {
|
||||||
|
println("import $name: $symbol: $record")
|
||||||
|
val newName = symbols?.let { ss: Map<String, String> ->
|
||||||
|
ss[symbol]
|
||||||
|
?.also { symbolsToImport!!.remove(it) }
|
||||||
|
?: scope.raiseError("internal error: symbol $symbol not found though the module is cached")
|
||||||
|
} ?: symbol
|
||||||
|
println("import $name.$symbol as $newName")
|
||||||
|
if (newName in scope.objects)
|
||||||
|
scope.raiseError("symbol $newName already exists, redefinition on import is not allowed")
|
||||||
|
scope.objects[newName] = record
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!symbolsToImport.isNullOrEmpty())
|
||||||
|
scope.raiseSymbolNotFound("symbols $name.{$symbolsToImport} are.were not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val emptyAllowAll = object : Pacman(rootScope = Script.defaultScope, securityManager = SecurityManager.allowAll) {
|
||||||
|
override suspend fun createModuleScope(name: String): ModuleScope? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Module scope supports importing and contains the [pacman]; it should be the same
|
||||||
|
* used in [Compiler];
|
||||||
|
*/
|
||||||
|
class ModuleScope(
|
||||||
|
val pacman: Pacman,
|
||||||
|
pos: Pos = Pos.builtIn,
|
||||||
|
val packageName: String
|
||||||
|
) : Scope(pacman.rootScope, Arguments.EMPTY, pos) {
|
||||||
|
|
||||||
|
constructor(pacman: Pacman,source: Source) : this(pacman, source.startPos, source.fileName)
|
||||||
|
|
||||||
|
override suspend fun checkImport(pos: Pos, name: String, symbols: Map<String, String>?) {
|
||||||
|
pacman.prepareImport(pos, name, symbols)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import symbols into the scope. It _is called_ after the module is imported by [checkImport].
|
||||||
|
* If [checkImport] was not called, the symbols will not be imported with exception as module is not found.
|
||||||
|
*/
|
||||||
|
override suspend fun importInto(scope: Scope, name: String, symbols: Map<String, String>?) {
|
||||||
|
pacman.performImport(scope, name, symbols)
|
||||||
|
}
|
||||||
|
|
||||||
|
val packageNameObj by lazy { ObjString(packageName).asReadonly}
|
||||||
|
|
||||||
|
override fun get(name: String): ObjRecord? {
|
||||||
|
return if( name == "__PACKAGE__")
|
||||||
|
packageNameObj
|
||||||
|
else
|
||||||
|
super.get(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class InlineSourcesPacman(pacman: Pacman,val sources: List<Source>) : Pacman(pacman) {
|
||||||
|
|
||||||
|
val modules: Deferred<Map<String,Deferred<ModuleScope>>> = globalDefer {
|
||||||
|
val result = mutableMapOf<String, Deferred<ModuleScope>>()
|
||||||
|
for (source in sources) {
|
||||||
|
// retrieve the module name and script for deferred execution:
|
||||||
|
val (name, script) = Compiler.compilePackage(source)
|
||||||
|
// scope is created used pacman's root scope:
|
||||||
|
val scope = ModuleScope(this@InlineSourcesPacman, source.startPos, name)
|
||||||
|
// we execute scripts in parallel which allow cross-imports to some extent:
|
||||||
|
result[name] = globalDefer { script.execute(scope); scope }
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun createModuleScope(name: String): ModuleScope? =
|
||||||
|
modules.await()[name]?.await()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -27,13 +27,13 @@ data class ObjRecord(
|
|||||||
* operator is implemented in [Compiler.allOps].
|
* operator is implemented in [Compiler.allOps].
|
||||||
*/
|
*/
|
||||||
data class Accessor(
|
data class Accessor(
|
||||||
val getter: suspend (Context) -> ObjRecord,
|
val getter: suspend (Scope) -> ObjRecord,
|
||||||
val setterOrNull: (suspend (Context, Obj) -> Unit)?
|
val setterOrNull: (suspend (Scope, Obj) -> Unit)?
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Simplified constructor for immutable stores.
|
* Simplified constructor for immutable stores.
|
||||||
*/
|
*/
|
||||||
constructor(getter: suspend (Context) -> ObjRecord) : this(getter, null)
|
constructor(getter: suspend (Scope) -> ObjRecord) : this(getter, null)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the setter or throw.
|
* Get the setter or throw.
|
||||||
@@ -70,33 +70,41 @@ open class Obj {
|
|||||||
someClass == rootObjectType
|
someClass == rootObjectType
|
||||||
|
|
||||||
|
|
||||||
suspend fun invokeInstanceMethod(context: Context, name: String, vararg args: Obj): Obj =
|
suspend fun invokeInstanceMethod(scope: Scope, name: String, vararg args: Obj): Obj =
|
||||||
invokeInstanceMethod(context, name, Arguments(args.toList()))
|
invokeInstanceMethod(scope, name, Arguments(args.toList()))
|
||||||
|
|
||||||
suspend inline fun <reified T : Obj> callMethod(
|
suspend inline fun <reified T : Obj> callMethod(
|
||||||
context: Context,
|
scope: Scope,
|
||||||
name: String,
|
name: String,
|
||||||
args: Arguments = Arguments.EMPTY
|
args: Arguments = Arguments.EMPTY
|
||||||
): T = invokeInstanceMethod(context, name, args) as T
|
): T = invokeInstanceMethod(scope, name, args) as T
|
||||||
|
|
||||||
open suspend fun invokeInstanceMethod(
|
open suspend fun invokeInstanceMethod(
|
||||||
context: Context,
|
scope: Scope,
|
||||||
name: String,
|
name: String,
|
||||||
args: Arguments = Arguments.EMPTY
|
args: Arguments = Arguments.EMPTY
|
||||||
): Obj =
|
): Obj =
|
||||||
// note that getInstanceMember traverses the hierarchy
|
// note that getInstanceMember traverses the hierarchy
|
||||||
objClass.getInstanceMember(context.pos, name).value.invoke(context, this, args)
|
objClass.getInstanceMember(scope.pos, name).value.invoke(scope, this, args)
|
||||||
|
|
||||||
|
open suspend fun getInstanceMethod(
|
||||||
|
scope: Scope,
|
||||||
|
name: String,
|
||||||
|
args: Arguments = Arguments.EMPTY
|
||||||
|
): Obj =
|
||||||
|
// note that getInstanceMember traverses the hierarchy
|
||||||
|
objClass.getInstanceMember(scope.pos, name).value
|
||||||
|
|
||||||
fun getMemberOrNull(name: String): Obj? = objClass.getInstanceMemberOrNull(name)?.value
|
fun getMemberOrNull(name: String): Obj? = objClass.getInstanceMemberOrNull(name)?.value
|
||||||
|
|
||||||
// methods that to override
|
// methods that to override
|
||||||
|
|
||||||
open suspend fun compareTo(context: Context, other: Obj): Int {
|
open suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
context.raiseNotImplemented()
|
scope.raiseNotImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun contains(context: Context, other: Obj): Boolean {
|
open suspend fun contains(scope: Scope, other: Obj): Boolean {
|
||||||
return invokeInstanceMethod(context, "contains", other).toBool()
|
return invokeInstanceMethod(scope, "contains", other).toBool()
|
||||||
}
|
}
|
||||||
|
|
||||||
open val asStr: ObjString by lazy {
|
open val asStr: ObjString by lazy {
|
||||||
@@ -109,90 +117,90 @@ open class Obj {
|
|||||||
*/
|
*/
|
||||||
open val objClass: ObjClass = rootObjectType
|
open val objClass: ObjClass = rootObjectType
|
||||||
|
|
||||||
open suspend fun plus(context: Context, other: Obj): Obj {
|
open suspend fun plus(scope: Scope, other: Obj): Obj {
|
||||||
context.raiseNotImplemented()
|
scope.raiseNotImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun minus(context: Context, other: Obj): Obj {
|
open suspend fun minus(scope: Scope, other: Obj): Obj {
|
||||||
context.raiseNotImplemented()
|
scope.raiseNotImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun mul(context: Context, other: Obj): Obj {
|
open suspend fun mul(scope: Scope, other: Obj): Obj {
|
||||||
context.raiseNotImplemented()
|
scope.raiseNotImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun div(context: Context, other: Obj): Obj {
|
open suspend fun div(scope: Scope, other: Obj): Obj {
|
||||||
context.raiseNotImplemented()
|
scope.raiseNotImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun mod(context: Context, other: Obj): Obj {
|
open suspend fun mod(scope: Scope, other: Obj): Obj {
|
||||||
context.raiseNotImplemented()
|
scope.raiseNotImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun logicalNot(context: Context): Obj {
|
open suspend fun logicalNot(scope: Scope): Obj {
|
||||||
context.raiseNotImplemented()
|
scope.raiseNotImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun logicalAnd(context: Context, other: Obj): Obj {
|
open suspend fun logicalAnd(scope: Scope, other: Obj): Obj {
|
||||||
context.raiseNotImplemented()
|
scope.raiseNotImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun logicalOr(context: Context, other: Obj): Obj {
|
open suspend fun logicalOr(scope: Scope, other: Obj): Obj {
|
||||||
context.raiseNotImplemented()
|
scope.raiseNotImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun assign(context: Context, other: Obj): Obj? = null
|
open suspend fun assign(scope: Scope, other: Obj): Obj? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* a += b
|
* a += b
|
||||||
* if( the operation is not defined, it returns null and the compiler would try
|
* if( the operation is not defined, it returns null and the compiler would try
|
||||||
* to generate it as 'this = this + other', reassigning its variable
|
* to generate it as 'this = this + other', reassigning its variable
|
||||||
*/
|
*/
|
||||||
open suspend fun plusAssign(context: Context, other: Obj): Obj? = null
|
open suspend fun plusAssign(scope: Scope, other: Obj): Obj? = null
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `-=` operations, see [plusAssign]
|
* `-=` operations, see [plusAssign]
|
||||||
*/
|
*/
|
||||||
open suspend fun minusAssign(context: Context, other: Obj): Obj? = null
|
open suspend fun minusAssign(scope: Scope, other: Obj): Obj? = null
|
||||||
open suspend fun mulAssign(context: Context, other: Obj): Obj? = null
|
open suspend fun mulAssign(scope: Scope, other: Obj): Obj? = null
|
||||||
open suspend fun divAssign(context: Context, other: Obj): Obj? = null
|
open suspend fun divAssign(scope: Scope, other: Obj): Obj? = null
|
||||||
open suspend fun modAssign(context: Context, other: Obj): Obj? = null
|
open suspend fun modAssign(scope: Scope, other: Obj): Obj? = null
|
||||||
|
|
||||||
open suspend fun getAndIncrement(context: Context): Obj {
|
open suspend fun getAndIncrement(scope: Scope): Obj {
|
||||||
context.raiseNotImplemented()
|
scope.raiseNotImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun incrementAndGet(context: Context): Obj {
|
open suspend fun incrementAndGet(scope: Scope): Obj {
|
||||||
context.raiseNotImplemented()
|
scope.raiseNotImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun decrementAndGet(context: Context): Obj {
|
open suspend fun decrementAndGet(scope: Scope): Obj {
|
||||||
context.raiseNotImplemented()
|
scope.raiseNotImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun getAndDecrement(context: Context): Obj {
|
open suspend fun getAndDecrement(scope: Scope): Obj {
|
||||||
context.raiseNotImplemented()
|
scope.raiseNotImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert Lyng object to its Kotlin counterpart
|
* Convert Lyng object to its Kotlin counterpart
|
||||||
*/
|
*/
|
||||||
open suspend fun toKotlin(context: Context): Any? {
|
open suspend fun toKotlin(scope: Scope): Any? {
|
||||||
return toString()
|
return toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun willMutate(context: Context) {
|
fun willMutate(scope: Scope) {
|
||||||
if (isFrozen) context.raiseError("attempt to mutate frozen object")
|
if (isFrozen) scope.raiseError("attempt to mutate frozen object")
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun <T> sync(block: () -> T): T = monitor.withLock { block() }
|
suspend fun <T> sync(block: () -> T): T = monitor.withLock { block() }
|
||||||
|
|
||||||
open suspend fun readField(context: Context, name: String): ObjRecord {
|
open suspend fun readField(scope: Scope, name: String): ObjRecord {
|
||||||
// could be property or class field:
|
// could be property or class field:
|
||||||
val obj = objClass.getInstanceMemberOrNull(name) ?: context.raiseError("no such field: $name")
|
val obj = objClass.getInstanceMemberOrNull(name) ?: scope.raiseError("no such field: $name")
|
||||||
return when (val value = obj.value) {
|
return when (val value = obj.value) {
|
||||||
is Statement -> {
|
is Statement -> {
|
||||||
ObjRecord(value.execute(context.copy(context.pos, newThisObj = this)), obj.isMutable)
|
ObjRecord(value.execute(scope.copy(scope.pos, newThisObj = this)), obj.isMutable)
|
||||||
}
|
}
|
||||||
// could be writable property naturally
|
// could be writable property naturally
|
||||||
// null -> ObjNull.asReadonly
|
// null -> ObjNull.asReadonly
|
||||||
@@ -200,47 +208,49 @@ open class Obj {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun writeField(context: Context, name: String, newValue: Obj) {
|
open suspend fun writeField(scope: Scope, name: String, newValue: Obj) {
|
||||||
willMutate(context)
|
willMutate(scope)
|
||||||
val field = objClass.getInstanceMemberOrNull(name) ?: context.raiseError("no such field: $name")
|
val field = objClass.getInstanceMemberOrNull(name) ?: scope.raiseError("no such field: $name")
|
||||||
if (field.isMutable) field.value = newValue else context.raiseError("can't assign to read-only field: $name")
|
if (field.isMutable) field.value = newValue else scope.raiseError("can't assign to read-only field: $name")
|
||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun getAt(context: Context, index: Obj): Obj {
|
open suspend fun getAt(scope: Scope, index: Obj): Obj {
|
||||||
context.raiseNotImplemented("indexing")
|
scope.raiseNotImplemented("indexing")
|
||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun putAt(context: Context, index: Int, newValue: Obj) {
|
suspend fun getAt(scope: Scope, index: Int): Obj = getAt(scope, ObjInt(index.toLong()))
|
||||||
context.raiseNotImplemented("indexing")
|
|
||||||
|
open suspend fun putAt(scope: Scope, index: Int, newValue: Obj) {
|
||||||
|
scope.raiseNotImplemented("indexing")
|
||||||
}
|
}
|
||||||
|
|
||||||
open suspend fun callOn(context: Context): Obj {
|
open suspend fun callOn(scope: Scope): Obj {
|
||||||
context.raiseNotImplemented()
|
scope.raiseNotImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun invoke(context: Context, thisObj: Obj, args: Arguments): Obj =
|
suspend fun invoke(scope: Scope, thisObj: Obj, args: Arguments): Obj =
|
||||||
callOn(context.copy(context.pos, args = args, newThisObj = thisObj))
|
callOn(scope.copy(scope.pos, args = args, newThisObj = thisObj))
|
||||||
|
|
||||||
suspend fun invoke(context: Context, thisObj: Obj, vararg args: Obj): Obj =
|
suspend fun invoke(scope: Scope, thisObj: Obj, vararg args: Obj): Obj =
|
||||||
callOn(
|
callOn(
|
||||||
context.copy(
|
scope.copy(
|
||||||
context.pos,
|
scope.pos,
|
||||||
args = Arguments(args.toList()),
|
args = Arguments(args.toList()),
|
||||||
newThisObj = thisObj
|
newThisObj = thisObj
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun invoke(context: Context, thisObj: Obj): Obj =
|
suspend fun invoke(scope: Scope, thisObj: Obj): Obj =
|
||||||
callOn(
|
callOn(
|
||||||
context.copy(
|
scope.copy(
|
||||||
context.pos,
|
scope.pos,
|
||||||
args = Arguments.EMPTY,
|
args = Arguments.EMPTY,
|
||||||
newThisObj = thisObj
|
newThisObj = thisObj
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
suspend fun invoke(context: Context, atPos: Pos, thisObj: Obj, args: Arguments): Obj =
|
suspend fun invoke(scope: Scope, atPos: Pos, thisObj: Obj, args: Arguments): Obj =
|
||||||
callOn(context.copy(atPos, args = args, newThisObj = thisObj))
|
callOn(scope.copy(atPos, args = args, newThisObj = thisObj))
|
||||||
|
|
||||||
|
|
||||||
val asReadonly: ObjRecord by lazy { ObjRecord(this, false) }
|
val asReadonly: ObjRecord by lazy { ObjRecord(this, false) }
|
||||||
@@ -261,7 +271,7 @@ open class Obj {
|
|||||||
args.firstAndOnly().callOn(copy(Arguments(thisObj)))
|
args.firstAndOnly().callOn(copy(Arguments(thisObj)))
|
||||||
}
|
}
|
||||||
addFn("apply") {
|
addFn("apply") {
|
||||||
val newContext = ( thisObj as? ObjInstance)?.instanceContext ?: this
|
val newContext = ( thisObj as? ObjInstance)?.instanceScope ?: this
|
||||||
args.firstAndOnly()
|
args.firstAndOnly()
|
||||||
.callOn(newContext)
|
.callOn(newContext)
|
||||||
thisObj
|
thisObj
|
||||||
@@ -309,7 +319,7 @@ object ObjVoid : Obj() {
|
|||||||
return other is ObjVoid || other is Unit
|
return other is ObjVoid || other is Unit
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
return if (other === this) 0 else -1
|
return if (other === this) 0 else -1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,7 +329,7 @@ object ObjVoid : Obj() {
|
|||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("null")
|
@SerialName("null")
|
||||||
object ObjNull : Obj() {
|
object ObjNull : Obj() {
|
||||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
return if (other === this) 0 else -1
|
return if (other === this) 0 else -1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,29 +337,29 @@ object ObjNull : Obj() {
|
|||||||
return other is ObjNull || other == null
|
return other is ObjNull || other == null
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun readField(context: Context, name: String): ObjRecord {
|
override suspend fun readField(scope: Scope, name: String): ObjRecord {
|
||||||
context.raiseNPE()
|
scope.raiseNPE()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun invokeInstanceMethod(context: Context, name: String, args: Arguments): Obj {
|
override suspend fun invokeInstanceMethod(scope: Scope, name: String, args: Arguments): Obj {
|
||||||
context.raiseNPE()
|
scope.raiseNPE()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getAt(context: Context, index: Obj): Obj {
|
override suspend fun getAt(scope: Scope, index: Obj): Obj {
|
||||||
context.raiseNPE()
|
scope.raiseNPE()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun putAt(context: Context, index: Int, newValue: Obj) {
|
override suspend fun putAt(scope: Scope, index: Int, newValue: Obj) {
|
||||||
context.raiseNPE()
|
scope.raiseNPE()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun callOn(context: Context): Obj {
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
context.raiseNPE()
|
scope.raiseNPE()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String = "null"
|
override fun toString(): String = "null"
|
||||||
|
|
||||||
override suspend fun toKotlin(context: Context): Any? {
|
override suspend fun toKotlin(scope: Scope): Any? {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -391,14 +401,14 @@ data class ObjNamespace(val name: String) : Obj() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
open class ObjException(exceptionClass: ExceptionClass, val context: Context, val message: String) : Obj() {
|
open class ObjException(exceptionClass: ExceptionClass, val scope: Scope, val message: String) : Obj() {
|
||||||
constructor(name: String, context: Context, message: String) : this(
|
constructor(name: String, scope: Scope, message: String) : this(
|
||||||
getOrCreateExceptionClass(name),
|
getOrCreateExceptionClass(name),
|
||||||
context,
|
scope,
|
||||||
message
|
message
|
||||||
)
|
)
|
||||||
|
|
||||||
constructor(context: Context, message: String) : this(Root, context, message)
|
constructor(scope: Scope, message: String) : this(Root, scope, message)
|
||||||
|
|
||||||
fun raise(): Nothing {
|
fun raise(): Nothing {
|
||||||
throw ExecutionError(this)
|
throw ExecutionError(this)
|
||||||
@@ -407,15 +417,15 @@ open class ObjException(exceptionClass: ExceptionClass, val context: Context, va
|
|||||||
override val objClass: ObjClass = exceptionClass
|
override val objClass: ObjClass = exceptionClass
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "ObjException:${objClass.className}:${context.pos}@${hashCode().encodeToHex()}"
|
return "ObjException:${objClass.className}:${scope.pos}@${hashCode().encodeToHex()}"
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
class ExceptionClass(val name: String, vararg parents: ObjClass) : ObjClass(name, *parents) {
|
class ExceptionClass(val name: String, vararg parents: ObjClass) : ObjClass(name, *parents) {
|
||||||
override suspend fun callOn(context: Context): Obj {
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
val message = context.args.getOrNull(0)?.toString() ?: name
|
val message = scope.args.getOrNull(0)?.toString() ?: name
|
||||||
return ObjException(this, context, message)
|
return ObjException(this, scope, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String = "ExceptionClass[$name]@${hashCode().encodeToHex()}"
|
override fun toString(): String = "ExceptionClass[$name]@${hashCode().encodeToHex()}"
|
||||||
@@ -448,8 +458,8 @@ open class ObjException(exceptionClass: ExceptionClass, val context: Context, va
|
|||||||
existingErrorClasses[name]
|
existingErrorClasses[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addExceptionsToContext(context: Context) {
|
fun addExceptionsToContext(scope: Scope) {
|
||||||
context.addConst("Exception", Root)
|
scope.addConst("Exception", Root)
|
||||||
existingErrorClasses["Exception"] = Root
|
existingErrorClasses["Exception"] = Root
|
||||||
for (name in listOf(
|
for (name in listOf(
|
||||||
"NullReferenceException",
|
"NullReferenceException",
|
||||||
@@ -464,39 +474,42 @@ open class ObjException(exceptionClass: ExceptionClass, val context: Context, va
|
|||||||
"AccessException",
|
"AccessException",
|
||||||
"UnknownException",
|
"UnknownException",
|
||||||
)) {
|
)) {
|
||||||
context.addConst(name, getOrCreateExceptionClass(name))
|
scope.addConst(name, getOrCreateExceptionClass(name))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ObjNullReferenceException(context: Context) : ObjException("NullReferenceException", context, "object is null")
|
class ObjNullReferenceException(scope: Scope) : ObjException("NullReferenceException", scope, "object is null")
|
||||||
|
|
||||||
class ObjAssertionFailedException(context: Context, message: String) :
|
class ObjAssertionFailedException(scope: Scope, message: String) :
|
||||||
ObjException("AssertionFailedException", context, message)
|
ObjException("AssertionFailedException", scope, message)
|
||||||
|
|
||||||
class ObjClassCastException(context: Context, message: String) : ObjException("ClassCastException", context, message)
|
class ObjClassCastException(scope: Scope, message: String) : ObjException("ClassCastException", scope, message)
|
||||||
class ObjIndexOutOfBoundsException(context: Context, message: String = "index out of bounds") :
|
class ObjIndexOutOfBoundsException(scope: Scope, message: String = "index out of bounds") :
|
||||||
ObjException("IndexOutOfBoundsException", context, message)
|
ObjException("IndexOutOfBoundsException", scope, message)
|
||||||
|
|
||||||
class ObjIllegalArgumentException(context: Context, message: String = "illegal argument") :
|
class ObjIllegalArgumentException(scope: Scope, message: String = "illegal argument") :
|
||||||
ObjException("IllegalArgumentException", context, message)
|
ObjException("IllegalArgumentException", scope, message)
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
class ObjNoSuchElementException(context: Context, message: String = "no such element") :
|
class ObjNoSuchElementException(scope: Scope, message: String = "no such element") :
|
||||||
ObjException("IllegalArgumentException", context, message)
|
ObjException("IllegalArgumentException", scope, message)
|
||||||
|
|
||||||
class ObjIllegalAssignmentException(context: Context, message: String = "illegal assignment") :
|
class ObjIllegalAssignmentException(scope: Scope, message: String = "illegal assignment") :
|
||||||
ObjException("NoSuchElementException", context, message)
|
ObjException("NoSuchElementException", scope, message)
|
||||||
|
|
||||||
class ObjSymbolNotDefinedException(context: Context, message: String = "symbol is not defined") :
|
class ObjSymbolNotDefinedException(scope: Scope, message: String = "symbol is not defined") :
|
||||||
ObjException("SymbolNotDefinedException", context, message)
|
ObjException("SymbolNotDefinedException", scope, message)
|
||||||
|
|
||||||
class ObjIterationFinishedException(context: Context) :
|
class ObjIterationFinishedException(scope: Scope) :
|
||||||
ObjException("IterationEndException", context, "iteration finished")
|
ObjException("IterationEndException", scope, "iteration finished")
|
||||||
|
|
||||||
class ObjAccessException(context: Context, message: String = "access not allowed error") :
|
class ObjAccessException(scope: Scope, message: String = "access not allowed error") :
|
||||||
ObjException("AccessException", context, message)
|
ObjException("AccessException", scope, message)
|
||||||
|
|
||||||
class ObjUnknownException(context: Context, message: String = "access not allowed error") :
|
class ObjUnknownException(scope: Scope, message: String = "access not allowed error") :
|
||||||
ObjException("UnknownException", context, message)
|
ObjException("UnknownException", scope, message)
|
||||||
|
|
||||||
|
class ObjIllegalOperationException(scope: Scope, message: String = "Operation is illegal") :
|
||||||
|
ObjException("IllegalOperationException", scope, message)
|
||||||
@@ -7,9 +7,9 @@ class ObjArrayIterator(val array: Obj) : Obj() {
|
|||||||
private var nextIndex = 0
|
private var nextIndex = 0
|
||||||
private var lastIndex = 0
|
private var lastIndex = 0
|
||||||
|
|
||||||
suspend fun init(context: Context) {
|
suspend fun init(scope: Scope) {
|
||||||
nextIndex = 0
|
nextIndex = 0
|
||||||
lastIndex = array.invokeInstanceMethod(context, "size").toInt()
|
lastIndex = array.invokeInstanceMethod(scope, "size").toInt()
|
||||||
ObjVoid
|
ObjVoid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package net.sergeych.lyng
|
|||||||
data class ObjBool(val value: Boolean) : Obj() {
|
data class ObjBool(val value: Boolean) : Obj() {
|
||||||
override val asStr by lazy { ObjString(value.toString()) }
|
override val asStr by lazy { ObjString(value.toString()) }
|
||||||
|
|
||||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
if (other !is ObjBool) return -2
|
if (other !is ObjBool) return -2
|
||||||
return value.compareTo(other.value)
|
return value.compareTo(other.value)
|
||||||
}
|
}
|
||||||
@@ -12,13 +12,13 @@ data class ObjBool(val value: Boolean) : Obj() {
|
|||||||
|
|
||||||
override val objClass: ObjClass = type
|
override val objClass: ObjClass = type
|
||||||
|
|
||||||
override suspend fun logicalNot(context: Context): Obj = ObjBool(!value)
|
override suspend fun logicalNot(scope: Scope): Obj = ObjBool(!value)
|
||||||
|
|
||||||
override suspend fun logicalAnd(context: Context, other: Obj): Obj = ObjBool(value && other.toBool())
|
override suspend fun logicalAnd(scope: Scope, other: Obj): Obj = ObjBool(value && other.toBool())
|
||||||
|
|
||||||
override suspend fun logicalOr(context: Context, other: Obj): Obj = ObjBool(value || other.toBool())
|
override suspend fun logicalOr(scope: Scope, other: Obj): Obj = ObjBool(value || other.toBool())
|
||||||
|
|
||||||
override suspend fun toKotlin(context: Context): Any {
|
override suspend fun toKotlin(scope: Scope): Any {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ class ObjChar(val value: Char): Obj() {
|
|||||||
|
|
||||||
override val objClass: ObjClass = type
|
override val objClass: ObjClass = type
|
||||||
|
|
||||||
override suspend fun compareTo(context: Context, other: Obj): Int =
|
override suspend fun compareTo(scope: Scope, other: Obj): Int =
|
||||||
(other as? ObjChar)?.let { value.compareTo(it.value) } ?: -1
|
(other as? ObjChar)?.let { value.compareTo(it.value) } ?: -1
|
||||||
|
|
||||||
override fun toString(): String = value.toString()
|
override fun toString(): String = value.toString()
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ open class ObjClass(
|
|||||||
|
|
||||||
override fun toString(): String = className
|
override fun toString(): String = className
|
||||||
|
|
||||||
override suspend fun compareTo(context: Context, other: Obj): Int = if (other === this) 0 else -1
|
override suspend fun compareTo(scope: Scope, other: Obj): Int = if (other === this) 0 else -1
|
||||||
|
|
||||||
override suspend fun callOn(context: Context): Obj {
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
val instance = ObjInstance(this)
|
val instance = ObjInstance(this)
|
||||||
instance.instanceContext = context.copy(newThisObj = instance,args = context.args)
|
instance.instanceScope = scope.copy(newThisObj = instance,args = scope.args)
|
||||||
if (instanceConstructor != null) {
|
if (instanceConstructor != null) {
|
||||||
instanceConstructor!!.execute(instance.instanceContext)
|
instanceConstructor!!.execute(instance.instanceScope)
|
||||||
}
|
}
|
||||||
return instance
|
return instance
|
||||||
}
|
}
|
||||||
@@ -49,7 +49,7 @@ open class ObjClass(
|
|||||||
members[name] = ObjRecord(initialValue, isMutable, visibility)
|
members[name] = ObjRecord(initialValue, isMutable, visibility)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addFn(name: String, isOpen: Boolean = false, code: suspend Context.() -> Obj) {
|
fun addFn(name: String, isOpen: Boolean = false, code: suspend Scope.() -> Obj) {
|
||||||
createField(name, statement { code() }, isOpen)
|
createField(name, statement { code() }, isOpen)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,52 +2,52 @@ package net.sergeych.lyng
|
|||||||
|
|
||||||
class ObjInstance(override val objClass: ObjClass) : Obj() {
|
class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||||
|
|
||||||
internal lateinit var instanceContext: Context
|
internal lateinit var instanceScope: Scope
|
||||||
|
|
||||||
override suspend fun readField(context: Context, name: String): ObjRecord {
|
override suspend fun readField(scope: Scope, name: String): ObjRecord {
|
||||||
return instanceContext[name]?.let {
|
return instanceScope[name]?.let {
|
||||||
if (it.visibility.isPublic)
|
if (it.visibility.isPublic)
|
||||||
it
|
it
|
||||||
else
|
else
|
||||||
context.raiseError(ObjAccessException(context, "can't access non-public field $name"))
|
scope.raiseError(ObjAccessException(scope, "can't access non-public field $name"))
|
||||||
}
|
}
|
||||||
?: super.readField(context, name)
|
?: super.readField(scope, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun writeField(context: Context, name: String, newValue: Obj) {
|
override suspend fun writeField(scope: Scope, name: String, newValue: Obj) {
|
||||||
instanceContext[name]?.let { f ->
|
instanceScope[name]?.let { f ->
|
||||||
if (!f.visibility.isPublic)
|
if (!f.visibility.isPublic)
|
||||||
ObjIllegalAssignmentException(context, "can't assign to non-public field $name")
|
ObjIllegalAssignmentException(scope, "can't assign to non-public field $name")
|
||||||
if (!f.isMutable) ObjIllegalAssignmentException(context, "can't reassign val $name").raise()
|
if (!f.isMutable) ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
|
||||||
if (f.value.assign(context, newValue) == null)
|
if (f.value.assign(scope, newValue) == null)
|
||||||
f.value = newValue
|
f.value = newValue
|
||||||
} ?: super.writeField(context, name, newValue)
|
} ?: super.writeField(scope, name, newValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun invokeInstanceMethod(context: Context, name: String, args: Arguments): Obj =
|
override suspend fun invokeInstanceMethod(scope: Scope, name: String, args: Arguments): Obj =
|
||||||
instanceContext[name]?.let {
|
instanceScope[name]?.let {
|
||||||
if (it.visibility.isPublic)
|
if (it.visibility.isPublic)
|
||||||
it.value.invoke(context, this, args)
|
it.value.invoke(scope, this, args)
|
||||||
else
|
else
|
||||||
context.raiseError(ObjAccessException(context, "can't invoke non-public method $name"))
|
scope.raiseError(ObjAccessException(scope, "can't invoke non-public method $name"))
|
||||||
}
|
}
|
||||||
?: super.invokeInstanceMethod(context, name, args)
|
?: super.invokeInstanceMethod(scope, name, args)
|
||||||
|
|
||||||
private val publicFields: Map<String, ObjRecord>
|
private val publicFields: Map<String, ObjRecord>
|
||||||
get() = instanceContext.objects.filter { it.value.visibility.isPublic }
|
get() = instanceScope.objects.filter { it.value.visibility.isPublic }
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
val fields = publicFields.map { "${it.key}=${it.value.value}" }.joinToString(",")
|
val fields = publicFields.map { "${it.key}=${it.value.value}" }.joinToString(",")
|
||||||
return "${objClass.className}($fields)"
|
return "${objClass.className}($fields)"
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
if (other !is ObjInstance) return -1
|
if (other !is ObjInstance) return -1
|
||||||
if (other.objClass != objClass) return -1
|
if (other.objClass != objClass) return -1
|
||||||
for (f in publicFields) {
|
for (f in publicFields) {
|
||||||
val a = f.value.value
|
val a = f.value.value
|
||||||
val b = other.instanceContext[f.key]!!.value
|
val b = other.instanceScope[f.key]!!.value
|
||||||
val d = a.compareTo(context, b)
|
val d = a.compareTo(scope, b)
|
||||||
if (d != 0) return d
|
if (d != 0) return d
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -13,23 +13,23 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
|
|||||||
return value.hashCode()
|
return value.hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getAndIncrement(context: Context): Obj {
|
override suspend fun getAndIncrement(scope: Scope): Obj {
|
||||||
return ObjInt(value).also { value++ }
|
return ObjInt(value).also { value++ }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getAndDecrement(context: Context): Obj {
|
override suspend fun getAndDecrement(scope: Scope): Obj {
|
||||||
return ObjInt(value).also { value-- }
|
return ObjInt(value).also { value-- }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun incrementAndGet(context: Context): Obj {
|
override suspend fun incrementAndGet(scope: Scope): Obj {
|
||||||
return ObjInt(++value)
|
return ObjInt(++value)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun decrementAndGet(context: Context): Obj {
|
override suspend fun decrementAndGet(scope: Scope): Obj {
|
||||||
return ObjInt(--value)
|
return ObjInt(--value)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
if (other !is Numeric) return -2
|
if (other !is Numeric) return -2
|
||||||
return value.compareTo(other.doubleValue)
|
return value.compareTo(other.doubleValue)
|
||||||
}
|
}
|
||||||
@@ -38,29 +38,29 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
|
|||||||
|
|
||||||
override val objClass: ObjClass = type
|
override val objClass: ObjClass = type
|
||||||
|
|
||||||
override suspend fun plus(context: Context, other: Obj): Obj =
|
override suspend fun plus(scope: Scope, other: Obj): Obj =
|
||||||
if (other is ObjInt)
|
if (other is ObjInt)
|
||||||
ObjInt(this.value + other.value)
|
ObjInt(this.value + other.value)
|
||||||
else
|
else
|
||||||
ObjReal(this.doubleValue + other.toDouble())
|
ObjReal(this.doubleValue + other.toDouble())
|
||||||
|
|
||||||
override suspend fun minus(context: Context, other: Obj): Obj =
|
override suspend fun minus(scope: Scope, other: Obj): Obj =
|
||||||
if (other is ObjInt)
|
if (other is ObjInt)
|
||||||
ObjInt(this.value - other.value)
|
ObjInt(this.value - other.value)
|
||||||
else
|
else
|
||||||
ObjReal(this.doubleValue - other.toDouble())
|
ObjReal(this.doubleValue - other.toDouble())
|
||||||
|
|
||||||
override suspend fun mul(context: Context, other: Obj): Obj =
|
override suspend fun mul(scope: Scope, other: Obj): Obj =
|
||||||
if (other is ObjInt) {
|
if (other is ObjInt) {
|
||||||
ObjInt(this.value * other.value)
|
ObjInt(this.value * other.value)
|
||||||
} else ObjReal(this.value * other.toDouble())
|
} else ObjReal(this.value * other.toDouble())
|
||||||
|
|
||||||
override suspend fun div(context: Context, other: Obj): Obj =
|
override suspend fun div(scope: Scope, other: Obj): Obj =
|
||||||
if (other is ObjInt)
|
if (other is ObjInt)
|
||||||
ObjInt(this.value / other.value)
|
ObjInt(this.value / other.value)
|
||||||
else ObjReal(this.value / other.toDouble())
|
else ObjReal(this.value / other.toDouble())
|
||||||
|
|
||||||
override suspend fun mod(context: Context, other: Obj): Obj =
|
override suspend fun mod(scope: Scope, other: Obj): Obj =
|
||||||
if (other is ObjInt)
|
if (other is ObjInt)
|
||||||
ObjInt(this.value % other.value)
|
ObjInt(this.value % other.value)
|
||||||
else ObjReal(this.value.toDouble() % other.toDouble())
|
else ObjReal(this.value.toDouble() % other.toDouble())
|
||||||
@@ -69,14 +69,14 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
|
|||||||
* We are by-value type ([byValueCopy] is implemented) so we can do in-place
|
* We are by-value type ([byValueCopy] is implemented) so we can do in-place
|
||||||
* assignment
|
* assignment
|
||||||
*/
|
*/
|
||||||
override suspend fun assign(context: Context, other: Obj): Obj? {
|
override suspend fun assign(scope: Scope, other: Obj): Obj? {
|
||||||
return if (other is ObjInt) {
|
return if (other is ObjInt) {
|
||||||
value = other.value
|
value = other.value
|
||||||
this
|
this
|
||||||
} else null
|
} else null
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun toKotlin(context: Context): Any {
|
override suspend fun toKotlin(scope: Scope): Any {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,12 +48,20 @@ val ObjIterable by lazy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addFn("toMap") {
|
addFn("toMap") {
|
||||||
val result = mutableListOf<Obj>()
|
val result = ObjMap()
|
||||||
val it = thisObj.invokeInstanceMethod(this, "iterator")
|
thisObj.toFlow(this).collect { pair ->
|
||||||
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
|
result.map[pair.getAt(this,0)] = pair.getAt(this, 1)
|
||||||
result += it.invokeInstanceMethod(this, "next")
|
|
||||||
}
|
}
|
||||||
ObjMap(ObjMap.listToMap(this, result))
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
addFn("associateBy") {
|
||||||
|
val association = requireOnlyArg<Statement>()
|
||||||
|
val result = ObjMap()
|
||||||
|
thisObj.toFlow(this).collect {
|
||||||
|
result.map[association.call(this, it)] = it
|
||||||
|
}
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
addFn("forEach", isOpen = true) {
|
addFn("forEach", isOpen = true) {
|
||||||
@@ -67,12 +75,10 @@ val ObjIterable by lazy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
addFn("map", isOpen = true) {
|
addFn("map", isOpen = true) {
|
||||||
val it = thisObj.invokeInstanceMethod(this, "iterator")
|
|
||||||
val fn = requiredArg<Statement>(0)
|
val fn = requiredArg<Statement>(0)
|
||||||
val result = mutableListOf<Obj>()
|
val result = mutableListOf<Obj>()
|
||||||
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
|
thisObj.toFlow(this).collect {
|
||||||
val x = it.invokeInstanceMethod(this, "next")
|
result += fn.call(this, it)
|
||||||
result += fn.execute(this.copy(Arguments(listOf(x))))
|
|
||||||
}
|
}
|
||||||
ObjList(result)
|
ObjList(result)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
|
|
||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Iterator wrapper to allow Kotlin collections to be returned from Lyng objects;
|
* Iterator wrapper to allow Kotlin collections to be returned from Lyng objects;
|
||||||
* each object is converted to a Lyng object.
|
* each object is converted to a Lyng object.
|
||||||
@@ -37,3 +40,18 @@ class ObjKotlinObjIterator(val iterator: Iterator<Obj>) : Obj() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert Lyng's Iterable to Kotlin's Flow.
|
||||||
|
*
|
||||||
|
* As Lyng is totally asynchronous, its iterator can't be trivially converted to Kotlin's synchronous iterator.
|
||||||
|
* It is, though, trivially convertible to Kotlin's Flow.
|
||||||
|
*/
|
||||||
|
fun Obj.toFlow(scope: Scope): Flow<Obj> = flow {
|
||||||
|
val iterator = invokeInstanceMethod(scope, "iterator")
|
||||||
|
val hasNext = iterator.getInstanceMethod(scope, "hasNext")
|
||||||
|
val next = iterator.getInstanceMethod(scope, "next")
|
||||||
|
while (hasNext.invoke(scope, iterator).toBool()) {
|
||||||
|
emit(next.invoke(scope, iterator))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
|||||||
list.joinToString(separator = ", ") { it.inspect() }
|
list.joinToString(separator = ", ") { it.inspect() }
|
||||||
}]"
|
}]"
|
||||||
|
|
||||||
override suspend fun getAt(context: Context, index: Obj): Obj {
|
override suspend fun getAt(scope: Scope, index: Obj): Obj {
|
||||||
return when (index) {
|
return when (index) {
|
||||||
is ObjInt -> {
|
is ObjInt -> {
|
||||||
list[index.toInt()]
|
list[index.toInt()]
|
||||||
@@ -47,23 +47,23 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> context.raiseIllegalArgument("Illegal index object for a list: ${index.inspect()}")
|
else -> scope.raiseIllegalArgument("Illegal index object for a list: ${index.inspect()}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun putAt(context: Context, index: Int, newValue: Obj) {
|
override suspend fun putAt(scope: Scope, index: Int, newValue: Obj) {
|
||||||
val i = index
|
val i = index
|
||||||
list[i] = newValue
|
list[i] = newValue
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
if (other !is ObjList) return -2
|
if (other !is ObjList) return -2
|
||||||
val mySize = list.size
|
val mySize = list.size
|
||||||
val otherSize = other.list.size
|
val otherSize = other.list.size
|
||||||
val commonSize = minOf(mySize, otherSize)
|
val commonSize = minOf(mySize, otherSize)
|
||||||
for (i in 0..<commonSize) {
|
for (i in 0..<commonSize) {
|
||||||
if (list[i].compareTo(context, other.list[i]) != 0) {
|
if (list[i].compareTo(scope, other.list[i]) != 0) {
|
||||||
return list[i].compareTo(context, other.list[i])
|
return list[i].compareTo(scope, other.list[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// equal so far, longer is greater:
|
// equal so far, longer is greater:
|
||||||
@@ -74,44 +74,44 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun plus(context: Context, other: Obj): Obj =
|
override suspend fun plus(scope: Scope, other: Obj): Obj =
|
||||||
when {
|
when {
|
||||||
other is ObjList ->
|
other is ObjList ->
|
||||||
ObjList((list + other.list).toMutableList())
|
ObjList((list + other.list).toMutableList())
|
||||||
|
|
||||||
other.isInstanceOf(ObjIterable) -> {
|
other.isInstanceOf(ObjIterable) -> {
|
||||||
val l = other.callMethod<ObjList>(context, "toList")
|
val l = other.callMethod<ObjList>(scope, "toList")
|
||||||
ObjList((list + l.list).toMutableList())
|
ObjList((list + l.list).toMutableList())
|
||||||
}
|
}
|
||||||
|
|
||||||
else ->
|
else ->
|
||||||
context.raiseError("'+': can't concatenate $this with $other")
|
scope.raiseError("'+': can't concatenate $this with $other")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override suspend fun plusAssign(context: Context, other: Obj): Obj {
|
override suspend fun plusAssign(scope: Scope, other: Obj): Obj {
|
||||||
// optimization
|
// optimization
|
||||||
if (other is ObjList) {
|
if (other is ObjList) {
|
||||||
list += other.list
|
list += other.list
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
if (other.isInstanceOf(ObjIterable)) {
|
if (other.isInstanceOf(ObjIterable)) {
|
||||||
val otherList = other.invokeInstanceMethod(context, "toList") as ObjList
|
val otherList = other.invokeInstanceMethod(scope, "toList") as ObjList
|
||||||
list += otherList.list
|
list += otherList.list
|
||||||
} else
|
} else
|
||||||
list += other
|
list += other
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun contains(context: Context, other: Obj): Boolean {
|
override suspend fun contains(scope: Scope, other: Obj): Boolean {
|
||||||
return list.contains(other)
|
return list.contains(other)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val objClass: ObjClass
|
override val objClass: ObjClass
|
||||||
get() = type
|
get() = type
|
||||||
|
|
||||||
override suspend fun toKotlin(context: Context): Any {
|
override suspend fun toKotlin(scope: Scope): Any {
|
||||||
return list.map { it.toKotlin(context) }
|
return list.map { it.toKotlin(scope) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
|
|||||||
@@ -2,17 +2,17 @@ package net.sergeych.lyng
|
|||||||
|
|
||||||
class ObjMapEntry(val key: Obj, val value: Obj) : Obj() {
|
class ObjMapEntry(val key: Obj, val value: Obj) : Obj() {
|
||||||
|
|
||||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
if (other !is ObjMapEntry) return -1
|
if (other !is ObjMapEntry) return -1
|
||||||
val c = key.compareTo(context, other.key)
|
val c = key.compareTo(scope, other.key)
|
||||||
if (c != 0) return c
|
if (c != 0) return c
|
||||||
return value.compareTo(context, other.value)
|
return value.compareTo(scope, other.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getAt(context: Context, index: Obj): Obj = when (index.toInt()) {
|
override suspend fun getAt(scope: Scope, index: Obj): Obj = when (index.toInt()) {
|
||||||
0 -> key
|
0 -> key
|
||||||
1 -> value
|
1 -> value
|
||||||
else -> context.raiseIndexOutOfBounds()
|
else -> scope.raiseIndexOutOfBounds()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
@@ -23,8 +23,8 @@ class ObjMapEntry(val key: Obj, val value: Obj) : Obj() {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val type = object : ObjClass("MapEntry", ObjArray) {
|
val type = object : ObjClass("MapEntry", ObjArray) {
|
||||||
override suspend fun callOn(context: Context): Obj {
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
return ObjMapEntry(context.requiredArg<Obj>(0), context.requiredArg<Obj>(1))
|
return ObjMapEntry(scope.requiredArg<Obj>(0), scope.requiredArg<Obj>(1))
|
||||||
}
|
}
|
||||||
}.apply {
|
}.apply {
|
||||||
addFn("key") { thisAs<ObjMapEntry>().key }
|
addFn("key") { thisAs<ObjMapEntry>().key }
|
||||||
@@ -34,18 +34,18 @@ class ObjMapEntry(val key: Obj, val value: Obj) : Obj() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ObjMap(val map: MutableMap<Obj, Obj>) : Obj() {
|
class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
|
||||||
|
|
||||||
override val objClass = type
|
override val objClass = type
|
||||||
|
|
||||||
override suspend fun getAt(context: Context, index: Obj): Obj =
|
override suspend fun getAt(scope: Scope, index: Obj): Obj =
|
||||||
map.getOrElse(index) { context.raiseNoSuchElement() }
|
map.getOrElse(index) { scope.raiseNoSuchElement() }
|
||||||
|
|
||||||
override suspend fun contains(context: Context, other: Obj): Boolean {
|
override suspend fun contains(scope: Scope, other: Obj): Boolean {
|
||||||
return other in map
|
return other in map
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
if( other is ObjMap && other.map == map) return 0
|
if( other is ObjMap && other.map == map) return 0
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
@@ -53,30 +53,30 @@ class ObjMap(val map: MutableMap<Obj, Obj>) : Obj() {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
suspend fun listToMap(context: Context, list: List<Obj>): MutableMap<Obj, Obj> {
|
suspend fun listToMap(scope: Scope, list: List<Obj>): MutableMap<Obj, Obj> {
|
||||||
val map = mutableMapOf<Obj, Obj>()
|
val map = mutableMapOf<Obj, Obj>()
|
||||||
if (list.isEmpty()) return map
|
if (list.isEmpty()) return map
|
||||||
|
|
||||||
val first = list.first()
|
val first = list.first()
|
||||||
if (first.isInstanceOf(ObjArray)) {
|
if (first.isInstanceOf(ObjArray)) {
|
||||||
if (first.invokeInstanceMethod(context, "size").toInt() != 2)
|
if (first.invokeInstanceMethod(scope, "size").toInt() != 2)
|
||||||
context.raiseIllegalArgument(
|
scope.raiseIllegalArgument(
|
||||||
"list to construct map entry should exactly be 2 element Array like [key,value], got $list"
|
"list to construct map entry should exactly be 2 element Array like [key,value], got $list"
|
||||||
)
|
)
|
||||||
} else context.raiseIllegalArgument("first element of map list be a Collection of 2 elements; got $first")
|
} else scope.raiseIllegalArgument("first element of map list be a Collection of 2 elements; got $first")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
list.forEach {
|
list.forEach {
|
||||||
map[it.getAt(context, ObjInt.Zero)] = it.getAt(context, ObjInt.One)
|
map[it.getAt(scope, ObjInt.Zero)] = it.getAt(scope, ObjInt.One)
|
||||||
}
|
}
|
||||||
return map
|
return map
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
val type = object : ObjClass("Map", ObjCollection) {
|
val type = object : ObjClass("Map", ObjCollection) {
|
||||||
override suspend fun callOn(context: Context): Obj {
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
return ObjMap(listToMap(context, context.args.list))
|
return ObjMap(listToMap(scope, scope.args.list))
|
||||||
}
|
}
|
||||||
}.apply {
|
}.apply {
|
||||||
addFn("getOrNull") {
|
addFn("getOrNull") {
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob
|
|||||||
return result.toString()
|
return result.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun containsRange(context: Context, other: ObjRange): Boolean {
|
suspend fun containsRange(scope: Scope, other: ObjRange): Boolean {
|
||||||
if (start != null) {
|
if (start != null) {
|
||||||
// our start is not -∞ so other start should be GTE or is not contained:
|
// 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 (other.start != null && start.compareTo(scope, other.start) > 0) return false
|
||||||
}
|
}
|
||||||
if (end != null) {
|
if (end != null) {
|
||||||
// same with the end: if it is open, it can't be contained in ours:
|
// same with the end: if it is open, it can't be contained in ours:
|
||||||
@@ -26,16 +26,16 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob
|
|||||||
// both exists, now there could be 4 cases:
|
// both exists, now there could be 4 cases:
|
||||||
return when {
|
return when {
|
||||||
other.isEndInclusive && isEndInclusive ->
|
other.isEndInclusive && isEndInclusive ->
|
||||||
end.compareTo(context, other.end) >= 0
|
end.compareTo(scope, other.end) >= 0
|
||||||
|
|
||||||
!other.isEndInclusive && !isEndInclusive ->
|
!other.isEndInclusive && !isEndInclusive ->
|
||||||
end.compareTo(context, other.end) >= 0
|
end.compareTo(scope, other.end) >= 0
|
||||||
|
|
||||||
other.isEndInclusive && !isEndInclusive ->
|
other.isEndInclusive && !isEndInclusive ->
|
||||||
end.compareTo(context, other.end) > 0
|
end.compareTo(scope, other.end) > 0
|
||||||
|
|
||||||
!other.isEndInclusive && isEndInclusive ->
|
!other.isEndInclusive && isEndInclusive ->
|
||||||
end.compareTo(context, other.end) >= 0
|
end.compareTo(scope, other.end) >= 0
|
||||||
|
|
||||||
else -> throw IllegalStateException("unknown comparison")
|
else -> throw IllegalStateException("unknown comparison")
|
||||||
}
|
}
|
||||||
@@ -43,17 +43,17 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun contains(context: Context, other: Obj): Boolean {
|
override suspend fun contains(scope: Scope, other: Obj): Boolean {
|
||||||
|
|
||||||
if (other is ObjRange)
|
if (other is ObjRange)
|
||||||
return containsRange(context, other)
|
return containsRange(scope, other)
|
||||||
|
|
||||||
if (start == null && end == null) return true
|
if (start == null && end == null) return true
|
||||||
if (start != null) {
|
if (start != null) {
|
||||||
if (start.compareTo(context, other) > 0) return false
|
if (start.compareTo(scope, other) > 0) return false
|
||||||
}
|
}
|
||||||
if (end != null) {
|
if (end != null) {
|
||||||
val cmp = end.compareTo(context, other)
|
val cmp = end.compareTo(scope, other)
|
||||||
if (isEndInclusive && cmp < 0 || !isEndInclusive && cmp <= 0) return false
|
if (isEndInclusive && cmp < 0 || !isEndInclusive && cmp <= 0) return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
@@ -67,7 +67,7 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob
|
|||||||
start is ObjChar && end is ObjChar
|
start is ObjChar && end is ObjChar
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
return (other as? ObjRange)?.let {
|
return (other as? ObjRange)?.let {
|
||||||
if( start == other.start && end == other.end ) 0 else -1
|
if( start == other.start && end == other.end ) 0 else -1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class ObjRangeIterator(val self: ObjRange) : Obj() {
|
|||||||
|
|
||||||
override val objClass: ObjClass = type
|
override val objClass: ObjClass = type
|
||||||
|
|
||||||
fun Context.init() {
|
fun Scope.init() {
|
||||||
if (self.start == null || self.end == null)
|
if (self.start == null || self.end == null)
|
||||||
raiseError("next is only available for finite ranges")
|
raiseError("next is only available for finite ranges")
|
||||||
isCharRange = self.isCharRange
|
isCharRange = self.isCharRange
|
||||||
@@ -24,7 +24,7 @@ class ObjRangeIterator(val self: ObjRange) : Obj() {
|
|||||||
|
|
||||||
fun hasNext(): Boolean = nextIndex < lastIndex
|
fun hasNext(): Boolean = nextIndex < lastIndex
|
||||||
|
|
||||||
fun next(context: Context): Obj =
|
fun next(scope: Scope): Obj =
|
||||||
if (nextIndex < lastIndex) {
|
if (nextIndex < lastIndex) {
|
||||||
val x = if (self.isEndInclusive)
|
val x = if (self.isEndInclusive)
|
||||||
self.start!!.toLong() + nextIndex++
|
self.start!!.toLong() + nextIndex++
|
||||||
@@ -33,7 +33,7 @@ class ObjRangeIterator(val self: ObjRange) : Obj() {
|
|||||||
if( isCharRange ) ObjChar(x.toInt().toChar()) else ObjInt(x)
|
if( isCharRange ) ObjChar(x.toInt().toChar()) else ObjInt(x)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
context.raiseError(ObjIterationFinishedException(context))
|
scope.raiseError(ObjIterationFinishedException(scope))
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
|
|||||||
|
|
||||||
override fun byValueCopy(): Obj = ObjReal(value)
|
override fun byValueCopy(): Obj = ObjReal(value)
|
||||||
|
|
||||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
if (other !is Numeric) return -2
|
if (other !is Numeric) return -2
|
||||||
return value.compareTo(other.doubleValue)
|
return value.compareTo(other.doubleValue)
|
||||||
}
|
}
|
||||||
@@ -25,25 +25,25 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
|
|||||||
return value.hashCode()
|
return value.hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun plus(context: Context, other: Obj): Obj =
|
override suspend fun plus(scope: Scope, other: Obj): Obj =
|
||||||
ObjReal(this.value + other.toDouble())
|
ObjReal(this.value + other.toDouble())
|
||||||
|
|
||||||
override suspend fun minus(context: Context, other: Obj): Obj =
|
override suspend fun minus(scope: Scope, other: Obj): Obj =
|
||||||
ObjReal(this.value - other.toDouble())
|
ObjReal(this.value - other.toDouble())
|
||||||
|
|
||||||
override suspend fun mul(context: Context, other: Obj): Obj =
|
override suspend fun mul(scope: Scope, other: Obj): Obj =
|
||||||
ObjReal(this.value * other.toDouble())
|
ObjReal(this.value * other.toDouble())
|
||||||
|
|
||||||
override suspend fun div(context: Context, other: Obj): Obj =
|
override suspend fun div(scope: Scope, other: Obj): Obj =
|
||||||
ObjReal(this.value / other.toDouble())
|
ObjReal(this.value / other.toDouble())
|
||||||
|
|
||||||
override suspend fun mod(context: Context, other: Obj): Obj =
|
override suspend fun mod(scope: Scope, other: Obj): Obj =
|
||||||
ObjReal(this.value % other.toDouble())
|
ObjReal(this.value % other.toDouble())
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns unboxed Double value
|
* Returns unboxed Double value
|
||||||
*/
|
*/
|
||||||
override suspend fun toKotlin(context: Context): Any {
|
override suspend fun toKotlin(scope: Scope): Any {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,6 +64,9 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
|
|||||||
(it.thisObj as ObjReal).value.roundToLong().toObj()
|
(it.thisObj as ObjReal).value.roundToLong().toObj()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
addFn("toInt") {
|
||||||
|
ObjInt(thisAs<ObjReal>().value.toLong())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4,11 +4,11 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
|
|||||||
|
|
||||||
override val objClass = type
|
override val objClass = type
|
||||||
|
|
||||||
override suspend fun contains(context: Context, other: Obj): Boolean {
|
override suspend fun contains(scope: Scope, other: Obj): Boolean {
|
||||||
return set.contains(other)
|
return set.contains(other)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun plus(context: Context, other: Obj): Obj {
|
override suspend fun plus(scope: Scope, other: Obj): Obj {
|
||||||
return ObjSet(
|
return ObjSet(
|
||||||
if (other is ObjSet)
|
if (other is ObjSet)
|
||||||
(set + other.set).toMutableSet()
|
(set + other.set).toMutableSet()
|
||||||
@@ -17,7 +17,7 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun plusAssign(context: Context, other: Obj): Obj {
|
override suspend fun plusAssign(scope: Scope, other: Obj): Obj {
|
||||||
when (other) {
|
when (other) {
|
||||||
is ObjSet -> {
|
is ObjSet -> {
|
||||||
set += other.set
|
set += other.set
|
||||||
@@ -29,9 +29,9 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
|
|||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
if (other.isInstanceOf(ObjIterable)) {
|
if (other.isInstanceOf(ObjIterable)) {
|
||||||
val i = other.invokeInstanceMethod(context, "iterable")
|
val i = other.invokeInstanceMethod(scope, "iterable")
|
||||||
while (i.invokeInstanceMethod(context, "hasNext").toBool()) {
|
while (i.invokeInstanceMethod(scope, "hasNext").toBool()) {
|
||||||
set += i.invokeInstanceMethod(context, "next")
|
set += i.invokeInstanceMethod(scope, "next")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
set += other
|
set += other
|
||||||
@@ -40,16 +40,16 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun mul(context: Context, other: Obj): Obj {
|
override suspend fun mul(scope: Scope, other: Obj): Obj {
|
||||||
return if (other is ObjSet) {
|
return if (other is ObjSet) {
|
||||||
ObjSet(set.intersect(other.set).toMutableSet())
|
ObjSet(set.intersect(other.set).toMutableSet())
|
||||||
} else
|
} else
|
||||||
context.raiseIllegalArgument("set operator * requires another set")
|
scope.raiseIllegalArgument("set operator * requires another set")
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun minus(context: Context, other: Obj): Obj {
|
override suspend fun minus(scope: Scope, other: Obj): Obj {
|
||||||
if (other !is ObjSet)
|
if (other !is ObjSet)
|
||||||
context.raiseIllegalArgument("set operator - requires another set")
|
scope.raiseIllegalArgument("set operator - requires another set")
|
||||||
return ObjSet(set.minus(other.set).toMutableSet())
|
return ObjSet(set.minus(other.set).toMutableSet())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
|
|||||||
return "Set(${set.joinToString(", ")})"
|
return "Set(${set.joinToString(", ")})"
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
return if (other !is ObjSet) -1
|
return if (other !is ObjSet) -1
|
||||||
else {
|
else {
|
||||||
if (set == other.set) 0
|
if (set == other.set) 0
|
||||||
@@ -69,8 +69,8 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
|
|||||||
|
|
||||||
|
|
||||||
val type = object : ObjClass("Set", ObjCollection) {
|
val type = object : ObjClass("Set", ObjCollection) {
|
||||||
override suspend fun callOn(context: Context): Obj {
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
return ObjSet(context.args.list.toMutableSet())
|
return ObjSet(scope.args.list.toMutableSet())
|
||||||
}
|
}
|
||||||
}.apply {
|
}.apply {
|
||||||
addFn("size") {
|
addFn("size") {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ data class ObjString(val value: String) : Obj() {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
if (other !is ObjString) return -2
|
if (other !is ObjString) return -2
|
||||||
return this.value.compareTo(other.value)
|
return this.value.compareTo(other.value)
|
||||||
}
|
}
|
||||||
@@ -32,11 +32,11 @@ data class ObjString(val value: String) : Obj() {
|
|||||||
override val objClass: ObjClass
|
override val objClass: ObjClass
|
||||||
get() = type
|
get() = type
|
||||||
|
|
||||||
override suspend fun plus(context: Context, other: Obj): Obj {
|
override suspend fun plus(scope: Scope, other: Obj): Obj {
|
||||||
return ObjString(value + other.asStr.value)
|
return ObjString(value + other.asStr.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getAt(context: Context, index: Obj): Obj {
|
override suspend fun getAt(scope: Scope, index: Obj): Obj {
|
||||||
if( index is ObjInt ) return ObjChar(value[index.toInt()])
|
if( index is ObjInt ) return ObjChar(value[index.toInt()])
|
||||||
if( index is ObjRange ) {
|
if( index is ObjRange ) {
|
||||||
val start = if(index.start == null || index.start.isNull) 0 else index.start.toInt()
|
val start = if(index.start == null || index.start.isNull) 0 else index.start.toInt()
|
||||||
@@ -46,23 +46,23 @@ data class ObjString(val value: String) : Obj() {
|
|||||||
}
|
}
|
||||||
return ObjString(value.substring(start, end))
|
return ObjString(value.substring(start, end))
|
||||||
}
|
}
|
||||||
context.raiseIllegalArgument("String index must be Int or Range")
|
scope.raiseIllegalArgument("String index must be Int or Range")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
return value.hashCode()
|
return value.hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun callOn(context: Context): Obj {
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
return ObjString(this.value.sprintf(*context.args.toKotlinList(context).toTypedArray()))
|
return ObjString(this.value.sprintf(*scope.args.toKotlinList(scope).toTypedArray()))
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun contains(context: Context, other: Obj): Boolean {
|
override suspend fun contains(scope: Scope, other: Obj): Boolean {
|
||||||
return if (other is ObjString)
|
return if (other is ObjString)
|
||||||
value.contains(other.value)
|
value.contains(other.value)
|
||||||
else if (other is ObjChar)
|
else if (other is ObjChar)
|
||||||
value.contains(other.value)
|
value.contains(other.value)
|
||||||
else context.raiseIllegalArgument("String.contains can't take $other")
|
else scope.raiseIllegalArgument("String.contains can't take $other")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
@@ -76,6 +76,9 @@ data class ObjString(val value: String) : Obj() {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val type = ObjClass("String").apply {
|
val type = ObjClass("String").apply {
|
||||||
|
addFn("toInt") {
|
||||||
|
ObjInt(thisAs<ObjString>().value.toLong())
|
||||||
|
}
|
||||||
addFn("startsWith") {
|
addFn("startsWith") {
|
||||||
ObjBool(thisAs<ObjString>().value.startsWith(requiredArg<ObjString>(0).value))
|
ObjBool(thisAs<ObjString>().value.startsWith(requiredArg<ObjString>(0).value))
|
||||||
}
|
}
|
||||||
@@ -112,6 +115,7 @@ data class ObjString(val value: String) : Obj() {
|
|||||||
thisAs<ObjString>().value.uppercase().let(::ObjString)
|
thisAs<ObjString>().value.uppercase().let(::ObjString)
|
||||||
}
|
}
|
||||||
addFn("size") { ObjInt(thisAs<ObjString>().value.length.toLong()) }
|
addFn("size") { ObjInt(thisAs<ObjString>().value.length.toLong()) }
|
||||||
|
addFn("toReal") { ObjReal(thisAs<ObjString>().value.toDouble())}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+36
-25
@@ -1,26 +1,26 @@
|
|||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
class Context(
|
/**
|
||||||
val parent: Context?,
|
* Scope is where local variables and methods are stored. Scope is also a parent scope for other scopes.
|
||||||
|
* Each block usually creates a scope. Accessing Lyng closures usually is done via a scope.
|
||||||
|
*
|
||||||
|
* There are special types of scopes:
|
||||||
|
*
|
||||||
|
* - [Script.defaultScope] - root scope for a script, safe one
|
||||||
|
* - [AppliedScope] - scope used to apply a closure to some thisObj scope
|
||||||
|
*/
|
||||||
|
open class Scope(
|
||||||
|
val parent: Scope?,
|
||||||
val args: Arguments = Arguments.EMPTY,
|
val args: Arguments = Arguments.EMPTY,
|
||||||
var pos: Pos = Pos.builtIn,
|
var pos: Pos = Pos.builtIn,
|
||||||
var thisObj: Obj = ObjVoid,
|
var thisObj: Obj = ObjVoid,
|
||||||
var skipContextCreation: Boolean = false,
|
var skipScopeCreation: Boolean = false,
|
||||||
) {
|
) {
|
||||||
constructor(
|
constructor(
|
||||||
args: Arguments = Arguments.EMPTY,
|
args: Arguments = Arguments.EMPTY,
|
||||||
pos: Pos = Pos.builtIn,
|
pos: Pos = Pos.builtIn,
|
||||||
)
|
)
|
||||||
: this(Script.defaultContext, args, pos)
|
: this(Script.defaultScope, args, pos)
|
||||||
|
|
||||||
/**
|
|
||||||
* Making this context priority one
|
|
||||||
*/
|
|
||||||
fun applyContext(other: Context): Context {
|
|
||||||
if (other.thisObj != ObjVoid) thisObj = other.thisObj
|
|
||||||
appliedContext = other
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
fun raiseNotImplemented(what: String = "operation"): Nothing = raiseError("$what is not implemented")
|
fun raiseNotImplemented(what: String = "operation"): Nothing = raiseError("$what is not implemented")
|
||||||
|
|
||||||
@@ -74,24 +74,23 @@ class Context(
|
|||||||
inline fun <reified T : Obj> thisAs(): T = (thisObj as? T)
|
inline fun <reified T : Obj> thisAs(): T = (thisObj as? T)
|
||||||
?: raiseClassCastError("Cannot cast ${thisObj.objClass.className} to ${T::class.simpleName}")
|
?: raiseClassCastError("Cannot cast ${thisObj.objClass.className} to ${T::class.simpleName}")
|
||||||
|
|
||||||
internal var appliedContext: Context? = null
|
|
||||||
internal val objects = mutableMapOf<String, ObjRecord>()
|
internal val objects = mutableMapOf<String, ObjRecord>()
|
||||||
|
|
||||||
operator fun get(name: String): ObjRecord? =
|
open operator fun get(name: String): ObjRecord? =
|
||||||
if (name == "this") thisObj.asReadonly
|
if (name == "this") thisObj.asReadonly
|
||||||
else {
|
else {
|
||||||
objects[name]
|
objects[name]
|
||||||
?: parent?.get(name)
|
?: parent?.get(name)
|
||||||
?: appliedContext?.get(name)
|
?: thisObj.objClass.getInstanceMemberOrNull(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun copy(pos: Pos, args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Context =
|
fun copy(pos: Pos, args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Scope =
|
||||||
Context(this, args, pos, newThisObj ?: thisObj)
|
Scope(this, args, pos, newThisObj ?: thisObj)
|
||||||
|
|
||||||
fun copy(args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Context =
|
fun copy(args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Scope =
|
||||||
Context(this, args, pos, newThisObj ?: thisObj)
|
Scope(this, args, pos, newThisObj ?: thisObj)
|
||||||
|
|
||||||
fun copy() = Context(this, args, pos, thisObj)
|
fun copy() = Scope(this, args, pos, thisObj)
|
||||||
|
|
||||||
fun addItem(
|
fun addItem(
|
||||||
name: String,
|
name: String,
|
||||||
@@ -107,18 +106,18 @@ class Context(
|
|||||||
return ns.objClass
|
return ns.objClass
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun addVoidFn(vararg names: String, crossinline fn: suspend Context.() -> Unit) {
|
inline fun addVoidFn(vararg names: String, crossinline fn: suspend Scope.() -> Unit) {
|
||||||
addFn<ObjVoid>(*names) {
|
addFn<ObjVoid>(*names) {
|
||||||
fn(this)
|
fn(this)
|
||||||
ObjVoid
|
ObjVoid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified T : Obj> addFn(vararg names: String, crossinline fn: suspend Context.() -> T) {
|
inline fun <reified T : Obj> addFn(vararg names: String, crossinline fn: suspend Scope.() -> T) {
|
||||||
val newFn = object : Statement() {
|
val newFn = object : Statement() {
|
||||||
override val pos: Pos = Pos.builtIn
|
override val pos: Pos = Pos.builtIn
|
||||||
|
|
||||||
override suspend fun execute(context: Context): Obj = context.fn()
|
override suspend fun execute(scope: Scope): Obj = scope.fn()
|
||||||
|
|
||||||
}
|
}
|
||||||
for (name in names) {
|
for (name in names) {
|
||||||
@@ -133,9 +132,21 @@ class Context(
|
|||||||
fun addConst(name: String, value: Obj) = addItem(name, false, value)
|
fun addConst(name: String, value: Obj) = addItem(name, false, value)
|
||||||
|
|
||||||
suspend fun eval(code: String): Obj =
|
suspend fun eval(code: String): Obj =
|
||||||
Compiler().compile(code.toSource()).execute(this)
|
Compiler.compile(code.toSource()).execute(this)
|
||||||
|
|
||||||
|
suspend fun eval(source: Source): Obj =
|
||||||
|
Compiler.compile(
|
||||||
|
source,
|
||||||
|
(this as? ModuleScope)?.pacman?.also { println("pacman found: $pacman")} ?: Pacman.emptyAllowAll
|
||||||
|
).execute(this)
|
||||||
|
|
||||||
fun containsLocal(name: String): Boolean = name in objects
|
fun containsLocal(name: String): Boolean = name in objects
|
||||||
|
|
||||||
|
open suspend fun checkImport(pos: Pos, name: String, symbols: Map<String, String>? = null) {
|
||||||
|
throw ImportException(pos, "Import is not allowed here: $name")
|
||||||
|
}
|
||||||
|
|
||||||
|
open suspend fun importInto(scope: Scope, name: String, symbols: Map<String, String>? = null) {
|
||||||
|
scope.raiseError(ObjIllegalOperationException(scope,"Import is not allowed here: import $name"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -9,18 +9,18 @@ class Script(
|
|||||||
// private val catchReturn: Boolean = false,
|
// private val catchReturn: Boolean = false,
|
||||||
) : Statement() {
|
) : Statement() {
|
||||||
|
|
||||||
override suspend fun execute(context: Context): Obj {
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
var lastResult: Obj = ObjVoid
|
var lastResult: Obj = ObjVoid
|
||||||
for (s in statements) {
|
for (s in statements) {
|
||||||
lastResult = s.execute(context)
|
lastResult = s.execute(scope)
|
||||||
}
|
}
|
||||||
return lastResult
|
return lastResult
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun execute() = execute(defaultContext.copy(pos = pos))
|
suspend fun execute() = execute(defaultScope.copy(pos = pos))
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val defaultContext: Context = Context().apply {
|
val defaultScope: Scope = Scope().apply {
|
||||||
ObjException.addExceptionsToContext(this)
|
ObjException.addExceptionsToContext(this)
|
||||||
addFn("println") {
|
addFn("println") {
|
||||||
for ((i, a) in args.withIndex()) {
|
for ((i, a) in args.withIndex()) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
open class ScriptError(val pos: Pos, val errorMessage: String,cause: Throwable?=null) : Exception(
|
open class ScriptError(val pos: Pos, val errorMessage: String, cause: Throwable? = null) : Exception(
|
||||||
"""
|
"""
|
||||||
$pos: Error: $errorMessage
|
$pos: Error: $errorMessage
|
||||||
|
|
||||||
@@ -12,4 +12,6 @@ open class ScriptError(val pos: Pos, val errorMessage: String,cause: Throwable?=
|
|||||||
cause
|
cause
|
||||||
)
|
)
|
||||||
|
|
||||||
class ExecutionError(val errorObject: ObjException) : ScriptError(errorObject.context.pos, errorObject.message)
|
class ExecutionError(val errorObject: ObjException) : ScriptError(errorObject.scope.pos, errorObject.message)
|
||||||
|
|
||||||
|
class ImportException(pos: Pos, message: String) : ScriptError(pos, message)
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
interface SecurityManager {
|
||||||
|
/**
|
||||||
|
* Check that any symbol from the corresponding module can be imported. If it returns false,
|
||||||
|
* the module will not be imported and no further processing will be done
|
||||||
|
*/
|
||||||
|
fun canImportModule(name: String): Boolean
|
||||||
|
|
||||||
|
/**\
|
||||||
|
* if [canImportModule] this method allows fine-grained control over symbols.
|
||||||
|
*/
|
||||||
|
fun canImportSymbol(moduleName: String, symbolName: String): Boolean = true
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val allowAll: SecurityManager = object : SecurityManager {
|
||||||
|
override fun canImportModule(name: String): Boolean = true
|
||||||
|
override fun canImportSymbol(moduleName: String, symbolName: String): Boolean = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,9 +1,14 @@
|
|||||||
|
@file:Suppress("unused")
|
||||||
|
|
||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
// this is highly experimental and subject to complete redesign
|
// this is highly experimental and subject to complete redesign
|
||||||
// very soon
|
// very soon
|
||||||
sealed class TypeDecl {
|
sealed class TypeDecl(val isNullable:Boolean = false) {
|
||||||
// ??
|
// ??
|
||||||
// data class Fn(val argTypes: List<ArgsDeclaration.Item>, val retType: TypeDecl) : TypeDecl()
|
// data class Fn(val argTypes: List<ArgsDeclaration.Item>, val retType: TypeDecl) : TypeDecl()
|
||||||
object Obj : TypeDecl()
|
object TypeAny : TypeDecl()
|
||||||
|
object TypeNullableAny : TypeDecl(true)
|
||||||
|
|
||||||
|
class Simple(val name: String,isNullable: Boolean) : TypeDecl(isNullable)
|
||||||
}
|
}
|
||||||
@@ -18,14 +18,14 @@ abstract class Statement(
|
|||||||
override val objClass: ObjClass = type
|
override val objClass: ObjClass = type
|
||||||
|
|
||||||
abstract val pos: Pos
|
abstract val pos: Pos
|
||||||
abstract suspend fun execute(context: Context): Obj
|
abstract suspend fun execute(scope: Scope): Obj
|
||||||
|
|
||||||
override suspend fun compareTo(context: Context,other: Obj): Int {
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
throw UnsupportedOperationException("not comparable")
|
throw UnsupportedOperationException("not comparable")
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun callOn(context: Context): Obj {
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
return execute(context)
|
return execute(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String = "Callable@${this.hashCode()}"
|
override fun toString(): String = "Callable@${this.hashCode()}"
|
||||||
@@ -34,6 +34,8 @@ abstract class Statement(
|
|||||||
val type = ObjClass("Callable")
|
val type = ObjClass("Callable")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun call(scope: Scope, vararg args: Obj) = execute(scope.copy(args = Arguments(*args)))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Statement.raise(text: String): Nothing {
|
fun Statement.raise(text: String): Nothing {
|
||||||
@@ -45,16 +47,16 @@ fun Statement.require(cond: Boolean, message: () -> String) {
|
|||||||
if (!cond) raise(message())
|
if (!cond) raise(message())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun statement(pos: Pos, isStaticConst: Boolean = false, isConst: Boolean = false, f: suspend (Context) -> Obj): Statement =
|
fun statement(pos: Pos, isStaticConst: Boolean = false, isConst: Boolean = false, f: suspend (Scope) -> Obj): Statement =
|
||||||
object : Statement(isStaticConst, isConst) {
|
object : Statement(isStaticConst, isConst) {
|
||||||
override val pos: Pos = pos
|
override val pos: Pos = pos
|
||||||
override suspend fun execute(context: Context): Obj = f(context)
|
override suspend fun execute(scope: Scope): Obj = f(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun statement(isStaticConst: Boolean = false, isConst: Boolean = false, f: suspend Context.() -> Obj): Statement =
|
fun statement(isStaticConst: Boolean = false, isConst: Boolean = false, f: suspend Scope.() -> Obj): Statement =
|
||||||
object : Statement(isStaticConst, isConst) {
|
object : Statement(isStaticConst, isConst) {
|
||||||
override val pos: Pos = Pos.builtIn
|
override val pos: Pos = Pos.builtIn
|
||||||
override suspend fun execute(context: Context): Obj = f(context)
|
override suspend fun execute(scope: Scope): Obj = f(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package io.github.kotlin.fibonacci
|
|
||||||
|
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.toList
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import net.sergeych.lyng.*
|
import net.sergeych.lyng.*
|
||||||
import kotlin.test.*
|
import kotlin.test.*
|
||||||
@@ -172,55 +173,55 @@ class ScriptTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun varsAndConstsTest() = runTest {
|
fun varsAndConstsTest() = runTest {
|
||||||
val context = Context(pos = Pos.builtIn)
|
val scope = Scope(pos = Pos.builtIn)
|
||||||
assertEquals(
|
assertEquals(
|
||||||
ObjInt(3L), context.eval(
|
ObjInt(3L), scope.eval(
|
||||||
"""
|
"""
|
||||||
val a = 17
|
val a = 17
|
||||||
var b = 3
|
var b = 3
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
assertEquals(17, context.eval("a").toInt())
|
assertEquals(17, scope.eval("a").toInt())
|
||||||
assertEquals(20, context.eval("b + a").toInt())
|
assertEquals(20, scope.eval("b + a").toInt())
|
||||||
assertFailsWith<ScriptError> {
|
assertFailsWith<ScriptError> {
|
||||||
context.eval("a = 10")
|
scope.eval("a = 10")
|
||||||
}
|
}
|
||||||
assertEquals(17, context.eval("a").toInt())
|
assertEquals(17, scope.eval("a").toInt())
|
||||||
assertEquals(5, context.eval("b = a - 7 - 5").toInt())
|
assertEquals(5, scope.eval("b = a - 7 - 5").toInt())
|
||||||
assertEquals(5, context.eval("b").toInt())
|
assertEquals(5, scope.eval("b").toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun functionTest() = runTest {
|
fun functionTest() = runTest {
|
||||||
val context = Context(pos = Pos.builtIn)
|
val scope = Scope(pos = Pos.builtIn)
|
||||||
context.eval(
|
scope.eval(
|
||||||
"""
|
"""
|
||||||
fun foo(a, b) {
|
fun foo(a, b) {
|
||||||
a + b
|
a + b
|
||||||
}
|
}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
assertEquals(17, context.eval("foo(3,14)").toInt())
|
assertEquals(17, scope.eval("foo(3,14)").toInt())
|
||||||
assertFailsWith<ScriptError> {
|
assertFailsWith<ScriptError> {
|
||||||
assertEquals(17, context.eval("foo(3)").toInt())
|
assertEquals(17, scope.eval("foo(3)").toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
context.eval(
|
scope.eval(
|
||||||
"""
|
"""
|
||||||
fn bar(a, b=10) {
|
fn bar(a, b=10) {
|
||||||
a + b + 1
|
a + b + 1
|
||||||
}
|
}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
assertEquals(10, context.eval("bar(3, 6)").toInt())
|
assertEquals(10, scope.eval("bar(3, 6)").toInt())
|
||||||
assertEquals(14, context.eval("bar(3)").toInt())
|
assertEquals(14, scope.eval("bar(3)").toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun simpleClosureTest() = runTest {
|
fun simpleClosureTest() = runTest {
|
||||||
val context = Context(pos = Pos.builtIn)
|
val scope = Scope(pos = Pos.builtIn)
|
||||||
context.eval(
|
scope.eval(
|
||||||
"""
|
"""
|
||||||
var global = 10
|
var global = 10
|
||||||
|
|
||||||
@@ -229,16 +230,16 @@ class ScriptTest {
|
|||||||
}
|
}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
assertEquals(27, context.eval("foo(3,14)").toInt())
|
assertEquals(27, scope.eval("foo(3,14)").toInt())
|
||||||
context.eval("global = 20")
|
scope.eval("global = 20")
|
||||||
assertEquals(37, context.eval("foo(3,14)").toInt())
|
assertEquals(37, scope.eval("foo(3,14)").toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun nullAndVoidTest() = runTest {
|
fun nullAndVoidTest() = runTest {
|
||||||
val context = Context(pos = Pos.builtIn)
|
val scope = Scope(pos = Pos.builtIn)
|
||||||
assertEquals(ObjVoid, context.eval("void"))
|
assertEquals(ObjVoid, scope.eval("void"))
|
||||||
assertEquals(ObjNull, context.eval("null"))
|
assertEquals(ObjNull, scope.eval("null"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -345,8 +346,8 @@ class ScriptTest {
|
|||||||
@Test
|
@Test
|
||||||
fun ifTest() = runTest {
|
fun ifTest() = runTest {
|
||||||
// if - single line
|
// if - single line
|
||||||
var context = Context(pos = Pos.builtIn)
|
var scope = Scope(pos = Pos.builtIn)
|
||||||
context.eval(
|
scope.eval(
|
||||||
"""
|
"""
|
||||||
fn test1(n) {
|
fn test1(n) {
|
||||||
var result = "more"
|
var result = "more"
|
||||||
@@ -356,12 +357,12 @@ class ScriptTest {
|
|||||||
}
|
}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
assertEquals("enough", context.eval("test1(11)").toString())
|
assertEquals("enough", scope.eval("test1(11)").toString())
|
||||||
assertEquals("more", context.eval("test1(1)").toString())
|
assertEquals("more", scope.eval("test1(1)").toString())
|
||||||
|
|
||||||
// if - multiline (block)
|
// if - multiline (block)
|
||||||
context = Context(pos = Pos.builtIn)
|
scope = Scope(pos = Pos.builtIn)
|
||||||
context.eval(
|
scope.eval(
|
||||||
"""
|
"""
|
||||||
fn test1(n) {
|
fn test1(n) {
|
||||||
var prefix = "answer: "
|
var prefix = "answer: "
|
||||||
@@ -375,12 +376,12 @@ class ScriptTest {
|
|||||||
}
|
}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
assertEquals("answer: enough", context.eval("test1(11)").toString())
|
assertEquals("answer: enough", scope.eval("test1(11)").toString())
|
||||||
assertEquals("answer: more", context.eval("test1(1)").toString())
|
assertEquals("answer: more", scope.eval("test1(1)").toString())
|
||||||
|
|
||||||
// else single line1
|
// else single line1
|
||||||
context = Context(pos = Pos.builtIn)
|
scope = Scope(pos = Pos.builtIn)
|
||||||
context.eval(
|
scope.eval(
|
||||||
"""
|
"""
|
||||||
fn test1(n) {
|
fn test1(n) {
|
||||||
if( n >= 10 )
|
if( n >= 10 )
|
||||||
@@ -390,12 +391,12 @@ class ScriptTest {
|
|||||||
}
|
}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
assertEquals("enough", context.eval("test1(11)").toString())
|
assertEquals("enough", scope.eval("test1(11)").toString())
|
||||||
assertEquals("more", context.eval("test1(1)").toString())
|
assertEquals("more", scope.eval("test1(1)").toString())
|
||||||
|
|
||||||
// if/else with blocks
|
// if/else with blocks
|
||||||
context = Context(pos = Pos.builtIn)
|
scope = Scope(pos = Pos.builtIn)
|
||||||
context.eval(
|
scope.eval(
|
||||||
"""
|
"""
|
||||||
fn test1(n) {
|
fn test1(n) {
|
||||||
if( n > 20 ) {
|
if( n > 20 ) {
|
||||||
@@ -409,9 +410,9 @@ class ScriptTest {
|
|||||||
}
|
}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
assertEquals("enough", context.eval("test1(11)").toString())
|
assertEquals("enough", scope.eval("test1(11)").toString())
|
||||||
assertEquals("more", context.eval("test1(1)").toString())
|
assertEquals("more", scope.eval("test1(1)").toString())
|
||||||
assertEquals("too much", context.eval("test1(100)").toString())
|
assertEquals("too much", scope.eval("test1(100)").toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -511,13 +512,13 @@ class ScriptTest {
|
|||||||
ArgsDeclaration.Item("c"),
|
ArgsDeclaration.Item("c"),
|
||||||
), ttEnd
|
), ttEnd
|
||||||
)
|
)
|
||||||
var c = Context(pos = Pos.builtIn, args = Arguments.from(listOf(1, 2, 3).map { it.toObj() }))
|
var c = Scope(pos = Pos.builtIn, args = Arguments.from(listOf(1, 2, 3).map { it.toObj() }))
|
||||||
pa.assignToContext(c)
|
pa.assignToContext(c)
|
||||||
assertEquals(ObjInt(1), c["a"]?.value)
|
assertEquals(ObjInt(1), c["a"]?.value)
|
||||||
assertEquals(ObjInt(2), c["b"]?.value)
|
assertEquals(ObjInt(2), c["b"]?.value)
|
||||||
assertEquals(ObjInt(3), c["c"]?.value)
|
assertEquals(ObjInt(3), c["c"]?.value)
|
||||||
// less args: error
|
// less args: error
|
||||||
c = Context(pos = Pos.builtIn, args = Arguments.from(listOf(1, 2).map { it.toObj() }))
|
c = Scope(pos = Pos.builtIn, args = Arguments.from(listOf(1, 2).map { it.toObj() }))
|
||||||
assertFailsWith<ScriptError> {
|
assertFailsWith<ScriptError> {
|
||||||
pa.assignToContext(c)
|
pa.assignToContext(c)
|
||||||
}
|
}
|
||||||
@@ -534,7 +535,7 @@ class ScriptTest {
|
|||||||
assertEquals(ObjInt(2), c["b"]?.value)
|
assertEquals(ObjInt(2), c["b"]?.value)
|
||||||
assertEquals(ObjInt(100), c["c"]?.value)
|
assertEquals(ObjInt(100), c["c"]?.value)
|
||||||
// enough args. default value is ignored:
|
// enough args. default value is ignored:
|
||||||
c = Context(pos = Pos.builtIn, args = Arguments.from(listOf(10, 2, 5).map { it.toObj() }))
|
c = Scope(pos = Pos.builtIn, args = Arguments.from(listOf(10, 2, 5).map { it.toObj() }))
|
||||||
pa.assignToContext(c)
|
pa.assignToContext(c)
|
||||||
assertEquals(ObjInt(10), c["a"]?.value)
|
assertEquals(ObjInt(10), c["a"]?.value)
|
||||||
assertEquals(ObjInt(2), c["b"]?.value)
|
assertEquals(ObjInt(2), c["b"]?.value)
|
||||||
@@ -552,17 +553,17 @@ class ScriptTest {
|
|||||||
ArgsDeclaration.Item("b", isEllipsis = true),
|
ArgsDeclaration.Item("b", isEllipsis = true),
|
||||||
), ttEnd
|
), ttEnd
|
||||||
)
|
)
|
||||||
var c = Context(args = Arguments.from(listOf(1, 2, 3).map { it.toObj() }))
|
var c = Scope(args = Arguments.from(listOf(1, 2, 3).map { it.toObj() }))
|
||||||
pa.assignToContext(c)
|
pa.assignToContext(c)
|
||||||
c.eval("assert( a == 1 ); println(b)")
|
c.eval("assert( a == 1 ); println(b)")
|
||||||
c.eval("assert( b == [2,3] )")
|
c.eval("assert( b == [2,3] )")
|
||||||
|
|
||||||
c = Context(args = Arguments.from(listOf(1, 2).map { it.toObj() }))
|
c = Scope(args = Arguments.from(listOf(1, 2).map { it.toObj() }))
|
||||||
pa.assignToContext(c)
|
pa.assignToContext(c)
|
||||||
c.eval("assertEquals( a, 1 ); println(b)")
|
c.eval("assertEquals( a, 1 ); println(b)")
|
||||||
c.eval("assertEquals( b, [2] )")
|
c.eval("assertEquals( b, [2] )")
|
||||||
|
|
||||||
c = Context(args = Arguments.from(listOf(1).map { it.toObj() }))
|
c = Scope(args = Arguments.from(listOf(1).map { it.toObj() }))
|
||||||
pa.assignToContext(c)
|
pa.assignToContext(c)
|
||||||
c.eval("assert( a == 1 ); println(b)")
|
c.eval("assert( a == 1 ); println(b)")
|
||||||
c.eval("assert( b == [] )")
|
c.eval("assert( b == [] )")
|
||||||
@@ -578,25 +579,25 @@ class ScriptTest {
|
|||||||
ArgsDeclaration.Item("c"),
|
ArgsDeclaration.Item("c"),
|
||||||
), ttEnd
|
), ttEnd
|
||||||
)
|
)
|
||||||
var c = Context(args = Arguments.from(listOf(0, 1, 2, 3).map { it.toObj() }))
|
var c = Scope(args = Arguments.from(listOf(0, 1, 2, 3).map { it.toObj() }))
|
||||||
pa.assignToContext(c)
|
pa.assignToContext(c)
|
||||||
c.eval("assertEquals( a,[0,1] )")
|
c.eval("assertEquals( a,[0,1] )")
|
||||||
c.eval("assertEquals( b, 2 )")
|
c.eval("assertEquals( b, 2 )")
|
||||||
c.eval("assertEquals( c, 3 )")
|
c.eval("assertEquals( c, 3 )")
|
||||||
|
|
||||||
c = Context(args = Arguments.from(listOf(1, 2, 3).map { it.toObj() }))
|
c = Scope(args = Arguments.from(listOf(1, 2, 3).map { it.toObj() }))
|
||||||
pa.assignToContext(c)
|
pa.assignToContext(c)
|
||||||
c.eval("assertEquals( a,[1] )")
|
c.eval("assertEquals( a,[1] )")
|
||||||
c.eval("assertEquals( b, 2 )")
|
c.eval("assertEquals( b, 2 )")
|
||||||
c.eval("assertEquals( c, 3 )")
|
c.eval("assertEquals( c, 3 )")
|
||||||
|
|
||||||
c = Context(args = Arguments.from(listOf(2, 3).map { it.toObj() }))
|
c = Scope(args = Arguments.from(listOf(2, 3).map { it.toObj() }))
|
||||||
pa.assignToContext(c)
|
pa.assignToContext(c)
|
||||||
c.eval("assertEquals( a,[] )")
|
c.eval("assertEquals( a,[] )")
|
||||||
c.eval("assertEquals( b, 2 )")
|
c.eval("assertEquals( b, 2 )")
|
||||||
c.eval("assertEquals( c, 3 )")
|
c.eval("assertEquals( c, 3 )")
|
||||||
|
|
||||||
c = Context(args = Arguments.from(listOf(3).map { it.toObj() }))
|
c = Scope(args = Arguments.from(listOf(3).map { it.toObj() }))
|
||||||
assertFailsWith<ExecutionError> {
|
assertFailsWith<ExecutionError> {
|
||||||
pa.assignToContext(c)
|
pa.assignToContext(c)
|
||||||
}
|
}
|
||||||
@@ -613,28 +614,28 @@ class ScriptTest {
|
|||||||
ArgsDeclaration.Item("c"),
|
ArgsDeclaration.Item("c"),
|
||||||
), ttEnd
|
), ttEnd
|
||||||
)
|
)
|
||||||
var c = Context(args = Arguments.from(listOf(-1, 0, 1, 2, 3).map { it.toObj() }))
|
var c = Scope(args = Arguments.from(listOf(-1, 0, 1, 2, 3).map { it.toObj() }))
|
||||||
pa.assignToContext(c)
|
pa.assignToContext(c)
|
||||||
c.eval("assertEquals( i, -1 )")
|
c.eval("assertEquals( i, -1 )")
|
||||||
c.eval("assertEquals( a,[0,1] )")
|
c.eval("assertEquals( a,[0,1] )")
|
||||||
c.eval("assertEquals( b, 2 )")
|
c.eval("assertEquals( b, 2 )")
|
||||||
c.eval("assertEquals( c, 3 )")
|
c.eval("assertEquals( c, 3 )")
|
||||||
|
|
||||||
c = Context(args = Arguments.from(listOf(0, 1, 2, 3).map { it.toObj() }))
|
c = Scope(args = Arguments.from(listOf(0, 1, 2, 3).map { it.toObj() }))
|
||||||
pa.assignToContext(c)
|
pa.assignToContext(c)
|
||||||
c.eval("assertEquals( i, 0 )")
|
c.eval("assertEquals( i, 0 )")
|
||||||
c.eval("assertEquals( a,[1] )")
|
c.eval("assertEquals( a,[1] )")
|
||||||
c.eval("assertEquals( b, 2 )")
|
c.eval("assertEquals( b, 2 )")
|
||||||
c.eval("assertEquals( c, 3 )")
|
c.eval("assertEquals( c, 3 )")
|
||||||
|
|
||||||
c = Context(args = Arguments.from(listOf(1, 2, 3).map { it.toObj() }))
|
c = Scope(args = Arguments.from(listOf(1, 2, 3).map { it.toObj() }))
|
||||||
pa.assignToContext(c)
|
pa.assignToContext(c)
|
||||||
c.eval("assertEquals( i, 1)")
|
c.eval("assertEquals( i, 1)")
|
||||||
c.eval("assertEquals( a,[] )")
|
c.eval("assertEquals( a,[] )")
|
||||||
c.eval("assertEquals( b, 2 )")
|
c.eval("assertEquals( b, 2 )")
|
||||||
c.eval("assertEquals( c, 3 )")
|
c.eval("assertEquals( c, 3 )")
|
||||||
|
|
||||||
c = Context(args = Arguments.from(listOf(2, 3).map { it.toObj() }))
|
c = Scope(args = Arguments.from(listOf(2, 3).map { it.toObj() }))
|
||||||
assertFailsWith<ExecutionError> {
|
assertFailsWith<ExecutionError> {
|
||||||
pa.assignToContext(c)
|
pa.assignToContext(c)
|
||||||
}
|
}
|
||||||
@@ -755,7 +756,7 @@ class ScriptTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testIncr() = runTest {
|
fun testIncr() = runTest {
|
||||||
val c = Context()
|
val c = Scope()
|
||||||
c.eval("var x = 10")
|
c.eval("var x = 10")
|
||||||
assertEquals(10, c.eval("x++").toInt())
|
assertEquals(10, c.eval("x++").toInt())
|
||||||
assertEquals(11, c.eval("x++").toInt())
|
assertEquals(11, c.eval("x++").toInt())
|
||||||
@@ -767,7 +768,7 @@ class ScriptTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testDecr() = runTest {
|
fun testDecr() = runTest {
|
||||||
val c = Context()
|
val c = Scope()
|
||||||
c.eval("var x = 9")
|
c.eval("var x = 9")
|
||||||
assertEquals(9, c.eval("x--").toInt())
|
assertEquals(9, c.eval("x--").toInt())
|
||||||
assertEquals(8, c.eval("x--").toInt())
|
assertEquals(8, c.eval("x--").toInt())
|
||||||
@@ -778,7 +779,7 @@ class ScriptTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testDecrIncr() = runTest {
|
fun testDecrIncr() = runTest {
|
||||||
val c = Context()
|
val c = Scope()
|
||||||
c.eval("var x = 9")
|
c.eval("var x = 9")
|
||||||
assertEquals(9, c.eval("x++").toInt())
|
assertEquals(9, c.eval("x++").toInt())
|
||||||
assertEquals(10, c.eval("x++").toInt())
|
assertEquals(10, c.eval("x++").toInt())
|
||||||
@@ -792,7 +793,7 @@ class ScriptTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testDecrIncr2() = runTest {
|
fun testDecrIncr2() = runTest {
|
||||||
val c = Context()
|
val c = Scope()
|
||||||
c.eval("var x = 9")
|
c.eval("var x = 9")
|
||||||
assertEquals(9, c.eval("x--").toInt())
|
assertEquals(9, c.eval("x--").toInt())
|
||||||
assertEquals(8, c.eval("x--").toInt())
|
assertEquals(8, c.eval("x--").toInt())
|
||||||
@@ -809,7 +810,7 @@ class ScriptTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testDecrIncr3() = runTest {
|
fun testDecrIncr3() = runTest {
|
||||||
val c = Context()
|
val c = Scope()
|
||||||
c.eval("var x = 9")
|
c.eval("var x = 9")
|
||||||
assertEquals(9, c.eval("x++").toInt())
|
assertEquals(9, c.eval("x++").toInt())
|
||||||
assertEquals(10, c.eval("x++").toInt())
|
assertEquals(10, c.eval("x++").toInt())
|
||||||
@@ -821,7 +822,7 @@ class ScriptTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testIncrAndDecr() = runTest {
|
fun testIncrAndDecr() = runTest {
|
||||||
val c = Context()
|
val c = Scope()
|
||||||
assertEquals(
|
assertEquals(
|
||||||
"8", c.eval(
|
"8", c.eval(
|
||||||
"""
|
"""
|
||||||
@@ -856,7 +857,7 @@ class ScriptTest {
|
|||||||
@Test
|
@Test
|
||||||
fun testAssign1() = runTest {
|
fun testAssign1() = runTest {
|
||||||
assertEquals(10, eval("var x = 5; x=10; x").toInt())
|
assertEquals(10, eval("var x = 5; x=10; x").toInt())
|
||||||
val ctx = Context()
|
val ctx = Scope()
|
||||||
ctx.eval(
|
ctx.eval(
|
||||||
"""
|
"""
|
||||||
var a = 1
|
var a = 1
|
||||||
@@ -870,7 +871,7 @@ class ScriptTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testAssign2() = runTest {
|
fun testAssign2() = runTest {
|
||||||
val ctx = Context()
|
val ctx = Scope()
|
||||||
ctx.eval("var x = 10")
|
ctx.eval("var x = 10")
|
||||||
assertEquals(14, ctx.eval("x += 4").toInt())
|
assertEquals(14, ctx.eval("x += 4").toInt())
|
||||||
assertEquals(14, ctx.eval("x").toInt())
|
assertEquals(14, ctx.eval("x").toInt())
|
||||||
@@ -888,7 +889,7 @@ class ScriptTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testVals() = runTest {
|
fun testVals() = runTest {
|
||||||
val cxt = Context()
|
val cxt = Scope()
|
||||||
cxt.eval("val x = 11")
|
cxt.eval("val x = 11")
|
||||||
assertEquals(11, cxt.eval("x").toInt())
|
assertEquals(11, cxt.eval("x").toInt())
|
||||||
assertFails { cxt.eval("x = 12") }
|
assertFails { cxt.eval("x = 12") }
|
||||||
@@ -1435,7 +1436,7 @@ class ScriptTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testSimpleStruct() = runTest {
|
fun testSimpleStruct() = runTest {
|
||||||
val c = Context()
|
val c = Scope()
|
||||||
c.eval(
|
c.eval(
|
||||||
"""
|
"""
|
||||||
class Point(x,y)
|
class Point(x,y)
|
||||||
@@ -1456,7 +1457,7 @@ class ScriptTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testNonAssignalbeFieldInStruct() = runTest {
|
fun testNonAssignalbeFieldInStruct() = runTest {
|
||||||
val c = Context()
|
val c = Scope()
|
||||||
c.eval(
|
c.eval(
|
||||||
"""
|
"""
|
||||||
class Point(x,y)
|
class Point(x,y)
|
||||||
@@ -1473,7 +1474,7 @@ class ScriptTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testStructBodyVal() = runTest {
|
fun testStructBodyVal() = runTest {
|
||||||
val c = Context()
|
val c = Scope()
|
||||||
c.eval(
|
c.eval(
|
||||||
"""
|
"""
|
||||||
class Point(x,y) {
|
class Point(x,y) {
|
||||||
@@ -1495,7 +1496,7 @@ class ScriptTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testStructBodyFun() = runTest {
|
fun testStructBodyFun() = runTest {
|
||||||
val c = Context()
|
val c = Scope()
|
||||||
c.eval(
|
c.eval(
|
||||||
"""
|
"""
|
||||||
class Point(x,y) {
|
class Point(x,y) {
|
||||||
@@ -1515,7 +1516,7 @@ class ScriptTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testPrivateConstructorParams() = runTest {
|
fun testPrivateConstructorParams() = runTest {
|
||||||
val c = Context()
|
val c = Scope()
|
||||||
c.eval(
|
c.eval(
|
||||||
"""
|
"""
|
||||||
class Point(private var x,y)
|
class Point(private var x,y)
|
||||||
@@ -1870,7 +1871,7 @@ class ScriptTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testTryFinally() = runTest {
|
fun testTryFinally() = runTest {
|
||||||
val c = Context()
|
val c = Scope()
|
||||||
assertFails {
|
assertFails {
|
||||||
c.eval(
|
c.eval(
|
||||||
"""
|
"""
|
||||||
@@ -1893,7 +1894,7 @@ class ScriptTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testThrowFromKotlin() = runTest {
|
fun testThrowFromKotlin() = runTest {
|
||||||
val c = Context()
|
val c = Scope()
|
||||||
c.addFn("callThrow") {
|
c.addFn("callThrow") {
|
||||||
raiseIllegalArgument("fromKotlin")
|
raiseIllegalArgument("fromKotlin")
|
||||||
}
|
}
|
||||||
@@ -2285,4 +2286,83 @@ class ScriptTest {
|
|||||||
|
|
||||||
""".trimIndent())
|
""".trimIndent())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testExtend() = runTest() {
|
||||||
|
eval("""
|
||||||
|
|
||||||
|
fun Int.isEven() {
|
||||||
|
this % 2 == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Object.isInteger() {
|
||||||
|
when(this) {
|
||||||
|
is Int -> true
|
||||||
|
is Real -> toInt() == this
|
||||||
|
is String -> toReal().isInteger()
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert( 4.isEven() )
|
||||||
|
assert( !5.isEven() )
|
||||||
|
|
||||||
|
assert( 12.isInteger() == true )
|
||||||
|
assert( 12.1.isInteger() == false )
|
||||||
|
assert( "5".isInteger() )
|
||||||
|
assert( ! "5.2".isInteger() )
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testToFlow() = runTest() {
|
||||||
|
val c = Scope()
|
||||||
|
val arr = c.eval("[1,2,3]")
|
||||||
|
// array is iterable so we can:
|
||||||
|
assertEquals(listOf(1,2,3), arr.toFlow(c).map { it.toInt() }.toList())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testAssociateBy() = runTest() {
|
||||||
|
eval("""
|
||||||
|
val m = [123, 456].associateBy { "k:%s"(it) }
|
||||||
|
println(m)
|
||||||
|
assertEquals(123, m["k:123"])
|
||||||
|
assertEquals(456, m["k:456"])
|
||||||
|
""")
|
||||||
|
listOf(1,2,3).associateBy { it * 10 }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testImports1() = runTest() {
|
||||||
|
val foosrc = """
|
||||||
|
package lyng.foo
|
||||||
|
|
||||||
|
fun foo() { "foo1" }
|
||||||
|
""".trimIndent()
|
||||||
|
val pm = InlineSourcesPacman(Pacman.emptyAllowAll, listOf(Source("foosrc", foosrc)))
|
||||||
|
assertNotNull(pm.modules.await()["lyng.foo"])
|
||||||
|
assertIs<ModuleScope>(pm.modules.await()["lyng.foo"]!!.await())
|
||||||
|
|
||||||
|
assertEquals("foo1", pm.modules.await()["lyng.foo"]!!.await().eval("foo()").toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testImports2() = runTest() {
|
||||||
|
val foosrc = """
|
||||||
|
package lyng.foo
|
||||||
|
|
||||||
|
fun foo() { "foo1" }
|
||||||
|
""".trimIndent()
|
||||||
|
val pm = InlineSourcesPacman(Pacman.emptyAllowAll, listOf(Source("foosrc", foosrc)))
|
||||||
|
|
||||||
|
val src = """
|
||||||
|
import lyng.foo
|
||||||
|
|
||||||
|
foo()
|
||||||
|
""".trimIndent().toSource("test")
|
||||||
|
|
||||||
|
val scope = ModuleScope(pm, src)
|
||||||
|
assertEquals("foo1", scope.eval(src).toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import net.sergeych.lyng.eval
|
||||||
|
import kotlin.test.Test
|
||||||
|
|
||||||
|
class TypesTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testTypeCollection1() = runTest {
|
||||||
|
eval("""
|
||||||
|
class Point(x: Real, y: Real)
|
||||||
|
assert(Point(1,2).x == 1)
|
||||||
|
assert(Point(1,2).y == 2)
|
||||||
|
assert(Point(1,2) is Point)
|
||||||
|
""".trimIndent())
|
||||||
|
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
fun testTypeCollection2() = runTest {
|
||||||
|
eval("""
|
||||||
|
fun fn1(x: Real, y: Real): Real { x + y }
|
||||||
|
""".trimIndent())
|
||||||
|
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
fun testTypeCollection3() = runTest {
|
||||||
|
eval("""
|
||||||
|
class Test(a: Int) {
|
||||||
|
fun fn1(x: Real, y: Real): Real { x + y }
|
||||||
|
}
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testExternDeclarations() = runTest {
|
||||||
|
eval("""
|
||||||
|
extern fun foo1(a: String): Void
|
||||||
|
assertThrows { foo1("1") }
|
||||||
|
class Test(a: Int) {
|
||||||
|
extern fun fn1(x: Real, y: Real): Real
|
||||||
|
// extern val b: Int
|
||||||
|
}
|
||||||
|
// println("1")
|
||||||
|
val t = Test(0)
|
||||||
|
// println(t.b)
|
||||||
|
// println("2")
|
||||||
|
assertThrows {
|
||||||
|
t.fn1(1,2)
|
||||||
|
}
|
||||||
|
// println("4")
|
||||||
|
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,8 +3,8 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import net.sergeych.lyng.Context
|
|
||||||
import net.sergeych.lyng.ObjVoid
|
import net.sergeych.lyng.ObjVoid
|
||||||
|
import net.sergeych.lyng.Scope
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Files.readAllLines
|
import java.nio.file.Files.readAllLines
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
@@ -158,10 +158,10 @@ fun parseDocTests(fileName: String, bookMode: Boolean = false): Flow<DocTest> =
|
|||||||
}
|
}
|
||||||
.flowOn(Dispatchers.IO)
|
.flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
suspend fun DocTest.test(context: Context = Context()) {
|
suspend fun DocTest.test(scope: Scope = Scope()) {
|
||||||
val collectedOutput = StringBuilder()
|
val collectedOutput = StringBuilder()
|
||||||
val currentTest = this
|
val currentTest = this
|
||||||
context.apply {
|
scope.apply {
|
||||||
addFn("println") {
|
addFn("println") {
|
||||||
if( bookMode ) {
|
if( bookMode ) {
|
||||||
println("${currentTest.fileNamePart}:${currentTest.line}> ${args.joinToString(" "){it.asStr.value}}")
|
println("${currentTest.fileNamePart}:${currentTest.line}> ${args.joinToString(" "){it.asStr.value}}")
|
||||||
@@ -177,7 +177,7 @@ suspend fun DocTest.test(context: Context = Context()) {
|
|||||||
}
|
}
|
||||||
var error: Throwable? = null
|
var error: Throwable? = null
|
||||||
val result = try {
|
val result = try {
|
||||||
context.eval(code)
|
scope.eval(code)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
error = e
|
error = e
|
||||||
null
|
null
|
||||||
@@ -204,10 +204,10 @@ suspend fun DocTest.test(context: Context = Context()) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun runDocTests(fileName: String, bookMode: Boolean = false) {
|
suspend fun runDocTests(fileName: String, bookMode: Boolean = false) {
|
||||||
val bookContext = Context()
|
val bookScope = Scope()
|
||||||
var count = 0
|
var count = 0
|
||||||
parseDocTests(fileName, bookMode).collect { dt ->
|
parseDocTests(fileName, bookMode).collect { dt ->
|
||||||
if (bookMode) dt.test(bookContext)
|
if (bookMode) dt.test(bookScope)
|
||||||
else dt.test()
|
else dt.test()
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.datetime.Clock
|
import kotlinx.datetime.Clock
|
||||||
import net.sergeych.lyng.Context
|
import net.sergeych.lyng.Scope
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
import kotlin.io.path.extension
|
import kotlin.io.path.extension
|
||||||
@@ -13,7 +13,7 @@ suspend fun executeSampleTests(fileName: String) {
|
|||||||
Files.readString(Paths.get(fileName))
|
Files.readString(Paths.get(fileName))
|
||||||
}
|
}
|
||||||
runBlocking {
|
runBlocking {
|
||||||
val c = Context()
|
val c = Scope()
|
||||||
val start = Clock.System.now()
|
val start = Clock.System.now()
|
||||||
c.eval(sample)
|
c.eval(sample)
|
||||||
val time = Clock.System.now() - start
|
val time = Clock.System.now() - start
|
||||||
|
|||||||
Reference in new issue
Block a user