diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ArgsDeclaration.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ArgsDeclaration.kt index e4b035c..5f756ba 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ArgsDeclaration.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ArgsDeclaration.kt @@ -67,24 +67,40 @@ data class ArgsDeclaration(val params: List, val endTokenType: Token.Type) for (i in params.indices) { val a = params[i] val value = arguments.list[i] - scope.addItem(a.name, (a.accessType ?: defaultAccessType).isMutable, + val recordType = if (declaringClass != null && a.accessType != null) { + ObjRecord.Type.ConstructorField + } else { + ObjRecord.Type.Argument + } + scope.addItem( + a.name, + (a.accessType ?: defaultAccessType).isMutable, value.byValueCopy(), a.visibility ?: defaultVisibility, - recordType = ObjRecord.Type.Argument, + recordType = recordType, declaringClass = declaringClass, - isTransient = a.isTransient) + isTransient = a.isTransient + ) } return } } fun assign(a: Item, value: Obj) { - scope.addItem(a.name, (a.accessType ?: defaultAccessType).isMutable, + val recordType = if (declaringClass != null && a.accessType != null) { + ObjRecord.Type.ConstructorField + } else { + ObjRecord.Type.Argument + } + scope.addItem( + a.name, + (a.accessType ?: defaultAccessType).isMutable, value.byValueCopy(), a.visibility ?: defaultVisibility, - recordType = ObjRecord.Type.Argument, + recordType = recordType, declaringClass = declaringClass, - isTransient = a.isTransient) + isTransient = a.isTransient + ) } // Prepare positional args and parameter count, handle tail-block binding @@ -243,4 +259,4 @@ data class ArgsDeclaration(val params: List, val endTokenType: Token.Type) val visibility: Visibility? = null, val isTransient: Boolean = false, ) -} \ No newline at end of file +} diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index b352aa9..55b8a2d 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -391,7 +391,7 @@ class Compiler( return LocalVarRef(name, pos) } resolutionSink?.reference(name, pos) - if (allowUnresolvedRefs) { + if (allowUnresolvedRefs || (name.isNotEmpty() && name[0].isUpperCase())) { return LocalVarRef(name, pos) } throw ScriptError(pos, "unresolved name: $name") @@ -1741,6 +1741,11 @@ class Compiler( else -> null } + val effectiveAccess = if (isClassDeclaration && access == null) { + AccessType.Var + } else { + access + } // type information (semantic + mini syntax) val (typeInfo, miniType) = parseTypeDeclarationWithMini() @@ -1757,7 +1762,7 @@ class Compiler( t.pos, isEllipsis, defaultValue, - access, + effectiveAccess, visibility, isTransient ) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt index f291908..bab1f82 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt @@ -190,6 +190,7 @@ class BytecodeCompiler( private fun compileRef(ref: ObjRef): CompiledValue? { return when (ref) { is ConstRef -> compileConst(ref.constValue) + is IncDecRef -> compileIncDec(ref, true) is LocalSlotRef -> { if (ref.name == "__PACKAGE__") { return compileNameLookup(ref.name) @@ -1625,6 +1626,83 @@ class BytecodeCompiler( } } + val thisFieldTarget = ref.target as? ThisFieldSlotRef + if (thisFieldTarget != null) { + val nameId = builder.addConst(BytecodeConst.StringVal(thisFieldTarget.name)) + if (nameId > 0xFFFF) return null + val current = allocSlot() + builder.emit(Opcode.GET_THIS_MEMBER, nameId, current) + updateSlotType(current, SlotType.OBJ) + val oneSlot = allocSlot() + val oneId = builder.addConst(BytecodeConst.ObjRef(ObjInt.One)) + builder.emit(Opcode.CONST_OBJ, oneId, oneSlot) + updateSlotType(oneSlot, SlotType.OBJ) + val result = allocSlot() + val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ + if (wantResult && ref.isPost) { + val old = allocSlot() + builder.emit(Opcode.MOVE_OBJ, current, old) + builder.emit(op, current, oneSlot, result) + builder.emit(Opcode.SET_THIS_MEMBER, nameId, result) + return CompiledValue(old, SlotType.OBJ) + } + builder.emit(op, current, oneSlot, result) + builder.emit(Opcode.SET_THIS_MEMBER, nameId, result) + return CompiledValue(result, SlotType.OBJ) + } + + val implicitTarget = ref.target as? ImplicitThisMemberRef + if (implicitTarget != null) { + val nameId = builder.addConst(BytecodeConst.StringVal(implicitTarget.name)) + if (nameId > 0xFFFF) return null + val current = allocSlot() + builder.emit(Opcode.GET_THIS_MEMBER, nameId, current) + updateSlotType(current, SlotType.OBJ) + val oneSlot = allocSlot() + val oneId = builder.addConst(BytecodeConst.ObjRef(ObjInt.One)) + builder.emit(Opcode.CONST_OBJ, oneId, oneSlot) + updateSlotType(oneSlot, SlotType.OBJ) + val result = allocSlot() + val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ + if (wantResult && ref.isPost) { + val old = allocSlot() + builder.emit(Opcode.MOVE_OBJ, current, old) + builder.emit(op, current, oneSlot, result) + builder.emit(Opcode.SET_THIS_MEMBER, nameId, result) + return CompiledValue(old, SlotType.OBJ) + } + builder.emit(op, current, oneSlot, result) + builder.emit(Opcode.SET_THIS_MEMBER, nameId, result) + return CompiledValue(result, SlotType.OBJ) + } + + val fieldTarget = ref.target as? FieldRef + if (fieldTarget != null) { + if (fieldTarget.isOptional) return null + val receiver = compileRefWithFallback(fieldTarget.target, null, Pos.builtIn) ?: return null + val nameId = builder.addConst(BytecodeConst.StringVal(fieldTarget.name)) + if (nameId > 0xFFFF) return null + val current = allocSlot() + builder.emit(Opcode.GET_FIELD, receiver.slot, nameId, current) + updateSlotType(current, SlotType.OBJ) + val oneSlot = allocSlot() + val oneId = builder.addConst(BytecodeConst.ObjRef(ObjInt.One)) + builder.emit(Opcode.CONST_OBJ, oneId, oneSlot) + updateSlotType(oneSlot, SlotType.OBJ) + val result = allocSlot() + val op = if (ref.isIncrement) Opcode.ADD_OBJ else Opcode.SUB_OBJ + if (wantResult && ref.isPost) { + val old = allocSlot() + builder.emit(Opcode.MOVE_OBJ, current, old) + builder.emit(op, current, oneSlot, result) + builder.emit(Opcode.SET_FIELD, receiver.slot, nameId, result) + return CompiledValue(old, SlotType.OBJ) + } + builder.emit(op, current, oneSlot, result) + builder.emit(Opcode.SET_FIELD, receiver.slot, nameId, result) + return CompiledValue(result, SlotType.OBJ) + } + val indexTarget = ref.target as? IndexRef ?: return null if (indexTarget.optionalRef) return null val receiver = compileRefWithFallback(indexTarget.targetRef, null, Pos.builtIn) ?: return null diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt index 27fd9d7..4efceaa 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt @@ -725,7 +725,8 @@ open class Obj { (thisObj as? ObjInstance)?.let { body.callOn(ApplyScope(this, it.instanceScope)) } ?: run { - body.callOn(this) + val appliedScope = createChildScope(newThisObj = thisObj) + body.callOn(ApplyScope(this, appliedScope)) } thisObj } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt index 52855e3..acebdf6 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt @@ -118,28 +118,34 @@ open class ObjClass( /** * Map of public member names to their effective storage keys in instanceScope.objects. - * This is pre-calculated to avoid MRO traversal and string concatenation during common access. + * Cached and invalidated by layoutVersion to reflect newly added members. */ - val publicMemberResolution: Map by lazy { - val res = mutableMapOf() - // Traverse MRO in REVERSED order so that child classes override parent classes in the map. - for (cls in mro.reversed()) { - if (cls.className == "Obj") continue - for ((name, rec) in cls.members) { - if (rec.visibility == Visibility.Public) { - val key = if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name - res[name] = key - } - } - cls.classScope?.objects?.forEach { (name, rec) -> - if (rec.visibility == Visibility.Public && (rec.value is Statement || rec.type == ObjRecord.Type.Delegated)) { - val key = if (rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name - res[name] = key + private var publicMemberResolutionVersion: Int = -1 + private var publicMemberResolutionCache: Map = emptyMap() + val publicMemberResolution: Map + get() { + if (publicMemberResolutionVersion == layoutVersion) return publicMemberResolutionCache + val res = mutableMapOf() + // Traverse MRO in REVERSED order so that child classes override parent classes in the map. + for (cls in mro.reversed()) { + if (cls.className == "Obj") continue + for ((name, rec) in cls.members) { + if (rec.visibility == Visibility.Public) { + val key = if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField || rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name + res[name] = key + } + } + cls.classScope?.objects?.forEach { (name, rec) -> + if (rec.visibility == Visibility.Public && (rec.value is Statement || rec.type == ObjRecord.Type.Delegated)) { + val key = if (rec.type == ObjRecord.Type.Delegated) cls.mangledName(name) else name + res[name] = key + } } } + publicMemberResolutionCache = res + publicMemberResolutionVersion = layoutVersion + return res } - res - } val classNameObj by lazy { ObjString(className) } diff --git a/lynglib/src/commonTest/kotlin/ScriptTest.kt b/lynglib/src/commonTest/kotlin/ScriptTest.kt index 2de89f6..634a593 100644 --- a/lynglib/src/commonTest/kotlin/ScriptTest.kt +++ b/lynglib/src/commonTest/kotlin/ScriptTest.kt @@ -2671,7 +2671,6 @@ class ScriptTest { ) } - @Ignore("incremental enable") @Test fun testLet() = runTest { eval( @@ -2685,7 +2684,6 @@ class ScriptTest { ) } - @Ignore("incremental enable") @Test fun testApply() = runTest { eval( @@ -2700,7 +2698,6 @@ class ScriptTest { ) } - @Ignore("incremental enable") @Test fun testApplyThis() = runTest { eval( @@ -2717,7 +2714,6 @@ class ScriptTest { ) } - @Ignore("incremental enable") @Test fun testApplyFromStatic() = runTest { eval( @@ -2754,7 +2750,6 @@ class ScriptTest { } } - @Ignore("incremental enable") @Test fun TestApplyFromKotlin() = runTest { val scope = Script.newScope() @@ -2770,7 +2765,6 @@ class ScriptTest { ) } - @Ignore("incremental enable") @Test fun testParallels() = runTest { withContext(Dispatchers.Default) { @@ -2797,7 +2791,6 @@ class ScriptTest { } } - @Ignore("incremental enable") @Test fun testParallels2() = runTest { withContext(Dispatchers.Default) { @@ -2845,7 +2838,6 @@ class ScriptTest { } } - @Ignore("incremental enable") @Test fun testExtend() = runTest() { eval( @@ -2879,7 +2871,6 @@ class ScriptTest { ) } - @Ignore("incremental enable") @Test fun testToFlow() = runTest() { val c = Scope() @@ -2888,7 +2879,6 @@ class ScriptTest { assertEquals(listOf(1, 2, 3), arr.toFlow(c).map { it.toInt() }.toList()) } - @Ignore("incremental enable") @Test fun testAssociateBy() = runTest() { eval( @@ -2916,7 +2906,6 @@ class ScriptTest { // assertEquals("foo1", pm.modules["lyng.foo"]!!.deferredModule.await().eval("foo()").toString()) // } - @Ignore("incremental enable") @Test fun testImports2() = runTest() { val foosrc = """ @@ -2936,7 +2925,6 @@ class ScriptTest { assertEquals("foo1", scope.eval(src).toString()) } - @Ignore("incremental enable") @Test fun testImports3() = runTest { val foosrc = """ @@ -2968,7 +2956,6 @@ class ScriptTest { assertEquals("foo1 / bar1", scope.eval(src).toString()) } - @Ignore("incremental enable") @Test fun testImportsCircular() = runTest { val foosrc = """ @@ -3002,7 +2989,6 @@ class ScriptTest { assertEquals("foo1 / bar1", scope.eval(src).toString()) } - @Ignore("incremental enable") @Test fun testDefaultImportManager() = runTest { val scope = Scope.new() @@ -3029,7 +3015,6 @@ class ScriptTest { ) } - @Ignore("incremental enable") @Test fun testMaps() = runTest { eval( @@ -3063,7 +3048,6 @@ class ScriptTest { ) } - @Ignore("incremental enable") @Test fun testExternDeclarations() = runTest { eval( @@ -3087,7 +3071,6 @@ class ScriptTest { ) } - @Ignore("incremental enable") @Test fun testExternExtension() = runTest { eval( @@ -3098,7 +3081,6 @@ class ScriptTest { ) } - @Ignore("incremental enable") @Test fun testBuffer() = runTest { eval( @@ -3125,7 +3107,6 @@ class ScriptTest { ) } - @Ignore("incremental enable") @Test fun testBufferEncodings() = runTest { eval( @@ -3148,7 +3129,6 @@ class ScriptTest { ) } - @Ignore("incremental enable") @Test fun testBufferCompare() = runTest { eval( @@ -3174,7 +3154,6 @@ class ScriptTest { ) } - @Ignore("incremental enable") @Test fun testInstant() = runTest { eval( @@ -3214,7 +3193,6 @@ class ScriptTest { delay(1000) } - @Ignore("incremental enable") @Test fun testTimeStatics() = runTest { eval( @@ -3236,7 +3214,6 @@ class ScriptTest { ) } - @Ignore("incremental enable") @Test fun testInstantFormatting() = runTest { eval( @@ -3251,7 +3228,6 @@ class ScriptTest { ) } - @Ignore("incremental enable") @Test fun testDateTimeComprehensive() = runTest { eval(