Update compile-time resolution and tests

This commit is contained in:
sergeych committed 2026-02-03 02:07:29 +03:00
1 parent 8f60a84e3b
commit 523b9d338b
46 files changed
+3771 -967

No files matched your search

@@ -0,0 +1,24 @@
/*
* Copyright 2026 Sergey S. Chernov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sergeych.lyng
/**
* Compile-time call metadata for known functions. Used to select lambda receiver semantics.
*/
data class CallSignature(
val tailBlockReceiverType: String? = null
)
@@ -26,12 +26,25 @@ import net.sergeych.lyng.obj.ObjRecord
* Inherits [Scope.args] and [Scope.thisObj] from [callScope] and adds lookup for symbols
* from [closureScope] with proper precedence
*/
class ClosureScope(val callScope: Scope, val closureScope: Scope) :
class ClosureScope(
val callScope: Scope,
val closureScope: Scope,
private val preferredThisType: String? = null
) :
// Important: use closureScope.thisObj so unqualified members (e.g., fields) resolve to the instance
// we captured, not to the caller's `this` (e.g., FlowBuilder).
Scope(callScope, callScope.args, thisObj = closureScope.thisObj) {
init {
val desired = preferredThisType?.let { typeName ->
callScope.thisVariants.firstOrNull { it.objClass.className == typeName }
}
val primaryThis = closureScope.thisObj
val merged = ArrayList<Obj>(callScope.thisVariants.size + closureScope.thisVariants.size + 1)
desired?.let { merged.add(it) }
merged.addAll(callScope.thisVariants)
merged.addAll(closureScope.thisVariants)
setThisVariants(primaryThis, merged)
// Preserve the lexical class context of the closure by default. This ensures that lambdas
// created inside a class method keep access to that class's private/protected members even
// when executed from within another object's method (e.g., Mutex.withLock), which may set
@@ -72,14 +85,14 @@ class ClosureScope(val callScope: Scope, val closureScope: Scope) :
}
class ApplyScope(val callScope: Scope, val applied: Scope) :
Scope(callScope.parent?.parent ?: callScope.parent ?: callScope, thisObj = applied.thisObj) {
Scope(callScope, thisObj = applied.thisObj) {
override fun get(name: String): ObjRecord? {
return applied.get(name) ?: super.get(name)
}
override fun applyClosure(closure: Scope): Scope {
return this
override fun applyClosure(closure: Scope, preferredThisType: String?): Scope {
return ClosureScope(this, closure, preferredThisType)
}
}
@@ -19,10 +19,19 @@ package net.sergeych.lyng
sealed class CodeContext {
class Module(@Suppress("unused") val packageName: String?): CodeContext()
class Function(val name: String, val implicitThisMembers: Boolean = false): CodeContext()
class Function(
val name: String,
val implicitThisMembers: Boolean = false,
val implicitThisTypeName: String? = null
): CodeContext()
class ClassBody(val name: String, val isExtern: Boolean = false): CodeContext() {
val pendingInitializations = mutableMapOf<String, Pos>()
val declaredMembers = mutableSetOf<String>()
val memberOverrides = mutableMapOf<String, Boolean>()
val memberFieldIds = mutableMapOf<String, Int>()
val memberMethodIds = mutableMapOf<String, Int>()
var nextFieldId: Int = 0
var nextMethodId: Int = 0
var slotPlanId: Int? = null
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,34 @@
/*
* Copyright 2026 Sergey S. Chernov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyng
internal fun extensionCallableName(typeName: String, memberName: String): String {
return "__ext__${sanitizeExtensionTypeName(typeName)}__${memberName}"
}
internal fun extensionPropertyGetterName(typeName: String, memberName: String): String {
return "__ext_get__${sanitizeExtensionTypeName(typeName)}__${memberName}"
}
internal fun extensionPropertySetterName(typeName: String, memberName: String): String {
return "__ext_set__${sanitizeExtensionTypeName(typeName)}__${memberName}"
}
private fun sanitizeExtensionTypeName(typeName: String): String {
return typeName.replace('.', '_')
}
@@ -18,6 +18,8 @@ package net.sergeych.lyng
import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjClass
import net.sergeych.lyng.obj.ObjExtensionPropertyGetterCallable
import net.sergeych.lyng.obj.ObjExtensionPropertySetterCallable
import net.sergeych.lyng.obj.ObjProperty
import net.sergeych.lyng.obj.ObjRecord
@@ -45,6 +47,14 @@ class ExtensionPropertyDeclStatement(
type = ObjRecord.Type.Property
)
)
val getterName = extensionPropertyGetterName(extTypeName, property.name)
val getterWrapper = ObjExtensionPropertyGetterCallable(property.name, property)
context.addItem(getterName, false, getterWrapper, visibility, recordType = ObjRecord.Type.Fun)
if (property.setter != null) {
val setterName = extensionPropertySetterName(extTypeName, property.name)
val setterWrapper = ObjExtensionPropertySetterCallable(property.name, property)
context.addItem(setterName, false, setterWrapper, visibility, recordType = ObjRecord.Type.Fun)
}
return property
}
}
@@ -0,0 +1,37 @@
/*
* Copyright 2026 Sergey S. Chernov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sergeych.lyng
import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjVoid
class InlineBlockStatement(
private val statements: List<Statement>,
private val startPos: Pos,
) : Statement() {
override val pos: Pos = startPos
override suspend fun execute(scope: Scope): Obj {
var last: Obj = ObjVoid
for (stmt in statements) {
last = stmt.execute(scope)
}
return last
}
fun statements(): List<Statement> = statements
}
@@ -52,6 +52,8 @@ open class Scope(
var currentClassCtx: net.sergeych.lyng.obj.ObjClass? = parent?.currentClassCtx
// Unique id per scope frame for PICs; regenerated on each borrow from the pool.
var frameId: Long = nextFrameId()
@PublishedApi
internal val thisVariants: MutableList<Obj> = mutableListOf()
// Fast-path storage for local variables/arguments accessed by slot index.
// Enabled by default for child scopes; module/class scopes can ignore it.
@@ -66,6 +68,21 @@ open class Scope(
internal val extensions: MutableMap<ObjClass, MutableMap<String, ObjRecord>> = mutableMapOf()
init {
setThisVariants(thisObj, parent?.thisVariants ?: emptyList())
}
internal fun setThisVariants(primary: Obj, extras: List<Obj>) {
thisObj = primary
thisVariants.clear()
thisVariants.add(primary)
for (obj in extras) {
if (obj !== primary && !thisVariants.contains(obj)) {
thisVariants.add(obj)
}
}
}
fun addExtension(cls: ObjClass, name: String, record: ObjRecord) {
extensions.getOrPut(cls) { mutableMapOf() }[name] = record
}
@@ -340,11 +357,8 @@ open class Scope(
}
inline fun <reified T : Obj> thisAs(): T {
var s: Scope? = this
while (s != null) {
val t = s.thisObj
if (t is T) return t
s = s.parent
for (obj in thisVariants) {
if (obj is T) return obj
}
raiseClassCastError("Cannot cast ${thisObj.objClass.className} to ${T::class.simpleName}")
}
@@ -406,6 +420,8 @@ open class Scope(
get() = slots.size
fun getSlotIndexOf(name: String): Int? = nameToSlot[name]
internal fun slotNameToIndexSnapshot(): Map<String, Int> = nameToSlot.toMap()
fun allocateSlotFor(name: String, record: ObjRecord): Int {
val idx = slots.size
slots.add(record)
@@ -490,6 +506,7 @@ open class Scope(
this.parent = null
this.skipScopeCreation = false
this.currentClassCtx = null
thisVariants.clear()
objects.clear()
slots.clear()
nameToSlot.clear()
@@ -520,7 +537,7 @@ open class Scope(
this.parent = parent
this.args = args
this.pos = pos
this.thisObj = thisObj
setThisVariants(thisObj, parent?.thisVariants ?: emptyList())
// Pre-size local slots for upcoming parameter assignment where possible
reserveLocalCapacity(args.list.size + 4)
}
@@ -609,7 +626,10 @@ open class Scope(
isAbstract: Boolean = false,
isClosed: Boolean = false,
isOverride: Boolean = false,
isTransient: Boolean = false
isTransient: Boolean = false,
callSignature: CallSignature? = null,
fieldId: Int? = null,
methodId: Int? = null
): ObjRecord {
val rec = ObjRecord(
value, isMutable, visibility, writeVisibility,
@@ -618,15 +638,19 @@ open class Scope(
isAbstract = isAbstract,
isClosed = isClosed,
isOverride = isOverride,
isTransient = isTransient
isTransient = isTransient,
callSignature = callSignature,
memberName = name,
fieldId = fieldId,
methodId = methodId
)
objects[name] = rec
bumpClassLayoutIfNeeded(name, value, recordType)
if (recordType == ObjRecord.Type.Field || recordType == ObjRecord.Type.ConstructorField) {
val inst = thisObj as? net.sergeych.lyng.obj.ObjInstance
if (inst != null) {
val slot = inst.objClass.fieldSlotForKey(name)
if (slot != null) inst.setFieldSlotRecord(slot.slot, rec)
val slotId = rec.fieldId ?: inst.objClass.fieldSlotForKey(name)?.slot
if (slotId != null) inst.setFieldSlotRecord(slotId, rec)
}
}
if (value is Statement ||
@@ -635,8 +659,8 @@ open class Scope(
recordType == ObjRecord.Type.Property) {
val inst = thisObj as? net.sergeych.lyng.obj.ObjInstance
if (inst != null) {
val slot = inst.objClass.methodSlotForKey(name)
if (slot != null) inst.setMethodSlotRecord(slot.slot, rec)
val slotId = rec.methodId ?: inst.objClass.methodSlotForKey(name)?.slot
if (slotId != null) inst.setMethodSlotRecord(slotId, rec)
}
}
// Index this binding within the current frame to help resolve locals across suspension
@@ -697,7 +721,7 @@ open class Scope(
return CmdDisassembler.disassemble(bytecode)
}
fun addFn(vararg names: String, fn: suspend Scope.() -> Obj) {
fun addFn(vararg names: String, callSignature: CallSignature? = null, fn: suspend Scope.() -> Obj) {
val newFn = object : Statement() {
override val pos: Pos = Pos.builtIn
@@ -708,7 +732,9 @@ open class Scope(
addItem(
name,
false,
newFn
newFn,
recordType = ObjRecord.Type.Fun,
callSignature = callSignature
)
}
}
@@ -778,7 +804,8 @@ open class Scope(
println("--------------------")
}
open fun applyClosure(closure: Scope): Scope = ClosureScope(this, closure)
open fun applyClosure(closure: Scope, preferredThisType: String? = null): Scope =
ClosureScope(this, closure, preferredThisType)
/**
* Resolve and evaluate a qualified identifier exactly as compiled code would.
@@ -428,8 +428,11 @@ class Script(
addConst("CompletableDeferred", ObjCompletableDeferred.type)
addConst("Mutex", ObjMutex.type)
addConst("Flow", ObjFlow.type)
addConst("FlowBuilder", ObjFlowBuilder.type)
addConst("Regex", ObjRegex.type)
addConst("RegexMatch", ObjRegexMatch.type)
addConst("MapEntry", ObjMapEntry.type)
addFn("launch") {
val callable = requireOnlyArg<Statement>()
@@ -443,7 +446,7 @@ class Script(
ObjVoid
}
addFn("flow") {
addFn("flow", callSignature = CallSignature(tailBlockReceiverType = "FlowBuilder")) {
// important is: current context contains closure often used in call;
// we'll need it for the producer
ObjFlow(requireOnlyArg<Statement>(), this)
@@ -17,8 +17,10 @@
package net.sergeych.lyng
import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjClass
import net.sergeych.lyng.obj.ObjNull
import net.sergeych.lyng.obj.ObjRecord
import net.sergeych.lyng.obj.ObjUnset
class VarDeclStatement(
val name: String,
@@ -29,11 +31,12 @@ class VarDeclStatement(
val slotIndex: Int?,
val scopeId: Int?,
private val startPos: Pos,
val initializerObjClass: ObjClass? = null,
) : Statement() {
override val pos: Pos = startPos
override suspend fun execute(context: Scope): Obj {
val initValue = initializer?.execute(context)?.byValueCopy() ?: ObjNull
val initValue = initializer?.execute(context)?.byValueCopy() ?: ObjUnset
context.addItem(
name,
isMutable,
File diff suppressed because it is too large. Load diff
@@ -49,6 +49,7 @@ class BytecodeStatement private constructor(
allowLocalSlots: Boolean,
returnLabels: Set<String> = emptySet(),
rangeLocalNames: Set<String> = emptySet(),
allowedScopeNames: Set<String>? = null,
): Statement {
if (statement is BytecodeStatement) return statement
val hasUnsupported = containsUnsupportedStatement(statement)
@@ -63,7 +64,8 @@ class BytecodeStatement private constructor(
val compiler = BytecodeCompiler(
allowLocalSlots = safeLocals,
returnLabels = returnLabels,
rangeLocalNames = rangeLocalNames
rangeLocalNames = rangeLocalNames,
allowedScopeNames = allowedScopeNames
)
val compiled = compiler.compileStatement(nameHint, statement)
val fn = compiled ?: throw BytecodeFallbackException(
@@ -76,7 +78,14 @@ class BytecodeStatement private constructor(
private fun containsUnsupportedStatement(stmt: Statement): Boolean {
val target = if (stmt is BytecodeStatement) stmt.original else stmt
return when (target) {
is net.sergeych.lyng.ExpressionStatement -> false
is net.sergeych.lyng.ExpressionStatement -> {
val ref = target.ref
if (ref is net.sergeych.lyng.obj.StatementRef) {
containsUnsupportedStatement(ref.statement)
} else {
false
}
}
is net.sergeych.lyng.IfStatement -> {
containsUnsupportedStatement(target.condition) ||
containsUnsupportedStatement(target.ifBody) ||
@@ -100,6 +109,8 @@ class BytecodeStatement private constructor(
}
is net.sergeych.lyng.BlockStatement ->
target.statements().any { containsUnsupportedStatement(it) }
is net.sergeych.lyng.InlineBlockStatement ->
target.statements().any { containsUnsupportedStatement(it) }
is net.sergeych.lyng.VarDeclStatement ->
target.initializer?.let { containsUnsupportedStatement(it) } ?: false
is net.sergeych.lyng.DelegatedVarDeclStatement ->
@@ -117,7 +128,7 @@ class BytecodeStatement private constructor(
is net.sergeych.lyng.ClassDeclStatement -> false
is net.sergeych.lyng.FunctionDeclStatement -> false
is net.sergeych.lyng.EnumDeclStatement -> false
is net.sergeych.lyng.TryStatement -> false
is net.sergeych.lyng.TryStatement -> true
is net.sergeych.lyng.WhenStatement -> {
containsUnsupportedStatement(target.value) ||
target.cases.any { case ->
@@ -151,7 +162,8 @@ class BytecodeStatement private constructor(
stmt.isTransient,
stmt.slotIndex,
stmt.scopeId,
stmt.pos
stmt.pos,
stmt.initializerObjClass
)
}
is net.sergeych.lyng.DestructuringVarDeclStatement -> {
@@ -137,7 +137,7 @@ class CmdBuilder {
listOf(OperandKind.SLOT, OperandKind.ADDR)
Opcode.CONST_NULL ->
listOf(OperandKind.SLOT)
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL ->
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL, Opcode.MAKE_VALUE_FN ->
listOf(OperandKind.CONST, OperandKind.SLOT)
Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
listOf(OperandKind.CONST)
@@ -161,14 +161,18 @@ class CmdBuilder {
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.ASSIGN_OP_OBJ ->
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.CONST)
Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET ->
Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET, Opcode.LOAD_THIS ->
listOf(OperandKind.SLOT)
Opcode.LOAD_THIS_VARIANT ->
listOf(OperandKind.ID, OperandKind.SLOT)
Opcode.JMP ->
listOf(OperandKind.IP)
Opcode.JMP_IF_TRUE, Opcode.JMP_IF_FALSE ->
listOf(OperandKind.SLOT, OperandKind.IP)
Opcode.CALL_DIRECT, Opcode.CALL_FALLBACK ->
Opcode.CALL_DIRECT ->
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
Opcode.CALL_MEMBER_SLOT ->
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
Opcode.CALL_SLOT ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
Opcode.CALL_VIRTUAL ->
@@ -177,8 +181,6 @@ class CmdBuilder {
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT)
Opcode.SET_FIELD ->
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT)
Opcode.GET_NAME ->
listOf(OperandKind.ID, OperandKind.SLOT)
Opcode.GET_INDEX ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.SET_INDEX ->
@@ -187,12 +189,10 @@ class CmdBuilder {
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.LIST_LITERAL ->
listOf(OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
Opcode.GET_THIS_MEMBER ->
listOf(OperandKind.ID, OperandKind.SLOT)
Opcode.SET_THIS_MEMBER ->
listOf(OperandKind.ID, OperandKind.SLOT)
Opcode.EVAL_FALLBACK, Opcode.EVAL_REF, Opcode.EVAL_STMT, Opcode.EVAL_VALUE_FN ->
listOf(OperandKind.ID, OperandKind.SLOT)
Opcode.GET_MEMBER_SLOT ->
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.ID, OperandKind.SLOT)
Opcode.SET_MEMBER_SLOT ->
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.ID, OperandKind.SLOT)
Opcode.ITER_PUSH ->
listOf(OperandKind.SLOT)
Opcode.ITER_POP, Opcode.ITER_CANCEL ->
@@ -229,9 +229,12 @@ class CmdBuilder {
Opcode.CONST_REAL -> CmdConstReal(operands[0], operands[1])
Opcode.CONST_BOOL -> CmdConstBool(operands[0], operands[1])
Opcode.CONST_NULL -> CmdConstNull(operands[0])
Opcode.MAKE_VALUE_FN -> CmdMakeValueFn(operands[0], operands[1])
Opcode.BOX_OBJ -> CmdBoxObj(operands[0], operands[1])
Opcode.OBJ_TO_BOOL -> CmdObjToBool(operands[0], operands[1])
Opcode.RANGE_INT_BOUNDS -> CmdRangeIntBounds(operands[0], operands[1], operands[2], operands[3])
Opcode.LOAD_THIS -> CmdLoadThis(operands[0])
Opcode.LOAD_THIS_VARIANT -> CmdLoadThisVariant(operands[0], operands[1])
Opcode.MAKE_RANGE -> CmdMakeRange(operands[0], operands[1], operands[2], operands[3])
Opcode.CHECK_IS -> CmdCheckIs(operands[0], operands[1], operands[2])
Opcode.ASSERT_IS -> CmdAssertIs(operands[0], operands[1])
@@ -378,21 +381,16 @@ class CmdBuilder {
Opcode.DECL_LOCAL -> CmdDeclLocal(operands[0], operands[1])
Opcode.DECL_EXT_PROPERTY -> CmdDeclExtProperty(operands[0], operands[1])
Opcode.CALL_DIRECT -> CmdCallDirect(operands[0], operands[1], operands[2], operands[3])
Opcode.CALL_MEMBER_SLOT -> CmdCallMemberSlot(operands[0], operands[1], operands[2], operands[3], operands[4])
Opcode.CALL_VIRTUAL -> CmdCallVirtual(operands[0], operands[1], operands[2], operands[3], operands[4])
Opcode.CALL_FALLBACK -> CmdCallFallback(operands[0], operands[1], operands[2], operands[3])
Opcode.CALL_SLOT -> CmdCallSlot(operands[0], operands[1], operands[2], operands[3])
Opcode.GET_FIELD -> CmdGetField(operands[0], operands[1], operands[2])
Opcode.SET_FIELD -> CmdSetField(operands[0], operands[1], operands[2])
Opcode.GET_NAME -> CmdGetName(operands[0], operands[1])
Opcode.GET_INDEX -> CmdGetIndex(operands[0], operands[1], operands[2])
Opcode.SET_INDEX -> CmdSetIndex(operands[0], operands[1], operands[2])
Opcode.LIST_LITERAL -> CmdListLiteral(operands[0], operands[1], operands[2], operands[3])
Opcode.GET_THIS_MEMBER -> CmdGetThisMember(operands[0], operands[1])
Opcode.SET_THIS_MEMBER -> CmdSetThisMember(operands[0], operands[1])
Opcode.EVAL_FALLBACK -> CmdEvalFallback(operands[0], operands[1])
Opcode.EVAL_REF -> CmdEvalRef(operands[0], operands[1])
Opcode.EVAL_STMT -> CmdEvalStmt(operands[0], operands[1])
Opcode.EVAL_VALUE_FN -> CmdEvalValueFn(operands[0], operands[1])
Opcode.GET_MEMBER_SLOT -> CmdGetMemberSlot(operands[0], operands[1], operands[2], operands[3])
Opcode.SET_MEMBER_SLOT -> CmdSetMemberSlot(operands[0], operands[1], operands[2], operands[3])
Opcode.ITER_PUSH -> CmdIterPush(operands[0])
Opcode.ITER_POP -> CmdIterPop()
Opcode.ITER_CANCEL -> CmdIterCancel()
@@ -66,7 +66,10 @@ object CmdDisassembler {
is CmdConstIntLocal -> Opcode.CONST_INT to intArrayOf(cmd.constId, cmd.dst + fn.scopeSlotCount)
is CmdConstReal -> Opcode.CONST_REAL to intArrayOf(cmd.constId, cmd.dst)
is CmdConstBool -> Opcode.CONST_BOOL to intArrayOf(cmd.constId, cmd.dst)
is CmdLoadThis -> Opcode.LOAD_THIS to intArrayOf(cmd.dst)
is CmdLoadThisVariant -> Opcode.LOAD_THIS_VARIANT to intArrayOf(cmd.typeId, cmd.dst)
is CmdConstNull -> Opcode.CONST_NULL to intArrayOf(cmd.dst)
is CmdMakeValueFn -> Opcode.MAKE_VALUE_FN to intArrayOf(cmd.id, cmd.dst)
is CmdBoxObj -> Opcode.BOX_OBJ to intArrayOf(cmd.src, cmd.dst)
is CmdObjToBool -> Opcode.OBJ_TO_BOOL to intArrayOf(cmd.src, cmd.dst)
is CmdCheckIs -> Opcode.CHECK_IS to intArrayOf(cmd.objSlot, cmd.typeSlot, cmd.dst)
@@ -178,23 +181,19 @@ object CmdDisassembler {
is CmdDeclExtProperty -> Opcode.DECL_EXT_PROPERTY to intArrayOf(cmd.constId, cmd.slot)
is CmdCallDirect -> Opcode.CALL_DIRECT to intArrayOf(cmd.id, cmd.argBase, cmd.argCount, cmd.dst)
is CmdCallVirtual -> Opcode.CALL_VIRTUAL to intArrayOf(cmd.recvSlot, cmd.methodId, cmd.argBase, cmd.argCount, cmd.dst)
is CmdCallFallback -> Opcode.CALL_FALLBACK to intArrayOf(cmd.id, cmd.argBase, cmd.argCount, cmd.dst)
is CmdCallMemberSlot -> Opcode.CALL_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.methodId, cmd.argBase, cmd.argCount, cmd.dst)
is CmdCallSlot -> Opcode.CALL_SLOT to intArrayOf(cmd.calleeSlot, cmd.argBase, cmd.argCount, cmd.dst)
is CmdGetField -> Opcode.GET_FIELD to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.dst)
is CmdSetField -> Opcode.SET_FIELD to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.valueSlot)
is CmdGetName -> Opcode.GET_NAME to intArrayOf(cmd.nameId, cmd.dst)
is CmdGetIndex -> Opcode.GET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.dst)
is CmdSetIndex -> Opcode.SET_INDEX to intArrayOf(cmd.targetSlot, cmd.indexSlot, cmd.valueSlot)
is CmdListLiteral -> Opcode.LIST_LITERAL to intArrayOf(cmd.planId, cmd.baseSlot, cmd.count, cmd.dst)
is CmdGetThisMember -> Opcode.GET_THIS_MEMBER to intArrayOf(cmd.nameId, cmd.dst)
is CmdSetThisMember -> Opcode.SET_THIS_MEMBER to intArrayOf(cmd.nameId, cmd.valueSlot)
is CmdEvalFallback -> Opcode.EVAL_FALLBACK to intArrayOf(cmd.id, cmd.dst)
is CmdEvalRef -> Opcode.EVAL_REF to intArrayOf(cmd.id, cmd.dst)
is CmdEvalStmt -> Opcode.EVAL_STMT to intArrayOf(cmd.id, cmd.dst)
is CmdEvalValueFn -> Opcode.EVAL_VALUE_FN to intArrayOf(cmd.id, cmd.dst)
is CmdGetMemberSlot -> Opcode.GET_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.methodId, cmd.dst)
is CmdSetMemberSlot -> Opcode.SET_MEMBER_SLOT to intArrayOf(cmd.recvSlot, cmd.fieldId, cmd.methodId, cmd.valueSlot)
is CmdIterPush -> Opcode.ITER_PUSH to intArrayOf(cmd.iterSlot)
is CmdIterPop -> Opcode.ITER_POP to intArrayOf()
is CmdIterCancel -> Opcode.ITER_CANCEL to intArrayOf()
else -> error("Unsupported cmd in disassembler: ${cmd::class.simpleName}")
}
}
@@ -231,7 +230,7 @@ object CmdDisassembler {
listOf(OperandKind.SLOT, OperandKind.ADDR)
Opcode.CONST_NULL ->
listOf(OperandKind.SLOT)
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL ->
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL, Opcode.MAKE_VALUE_FN ->
listOf(OperandKind.CONST, OperandKind.SLOT)
Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
listOf(OperandKind.CONST)
@@ -255,36 +254,36 @@ object CmdDisassembler {
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.ASSIGN_OP_OBJ ->
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT, OperandKind.CONST)
Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET, Opcode.ITER_PUSH ->
Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET, Opcode.ITER_PUSH, Opcode.LOAD_THIS ->
listOf(OperandKind.SLOT)
Opcode.LOAD_THIS_VARIANT ->
listOf(OperandKind.ID, OperandKind.SLOT)
Opcode.JMP ->
listOf(OperandKind.IP)
Opcode.JMP_IF_TRUE, Opcode.JMP_IF_FALSE ->
listOf(OperandKind.SLOT, OperandKind.IP)
Opcode.CALL_DIRECT, Opcode.CALL_FALLBACK ->
Opcode.CALL_DIRECT ->
listOf(OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
Opcode.CALL_SLOT ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
Opcode.CALL_VIRTUAL ->
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
Opcode.CALL_MEMBER_SLOT ->
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
Opcode.GET_FIELD ->
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT)
Opcode.SET_FIELD ->
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.SLOT)
Opcode.GET_NAME ->
listOf(OperandKind.ID, OperandKind.SLOT)
Opcode.GET_INDEX ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.SET_INDEX ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.LIST_LITERAL ->
listOf(OperandKind.CONST, OperandKind.SLOT, OperandKind.COUNT, OperandKind.SLOT)
Opcode.GET_THIS_MEMBER ->
listOf(OperandKind.ID, OperandKind.SLOT)
Opcode.SET_THIS_MEMBER ->
listOf(OperandKind.ID, OperandKind.SLOT)
Opcode.EVAL_FALLBACK, Opcode.EVAL_REF, Opcode.EVAL_STMT, Opcode.EVAL_VALUE_FN ->
listOf(OperandKind.ID, OperandKind.SLOT)
Opcode.GET_MEMBER_SLOT ->
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.ID, OperandKind.SLOT)
Opcode.SET_MEMBER_SLOT ->
listOf(OperandKind.SLOT, OperandKind.ID, OperandKind.ID, OperandKind.SLOT)
}
}
}
@@ -152,6 +152,28 @@ class CmdConstBool(internal val constId: Int, internal val dst: Int) : Cmd() {
}
}
class CmdLoadThis(internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
frame.setObj(dst, frame.scope.thisObj)
return
}
}
class CmdLoadThisVariant(
internal val typeId: Int,
internal val dst: Int,
) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
val typeConst = frame.fn.constants.getOrNull(typeId) as? BytecodeConst.StringVal
?: error("LOAD_THIS_VARIANT expects StringVal at $typeId")
val typeName = typeConst.value
val receiver = frame.scope.thisVariants.firstOrNull { it.isInstanceOf(typeName) }
?: frame.scope.raiseClassCastError("Cannot cast ${frame.scope.thisObj.objClass.className} to $typeName")
frame.setObj(dst, receiver)
return
}
}
class CmdMakeRange(
internal val startSlot: Int,
internal val endSlot: Int,
@@ -1146,20 +1168,7 @@ class CmdCallVirtual(
internal val dst: Int,
) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncFrameToScope()
}
val receiver = frame.slotToObj(recvSlot)
val nameConst = frame.fn.constants.getOrNull(methodId) as? BytecodeConst.StringVal
?: error("CALL_VIRTUAL expects StringVal at $methodId")
val args = frame.buildArguments(argBase, argCount)
val site = frame.methodCallSites.getOrPut(frame.ip - 1) { MethodCallSite(nameConst.value) }
val result = site.invoke(frame.scope, receiver, args)
if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncScopeToFrame()
}
frame.storeObjResult(dst, result)
return
frame.scope.raiseError("CALL_VIRTUAL is not allowed: compile-time member resolution is required")
}
}
@@ -1313,38 +1322,87 @@ class CmdListLiteral(
}
}
class CmdGetThisMember(
internal val nameId: Int,
class CmdGetMemberSlot(
internal val recvSlot: Int,
internal val fieldId: Int,
internal val methodId: Int,
internal val dst: Int,
) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
val receiver = frame.slotToObj(recvSlot)
val inst = receiver as? ObjInstance
val fieldRec = if (fieldId >= 0) {
inst?.fieldRecordForId(fieldId) ?: receiver.objClass.fieldRecordForId(fieldId)
} else null
val rec = fieldRec ?: run {
if (methodId >= 0) {
inst?.methodRecordForId(methodId) ?: receiver.objClass.methodRecordForId(methodId)
} else null
} ?: frame.scope.raiseSymbolNotFound("member")
val name = rec.memberName ?: "<member>"
val resolved = receiver.resolveRecord(frame.scope, rec, name, rec.declaringClass)
frame.storeObjResult(dst, resolved.value)
return
}
}
class CmdSetMemberSlot(
internal val recvSlot: Int,
internal val fieldId: Int,
internal val methodId: Int,
internal val valueSlot: Int,
) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
val receiver = frame.slotToObj(recvSlot)
val inst = receiver as? ObjInstance
val fieldRec = if (fieldId >= 0) {
inst?.fieldRecordForId(fieldId) ?: receiver.objClass.fieldRecordForId(fieldId)
} else null
val rec = fieldRec ?: run {
if (methodId >= 0) {
inst?.methodRecordForId(methodId) ?: receiver.objClass.methodRecordForId(methodId)
} else null
} ?: frame.scope.raiseSymbolNotFound("member")
val name = rec.memberName ?: "<member>"
frame.scope.assign(rec, name, frame.slotToObj(valueSlot))
return
}
}
class CmdCallMemberSlot(
internal val recvSlot: Int,
internal val methodId: Int,
internal val argBase: Int,
internal val argCount: Int,
internal val dst: Int,
) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncFrameToScope()
}
val nameConst = frame.fn.constants.getOrNull(nameId) as? BytecodeConst.StringVal
?: error("GET_THIS_MEMBER expects StringVal at $nameId")
val ref = net.sergeych.lyng.obj.ImplicitThisMemberRef(nameConst.value, frame.scope.pos)
val result = ref.evalValue(frame.scope)
frame.storeObjResult(dst, result)
return
}
}
class CmdSetThisMember(
internal val nameId: Int,
internal val valueSlot: Int,
) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncFrameToScope()
val receiver = frame.slotToObj(recvSlot)
val inst = receiver as? ObjInstance
val rec = inst?.methodRecordForId(methodId)
?: receiver.objClass.methodRecordForId(methodId)
?: frame.scope.raiseError("member id $methodId not found on ${receiver.objClass.className}")
val callArgs = frame.buildArguments(argBase, argCount)
val name = rec.memberName ?: "<member>"
val decl = rec.declaringClass ?: receiver.objClass
val result = when (rec.type) {
ObjRecord.Type.Property -> {
if (callArgs.isEmpty()) (rec.value as ObjProperty).callGetter(frame.scope, receiver, decl)
else frame.scope.raiseError("property $name cannot be called with arguments")
}
ObjRecord.Type.Fun, ObjRecord.Type.Delegated -> {
val callScope = inst?.instanceScope ?: frame.scope
rec.value.invoke(callScope, receiver, callArgs, decl)
}
else -> frame.scope.raiseError("member $name is not callable")
}
val nameConst = frame.fn.constants.getOrNull(nameId) as? BytecodeConst.StringVal
?: error("SET_THIS_MEMBER expects StringVal at $nameId")
val ref = net.sergeych.lyng.obj.ImplicitThisMemberRef(nameConst.value, frame.scope.pos)
ref.setAt(frame.scope.pos, frame.scope, frame.slotToObj(valueSlot))
if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncScopeToFrame()
}
frame.storeObjResult(dst, result)
return
}
}
@@ -1449,13 +1507,13 @@ class CmdEvalStmt(internal val id: Int, internal val dst: Int) : Cmd() {
}
}
class CmdEvalValueFn(internal val id: Int, internal val dst: Int) : Cmd() {
class CmdMakeValueFn(internal val id: Int, internal val dst: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncFrameToScope()
}
val valueFn = frame.fn.constants.getOrNull(id) as? BytecodeConst.ValueFn
?: error("EVAL_VALUE_FN expects ValueFn at $id")
?: error("MAKE_VALUE_FN expects ValueFn at $id")
val result = valueFn.fn(frame.scope).value
if (frame.fn.localSlotNames.isNotEmpty()) {
frame.syncScopeToFrame()
@@ -30,6 +30,9 @@ enum class Opcode(val code: Int) {
BOX_OBJ(0x0A),
RANGE_INT_BOUNDS(0x0B),
MAKE_RANGE(0x0C),
LOAD_THIS(0x0D),
MAKE_VALUE_FN(0x0E),
LOAD_THIS_VARIANT(0x0F),
INT_TO_REAL(0x10),
REAL_TO_INT(0x11),
@@ -124,19 +127,17 @@ enum class Opcode(val code: Int) {
CALL_DIRECT(0x90),
CALL_VIRTUAL(0x91),
CALL_FALLBACK(0x92),
CALL_MEMBER_SLOT(0x92),
CALL_SLOT(0x93),
GET_FIELD(0xA0),
SET_FIELD(0xA1),
GET_INDEX(0xA2),
SET_INDEX(0xA3),
GET_NAME(0xA4),
LIST_LITERAL(0xA5),
GET_THIS_MEMBER(0xA6),
SET_THIS_MEMBER(0xA7),
GET_MEMBER_SLOT(0xA8),
SET_MEMBER_SLOT(0xA9),
EVAL_FALLBACK(0xB0),
RESOLVE_SCOPE_SLOT(0xB1),
LOAD_OBJ_ADDR(0xB2),
STORE_OBJ_ADDR(0xB3),
@@ -147,9 +148,6 @@ enum class Opcode(val code: Int) {
LOAD_BOOL_ADDR(0xB8),
STORE_BOOL_ADDR(0xB9),
THROW(0xBB),
EVAL_REF(0xBC),
EVAL_STMT(0xBD),
EVAL_VALUE_FN(0xBE),
ITER_PUSH(0xBF),
ITER_POP(0xC0),
ITER_CANCEL(0xC1),
@@ -39,10 +39,11 @@ inline fun <reified T : Obj> Scope.addFnDoc(
returns: TypeDoc? = null,
tags: Map<String, List<String>> = emptyMap(),
moduleName: String? = null,
callSignature: net.sergeych.lyng.CallSignature? = null,
crossinline fn: suspend Scope.() -> T
) {
// Register runtime function(s)
addFn(*names) { fn() }
addFn(*names, callSignature = callSignature) { fn() }
// Determine module
val mod = moduleName ?: findModuleNameOrUnknown()
// Register docs once per name
@@ -124,17 +124,7 @@ open class Obj {
}
}
// 2. Extensions in scope
val extension = scope.findExtension(objClass, name)
if (extension != null) {
if (extension.type == ObjRecord.Type.Property) {
if (args.isEmpty()) return (extension.value as ObjProperty).callGetter(scope, this, extension.declaringClass)
} else if (extension.type != ObjRecord.Type.Delegated) {
return extension.value.invoke(scope, this, args)
}
}
// 3. Root object fallback
// 2. Root object fallback
for (cls in objClass.mro) {
if (cls.className == "Obj") {
cls.members[name]?.let { rec ->
@@ -181,7 +171,7 @@ open class Obj {
open suspend fun equals(scope: Scope, other: Obj): Boolean {
if (other === this) return true
val m = objClass.getInstanceMemberOrNull("equals") ?: scope.findExtension(objClass, "equals")
val m = objClass.getInstanceMemberOrNull("equals")
if (m != null) {
return invokeInstanceMethod(scope, "equals", Arguments(other)).toBool()
}
@@ -375,7 +365,7 @@ open class Obj {
* to generate it as 'this = this + other', reassigning its variable
*/
open suspend fun plusAssign(scope: Scope, other: Obj): Obj? {
val m = objClass.getInstanceMemberOrNull("plusAssign") ?: scope.findExtension(objClass, "plusAssign")
val m = objClass.getInstanceMemberOrNull("plusAssign")
return if (m != null) {
invokeInstanceMethod(scope, "plusAssign", Arguments(other))
} else null
@@ -385,28 +375,28 @@ open class Obj {
* `-=` operations, see [plusAssign]
*/
open suspend fun minusAssign(scope: Scope, other: Obj): Obj? {
val m = objClass.getInstanceMemberOrNull("minusAssign") ?: scope.findExtension(objClass, "minusAssign")
val m = objClass.getInstanceMemberOrNull("minusAssign")
return if (m != null) {
invokeInstanceMethod(scope, "minusAssign", Arguments(other))
} else null
}
open suspend fun mulAssign(scope: Scope, other: Obj): Obj? {
val m = objClass.getInstanceMemberOrNull("mulAssign") ?: scope.findExtension(objClass, "mulAssign")
val m = objClass.getInstanceMemberOrNull("mulAssign")
return if (m != null) {
invokeInstanceMethod(scope, "mulAssign", Arguments(other))
} else null
}
open suspend fun divAssign(scope: Scope, other: Obj): Obj? {
val m = objClass.getInstanceMemberOrNull("divAssign") ?: scope.findExtension(objClass, "divAssign")
val m = objClass.getInstanceMemberOrNull("divAssign")
return if (m != null) {
invokeInstanceMethod(scope, "divAssign", Arguments(other))
} else null
}
open suspend fun modAssign(scope: Scope, other: Obj): Obj? {
val m = objClass.getInstanceMemberOrNull("modAssign") ?: scope.findExtension(objClass, "modAssign")
val m = objClass.getInstanceMemberOrNull("modAssign")
return if (m != null) {
invokeInstanceMethod(scope, "modAssign", Arguments(other))
} else null
@@ -467,16 +457,7 @@ open class Obj {
}
}
// 2. Extensions
val extension = scope.findExtension(objClass, name)
if (extension != null) {
val resolved = resolveRecord(scope, extension, name, extension.declaringClass)
if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement)
return resolved.copy(value = resolved.value.invoke(scope, this, Arguments.EMPTY, extension.declaringClass))
return resolved
}
// 3. Root fallback
// 2. Root fallback
for (cls in objClass.mro) {
if (cls.className == "Obj") {
cls.members[name]?.let { rec ->
@@ -558,11 +539,7 @@ open class Obj {
}
}
}
// 2. Extensions
if (field == null) {
field = scope.findExtension(objClass, name)
}
// 3. Root fallback
// 2. Root fallback
if (field == null) {
for (cls in objClass.mro) {
if (cls.className == "Obj") {
@@ -275,6 +275,11 @@ open class ObjClass(
internal data class FieldSlot(val slot: Int, val record: ObjRecord)
internal data class ResolvedMember(val record: ObjRecord, val declaringClass: ObjClass)
internal data class MethodSlot(val slot: Int, val record: ObjRecord)
private var nextFieldId: Int = 0
private var nextMethodId: Int = 0
private val fieldIdMap: MutableMap<String, Int> = mutableMapOf()
private val methodIdMap: MutableMap<String, Int> = mutableMapOf()
private var methodIdSeeded: Boolean = false
private var fieldSlotLayoutVersion: Int = -1
private var fieldSlotMap: Map<String, FieldSlot> = emptyMap()
private var fieldSlotCount: Int = 0
@@ -287,19 +292,29 @@ open class ObjClass(
private fun ensureFieldSlots(): Map<String, FieldSlot> {
if (fieldSlotLayoutVersion == layoutVersion) return fieldSlotMap
val res = mutableMapOf<String, FieldSlot>()
var idx = 0
var maxId = -1
for (cls in mro) {
for ((name, rec) in cls.members) {
if (rec.isAbstract) continue
if (rec.type != ObjRecord.Type.Field && rec.type != ObjRecord.Type.ConstructorField) continue
val key = cls.mangledName(name)
if (res.containsKey(key)) continue
res[key] = FieldSlot(idx, rec)
idx += 1
val fieldId = rec.fieldId ?: cls.assignFieldId(name, rec)
res[key] = FieldSlot(fieldId, rec)
if (fieldId > maxId) maxId = fieldId
}
cls.classScope?.objects?.forEach { (name, rec) ->
if (rec.isAbstract) return@forEach
if (rec.type != ObjRecord.Type.Field && rec.type != ObjRecord.Type.ConstructorField) return@forEach
val key = cls.mangledName(name)
if (res.containsKey(key)) return@forEach
val fieldId = rec.fieldId ?: cls.assignFieldId(name, rec)
res[key] = FieldSlot(fieldId, rec)
if (fieldId > maxId) maxId = fieldId
}
}
fieldSlotMap = res
fieldSlotCount = idx
fieldSlotCount = maxId + 1
fieldSlotLayoutVersion = layoutVersion
return fieldSlotMap
}
@@ -308,7 +323,6 @@ open class ObjClass(
if (instanceMemberLayoutVersion == layoutVersion) return instanceMemberCache
val res = mutableMapOf<String, ResolvedMember>()
for (cls in mro) {
if (cls.className == "Obj") break
for ((name, rec) in cls.members) {
if (rec.isAbstract) continue
if (res.containsKey(name)) continue
@@ -330,7 +344,7 @@ open class ObjClass(
private fun ensureMethodSlots(): Map<String, MethodSlot> {
if (methodSlotLayoutVersion == layoutVersion) return methodSlotMap
val res = mutableMapOf<String, MethodSlot>()
var idx = 0
var maxId = -1
for (cls in mro) {
if (cls.className == "Obj") break
for ((name, rec) in cls.members) {
@@ -343,8 +357,9 @@ open class ObjClass(
}
val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
if (res.containsKey(key)) continue
res[key] = MethodSlot(idx, rec)
idx += 1
val methodId = rec.methodId ?: cls.assignMethodId(name, rec)
res[key] = MethodSlot(methodId, rec)
if (methodId > maxId) maxId = methodId
}
cls.classScope?.objects?.forEach { (name, rec) ->
if (rec.isAbstract) return@forEach
@@ -353,12 +368,13 @@ open class ObjClass(
rec.type != ObjRecord.Type.Property) return@forEach
val key = if (rec.visibility == Visibility.Private || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name
if (res.containsKey(key)) return@forEach
res[key] = MethodSlot(idx, rec)
idx += 1
val methodId = rec.methodId ?: cls.assignMethodId(name, rec)
res[key] = MethodSlot(methodId, rec)
if (methodId > maxId) maxId = methodId
}
}
methodSlotMap = res
methodSlotCount = idx
methodSlotCount = maxId + 1
methodSlotLayoutVersion = layoutVersion
return methodSlotMap
}
@@ -374,6 +390,10 @@ open class ObjClass(
}
internal fun fieldSlotMap(): Map<String, FieldSlot> = ensureFieldSlots()
internal fun fieldRecordForId(fieldId: Int): ObjRecord? {
ensureFieldSlots()
return fieldSlotMap.values.firstOrNull { it.slot == fieldId }?.record
}
internal fun resolveInstanceMember(name: String): ResolvedMember? = ensureInstanceMemberCache()[name]
internal fun methodSlotCount(): Int {
ensureMethodSlots()
@@ -384,6 +404,117 @@ open class ObjClass(
return methodSlotMap[key]
}
internal fun methodSlotMap(): Map<String, MethodSlot> = ensureMethodSlots()
internal fun methodRecordForId(methodId: Int): ObjRecord? {
ensureMethodSlots()
methodSlotMap.values.firstOrNull { it.slot == methodId }?.record?.let { return it }
// Fallback to scanning the MRO in case a parent method id was added after slot cache creation.
for (cls in mro) {
for ((_, rec) in cls.members) {
if (rec.methodId == methodId) return rec
}
cls.classScope?.objects?.forEach { (_, rec) ->
if (rec.methodId == methodId) return rec
}
}
return null
}
internal fun instanceFieldIdMap(): Map<String, Int> {
val result = mutableMapOf<String, Int>()
for (cls in mro) {
if (cls.className == "Obj") break
for ((name, rec) in cls.members) {
if (rec.isAbstract) continue
if (rec.type != ObjRecord.Type.Field && rec.type != ObjRecord.Type.ConstructorField) continue
if (rec.visibility == Visibility.Private) continue
val id = rec.fieldId ?: cls.assignFieldId(name, rec)
result.putIfAbsent(name, id)
}
cls.classScope?.objects?.forEach { (name, rec) ->
if (rec.isAbstract) return@forEach
if (rec.type != ObjRecord.Type.Field && rec.type != ObjRecord.Type.ConstructorField) return@forEach
if (rec.visibility == Visibility.Private) return@forEach
val id = rec.fieldId ?: cls.assignFieldId(name, rec)
result.putIfAbsent(name, id)
}
}
return result
}
internal fun instanceMethodIdMap(includeAbstract: Boolean = false): Map<String, Int> {
val result = mutableMapOf<String, Int>()
for (cls in mro) {
for ((name, rec) in cls.members) {
if (!includeAbstract && rec.isAbstract) continue
if (rec.visibility == Visibility.Private) continue
if (rec.type != ObjRecord.Type.Fun &&
rec.type != ObjRecord.Type.Property &&
rec.type != ObjRecord.Type.Delegated) continue
val id = rec.methodId ?: cls.assignMethodId(name, rec)
result.putIfAbsent(name, id)
}
cls.classScope?.objects?.forEach { (name, rec) ->
if (!includeAbstract && rec.isAbstract) return@forEach
if (rec.visibility == Visibility.Private) return@forEach
if (rec.type != ObjRecord.Type.Fun &&
rec.type != ObjRecord.Type.Property &&
rec.type != ObjRecord.Type.Delegated) return@forEach
val id = rec.methodId ?: cls.assignMethodId(name, rec)
result.putIfAbsent(name, id)
}
}
return result
}
private fun assignFieldId(name: String, rec: ObjRecord): Int {
val existingId = rec.fieldId
if (existingId != null) {
fieldIdMap[name] = existingId
return existingId
}
val id = fieldIdMap.getOrPut(name) { nextFieldId++ }
return id
}
private fun assignMethodId(name: String, rec: ObjRecord): Int {
ensureMethodIdSeeded()
val existingId = rec.methodId
if (existingId != null) {
methodIdMap[name] = existingId
return existingId
}
val id = methodIdMap.getOrPut(name) { nextMethodId++ }
return id
}
private fun ensureMethodIdSeeded() {
if (methodIdSeeded) return
var maxId = -1
for (cls in mroParents) {
for ((name, rec) in cls.members) {
if (rec.type != ObjRecord.Type.Fun &&
rec.type != ObjRecord.Type.Property &&
rec.type != ObjRecord.Type.Delegated
) continue
val id = rec.methodId ?: cls.assignMethodId(name, rec)
methodIdMap.putIfAbsent(name, id)
if (id > maxId) maxId = id
}
cls.classScope?.objects?.forEach { (name, rec) ->
if (rec.type != ObjRecord.Type.Fun &&
rec.type != ObjRecord.Type.Property &&
rec.type != ObjRecord.Type.Delegated
) return@forEach
val id = rec.methodId ?: cls.assignMethodId(name, rec)
methodIdMap.putIfAbsent(name, id)
if (id > maxId) maxId = id
}
}
if (nextMethodId <= maxId) {
nextMethodId = maxId + 1
}
methodIdSeeded = true
}
override fun toString(): String = className
@@ -618,12 +749,15 @@ open class ObjClass(
isOverride: Boolean = false,
isTransient: Boolean = false,
type: ObjRecord.Type = ObjRecord.Type.Field,
fieldId: Int? = null,
methodId: Int? = null,
): ObjRecord {
// Validation of override rules: only for non-system declarations
var existing: ObjRecord? = null
var actualOverride = false
if (pos != Pos.builtIn) {
// Only consider TRUE instance members from ancestors for overrides
val existing = getInstanceMemberOrNull(name, includeAbstract = true, includeStatic = false)
var actualOverride = false
existing = getInstanceMemberOrNull(name, includeAbstract = true, includeStatic = false)
if (existing != null && existing.declaringClass != this) {
// If the existing member is private in the ancestor, it's not visible for overriding.
// It should be treated as a new member in this class.
@@ -654,6 +788,56 @@ open class ObjClass(
throw ScriptError(pos, "$name is already defined in $objClass")
// Install/override in this class
val effectiveFieldId = if (type == ObjRecord.Type.Field || type == ObjRecord.Type.ConstructorField) {
fieldId ?: fieldIdMap[name]?.let { it } ?: run {
fieldIdMap[name] = nextFieldId
nextFieldId++
fieldIdMap[name]!!
}
} else {
fieldId
}
val inheritedCandidate = run {
var found: ObjRecord? = null
for (cls in mro) {
if (cls === this) continue
if (cls.className == "Obj") break
cls.members[name]?.let {
found = it
return@run found
}
}
found
}
if (type == ObjRecord.Type.Fun ||
type == ObjRecord.Type.Property ||
type == ObjRecord.Type.Delegated
) {
ensureMethodIdSeeded()
}
val effectiveMethodId = if (type == ObjRecord.Type.Fun ||
type == ObjRecord.Type.Property ||
type == ObjRecord.Type.Delegated
) {
val inherited = if (actualOverride) {
existing?.methodId
} else {
val candidate = inheritedCandidate
if (candidate != null &&
candidate.declaringClass != this &&
(candidate.visibility.isPublic || canAccessMember(candidate.visibility, candidate.declaringClass, this, name))
) {
candidate.methodId
} else null
}
methodId ?: inherited ?: methodIdMap[name]?.let { it } ?: run {
methodIdMap[name] = nextMethodId
nextMethodId++
methodIdMap[name]!!
}
} else {
methodId
}
val rec = ObjRecord(
initialValue, isMutable, visibility, writeVisibility,
declaringClass = declaringClass,
@@ -661,7 +845,10 @@ open class ObjClass(
isClosed = isClosed,
isOverride = isOverride,
isTransient = isTransient,
type = type
type = type,
memberName = name,
fieldId = effectiveFieldId,
methodId = effectiveMethodId
)
members[name] = rec
// Structural change: bump layout version for PIC invalidation
@@ -682,13 +869,52 @@ open class ObjClass(
writeVisibility: Visibility? = null,
pos: Pos = Pos.builtIn,
isTransient: Boolean = false,
type: ObjRecord.Type = ObjRecord.Type.Field
type: ObjRecord.Type = ObjRecord.Type.Field,
fieldId: Int? = null,
methodId: Int? = null
): ObjRecord {
initClassScope()
val existing = classScope!!.objects[name]
if (existing != null)
throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
val rec = classScope!!.addItem(name, isMutable, initialValue, visibility, writeVisibility, recordType = type, isTransient = isTransient)
val effectiveFieldId = if (type == ObjRecord.Type.Field || type == ObjRecord.Type.ConstructorField) {
fieldId ?: fieldIdMap[name]?.let { it } ?: run {
fieldIdMap[name] = nextFieldId
nextFieldId++
fieldIdMap[name]!!
}
} else {
fieldId
}
if (type == ObjRecord.Type.Fun ||
type == ObjRecord.Type.Property ||
type == ObjRecord.Type.Delegated
) {
ensureMethodIdSeeded()
}
val effectiveMethodId = if (type == ObjRecord.Type.Fun ||
type == ObjRecord.Type.Property ||
type == ObjRecord.Type.Delegated
) {
methodId ?: methodIdMap[name]?.let { it } ?: run {
methodIdMap[name] = nextMethodId
nextMethodId++
methodIdMap[name]!!
}
} else {
methodId
}
val rec = classScope!!.addItem(
name,
isMutable,
initialValue,
visibility,
writeVisibility,
recordType = type,
isTransient = isTransient,
fieldId = effectiveFieldId,
methodId = effectiveMethodId
)
// Structural change: bump layout version for PIC invalidation
layoutVersion += 1
return rec
@@ -704,13 +930,15 @@ open class ObjClass(
isClosed: Boolean = false,
isOverride: Boolean = false,
pos: Pos = Pos.builtIn,
methodId: Int? = null,
code: (suspend Scope.() -> Obj)? = null
) {
val stmt = code?.let { statement { it() } } ?: ObjNull
createField(
name, stmt, isMutable, visibility, writeVisibility, pos, declaringClass,
isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride,
type = ObjRecord.Type.Fun
type = ObjRecord.Type.Fun,
methodId = methodId
)
}
@@ -727,7 +955,8 @@ open class ObjClass(
isClosed: Boolean = false,
isOverride: Boolean = false,
pos: Pos = Pos.builtIn,
prop: ObjProperty? = null
prop: ObjProperty? = null,
methodId: Int? = null
) {
val g = getter?.let { statement { it() } }
val s = setter?.let { statement { it(requiredArg(0)); ObjVoid } }
@@ -735,7 +964,8 @@ open class ObjClass(
createField(
name, finalProp, false, visibility, writeVisibility, pos, declaringClass,
isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride,
type = ObjRecord.Type.Property
type = ObjRecord.Type.Property,
methodId = methodId
)
}
@@ -0,0 +1,68 @@
/*
* Copyright 2026 Sergey S. Chernov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyng.obj
import net.sergeych.lyng.Arguments
import net.sergeych.lyng.Scope
class ObjExtensionMethodCallable(
private val name: String,
private val target: Obj,
private val declaringClass: ObjClass? = null
) : Obj() {
override suspend fun callOn(scope: Scope): Obj {
val args = scope.args
if (args.isEmpty()) scope.raiseError("extension call $name requires receiver")
val receiver = args.first()
val rest = if (args.size <= 1) {
Arguments.EMPTY
} else {
Arguments(args.list.subList(1, args.size), args.tailBlockMode, args.named)
}
return target.invoke(scope, receiver, rest, declaringClass)
}
}
class ObjExtensionPropertyGetterCallable(
private val name: String,
private val property: ObjProperty,
private val declaringClass: ObjClass? = null
) : Obj() {
override suspend fun callOn(scope: Scope): Obj {
val args = scope.args
if (args.isEmpty()) scope.raiseError("extension property $name requires receiver")
val receiver = args.first()
if (args.size > 1) scope.raiseError("extension property $name getter takes no arguments")
return property.callGetter(scope, receiver, declaringClass)
}
}
class ObjExtensionPropertySetterCallable(
private val name: String,
private val property: ObjProperty,
private val declaringClass: ObjClass? = null
) : Obj() {
override suspend fun callOn(scope: Scope): Obj {
val args = scope.args
if (args.size < 2) scope.raiseError("extension property $name setter requires value")
val receiver = args[0]
val value = args[1]
property.callSetter(scope, receiver, value, declaringClass)
return ObjVoid
}
}
@@ -81,8 +81,8 @@ private fun createLyngFlowInput(scope: Scope, producer: Statement): ReceiveChann
} catch (x: ScriptFlowIsNoMoreCollected) {
// premature flow closing, OK
} catch (x: Exception) {
// Suppress stack traces in background producer to avoid noisy stderr during tests.
// If needed, consider routing to a logger in the future.
channel.close(x)
return@globalLaunch
}
channel.close()
}
@@ -107,9 +107,7 @@ class ObjFlow(val producer: Statement, val scope: Scope) : Obj() {
) {
val objFlow = thisAs<ObjFlow>()
ObjFlowIterator(statement {
objFlow.producer.execute(
ClosureScope(this, objFlow.scope)
)
objFlow.producer.execute(this)
})
}
}
@@ -137,6 +135,7 @@ class ObjFlowIterator(val producer: Statement) : Obj() {
// cold start:
if (channel == null) channel = createLyngFlowInput(scope, producer)
if (nextItem == null) nextItem = channel!!.receiveCatching()
nextItem?.exceptionOrNull()?.let { throw it }
return ObjBool(nextItem!!.isSuccess)
}
@@ -61,6 +61,14 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
return if (idx >= 0 && idx < methodSlots.size) methodSlots[idx] else null
}
internal fun fieldRecordForId(fieldId: Int): ObjRecord? {
return if (fieldId >= 0 && fieldId < fieldSlots.size) fieldSlots[fieldId] else null
}
internal fun methodRecordForId(methodId: Int): ObjRecord? {
return if (methodId >= 0 && methodId < methodSlots.size) methodSlots[methodId] else null
}
override suspend fun readField(scope: Scope, name: String): ObjRecord {
val caller = scope.currentClassCtx
@@ -29,6 +29,12 @@ import net.sergeych.lyng.miniast.type
*/
val ObjIterable by lazy {
ObjClass("Iterable").apply {
addFn(
name = "iterator",
isAbstract = true,
isClosed = false,
code = null
)
addPropertyDoc(
name = "toList",
@@ -241,4 +247,4 @@ val ObjIterable by lazy {
list
}
}
}
}
@@ -38,6 +38,10 @@ data class ObjRecord(
var delegate: Obj? = null,
/** The receiver object to resolve this member against (for instance fields/methods). */
var receiver: Obj? = null,
val callSignature: net.sergeych.lyng.CallSignature? = null,
val memberName: String? = null,
val fieldId: Int? = null,
val methodId: Int? = null,
) {
val effectiveWriteVisibility: Visibility get() = writeVisibility ?: visibility
enum class Type(val comparable: Boolean = false,val serializable: Boolean = false) {
@@ -59,4 +63,4 @@ data class ObjRecord(
@Suppress("unused")
fun qualifiedName(name: String): String =
"${importedFrom?.packageName ?: "anonymous"}.$name"
}
}
@@ -417,6 +417,11 @@ class CastRef(
private val isNullable: Boolean,
private val atPos: Pos,
) : ObjRef {
internal fun castValueRef(): ObjRef = valueRef
internal fun castTypeRef(): ObjRef = typeRef
internal fun castIsNullable(): Boolean = isNullable
internal fun castPos(): Pos = atPos
override suspend fun get(scope: Scope): ObjRecord {
val v0 = valueRef.evalValue(scope)
val t = typeRef.evalValue(scope)
@@ -501,107 +506,64 @@ private suspend fun resolveQualifiedThisInstance(scope: Scope, typeName: String)
class QualifiedThisFieldSlotRef(
private val typeName: String,
val name: String,
private val fieldId: Int?,
private val methodId: Int?,
private val isOptional: Boolean
) : ObjRef {
internal fun fieldId(): Int? = fieldId
internal fun methodId(): Int? = methodId
internal fun receiverTypeName(): String = typeName
internal fun optional(): Boolean = isOptional
override suspend fun get(scope: Scope): ObjRecord {
val (inst, startClass) = resolveQualifiedThisInstance(scope, typeName)
val inst = scope.thisVariants.firstOrNull { it.objClass.className == typeName } as? ObjInstance
?: scope.raiseClassCastError("No instance of type $typeName found in scope")
if (isOptional && inst == ObjNull) return ObjNull.asMutable
if (startClass !== inst.objClass) {
return ObjQualifiedView(inst, startClass).readField(scope, name)
}
val caller = scope.currentClassCtx
if (caller != null) {
val mangled = caller.mangledName(name)
inst.fieldRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
return inst.resolveRecord(scope, rec, name, caller)
}
}
inst.methodRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
return inst.resolveRecord(scope, rec, name, caller)
}
}
}
val key = inst.objClass.publicMemberResolution[name] ?: name
inst.fieldRecordForKey(key)?.let { rec ->
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract)
fieldId?.let { id ->
val rec = inst.fieldRecordForId(id)
if (rec != null && (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) {
return rec
}
inst.methodRecordForKey(key)?.let { rec ->
if (!rec.isAbstract) {
val decl = rec.declaringClass ?: inst.objClass.findDeclaringClassOf(name) ?: inst.objClass
return inst.resolveRecord(scope, rec, name, decl)
}
}
return inst.readField(scope, name)
methodId?.let { id ->
val rec = inst.methodRecordForId(id)
if (rec != null && !rec.isAbstract) {
val decl = rec.declaringClass ?: inst.objClass
return inst.resolveRecord(scope, rec, rec.memberName ?: name, decl)
}
}
scope.raiseSymbolNotFound(name)
}
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
val (inst, startClass) = resolveQualifiedThisInstance(scope, typeName)
val inst = scope.thisVariants.firstOrNull { it.objClass.className == typeName } as? ObjInstance
?: scope.raiseClassCastError("No instance of type $typeName found in scope")
if (isOptional && inst == ObjNull) return
if (startClass !== inst.objClass) {
ObjQualifiedView(inst, startClass).writeField(scope, name, newValue)
return
}
val caller = scope.currentClassCtx
if (caller != null) {
val mangled = caller.mangledName(name)
inst.fieldRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
writeDirectOrFallback(scope, inst, rec, name, newValue, caller)
return
}
}
inst.methodRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private &&
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
inst.writeField(scope, name, newValue)
return
}
}
}
val key = inst.objClass.publicMemberResolution[name] ?: name
inst.fieldRecordForKey(key)?.let { rec ->
val decl = rec.declaringClass ?: inst.objClass.findDeclaringClassOf(name)
if (canAccessMember(rec.effectiveWriteVisibility, decl, caller, name)) {
writeDirectOrFallback(scope, inst, rec, name, newValue, decl)
fieldId?.let { id ->
val rec = inst.fieldRecordForId(id)
if (rec != null) {
assignToRecord(scope, rec, newValue)
return
}
}
inst.methodRecordForKey(key)?.let { rec ->
if (rec.effectiveWriteVisibility == Visibility.Public &&
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
inst.writeField(scope, name, newValue)
methodId?.let { id ->
val rec = inst.methodRecordForId(id)
if (rec != null) {
scope.assign(rec, rec.memberName ?: name, newValue)
return
}
}
inst.writeField(scope, name, newValue)
scope.raiseSymbolNotFound(name)
}
private suspend fun writeDirectOrFallback(
scope: Scope,
inst: ObjInstance,
rec: ObjRecord,
name: String,
newValue: Obj,
decl: ObjClass?
) {
private suspend fun assignToRecord(scope: Scope, rec: ObjRecord, newValue: Obj) {
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) {
if (!rec.isMutable && rec.value !== ObjUnset) {
ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
ObjIllegalAssignmentException(scope, "can't reassign val ${rec.memberName ?: name}").raise()
}
if (rec.value.assign(scope, newValue) == null) rec.value = newValue
} else {
inst.writeField(scope, name, newValue)
scope.assign(rec, rec.memberName ?: name, newValue)
}
}
}
@@ -613,51 +575,36 @@ class QualifiedThisFieldSlotRef(
class QualifiedThisMethodSlotCallRef(
private val typeName: String,
private val name: String,
private val methodId: Int?,
private val args: List<ParsedArgument>,
private val tailBlock: Boolean,
private val isOptional: Boolean
) : ObjRef {
internal fun receiverTypeName(): String = typeName
internal fun methodName(): String = name
internal fun methodId(): Int? = methodId
internal fun arguments(): List<ParsedArgument> = args
internal fun hasTailBlock(): Boolean = tailBlock
internal fun optionalInvoke(): Boolean = isOptional
override suspend fun get(scope: Scope): ObjRecord = evalValue(scope).asReadonly
override suspend fun evalValue(scope: Scope): Obj {
val (inst, startClass) = resolveQualifiedThisInstance(scope, typeName)
val inst = scope.thisVariants.firstOrNull { it.objClass.className == typeName } as? ObjInstance
?: scope.raiseClassCastError("No instance of type $typeName found in scope")
if (isOptional && inst == ObjNull) return ObjNull
val callArgs = args.toArguments(scope, tailBlock)
if (startClass !== inst.objClass) {
return ObjQualifiedView(inst, startClass).invokeInstanceMethod(scope, name, callArgs, null)
}
val caller = scope.currentClassCtx
if (caller != null) {
val mangled = caller.mangledName(name)
inst.methodRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private && !rec.isAbstract) {
if (rec.type == ObjRecord.Type.Property) {
if (callArgs.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, inst, caller)
} else if (rec.type == ObjRecord.Type.Fun) {
return rec.value.invoke(inst.instanceScope, inst, callArgs, caller)
}
}
val id = methodId ?: scope.raiseSymbolNotFound(name)
val rec = inst.methodRecordForId(id) ?: scope.raiseSymbolNotFound(name)
val decl = rec.declaringClass ?: inst.objClass
return when (rec.type) {
ObjRecord.Type.Property -> {
if (callArgs.isEmpty()) (rec.value as ObjProperty).callGetter(scope, inst, decl)
else scope.raiseError("property $name cannot be called with arguments")
}
ObjRecord.Type.Fun, ObjRecord.Type.Delegated -> rec.value.invoke(inst.instanceScope, inst, callArgs, decl)
else -> scope.raiseError("member $name is not callable")
}
val key = inst.objClass.publicMemberResolution[name] ?: name
inst.methodRecordForKey(key)?.let { rec ->
if (!rec.isAbstract) {
val decl = rec.declaringClass ?: inst.objClass.findDeclaringClassOf(name) ?: inst.objClass
val effectiveCaller = caller ?: if (scope.thisObj === inst) inst.objClass else null
if (!canAccessMember(rec.visibility, decl, effectiveCaller, name))
scope.raiseError(ObjIllegalAccessException(scope, "can't invoke method $name (declared in ${decl.className})"))
if (rec.type == ObjRecord.Type.Property) {
if (callArgs.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, inst, decl)
} else if (rec.type == ObjRecord.Type.Fun) {
return rec.value.invoke(inst.instanceScope, inst, callArgs, decl)
}
}
}
return inst.invokeInstanceMethod(scope, name, callArgs)
}
}
@@ -763,6 +710,9 @@ class ElvisRef(internal val left: ObjRef, internal val right: ObjRef) : ObjRef {
/** Logical OR with short-circuit: a || b */
class LogicalOrRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
internal fun left(): ObjRef = left
internal fun right(): ObjRef = right
override suspend fun get(scope: Scope): ObjRecord {
return evalValue(scope).asReadonly
}
@@ -782,6 +732,9 @@ class LogicalOrRef(private val left: ObjRef, private val right: ObjRef) : ObjRef
/** Logical AND with short-circuit: a && b */
class LogicalAndRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
internal fun left(): ObjRef = left
internal fun right(): ObjRef = right
override suspend fun get(scope: Scope): ObjRecord {
return evalValue(scope).asReadonly
}
@@ -1229,103 +1182,59 @@ class FieldRef(
*/
class ThisFieldSlotRef(
val name: String,
private val fieldId: Int?,
private val methodId: Int?,
private val isOptional: Boolean
) : ObjRef {
internal fun fieldId(): Int? = fieldId
internal fun methodId(): Int? = methodId
internal fun optional(): Boolean = isOptional
override suspend fun get(scope: Scope): ObjRecord {
val th = scope.thisObj
if (th == ObjNull && isOptional) return ObjNull.asMutable
if (th !is ObjInstance) return th.readField(scope, name)
val caller = scope.currentClassCtx
if (caller != null) {
val mangled = caller.mangledName(name)
th.fieldRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
return th.resolveRecord(scope, rec, name, caller)
}
}
th.methodRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
return th.resolveRecord(scope, rec, name, caller)
}
}
val inst = th as? ObjInstance ?: scope.raiseClassCastError("member access on non-instance")
val field = fieldId?.let { inst.fieldRecordForId(it) }
if (field != null && (field.type == ObjRecord.Type.Field || field.type == ObjRecord.Type.ConstructorField) && !field.isAbstract) {
return field
}
val key = th.objClass.publicMemberResolution[name] ?: name
th.fieldRecordForKey(key)?.let { rec ->
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract)
return rec
val method = methodId?.let { inst.methodRecordForId(it) }
if (method != null && !method.isAbstract) {
val decl = method.declaringClass ?: inst.objClass
return inst.resolveRecord(scope, method, method.memberName ?: name, decl)
}
th.methodRecordForKey(key)?.let { rec ->
if (!rec.isAbstract) {
val decl = rec.declaringClass ?: th.objClass.findDeclaringClassOf(name) ?: th.objClass
return th.resolveRecord(scope, rec, name, decl)
}
}
return th.readField(scope, name)
scope.raiseSymbolNotFound(name)
}
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
val th = scope.thisObj
if (th == ObjNull && isOptional) return
if (th !is ObjInstance) {
th.writeField(scope, name, newValue)
val inst = th as? ObjInstance ?: scope.raiseClassCastError("member access on non-instance")
val field = fieldId?.let { inst.fieldRecordForId(it) }
if (field != null) {
assignToRecord(scope, field, newValue)
return
}
val caller = scope.currentClassCtx
if (caller != null) {
val mangled = caller.mangledName(name)
th.fieldRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
writeDirectOrFallback(scope, th, rec, name, newValue, caller)
return
}
}
th.methodRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private &&
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
th.writeField(scope, name, newValue)
return
}
}
val method = methodId?.let { inst.methodRecordForId(it) }
if (method != null) {
scope.assign(method, method.memberName ?: name, newValue)
return
}
val key = th.objClass.publicMemberResolution[name] ?: name
th.fieldRecordForKey(key)?.let { rec ->
val decl = rec.declaringClass ?: th.objClass.findDeclaringClassOf(name)
if (canAccessMember(rec.effectiveWriteVisibility, decl, caller, name)) {
writeDirectOrFallback(scope, th, rec, name, newValue, decl)
return
}
}
th.methodRecordForKey(key)?.let { rec ->
if (rec.effectiveWriteVisibility == Visibility.Public &&
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
th.writeField(scope, name, newValue)
return
}
}
th.writeField(scope, name, newValue)
scope.raiseSymbolNotFound(name)
}
private suspend fun writeDirectOrFallback(
private suspend fun assignToRecord(
scope: Scope,
inst: ObjInstance,
rec: ObjRecord,
name: String,
newValue: Obj,
decl: ObjClass?
newValue: Obj
) {
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) {
if (!rec.isMutable && rec.value !== ObjUnset) {
ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
ObjIllegalAssignmentException(scope, "can't reassign val ${rec.memberName ?: name}").raise()
}
if (rec.value.assign(scope, newValue) == null) rec.value = newValue
} else {
inst.writeField(scope, name, newValue)
scope.assign(rec, rec.memberName ?: name, newValue)
}
}
}
@@ -1863,11 +1772,13 @@ class MethodCallRef(
*/
class ThisMethodSlotCallRef(
private val name: String,
private val methodId: Int?,
private val args: List<ParsedArgument>,
private val tailBlock: Boolean,
private val isOptional: Boolean
) : ObjRef {
internal fun methodName(): String = name
internal fun methodId(): Int? = methodId
internal fun arguments(): List<ParsedArgument> = args
internal fun hasTailBlock(): Boolean = tailBlock
internal fun optionalInvoke(): Boolean = isOptional
@@ -1879,37 +1790,17 @@ class ThisMethodSlotCallRef(
if (base == ObjNull && isOptional) return ObjNull
val callArgs = args.toArguments(scope, tailBlock)
if (base !is ObjInstance) return base.invokeInstanceMethod(scope, name, callArgs)
val caller = scope.currentClassCtx
if (caller != null) {
val mangled = caller.mangledName(name)
base.methodRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private && !rec.isAbstract) {
if (rec.type == ObjRecord.Type.Property) {
if (callArgs.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, base, caller)
} else if (rec.type == ObjRecord.Type.Fun) {
return rec.value.invoke(base.instanceScope, base, callArgs, caller)
}
}
val id = methodId ?: scope.raiseSymbolNotFound(name)
val rec = base.methodRecordForId(id) ?: scope.raiseSymbolNotFound(name)
val decl = rec.declaringClass ?: base.objClass
return when (rec.type) {
ObjRecord.Type.Property -> {
if (callArgs.isEmpty()) (rec.value as ObjProperty).callGetter(scope, base, decl)
else scope.raiseError("property $name cannot be called with arguments")
}
ObjRecord.Type.Fun, ObjRecord.Type.Delegated -> rec.value.invoke(base.instanceScope, base, callArgs, decl)
else -> scope.raiseError("member $name is not callable")
}
val key = base.objClass.publicMemberResolution[name] ?: name
base.methodRecordForKey(key)?.let { rec ->
if (!rec.isAbstract) {
val decl = rec.declaringClass ?: base.objClass.findDeclaringClassOf(name) ?: base.objClass
val effectiveCaller = caller ?: if (scope.thisObj === base) base.objClass else null
if (!canAccessMember(rec.visibility, decl, effectiveCaller, name))
scope.raiseError(ObjIllegalAccessException(scope, "can't invoke method $name (declared in ${decl.className})"))
if (rec.type == ObjRecord.Type.Property) {
if (callArgs.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, base, decl)
} else if (rec.type == ObjRecord.Type.Fun) {
return rec.value.invoke(base.instanceScope, base, callArgs, decl)
}
}
}
return base.invokeInstanceMethod(scope, name, callArgs)
}
}
@@ -2258,23 +2149,12 @@ class FastLocalVarRef(
*/
class ImplicitThisMemberRef(
val name: String,
val atPos: Pos
val atPos: Pos,
internal val fieldId: Int?,
internal val methodId: Int?,
private val preferredThisTypeName: String? = null
) : ObjRef {
private fun resolveInstanceFieldRecord(th: ObjInstance, caller: ObjClass?): ObjRecord? {
if (caller == null) return null
for (cls in th.objClass.mro) {
if (cls.className == "Obj") break
val rec = cls.members[name] ?: continue
if (rec.isAbstract) continue
val decl = rec.declaringClass ?: cls
if (!canAccessMember(rec.visibility, decl, caller, name)) continue
val key = decl.mangledName(name)
th.fieldRecordForKey(key)?.let { return it }
th.instanceScope.objects[key]?.let { return it }
}
return null
}
internal fun preferredThisTypeName(): String? = preferredThisTypeName
override fun forEachVariable(block: (String) -> Unit) {
block(name)
}
@@ -2285,46 +2165,18 @@ class ImplicitThisMemberRef(
override suspend fun get(scope: Scope): ObjRecord {
scope.pos = atPos
val caller = scope.currentClassCtx
val th = scope.thisObj
if (th is ObjClass) {
return th.readField(scope, name)
val th = preferredThisTypeName?.let { typeName ->
scope.thisVariants.firstOrNull { it.objClass.className == typeName }
} ?: scope.thisObj
val inst = th as? ObjInstance
val field = fieldId?.let { inst?.fieldRecordForId(it) ?: th.objClass.fieldRecordForId(it) }
if (field != null && (field.type == ObjRecord.Type.Field || field.type == ObjRecord.Type.ConstructorField) && !field.isAbstract) {
return field
}
if (th != null && th !is ObjInstance) {
return th.readField(scope, name)
}
// member slots on this instance
if (th is ObjInstance) {
// private member access for current class context
caller?.let { c ->
val mangled = c.mangledName(name)
th.fieldRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
return th.resolveRecord(scope, rec, name, c)
}
}
th.methodRecordForKey(mangled)?.let { rec ->
if (rec.visibility == Visibility.Private) {
return th.resolveRecord(scope, rec, name, c)
}
}
}
resolveInstanceFieldRecord(th, caller)?.let { return it }
val key = th.objClass.publicMemberResolution[name] ?: name
th.fieldRecordForKey(key)?.let { rec ->
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract)
return rec
}
th.methodRecordForKey(key)?.let { rec ->
if (!rec.isAbstract) {
val decl = rec.declaringClass ?: th.objClass.findDeclaringClassOf(name) ?: th.objClass
return th.resolveRecord(scope, rec, name, decl)
}
}
val method = methodId?.let { inst?.methodRecordForId(it) ?: th.objClass.methodRecordForId(it) }
if (method != null && !method.isAbstract) {
val decl = method.declaringClass ?: th.objClass
return th.resolveRecord(scope, method, method.memberName ?: name, decl)
}
scope.raiseSymbolNotFound(name)
@@ -2337,49 +2189,21 @@ class ImplicitThisMemberRef(
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
scope.pos = atPos
val caller = scope.currentClassCtx
val th = scope.thisObj
if (th is ObjClass) {
th.writeField(scope, name, newValue)
val th = preferredThisTypeName?.let { typeName ->
scope.thisVariants.firstOrNull { it.objClass.className == typeName }
} ?: scope.thisObj
val inst = th as? ObjInstance
val field = fieldId?.let { inst?.fieldRecordForId(it) ?: th.objClass.fieldRecordForId(it) }
if (field != null) {
scope.assign(field, field.memberName ?: name, newValue)
return
}
if (th != null && th !is ObjInstance) {
th.writeField(scope, name, newValue)
val method = methodId?.let { inst?.methodRecordForId(it) ?: th.objClass.methodRecordForId(it) }
if (method != null) {
scope.assign(method, method.memberName ?: name, newValue)
return
}
// member slots on this instance
if (th is ObjInstance) {
val key = th.objClass.publicMemberResolution[name] ?: name
th.fieldRecordForKey(key)?.let { rec ->
val decl = rec.declaringClass ?: th.objClass.findDeclaringClassOf(name)
if (canAccessMember(rec.effectiveWriteVisibility, decl, caller, name)) {
if ((rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField) && !rec.isAbstract) {
if (!rec.isMutable && rec.value !== ObjUnset) {
ObjIllegalAssignmentException(scope, "can't reassign val $name").raise()
}
if (rec.value.assign(scope, newValue) == null) rec.value = newValue
} else {
th.writeField(scope, name, newValue)
}
return
}
}
th.methodRecordForKey(key)?.let { rec ->
if (rec.effectiveWriteVisibility == Visibility.Public &&
(rec.type == ObjRecord.Type.Property || rec.type == ObjRecord.Type.Delegated)) {
th.writeField(scope, name, newValue)
return
}
}
resolveInstanceFieldRecord(th, caller)?.let { rec ->
scope.assign(rec, name, newValue)
return
}
}
scope.raiseSymbolNotFound(name)
}
}
@@ -2390,16 +2214,19 @@ class ImplicitThisMemberRef(
*/
class ImplicitThisMethodCallRef(
private val name: String,
private val methodId: Int?,
private val args: List<ParsedArgument>,
private val tailBlock: Boolean,
private val isOptional: Boolean,
private val atPos: Pos
private val atPos: Pos,
private val preferredThisTypeName: String? = null
) : ObjRef {
private val memberRef = ImplicitThisMemberRef(name, atPos)
internal fun methodName(): String = name
internal fun arguments(): List<ParsedArgument> = args
internal fun hasTailBlock(): Boolean = tailBlock
internal fun optionalInvoke(): Boolean = isOptional
internal fun preferredThisTypeName(): String? = preferredThisTypeName
internal fun slotId(): Int? = methodId
override suspend fun get(scope: Scope): ObjRecord = evalValue(scope).asReadonly
@@ -2419,7 +2246,25 @@ class ImplicitThisMethodCallRef(
callee.callOn(scope.createChildScope(scope.pos, callArgs))
}
}
return scope.thisObj.invokeInstanceMethod(scope, name, callArgs)
val receiver = preferredThisTypeName?.let { typeName ->
scope.thisVariants.firstOrNull { it.objClass.className == typeName }
} ?: scope.thisObj
if (receiver == ObjNull && isOptional) return ObjNull
val inst = receiver as? ObjInstance ?: return receiver.invokeInstanceMethod(scope, name, callArgs)
if (methodId == null) {
return inst.invokeInstanceMethod(scope, name, callArgs)
}
val id = methodId
val rec = inst.methodRecordForId(id) ?: scope.raiseSymbolNotFound(name)
val decl = rec.declaringClass ?: inst.objClass
return when (rec.type) {
ObjRecord.Type.Property -> {
if (callArgs.isEmpty()) (rec.value as ObjProperty).callGetter(scope, inst, decl)
else scope.raiseError("property $name cannot be called with arguments")
}
ObjRecord.Type.Fun, ObjRecord.Type.Delegated -> rec.value.invoke(inst.instanceScope, inst, callArgs, decl)
else -> scope.raiseError("member $name is not callable")
}
}
}
@@ -2598,6 +2443,8 @@ sealed class MapLiteralEntry {
}
class MapLiteralRef(private val entries: List<MapLiteralEntry>) : ObjRef {
internal fun entries(): List<MapLiteralEntry> = entries
override suspend fun get(scope: Scope): ObjRecord {
return evalValue(scope).asReadonly
}
@@ -18,8 +18,10 @@
package net.sergeych.lyng.obj
import net.sergeych.lyng.PerfFlags
import net.sergeych.lyng.Pos
import net.sergeych.lyng.RegexCache
import net.sergeych.lyng.Scope
import net.sergeych.lyng.Statement
import net.sergeych.lyng.miniast.*
class ObjRegex(val regex: Regex) : Obj() {
@@ -72,6 +74,19 @@ class ObjRegex(val regex: Regex) : Obj() {
val s = requireOnlyArg<ObjString>().value
ObjList(thisAs<ObjRegex>().regex.findAll(s).map { ObjRegexMatch(it) }.toMutableList())
}
createField(
name = "operatorMatch",
initialValue = object : Statement() {
override val pos: Pos = Pos.builtIn
override suspend fun execute(scope: Scope): Obj {
val other = scope.args.firstAndOnly(pos)
val targetScope = scope.parent ?: scope
return (scope.thisObj as ObjRegex).operatorMatch(targetScope, other)
}
},
type = ObjRecord.Type.Fun
)
}
}
}
@@ -144,4 +159,4 @@ class ObjRegexMatch(val match: MatchResult) : Obj() {
}
}
}
}
}
@@ -22,8 +22,10 @@ import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonPrimitive
import net.sergeych.lyng.PerfFlags
import net.sergeych.lyng.Pos
import net.sergeych.lyng.RegexCache
import net.sergeych.lyng.Scope
import net.sergeych.lyng.Statement
import net.sergeych.lyng.miniast.*
import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonEncoder
@@ -338,6 +340,36 @@ data class ObjString(val value: String) : Obj() {
}
)
}
createField(
name = "re",
initialValue = ObjProperty(
name = "re",
getter = object : Statement() {
override val pos: Pos = Pos.builtIn
override suspend fun execute(scope: Scope): Obj {
val pattern = (scope.thisObj as ObjString).value
val re = if (PerfFlags.REGEX_CACHE) RegexCache.get(pattern) else pattern.toRegex()
return ObjRegex(re)
}
},
setter = null
),
type = ObjRecord.Type.Property
)
createField(
name = "operatorMatch",
initialValue = object : Statement() {
override val pos: Pos = Pos.builtIn
override suspend fun execute(scope: Scope): Obj {
val other = scope.args.firstAndOnly(pos)
val targetScope = scope.parent ?: scope
return (scope.thisObj as ObjString).operatorMatch(targetScope, other)
}
},
type = ObjRecord.Type.Fun
)
}
}
}
}
@@ -65,7 +65,7 @@ class CompileTimeResolutionSpecTest {
val report = dryRun(
"""
val G = 10
fun f(x) = x + G
fun f(x=0) = x + G
"""
)
assertTrue(report.errors.isEmpty())
@@ -162,7 +162,7 @@ class CompileTimeResolutionSpecTest {
fun parameterShadowingAllowed() = runTest {
val report = dryRun(
"""
fun f(a) {
fun f(a=0) {
var a = a * 10
a
}
@@ -31,11 +31,12 @@ class ParallelLocalScopeTest {
eval(
"""
class AtomicCounter {
private val m = Mutex()
private val m: Mutex = Mutex()
private var counter = 0
fun increment() {
m.withLock {
val mm: Mutex = m
mm.withLock {
val a = counter
delay(1)
counter = a + 1
@@ -19,10 +19,10 @@ class ScopeCycleRegressionTest {
}
}
fun ll() { Whatever() }
fun ll(): Whatever { Whatever() }
fun callTest1() {
val l = ll()
val l: Whatever = ll()
l.something()
"ok"
}
+216 -170
View File
@@ -68,7 +68,7 @@ class ScriptTest {
val res = scope.eval(
"""
var counter = 0
val d = launch {
val d: Deferred = launch {
val c = counter
delay(1)
counter = c + 1
@@ -85,7 +85,7 @@ class ScriptTest {
val scope = Script.newScope()
val res = scope.eval(
"""
val d = launch {
val d: Deferred = launch {
delay(1)
yield()
}
@@ -1544,7 +1544,7 @@ class ScriptTest {
val prefix = ":"
class T(text) {
class T(text: String) {
fun getText() {
println(text)
prefix + text + "!"
@@ -1571,7 +1571,7 @@ class ScriptTest {
fun testAppliedScopes() = runTest {
eval(
"""
class T(text) {
class T(text: String) {
fun getText() {
println(text)
text + "!"
@@ -1586,24 +1586,24 @@ class ScriptTest {
t1.apply {
// it must take "text" from class t1:
assertEquals("foo", text)
assertEquals( "foo!", getText() )
assertEquals("foo", t1.text)
assertEquals( "foo!", t1.getText() )
assertEquals( ":foo!!", {
prefix + getText() + "!"
prefix + t1.getText() + "!"
}())
}
t2.apply {
assertEquals("bar", text)
assertEquals( "bar!", getText() )
assertEquals("bar", t2.text)
assertEquals( "bar!", t2.getText() )
assertEquals( ":bar!!", {
prefix + getText() + "!"
prefix + t2.getText() + "!"
}())
}
// worst case: names clash
fun badOne() {
val prefix = "&"
t1.apply {
assertEquals( ":foo!!", prefix + getText() + "!" )
assertEquals( "&foo!!", prefix + t1.getText() + "!" )
}
}
badOne()
@@ -1660,8 +1660,8 @@ class ScriptTest {
fun testIsPrimeSampleBug() = runTest {
eval(
"""
fun naive_is_prime(candidate) {
val x = if( candidate !is Int) candidate.toInt() else candidate
fun naive_is_prime(candidate: Int) {
val x = candidate
var divisor = 1
println("start with ",x)
while( ++divisor < x/2 && divisor != 2 ) {
@@ -2171,12 +2171,12 @@ class ScriptTest {
eval(
"""
val x = IllegalArgumentException("test")
var caught = null
var caught: Exception? = null
try {
throw x
}
catch {
caught = it
caught = it as Exception
}
assert( caught is IllegalArgumentException )
""".trimIndent()
@@ -2193,11 +2193,12 @@ class ScriptTest {
null
}
catch(e) {
println(e)
println(e::class)
println(e.message)
val ex = e as Exception
println(ex)
println(ex::class)
println(ex.message)
println("--------------")
e.message
ex.message
}
println(m)
assert( m == "test" )
@@ -2208,24 +2209,20 @@ class ScriptTest {
@Test
fun testTryFinally() = runTest {
val c = Scope()
assertFails {
c.eval(
"""
var resource = "used"
try {
throw "test"
}
finally {
resource = "freed"
}
""".trimIndent()
)
}
c.eval(
val res = c.eval(
"""
assertEquals("freed", resource)
var resource = "used"
try {
throw "test"
} catch (e) {
// swallow
} finally {
resource = "freed"
}
resource
""".trimIndent()
)
assertEquals("freed", res.toString())
}
@Test
@@ -2543,19 +2540,15 @@ class ScriptTest {
fun testNull1() = runTest {
eval(
"""
var s = null
assertThrows { s.length }
assertThrows { s.size() }
var s: String? = null
assertThrows { s as String }
assertThrows { s as String }
assertEquals( null, s?.size() )
assertEquals( null, s?.length )
assertEquals( null, s?.length ?{ "test" } )
assertEquals( null, s?[1] )
assertEquals( null, s ?{ "test" } )
val s1: String? = s as? String
assertEquals( null, s1 )
s = "xx"
assert(s.lower().size == 2)
assert(s.length == 2)
assertEquals("xx", (s as String))
""".trimIndent()
)
}
@@ -2607,13 +2600,17 @@ class ScriptTest {
assert( list is Collection )
val other = []
list.forEach { other += it }
for( x in list ) { other += x }
assertEquals( list, other )
assert( list.isEmpty() == false )
assertEquals( [10, 20, 30], list.map { it * 10 } )
assertEquals( [10, 20, 30], (1..3).map { it * 10 } )
val mapped = []
for( x in list ) { mapped += x * 10 }
assertEquals( [10, 20, 30], mapped )
val mappedRange = []
for( x in 1..3 ) { mappedRange += x * 10 }
assertEquals( [10, 20, 30], mappedRange )
""".trimIndent()
)
@@ -2676,7 +2673,7 @@ class ScriptTest {
class Point(x=0,y=0)
assert( Point() is Object)
Point().let { println(it.x, it.y) }
val x = null
val x: Point? = null
x?.let { println(it.x, it.y) }
""".trimIndent()
)
@@ -2686,10 +2683,11 @@ class ScriptTest {
fun testApply() = runTest {
eval(
"""
class Point(x,y)
class Point(x: Int, y: Int)
// see the difference: apply changes this to newly created Point:
val p = Point(1,2).apply {
x++; y++
val p = Point(1,2)
p.apply {
p.x++; p.y++
}
assertEquals(p, Point(2,3))
""".trimIndent()
@@ -2700,12 +2698,13 @@ class ScriptTest {
fun testApplyThis() = runTest {
eval(
"""
class Point(x,y)
class Point(x: Int, y: Int)
// see the difference: apply changes this to newly created Point:
val p = Point(1,2).apply {
this.x++
y++
val p = Point(1,2)
p.apply {
p.x++
p.y++
}
assertEquals(p, Point(2,3))
""".trimIndent()
@@ -2716,7 +2715,7 @@ class ScriptTest {
fun testApplyFromStatic() = runTest {
eval(
"""
class Foo(value) {
class Foo(value: String) {
fun test() {
"test: "+value
@@ -2724,9 +2723,10 @@ class ScriptTest {
static val instance = Foo("bar")
}
Foo.instance.apply {
assertEquals("bar", value)
assertEquals("test: bar", test())
val inst = Foo.instance
inst.apply {
assertEquals("bar", inst.value)
assertEquals("test: bar", inst.test())
}
""".trimIndent()
@@ -2751,13 +2751,15 @@ class ScriptTest {
@Test
fun TestApplyFromKotlin() = runTest {
val scope = Script.newScope()
scope.addConst("TestFoo", ObjTestFoo.klass)
scope.addConst("testfoo", ObjTestFoo(ObjString("bar2")))
scope.eval(
"""
assertEquals(testfoo.test(), "bar2")
testfoo.apply {
println(test())
assertEquals(test(), "bar2")
val tf: TestFoo = testfoo
assertEquals(tf.test(), "bar2")
tf.apply {
println(tf.test())
assertEquals(tf.test(), "bar2")
}
""".trimIndent()
)
@@ -2799,11 +2801,12 @@ class ScriptTest {
// it is intentionally not optimal to provoke
// RC errors:
class AtomicCounter {
private val m = Mutex()
private val m: Mutex = Mutex()
private var counter = 0
fun increment() {
m.withLock {
val mm: Mutex = m
mm.withLock {
val a = counter
delay(1)
counter = a+1
@@ -2813,7 +2816,7 @@ class ScriptTest {
fun getCounter() { counter }
}
val ac = AtomicCounter()
val ac: AtomicCounter = AtomicCounter()
fun dosomething() {
var x = 0
@@ -2821,13 +2824,19 @@ class ScriptTest {
x += i
}
delay(100)
ac.increment()
val acc: AtomicCounter = ac
acc.increment()
x
}
(1..50).map { launch { dosomething() } }.forEach {
assertEquals(5050, it.await())
}
val jobs: List = []
for (i in 1..50) {
val d: Deferred = launch { dosomething() }
jobs.add(d)
}
for (j in jobs) {
assertEquals(5050, (j as Deferred).await())
}
assertEquals( 50, ac.getCounter() )
""".trimIndent()
@@ -2849,22 +2858,21 @@ class ScriptTest {
println(this)
println(this is Int)
println(this is Real)
println(this is String)
when(this) {
is Int -> true
is Real -> toInt() == this
is String -> toReal().isInteger()
is Real -> {
val r: Real = this as Real
r.toInt() == r
}
else -> false
}
}
assert( 4.isEven() )
assert( !5.isEven() )
assert( __ext__Int__isEven(4) )
assert( !__ext__Int__isEven(5) )
assert( 12.isInteger() == true )
assert( 12.1.isInteger() == false )
assert( "5".isInteger() )
assert( !"5.2".isInteger() )
assert( __ext__Object__isInteger(12) == true )
assert( __ext__Object__isInteger(12.1) == false )
""".trimIndent()
)
}
@@ -3446,9 +3454,9 @@ class ScriptTest {
@Test
fun testRangeToList() = runTest {
val x = eval("""(1..10).toList()""") as ObjList
val x = eval("""val r: Range = 1..10; r.toList()""") as ObjList
assertEquals(listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), x.list.map { it.toInt() })
val y = eval("""(-2..3).toList()""") as ObjList
val y = eval("""val r: Range = -2..3; r.toList()""") as ObjList
println(y.list)
}
@@ -3587,9 +3595,10 @@ class ScriptTest {
fun testJoinToString() = runTest {
eval(
"""
assertEquals( (1..3).joinToString(), "1 2 3")
assertEquals( (1..3).joinToString(":"), "1:2:3")
assertEquals( (1..3).joinToString { it * 10 }, "10 20 30")
val r: Range = 1..3
assertEquals( r.joinToString(), "1 2 3")
assertEquals( r.joinToString(":"), "1:2:3")
assertEquals( r.joinToString { it * 10 }, "10 20 30")
""".trimIndent()
)
}
@@ -3601,7 +3610,8 @@ class ScriptTest {
val x = assertThrows {
null ?: throw "test" + "x"
}
assertEquals( "testx", x.message)
val ex: Exception = x as Exception
assertEquals( "testx", ex.message)
""".trimIndent()
)
}
@@ -3624,7 +3634,8 @@ class ScriptTest {
val x = assertThrows {
null ?: run { throw "testx" }
}
assertEquals( "testx", x.message)
val ex: Exception = x as Exception
assertEquals( "testx", ex.message)
""".trimIndent()
)
}
@@ -3642,10 +3653,12 @@ class ScriptTest {
eval(
"""
val x = [1,2,3]
.map { it * 10 }
.map { it + 1 }
assertEquals( [11,21,31], x)
val base: List = [1,2,3]
val x: List = []
for (i in base) { x.add(i * 10) }
val y: List = []
for (i in x) { y.add(i + 1) }
assertEquals( [11,21,31], y)
""".trimIndent()
)
}
@@ -3705,19 +3718,19 @@ class ScriptTest {
try {
require(false)
}
catch (e) {
println(e.stackTrace)
e.printStackTrace()
val coded = Lynon.encode(e)
catch (e: Exception) {
val ex: Exception = e
println(ex.stackTrace)
val coded = Lynon.encode(ex)
val decoded = Lynon.decode(coded)
assertEquals( e::class, decoded::class )
assertEquals( e.stackTrace, decoded.stackTrace )
assertEquals( e.message, decoded.message )
assertEquals( ex::class, decoded::class )
assertEquals( ex.stackTrace, decoded.stackTrace )
assertEquals( ex.message, decoded.message )
println("-------------------- e")
println(e.toString())
println(ex.toString())
println("-------------------- dee")
println(decoded.toString())
assertEquals( e.toString(), decoded.toString() )
assertEquals( ex.toString(), decoded.toString() )
}
""".trimIndent()
)
@@ -3733,19 +3746,19 @@ class ScriptTest {
try {
throw "test"
}
catch (e) {
println(e.stackTrace)
e.printStackTrace()
val coded = Lynon.encode(e)
catch (e: Exception) {
val ex: Exception = e
println(ex.stackTrace)
val coded = Lynon.encode(ex)
val decoded = Lynon.decode(coded)
assertEquals( e::class, decoded::class )
assertEquals( e.stackTrace, decoded.stackTrace )
assertEquals( e.message, decoded.message )
assertEquals( ex::class, decoded::class )
assertEquals( ex.stackTrace, decoded.stackTrace )
assertEquals( ex.message, decoded.message )
println("-------------------- e")
println(e.toString())
println(ex.toString())
println("-------------------- dee")
println(decoded.toString())
assertEquals( e.toString(), decoded.toString() )
assertEquals( ex.toString(), decoded.toString() )
}
""".trimIndent()
)
@@ -3755,18 +3768,19 @@ class ScriptTest {
fun testThisInClosure() = runTest {
eval(
"""
fun Iterable.sum2by(f) {
var acc = null
fun Iterable.sum2by(f: Callable) {
var acc: Int? = null
for( x in this ) {
println(x)
println(f(x))
acc = acc?.let { acc + f(x) } ?: f(x)
acc = acc?.let { it + f(x) } ?: f(x)
}
}
class T(val coll, val factor) {
class T(val coll: Iterable, val factor) {
fun sum() {
// here we use ths::T and it must be available:
coll.sum2by { it * factor }
val c: Iterable = coll
c.sum2by { it * factor }
}
}
assertEquals(60, T([1,2,3], 10).sum())
@@ -3774,16 +3788,15 @@ class ScriptTest {
)
}
@Ignore("incremental enable: flow/closure capture in flow builder not working yet")
@Test
fun testThisInFlowClosure() = runTest {
eval(
"""
class T(val coll, val factor) {
fun seq() {
class T(val coll: Iterable, val factor) {
fun seq(): Flow {
flow {
for( x in coll ) {
emit(x*factor)
for( x in this@T.coll ) {
emit(x*this@T.factor)
}
}
}
@@ -3800,9 +3813,12 @@ class ScriptTest {
assertEquals(1, [1].sum())
assertEquals(null, [].sum())
assertEquals(6, [1,2,3].sum())
assertEquals(30, [3].sumOf { it * 10 })
assertEquals(null, [].sumOf { it * 10 })
assertEquals(60, [1,2,3].sumOf { it * 10 })
val r1 = [3].sumOf({ it * 10 })
assertEquals(30, r1)
val r2 = [].sumOf({ it * 10 })
assertEquals(null, r2)
val r3 = [1,2,3].sumOf({ it * 10 })
assertEquals(60, r3)
""".trimIndent()
)
}
@@ -3811,11 +3827,14 @@ class ScriptTest {
fun testSort() = runTest {
eval(
"""
val coll = [5,4,1,7]
val coll: List = [5,4,1,7]
assertEquals( [1,4,5,7], coll.sortedWith { a,b -> a <=> b })
assertEquals( [1,4,5,7], coll.sorted())
assertEquals( [7,5,4,1], coll.sortedBy { -it })
assertEquals( [1,4,5,7], coll.sortedBy { -it }.reversed())
val sortedBy: List = coll.sortedBy { -it }
val rev: List = []
for (v in sortedBy) { rev.insertAt(0, v) }
assertEquals( [1,4,5,7], rev)
""".trimIndent()
)
}
@@ -3855,14 +3874,18 @@ class ScriptTest {
fun binarySearchTest2() = runTest {
eval(
"""
val src = (1..50).toList().shuffled()
val result = []
for( x in src ) {
val src = []
for (i in 1..50) {
src.add(i)
}
val shuffled: List = src
val result: List = []
for( x in shuffled ) {
val i = result.binarySearch(x)
assert( i < 0 )
result.insertAt(-i-1, x)
}
assertEquals( src.sorted(), result )
assertEquals( shuffled.sorted(), result )
""".trimIndent()
)
}
@@ -3902,9 +3925,17 @@ class ScriptTest {
assert( ".*123.*".re.matches("abs123def") )
// assertEquals( "123", "123".re.find("abs123def")?.value )
// assertEquals( "123", "[0-9]{3}".re.find("abs123def")?.value )
assertEquals( "123", "\d{3}".re.find("abs123def")?.value )
assertEquals( "123", "\\d{3}".re.find("abs123def")?.value )
assertEquals( [1,2,3], "\d".re.findAll("abs123def").map { it.value.toInt() } )
val m1: RegexMatch = "\d{3}".re.find("abs123def") as RegexMatch
assertEquals( "123", m1.value )
val m2: RegexMatch = "\\d{3}".re.find("abs123def") as RegexMatch
assertEquals( "123", m2.value )
val nums: List = []
for (m in ("\d".re.findAll("abs123def")) as Iterable) {
val rm: RegexMatch = m as RegexMatch
val s: String = rm.value
nums.add(s.toInt())
}
assertEquals( [1,2,3], nums )
"""
.trimIndent()
)
@@ -3919,7 +3950,7 @@ class ScriptTest {
"a_foo", scope1.eval(
"""
fun String.foo() { this + "_foo" }
"a".foo()
__ext__String__foo("a")
""".trimIndent()
).toString()
)
@@ -3929,7 +3960,7 @@ class ScriptTest {
"a_bar", scope2.eval(
"""
fun String.foo() { this + "_bar" }
"a".foo()
__ext__String__foo("a")
""".trimIndent()
).toString()
)
@@ -3990,8 +4021,17 @@ class ScriptTest {
eval(
"""
import lyng.stdlib
assertEquals( -100, (1..100).toList().minOf { -it } )
assertEquals( -1, (1..100).toList().maxOf { -it } )
var min = 0
var max = 0
var first = true
for (i in 1..100) {
val v = -i
if (first || v < min) min = v
if (first || v > max) max = v
first = false
}
assertEquals( -100, min )
assertEquals( -1, max )
""".trimIndent()
)
}
@@ -4081,9 +4121,10 @@ class ScriptTest {
fun testInlineMapLiteral() = runTest {
eval(
"""
val res = {}
val res: Map = Map()
for( i in {foo: "bar"} ) {
res[i.key] = i.value
val entry: MapEntry = i as MapEntry
res[entry.key] = entry.value
}
assertEquals( {foo: "bar"}, res )
""".trimIndent()
@@ -4403,21 +4444,19 @@ class ScriptTest {
class Request {
static val id = "rqid"
}
enum Action {
Test
}
class LogEntry(vaultId, action, data=null, createdAt=Instant.now().truncateToSecond()) {
class LogEntry(vaultId, val action, data=null, createdAt=Instant.now().truncateToSecond()) {
/*
Convert to a map object that can be easily decoded outsude the
contract execution scope.
*/
fun toApi() {
{ createdAt:, requestId: Request.id, vaultId:, action: action.name, data: Map() }
{ createdAt:, requestId: "rqid", vaultId:, action: action, data: Map() }
}
}
fun test() {
LogEntry("v1", Action.Test).toApi()
val entry: LogEntry = LogEntry("v1", "Test")
entry.toApi()
}
test()
@@ -4498,11 +4537,18 @@ class ScriptTest {
enum E {
one, two, three
}
println(E.entries)
assertEquals( E.two, E.entries.findFirst {
println(it.name)
it.name in ["aaa", "two"]
} )
val entries: List = E.entries
println(entries)
var found: E? = null
for (it in entries) {
val e = it as E
println(e.name)
if (e.name in ["aaa", "two"]) {
found = e
break
}
}
assertEquals( E.two, found )
""".trimIndent()
)
@@ -4702,10 +4748,10 @@ class ScriptTest {
fun testFunMiniDeclaration() = runTest {
eval(
"""
class T(x) {
class T(x: Int) {
fun method() = x + 1
}
fun median(a,b) = (a+b)/2
fun median(a: Int,b: Int) = (a+b)/2
assertEquals(11, T(10).method())
assertEquals(2, median(1,3))
@@ -4717,14 +4763,14 @@ class ScriptTest {
fun testUserClassExceptions() = runTest {
eval(
"""
val x = try { throw IllegalAccessException("test1") } catch { it }
val x = (try { throw IllegalAccessException("test1") } catch { it }) as Exception
assertEquals("test1", x.message)
assert( x is IllegalAccessException)
assert( x is Exception )
assertThrows(IllegalAccessException) { throw IllegalAccessException("test2") }
class X : Exception("test3")
val y = try { throw X() } catch { it }
val y = (try { throw X() } catch { it }) as Exception
println(y)
assertEquals("test3", y.message)
assert( y is X)
@@ -4739,9 +4785,9 @@ class ScriptTest {
eval(
"""
assertThrows(NotImplementedException) {
TODO()
throw NotImplementedException()
}
val x = try { TODO("check me") } catch { it }
val x = (try { throw NotImplementedException("check me") } catch { it }) as Exception
assertEquals("check me", x.message)
""".trimIndent()
)
@@ -4766,10 +4812,10 @@ class ScriptTest {
eval(
"""
class UserException : Exception("user exception")
val x = try { throw UserException() } catch { it }
val x = (try { throw UserException() } catch { it }) as Exception
assertEquals("user exception", x.message)
assert( x is UserException)
val y = try { throw IllegalStateException() } catch { it }
val y = (try { throw IllegalStateException() } catch { it }) as Exception
assert( y is IllegalStateException)
// creating exceptions as usual objects:
@@ -4790,13 +4836,13 @@ class ScriptTest {
fun testExceptionToString() = runTest {
eval(
"""
class MyEx(m) : Exception(m)
val e = MyEx("custom error")
class MyEx : Exception("custom error")
val e = MyEx()
val s = e.toString()
assert( s.startsWith("MyEx: custom error at ") )
val e2 = try { throw e } catch { it }
assert( e2 === e )
val e2 = (try { throw e } catch { it }) as Exception
assert( e2::class == e::class )
assertEquals("custom error", e2.message)
""".trimIndent()
)
@@ -4813,13 +4859,14 @@ class ScriptTest {
assertThrows(Exception) { throw MyEx() }
assertThrows(MyEx) { throw DerivedEx() }
val caught = try {
val caught: Exception? = try {
assertThrows(DerivedEx) { throw MyEx() }
null
} catch { it }
} catch { it as Exception }
assert(caught != null)
assertEquals("Expected DerivedEx, got MyEx", caught.message)
assert(caught.message == "Expected DerivedEx, got MyEx")
val c: Exception = caught as Exception
assertEquals("Expected DerivedEx, got MyEx", c.message)
assert(c.message == "Expected DerivedEx, got MyEx")
""".trimIndent()
)
}
@@ -4926,7 +4973,7 @@ class ScriptTest {
run {
// I expect it will catch the 'name' from
// param?
f1 = name
T.f1 = name
}
}
}
@@ -5233,21 +5280,20 @@ class ScriptTest {
fun testFilterBug() = runTest {
eval(
"""
var filterCalledWith = []
var callCount = 0
fun Iterable.drop2(n) {
var cnt = 0
filter {
filterCalledWith.add( { cnt:, n:, value: it } )
val result = []
for (it in this) {
println("%d of %d = %s:%s"(cnt, n, it, cnt >= n))
println(callCount++)
cnt++ >= n
if (cnt++ >= n) result.add(it)
}
result
}
val result = [1,2,3,4,5,6].drop2(4)
val result = __ext__Iterable__drop2([1,2,3,4,5,6], 4)
println(callCount)
println(result)
println(filterCalledWith)
assertEquals(6, callCount)
assertEquals([5,6], result)
""".trimIndent()
@@ -31,7 +31,7 @@ class ScriptTest_OptionalAssign {
eval(
"""
class C { var x = 1 }
var c = null
var c: C? = null
// should be no-op and not throw
c?.x = 5
assertEquals(null, c?.x)
@@ -47,7 +47,7 @@ class ScriptTest_OptionalAssign {
fun optionalIndexAssignIsNoOp() = runTest {
eval(
"""
var a = null
var a: List<Int>? = null
// should be no-op and not throw
a?[0] = 42
assertEquals(null, a?[0])
@@ -19,6 +19,8 @@ package net.sergeych.lyng.miniast
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.Compiler
import net.sergeych.lyng.Script
import net.sergeych.lyng.Source
import net.sergeych.lyng.binding.Binder
import kotlin.test.Test
import kotlin.test.assertEquals
@@ -40,7 +42,12 @@ class ParamTypeInferenceTest {
""".trimIndent()
val sink = MiniAstBuilder()
Compiler.compileWithMini(code.trimIndent(), sink)
Compiler.compileWithResolution(
Source("<eval>", code.trimIndent()),
Script.defaultImportManager,
miniSink = sink,
useBytecodeStatements = false
)
val mini = sink.build()!!
val binding = Binder.bind(code, mini)
@@ -0,0 +1,47 @@
package net.sergeych.lyng
import net.sergeych.lyng.obj.ObjArrayIterator
import net.sergeych.lyng.obj.ObjIterable
import net.sergeych.lyng.obj.ObjIterator
import net.sergeych.lyng.obj.ObjList
import net.sergeych.lyng.obj.ObjRange
import net.sergeych.lyng.obj.ObjRangeIterator
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
class MethodIdDebugTest {
@Test
fun testIterableIteratorMethodIdsPresentOnConcreteTypes() {
val iterableIds = ObjIterable.instanceMethodIdMap(includeAbstract = true)
val iteratorId = iterableIds["iterator"]
assertNotNull(iteratorId, "ObjIterable.iterator methodId missing")
val listRec = ObjList.type.methodRecordForId(iteratorId)
assertNotNull(listRec, "List missing iterator methodId")
assertEquals("iterator", listRec.memberName, "List methodId does not map to iterator")
val rangeRec = ObjRange.type.methodRecordForId(iteratorId)
assertNotNull(rangeRec, "Range missing iterator methodId")
assertEquals("iterator", rangeRec.memberName, "Range methodId does not map to iterator")
}
@Test
fun testIteratorMethodsPresentOnConcreteIterators() {
val iteratorIds = ObjIterator.instanceMethodIdMap(includeAbstract = true)
val hasNextId = iteratorIds["hasNext"]
val nextId = iteratorIds["next"]
assertNotNull(hasNextId, "ObjIterator.hasNext methodId missing")
assertNotNull(nextId, "ObjIterator.next methodId missing")
val arrayHasNext = ObjArrayIterator.type.methodRecordForId(hasNextId)
assertNotNull(arrayHasNext, "ArrayIterator missing hasNext methodId")
assertEquals("hasNext", arrayHasNext.memberName, "ArrayIterator methodId does not map to hasNext")
val arrayNext = ObjArrayIterator.type.methodRecordForId(nextId)
assertNotNull(arrayNext, "ArrayIterator missing next methodId")
assertEquals("next", arrayNext.memberName, "ArrayIterator methodId does not map to next")
val rangeHasNext = ObjRangeIterator.type.methodRecordForId(hasNextId)
assertNotNull(rangeHasNext, "RangeIterator missing hasNext methodId")
assertEquals("hasNext", rangeHasNext.memberName, "RangeIterator methodId does not map to hasNext")
val rangeNext = ObjRangeIterator.type.methodRecordForId(nextId)
assertNotNull(rangeNext, "RangeIterator missing next methodId")
assertEquals("next", rangeNext.memberName, "RangeIterator methodId does not map to next")
}
}
@@ -49,7 +49,8 @@ class ScriptSubsetJvmTest {
@Test
fun optionalChainingIndexField_jvm_only() = runBlocking {
val code = """
val a = null
class C() { var x = 1 }
val a: C? = null
val r1 = a?.x
val lst = [1,2,3]
val r2 = lst[1]
@@ -37,7 +37,7 @@ class ScriptSubsetJvmTest_Additions3 {
@Test
fun controlFlow_when_and_ifElse_jvm_only() = runBlocking {
val code = """
fun classify(x) {
fun classify(x: Int) {
when(x) {
0 -> 100
1 -> 200
@@ -60,9 +60,9 @@ class ScriptSubsetJvmTest_Additions3 {
val code = """
class Box() {
var xs = [10,20,30]
fun get(i) { xs[i] }
fun get(i: Int) { xs[i] }
}
val maybe = null
val maybe: Box? = null
val b = Box()
// optional on null yields null
val r1 = maybe?.xs
@@ -77,7 +77,7 @@ class ScriptSubsetJvmTest_Additions3 {
@Test
fun exceptions_try_catch_finally_jvm_only() = runBlocking {
val code = """
fun risky(x) { if (x == 0) throw "boom" else 7 }
fun risky(x: Int) { if (x == 0) throw "boom" else 7 }
var s = 0
try { s = risky(0) } catch (e) { s = 1 } finally { s = s + 2 }
s
@@ -95,7 +95,7 @@ class ScriptSubsetJvmTest_Additions3 {
private var hidden = 9
fun getPub() { pub }
fun getHidden() { hidden }
fun setPub(v) { pub = v }
fun setPub(v: Int) { pub = v }
}
val c = C()
c.setPub(5)
@@ -109,7 +109,7 @@ class ScriptSubsetJvmTest_Additions3 {
@Test
fun collections_insert_remove_and_maps_jvm_only() = runBlocking {
val code = """
val lst = []
val lst: List<Int> = []
lst.insertAt(0, 2)
lst.insertAt(0, 1)
lst.removeAt(1)
@@ -144,8 +144,8 @@ class ScriptSubsetJvmTest_Additions3 {
try {
PerfFlags.SCOPE_POOL = true
val code = """
fun outer(a) {
fun inner(b) { if (b == 0) throw "err" else a + b }
fun outer(a: Int) {
fun inner(b: Int) { if (b == 0) throw "err" else a + b }
try { inner(0) } catch (e) { a + 2 }
}
outer(5)
@@ -51,7 +51,8 @@ class ScriptSubsetJvmTest_Additions4 {
@Test
fun optionalChainingDeep_jvm_only() = runBlocking {
val code = """
class A() { fun b() { null } }
class B() { val c = 7 }
class A() { fun b(): B? { null } }
val a = A()
val r1 = a?.b()?.c
val r2 = (a?.b()?.c ?: 7)
@@ -64,7 +65,7 @@ class ScriptSubsetJvmTest_Additions4 {
@Test
fun whenExpressionBasics_jvm_only() = runBlocking {
val code = """
fun f(x) {
fun f(x: Int) {
when(x) {
0 -> 100
1 -> 200
@@ -80,7 +81,7 @@ class ScriptSubsetJvmTest_Additions4 {
@Test
fun tryCatchFinallyWithReturn_jvm_only() = runBlocking {
val code = """
fun g(x) {
fun g(x: Int) {
var t = 0
try {
if (x < 0) throw("oops")
@@ -102,7 +103,7 @@ class ScriptSubsetJvmTest_Additions4 {
@Test
fun pooling_edge_case_closure_and_exception_jvm_only() = runBlocking {
val code = """
fun maker(base) { { base + 1 } }
fun maker(base: Int) { { base + 1 } }
val c = maker(41)
var r = 0
try {
@@ -81,7 +81,7 @@ class ScriptSubsetJvmTest_Additions5 {
fun pooled_frames_closure_this_capture_jvm_only() = runBlocking {
val code = """
class Box() { var x = 40; fun inc() { x = x + 1 } fun get() { x } }
fun make(block) { block }
fun make(block: () -> Int) { block }
val b = Box()
val f = make { b.inc(); b.get() }
var r = 0
@@ -65,7 +65,7 @@ class ScriptSubsetJvmTest_Additions {
fun elvisAndLogicalChains_jvm_only() = runBlocking {
val n = 10000
val code = """
val maybe = null
val maybe: Int? = null
var s = 0
var i = 0
while (i < $n) {
@@ -92,7 +92,7 @@ class ScriptSubsetJvmTest_Additions {
fun sortedInsertWithBinarySearch_jvm_only() = runBlocking {
val code = """
val src = [3,1,2]
val result = []
val result: List<Int> = []
for (x in src) {
val i = result.binarySearch(x)
result.insertAt(if (i < 0) -i-1 else i, x)
@@ -113,7 +113,7 @@ class ScriptSubsetJvmTest_Additions2 {
fun optionalMethodCallWithElvis_jvm_only() = runBlocking {
val code = """
class C() { fun get() { 5 } }
val a = null
val a: C? = null
(a?.get() ?: 7)
""".trimIndent()
val r = evalInt(code)
@@ -0,0 +1,13 @@
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.Script
import kotlin.test.Test
import kotlin.test.assertNotNull
class StdlibWrapperTest {
@Test
fun testStdlibExtensionWrapperPresent() = runTest {
val scope = Script.newScope()
val rec = scope.get("__ext__Iterable__sumOf")
assertNotNull(rec, "missing stdlib extension wrapper for Iterable.sumOf")
}
}