fix #30: let, apply, also. Fix in context combining for lambda calls.

This commit is contained in:
sergeych committed 2025-06-16 15:44:22 +04:00
1 parent f9416105ec
commit 2d4c4d345d
9 files changed
+176 -27

No files matched your search

@@ -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)
}
}
}
}