well redesigned class vals and vars, classScope introduced for class functions

This commit is contained in:
Sergey Chernov 2025-08-06 11:46:17 +03:00
parent 4165da5e81
commit 63b2808109
7 changed files with 125 additions and 11 deletions

View File

@ -17,6 +17,15 @@ class Compiler(
class Settings
private val initStack = mutableListOf<MutableList<Statement>>()
val currentInitScope: MutableList<Statement> get() =
initStack.lastOrNull() ?: cc.syntaxError("no initialization scope exists here")
private fun pushInitScope(): MutableList<Statement> = mutableListOf<Statement>().also { initStack.add(it)}
private fun popInitScope(): MutableList<Statement> = initStack.removeLast()
private suspend fun parseScript(): Script {
val statements = mutableListOf<Statement>()
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")

View File

@ -44,11 +44,14 @@ class CompilerContext(val tokens: List<Token>) {
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
/**

View File

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

View File

@ -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<ObjClass> =
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<String, ObjRecord>()
private val classMembers = mutableMapOf<String, ObjRecord>()
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)
}

View File

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

View File

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

View File

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