fix #30: let, apply, also. Fix in context combining for lambda calls.
This commit is contained in:
1 parent
f9416105ec
commit
2d4c4d345d
9 files changed
+176
-27
No files matched your search
@@ -4,7 +4,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
group = "net.sergeych"
|
||||
version = "0.6.5-SNAPSHOT"
|
||||
version = "0.6.7-SNAPSHOT"
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
|
||||
@@ -28,6 +28,8 @@ suspend fun Collection<ParsedArgument>.toArguments(context: Context,tailBlockMod
|
||||
|
||||
data class Arguments(val list: List<Obj>,val tailBlockMode: Boolean = false) : List<Obj> by list {
|
||||
|
||||
constructor(vararg values: Obj) : this(values.toList())
|
||||
|
||||
fun firstAndOnly(pos: Pos = Pos.UNKNOWN): Obj {
|
||||
if (list.size != 1) throw ScriptError(pos, "expected one argument, got ${list.size}")
|
||||
return list.first()
|
||||
|
||||
@@ -122,7 +122,7 @@ class Compiler(
|
||||
}
|
||||
|
||||
Token.Type.DOT, Token.Type.NULL_COALESCE -> {
|
||||
var isOptional = t.type == Token.Type.NULL_COALESCE
|
||||
val isOptional = t.type == Token.Type.NULL_COALESCE
|
||||
operand?.let { left ->
|
||||
// dotcall: calling method on the operand, if next is ID, "("
|
||||
var isCall = false
|
||||
@@ -154,7 +154,7 @@ class Compiler(
|
||||
|
||||
|
||||
Token.Type.LBRACE, Token.Type.NULL_COALESCE_BLOCKINVOKE -> {
|
||||
isOptional = nt.type == Token.Type.NULL_COALESCE_BLOCKINVOKE
|
||||
// isOptional = nt.type == Token.Type.NULL_COALESCE_BLOCKINVOKE
|
||||
// single lambda arg, like assertTrows { ... }
|
||||
cc.next()
|
||||
isCall = true
|
||||
@@ -393,7 +393,8 @@ class Compiler(
|
||||
var closure: Context? = null
|
||||
|
||||
val callStatement = statement {
|
||||
val context = closure!!.copy(pos, args)
|
||||
// and the source closure of the lambda which might have other thisObj.
|
||||
val context = closure!!.copy(pos, args).applyContext(this)
|
||||
if (argsDeclaration == null) {
|
||||
// no args: automatic var 'it'
|
||||
val l = args.list
|
||||
@@ -1667,7 +1668,7 @@ class Compiler(
|
||||
* The keywords that stop processing of expression term
|
||||
*/
|
||||
val stopKeywords =
|
||||
setOf("do", "break", "continue", "return", "if", "when", "do", "while", "for", "class", "struct")
|
||||
setOf("do", "break", "continue", "return", "if", "when", "do", "while", "for", "class")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ class Context(
|
||||
val parent: Context?,
|
||||
val args: Arguments = Arguments.EMPTY,
|
||||
var pos: Pos = Pos.builtIn,
|
||||
val thisObj: Obj = ObjVoid,
|
||||
var thisObj: Obj = ObjVoid,
|
||||
var skipContextCreation: Boolean = false,
|
||||
) {
|
||||
constructor(
|
||||
@@ -13,6 +13,15 @@ class Context(
|
||||
)
|
||||
: this(Script.defaultContext, 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")
|
||||
|
||||
@Suppress("unused")
|
||||
@@ -65,11 +74,16 @@ class Context(
|
||||
inline fun <reified T : Obj> thisAs(): T = (thisObj as? T)
|
||||
?: raiseClassCastError("Cannot cast ${thisObj.objClass.className} to ${T::class.simpleName}")
|
||||
|
||||
internal var appliedContext: Context? = null
|
||||
internal val objects = mutableMapOf<String, ObjRecord>()
|
||||
|
||||
operator fun get(name: String): ObjRecord? =
|
||||
objects[name]
|
||||
?: parent?.get(name)
|
||||
if (name == "this") thisObj.asReadonly
|
||||
else {
|
||||
objects[name]
|
||||
?: parent?.get(name)
|
||||
?: appliedContext?.get(name)
|
||||
}
|
||||
|
||||
fun copy(pos: Pos, args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Context =
|
||||
Context(this, args, pos, newThisObj ?: thisObj)
|
||||
|
||||
@@ -64,7 +64,11 @@ open class Obj {
|
||||
*/
|
||||
open fun byValueCopy(): Obj = this
|
||||
|
||||
fun isInstanceOf(someClass: Obj) = someClass === objClass || objClass.allParentsSet.contains(someClass)
|
||||
@Suppress("SuspiciousEqualsCombination")
|
||||
fun isInstanceOf(someClass: Obj) = someClass === objClass ||
|
||||
objClass.allParentsSet.contains(someClass) ||
|
||||
someClass == rootObjectType
|
||||
|
||||
|
||||
suspend fun invokeInstanceMethod(context: Context, name: String, vararg args: Obj): Obj =
|
||||
invokeInstanceMethod(context, name, Arguments(args.toList()))
|
||||
@@ -103,16 +107,7 @@ open class Obj {
|
||||
* Class of the object: definition of member functions (top-level), etc.
|
||||
* Note that using lazy allows to avoid endless recursion here
|
||||
*/
|
||||
open val objClass: ObjClass by lazy {
|
||||
ObjClass("Obj").apply {
|
||||
addFn("toString") {
|
||||
thisObj.asStr
|
||||
}
|
||||
addFn("contains") {
|
||||
ObjBool(thisObj.contains(this, args.firstAndOnly()))
|
||||
}
|
||||
}
|
||||
}
|
||||
open val objClass: ObjClass = rootObjectType
|
||||
|
||||
open suspend fun plus(context: Context, other: Obj): Obj {
|
||||
context.raiseNotImplemented()
|
||||
@@ -253,6 +248,31 @@ open class Obj {
|
||||
|
||||
|
||||
companion object {
|
||||
|
||||
val rootObjectType = ObjClass("Obj").apply {
|
||||
addFn("toString") {
|
||||
thisObj.asStr
|
||||
}
|
||||
addFn("contains") {
|
||||
ObjBool(thisObj.contains(this, args.firstAndOnly()))
|
||||
}
|
||||
// utilities
|
||||
addFn("let") {
|
||||
args.firstAndOnly().callOn(copy(Arguments(thisObj)))
|
||||
}
|
||||
addFn("apply") {
|
||||
val newContext = ( thisObj as? ObjInstance)?.instanceContext ?: this
|
||||
args.firstAndOnly()
|
||||
.callOn(newContext)
|
||||
thisObj
|
||||
}
|
||||
addFn("also") {
|
||||
args.firstAndOnly().callOn(copy(Arguments(thisObj)))
|
||||
thisObj
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
inline fun from(obj: Any?): Obj {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return when (obj) {
|
||||
@@ -272,6 +292,7 @@ open class Obj {
|
||||
obj as MutableMap.MutableEntry<Obj, Obj>
|
||||
ObjMapEntry(obj.key, obj.value)
|
||||
}
|
||||
|
||||
else -> throw IllegalArgumentException("cannot convert to Obj: $obj")
|
||||
}
|
||||
}
|
||||
@@ -371,7 +392,12 @@ data class ObjNamespace(val name: String) : Obj() {
|
||||
}
|
||||
|
||||
open class ObjException(exceptionClass: ExceptionClass, val context: Context, val message: String) : Obj() {
|
||||
constructor(name: String,context: Context, message: String) : this(getOrCreateExceptionClass(name), context, message)
|
||||
constructor(name: String, context: Context, message: String) : this(
|
||||
getOrCreateExceptionClass(name),
|
||||
context,
|
||||
message
|
||||
)
|
||||
|
||||
constructor(context: Context, message: String) : this(Root, context, message)
|
||||
|
||||
fun raise(): Nothing {
|
||||
@@ -386,13 +412,15 @@ open class ObjException(exceptionClass: ExceptionClass, val context: Context, va
|
||||
|
||||
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 {
|
||||
val message = context.args.getOrNull(0)?.toString() ?: name
|
||||
return ObjException(this, context, message)
|
||||
}
|
||||
|
||||
override fun toString(): String = "ExceptionClass[$name]@${hashCode().encodeToHex()}"
|
||||
}
|
||||
|
||||
val Root = ExceptionClass("Throwable").apply {
|
||||
addConst("message", statement {
|
||||
(thisObj as ObjException).message.toObj()
|
||||
|
||||
@@ -9,9 +9,10 @@ open class ObjClass(
|
||||
|
||||
var instanceConstructor: Statement? = null
|
||||
|
||||
val allParentsSet: Set<ObjClass> = parents.flatMap {
|
||||
listOf(it) + it.allParentsSet
|
||||
}.toSet()
|
||||
val allParentsSet: Set<ObjClass> =
|
||||
parents.flatMap {
|
||||
listOf(it) + it.allParentsSet
|
||||
}.toMutableSet()
|
||||
|
||||
override val objClass: ObjClass by lazy { ObjClassType }
|
||||
|
||||
@@ -61,7 +62,7 @@ open class ObjClass(
|
||||
fun getInstanceMemberOrNull(name: String): ObjRecord? {
|
||||
members[name]?.let { return it }
|
||||
allParentsSet.forEach { parent -> parent.getInstanceMemberOrNull(name)?.let { return it } }
|
||||
return null
|
||||
return rootObjectType.members[name]
|
||||
}
|
||||
|
||||
fun getInstanceMember(atPos: Pos, name: String): ObjRecord =
|
||||
|
||||
@@ -146,6 +146,7 @@ class Script(
|
||||
delay((this.args.firstAndOnly().toDouble()/1000.0).roundToLong())
|
||||
}
|
||||
|
||||
addConst("Object", rootObjectType)
|
||||
addConst("Real", ObjReal.type)
|
||||
addConst("String", ObjString.type)
|
||||
addConst("Int", ObjInt.type)
|
||||
@@ -163,13 +164,14 @@ class Script(
|
||||
addConst("Collection", ObjCollection)
|
||||
addConst("Array", ObjArray)
|
||||
addConst("Class", ObjClassType)
|
||||
addConst("Object", Obj().objClass)
|
||||
|
||||
val pi = ObjReal(PI)
|
||||
addConst("π", pi)
|
||||
getOrCreateNamespace("Math").apply {
|
||||
addConst("PI", pi)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1267,7 +1267,12 @@ class ScriptTest {
|
||||
eval(
|
||||
"""
|
||||
val x = { x, y, z ->
|
||||
println("-- x=",x)
|
||||
println("-- y=",y)
|
||||
println("-- z=",z)
|
||||
println([x,y,z])
|
||||
assert( [x, y, z] == [1,2,"end"])
|
||||
println("----:")
|
||||
}
|
||||
assert( x(1, 2, "end") == void)
|
||||
""".trimIndent()
|
||||
@@ -2149,7 +2154,6 @@ class ScriptTest {
|
||||
assertEquals( null, s?.length ?{ "test" } )
|
||||
assertEquals( null, s?[1] )
|
||||
assertEquals( null, s ?{ "test" } )
|
||||
assertEquals( null, s.test ?{ "test" } )
|
||||
|
||||
s = "xx"
|
||||
assert(s.lower().size == 2)
|
||||
@@ -2242,4 +2246,43 @@ class ScriptTest {
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testLet() = runTest {
|
||||
eval("""
|
||||
class Point(x=0,y=0)
|
||||
assert( Point() is Object)
|
||||
Point().let { println(it.x, it.y) }
|
||||
val x = null
|
||||
x?.let { println(it.x, it.y) }
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testApply() = runTest {
|
||||
eval("""
|
||||
class Point(x,y)
|
||||
// see the difference: apply changes this to newly created Point:
|
||||
val p = Point(1,2).apply {
|
||||
x++; y++
|
||||
}
|
||||
assertEquals(p, Point(2,3))
|
||||
>>> void
|
||||
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testApplyThis() = runTest {
|
||||
eval("""
|
||||
class Point(x,y)
|
||||
// see the difference: apply changes this to newly created Point:
|
||||
val p = Point(1,2).apply {
|
||||
this.x++; this.y++
|
||||
}
|
||||
assertEquals(p, Point(2,3))
|
||||
>>> void
|
||||
|
||||
""".trimIndent())
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user