From 63b28081097310860c27dea084d2aa7533eb0c28 Mon Sep 17 00:00:00 2001 From: sergeych Date: Wed, 6 Aug 2025 11:46:17 +0300 Subject: [PATCH] well redesigned class vals and vars, classScope introduced for class functions --- .../kotlin/net/sergeych/lyng/Compiler.kt | 43 ++++++++++++++++++- .../net/sergeych/lyng/CompilerContext.kt | 5 ++- .../kotlin/net/sergeych/lyng/Scope.kt | 3 ++ .../kotlin/net/sergeych/lyng/obj/ObjClass.kt | 38 +++++++++++++--- .../net/sergeych/lyng/obj/ObjInstanceClass.kt | 2 - .../kotlin/net/sergeych/lyng/statements.kt | 10 ++++- lynglib/src/commonTest/kotlin/OOTest.kt | 35 +++++++++++++++ 7 files changed, 125 insertions(+), 11 deletions(-) create mode 100644 lynglib/src/commonTest/kotlin/OOTest.kt diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index d2eee33..033025d 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -17,6 +17,15 @@ class Compiler( class Settings + private val initStack = mutableListOf>() + + val currentInitScope: MutableList get() = + initStack.lastOrNull() ?: cc.syntaxError("no initialization scope exists here") + + private fun pushInitScope(): MutableList = mutableListOf().also { initStack.add(it)} + + private fun popInitScope(): MutableList = initStack.removeLast() + private suspend fun parseScript(): Script { val statements = mutableListOf() val start = cc.currentPos() @@ -830,7 +839,11 @@ class Compiler( cc.matchQualifiers("fun") -> parseFunctionDeclaration(isOpen = false, isExtern = isExtern) cc.matchQualifiers("fn") -> parseFunctionDeclaration(isOpen = false, isExtern = isExtern) + cc.matchQualifiers("val", "private", "static") -> parseVarDeclaration(false, Visibility.Private, isStatic = true) + cc.matchQualifiers("val", "static") -> parseVarDeclaration(false, Visibility.Public, isStatic = true) cc.matchQualifiers("val", "private") -> parseVarDeclaration(false, Visibility.Private) + cc.matchQualifiers("var", "static") -> parseVarDeclaration(true, Visibility.Public, isStatic = true) + cc.matchQualifiers("var", "static", "private" ) -> parseVarDeclaration(true, Visibility.Private, isStatic = true) cc.matchQualifiers("var", "private") -> parseVarDeclaration(true, Visibility.Private) cc.matchQualifiers("val", "open") -> parseVarDeclaration(false, Visibility.Private, true) cc.matchQualifiers("var", "open") -> parseVarDeclaration(true, Visibility.Private, true) @@ -1090,6 +1103,8 @@ class Compiler( cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true) val t = cc.next() + pushInitScope() + val bodyInit: Statement? = if (t.type == Token.Type.LBRACE) { // parse body parseScript().also { @@ -1100,6 +1115,8 @@ class Compiler( null } + val initScope = popInitScope() + // create class val className = nameToken.value @@ -1131,6 +1148,13 @@ class Compiler( // the main statement should create custom ObjClass instance with field // accessors, constructor registration, etc. addItem(className, false, newClass) + if( initScope.isNotEmpty()) { + val classScope = copy(newThisObj = newClass) + newClass.classScope = classScope + for( s in initScope ) + s.execute(classScope) + .also { println("executed, ${classScope.objects}")} + } newClass } } @@ -1600,7 +1624,8 @@ class Compiler( private suspend fun parseVarDeclaration( isMutable: Boolean, visibility: Visibility, - @Suppress("UNUSED_PARAMETER") isOpen: Boolean = false + @Suppress("UNUSED_PARAMETER") isOpen: Boolean = false, + isStatic: Boolean = false ): Statement { val nameToken = cc.next() val start = nameToken.pos @@ -1622,6 +1647,22 @@ class Compiler( val initialExpression = if (setNull) null else parseStatement(true) ?: throw ScriptError(eqToken.pos, "Expected initializer expression") + if( isStatic) { + // find objclass instance: this is tricky: this code executes in object initializer, + // when creating instance, but we need to execute it in the class initializer which + // is missing as for now. Add it to the compiler context? + // add there + // return + currentInitScope += statement { + val initValue = initialExpression?.execute(this)?.byValueCopy() ?: ObjNull + // todo: get rid of classfield! + (thisObj as ObjClass).createClassField(name, initValue, isMutable, visibility, pos) + addItem(name, isMutable, initValue, visibility, ObjRecord.Type.Field) + ObjVoid + } + return NopStatement + } + return statement(nameToken.pos) { context -> if (context.containsLocal(name)) throw ScriptError(nameToken.pos, "Variable $name is already defined") diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/CompilerContext.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/CompilerContext.kt index cf87fa8..987db79 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/CompilerContext.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/CompilerContext.kt @@ -44,11 +44,14 @@ class CompilerContext(val tokens: List) { if (type != it.type) throw ScriptError(it.pos, message) } - @Suppress("unused") fun syntaxError(at: Pos, message: String = "Syntax error"): Nothing { throw ScriptError(at, message) } + fun syntaxError(message: String = "Syntax error"): Nothing { + throw ScriptError(currentPos(), message) + } + fun currentPos(): Pos = tokens[currentIndex].pos /** diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt index ac1018b..49073f2 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt @@ -50,6 +50,9 @@ open class Scope( fun raiseIllegalState(message: String = "Illegal argument error"): Nothing = raiseError(ObjIllegalStateException(this, message)) + fun raiseIllegalAssignment(message: String): Nothing = + raiseError(ObjIllegalAssignmentException(this, message)) + @Suppress("unused") fun raiseNoSuchElement(message: String = "No such element"): Nothing = raiseError(ObjIllegalArgumentException(this, message)) 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 6044101..4d658a0 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt @@ -16,6 +16,16 @@ open class ObjClass( var constructorMeta: ArgsDeclaration? = null var instanceConstructor: Statement? = null + /** + * the scope for class methods, initialize class vars, etc. + * + * Important notice. When create a user class, e.g. from Lyng source, it should + * be set to a scope by compiler, so it could access local closure, etc. Otherwise, + * it will be initialized to default scope on first necessity, e.g. when used in + * external, kotlin classes with [addClassConst] and [addClassFn], etc. + */ + var classScope: Scope? = null + val allParentsSet: Set = parents.flatMap { listOf(it) + it.allParentsSet @@ -23,9 +33,10 @@ open class ObjClass( override val objClass: ObjClass by lazy { ObjClassType } - // members: fields most often + /** + * members: fields most often. These are called with [ObjInstance] withs ths [ObjInstance.objClass] + */ private val members = mutableMapOf() - private val classMembers = mutableMapOf() override fun toString(): String = className @@ -40,6 +51,7 @@ open class ObjClass( return instance } + fun createField( name: String, initialValue: Obj, @@ -53,6 +65,11 @@ open class ObjClass( members[name] = ObjRecord(initialValue, isMutable, visibility) } + private fun initClassScope(): Scope { + if( classScope == null ) classScope = Scope() + return classScope!! + } + fun createClassField( name: String, initialValue: Obj, @@ -60,10 +77,11 @@ open class ObjClass( visibility: Visibility = Visibility.Public, pos: Pos = Pos.builtIn ) { - val existing = classMembers[name] + initClassScope() + val existing = classScope!!.objects[name] if( existing != null) throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes") - classMembers[name] = ObjRecord(initialValue, isMutable, visibility) + classScope!!.addItem(name, isMutable, initialValue, visibility) } fun addFn(name: String, isOpen: Boolean = false, code: suspend Scope.() -> Obj) { @@ -91,15 +109,23 @@ open class ObjClass( ?: throw ScriptError(atPos, "symbol doesn't exist: $name") override suspend fun readField(scope: Scope, name: String): ObjRecord { - classMembers[name]?.let { + classScope?.objects?.get(name)?.let { return it } return super.readField(scope, name) } + override suspend fun writeField(scope: Scope, name: String, value: Obj) { + initClassScope().objects[name]?.let { + if( it.isMutable) it.value = value + else scope.raiseIllegalAssignment("can't assign $name is not mutable") + } + ?: super.writeField(scope, name, value) + } + override suspend fun invokeInstanceMethod(scope: Scope, name: String, args: Arguments, onNotFoundResult: Obj?): Obj { - return classMembers[name]?.value?.invoke(scope, this, args) + return classScope?.objects?.get(name)?.value?.invoke(scope, this, args) ?: super.invokeInstanceMethod(scope, name, args, onNotFoundResult) } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstanceClass.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstanceClass.kt index c86e1d9..5492af8 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstanceClass.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstanceClass.kt @@ -10,8 +10,6 @@ import net.sergeych.lynon.LynonType */ class ObjInstanceClass(val name: String) : ObjClass(name) { -// val onDeserilaized = - override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj { val args = decoder.decodeAnyList(scope) println("deserializing constructor $name, $args params") diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/statements.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/statements.kt index 7154a4d..80540f9 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/statements.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/statements.kt @@ -2,12 +2,16 @@ package net.sergeych.lyng import net.sergeych.lyng.obj.Obj import net.sergeych.lyng.obj.ObjClass +import net.sergeych.lyng.obj.ObjVoid fun String.toSource(name: String = "eval"): Source = Source(name, this) sealed class ObjType { object Any : ObjType() - object Int : ObjType() + object Void: ObjType() + + companion object { + } } @@ -62,4 +66,8 @@ fun statement(isStaticConst: Boolean = false, isConst: Boolean = false, f: suspe override suspend fun execute(scope: Scope): Obj = f(scope) } +object NopStatement: Statement(true, true, ObjType.Void) { + override val pos: Pos = Pos.builtIn + override suspend fun execute(scope: Scope): Obj = ObjVoid +} diff --git a/lynglib/src/commonTest/kotlin/OOTest.kt b/lynglib/src/commonTest/kotlin/OOTest.kt new file mode 100644 index 0000000..9ed51a5 --- /dev/null +++ b/lynglib/src/commonTest/kotlin/OOTest.kt @@ -0,0 +1,35 @@ +import kotlinx.coroutines.test.runTest +import net.sergeych.lyng.eval +import kotlin.test.Test + +class OOTest { + @Test + fun testClassProps() = runTest { + eval(""" + import lyng.time + + class Point(x,y) { + static val origin = Point(0,0) + static var center = origin + } + assertEquals(Point(0,0), Point.origin) + assertEquals(Point(0,0), Point.center) + Point.center = Point(1,2) + assertEquals(Point(0,0), Point.origin) + assertEquals(Point(1,2), Point.center) + + """.trimIndent()) + } + @Test + fun testClassMethods() = runTest { + eval(""" + import lyng.time + + class Point(x,y) { + private static var data = null + } + assertEquals(Point(0,0), Point(0,0) ) + + """.trimIndent()) + } +} \ No newline at end of file