OOP: structs with field access!

This commit is contained in:
Sergey Chernov 2025-06-08 18:09:55 +04:00
parent 397dcaae92
commit d3f23be7fe
7 changed files with 155 additions and 72 deletions

View File

@ -76,12 +76,36 @@ Regular methods are called on instances as usual `instance.method()`. The method
The class is a some data record with named fields and fixed order, in fact. To define a class,
just Provide a name and a record like this:
class Vec2(x,y)
// creating new class with main constructor
// with all fields public and mutable:
This way, you have created a _constructor_, so calling `Vec2( 10, 20 )` would create an _instane_ of `Vec2` class:
struct Point(x,y)
assert( Point is Class )
// now we can create instance
val p1 = Point(3,4)
class Vec2(x,y)
Vec2(10,20)
>> eee
// is is of the newly created type:
assert( p1 is Point )
TBD
// we can read and write its fields:
assert( p1.x == 3 )
assert( p1.y == 4 )
p1.y++
assert( p1.y == 5 )
>>> void
Let's see in details. The statement `struct Point(x,y)` creates a struct, or public class,
with two field, which are mutable and publicly visible, because it is _struct_. `(x,y)` here
is the [argument list], same as when defining a function. All together creates a class with
a _constructor_ that requires two parameters for fields. So when creating it with
`Point(10, 20)` we say _calling Point constructor_ with these parameters.
Such declaration is identical to `class Point(var x,var y)` which does exactly the same.
TBD
[argument list](declaring_arguments.md)

View File

@ -4,15 +4,15 @@ package net.sergeych.lyng
* List of argument declarations in the __definition__ of the lambda, class constructor,
* function, etc. It is created by [Compiler.parseArgsDeclaration]
*/
data class ArgsDeclaration(val args: List<Item>, val endTokenType: Token.Type) {
data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type) {
init {
val i = args.count { it.isEllipsis }
if (i > 1) throw ScriptError(args[i].pos, "there can be only one argument")
val start = args.indexOfFirst { it.defaultValue != null }
val i = params.count { it.isEllipsis }
if (i > 1) throw ScriptError(params[i].pos, "there can be only one argument")
val start = params.indexOfFirst { it.defaultValue != null }
if (start >= 0)
for (j in start + 1 until args.size)
if (args[j].defaultValue == null) throw ScriptError(
args[j].pos,
for (j in start + 1 until params.size)
if (params[j].defaultValue == null) throw ScriptError(
params[j].pos,
"required argument can't follow default one"
)
}
@ -31,8 +31,8 @@ data class ArgsDeclaration(val args: List<Item>, val endTokenType: Token.Type) {
suspend fun processHead(index: Int): Int {
var i = index
while (i != args.size) {
val a = args[i]
while (i != params.size) {
val a = params[i]
if (a.isEllipsis) break
val value = when {
i < fromArgs.size -> fromArgs[i]
@ -46,10 +46,10 @@ data class ArgsDeclaration(val args: List<Item>, val endTokenType: Token.Type) {
}
suspend fun processTail(index: Int): Int {
var i = args.size - 1
var i = params.size - 1
var j = fromArgs.size - 1
while (i > index) {
val a = args[i]
val a = params[i]
if (a.isEllipsis) break
val value = when {
j >= index -> {
@ -66,14 +66,14 @@ data class ArgsDeclaration(val args: List<Item>, val endTokenType: Token.Type) {
}
fun processEllipsis(index: Int, toFromIndex: Int) {
val a = args[index]
val a = params[index]
val l = if (index > toFromIndex) ObjList()
else ObjList(fromArgs.values.subList(index, toFromIndex + 1).toMutableList())
assign(a, l)
}
val leftIndex = processHead(0)
if (leftIndex < args.size) {
if (leftIndex < params.size) {
val end = processTail(leftIndex)
processEllipsis(leftIndex, end)
} else {

View File

@ -16,14 +16,14 @@ class Compiler(
private fun parseScript(start: Pos, tokens: CompilerContext): Script {
val statements = mutableListOf<Statement>()
while (parseStatement(tokens,braceMeansLambda = true)?.also {
while (parseStatement(tokens, braceMeansLambda = true)?.also {
statements += it
} != null) {/**/
}
return Script(start, statements)
}
private fun parseStatement(cc: CompilerContext,braceMeansLambda: Boolean = false): Statement? {
private fun parseStatement(cc: CompilerContext, braceMeansLambda: Boolean = false): Statement? {
while (true) {
val t = cc.next()
return when (t.type) {
@ -49,7 +49,7 @@ class Compiler(
Token.Type.LBRACE -> {
cc.previous()
if( braceMeansLambda )
if (braceMeansLambda)
parseExpression(cc)
else
parseBlock(cc)
@ -141,8 +141,10 @@ class Compiler(
}
}
if (!isCall) {
operand = Accessor { context ->
operand = Accessor({ context ->
left.getter(context).value.readField(context, next.value)
}) { cc, newValue ->
left.getter(cc).value.writeField(cc, next.value, newValue)
}
}
} ?: throw ScriptError(t.pos, "Expecting expression before dot")
@ -354,7 +356,7 @@ class Compiler(
}
return Accessor { x ->
if( closure == null ) closure = x
if (closure == null) closure = x
callStatement.asReadonly
}
}
@ -695,10 +697,16 @@ class Compiler(
private fun parseClassDeclaration(cc: CompilerContext, isStruct: Boolean): Statement {
val nameToken = cc.requireToken(Token.Type.ID)
val parsedArgs = parseArgsDeclaration(cc)
val constructorArgsDeclaration =
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true))
parseArgsDeclaration(cc)
else null
if( parsedArgs != null && parsedArgs.endTokenType != Token.Type.RPAREN)
throw ScriptError(nameToken.pos, "Bad class declaration: expected ')' at the end of the primary constructor")
if (constructorArgsDeclaration != null && constructorArgsDeclaration.endTokenType != Token.Type.RPAREN)
throw ScriptError(
nameToken.pos,
"Bad class declaration: expected ')' at the end of the primary constructor"
)
cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
val t = cc.next()
@ -707,7 +715,10 @@ class Compiler(
val bodyInit: Statement? = if (t.type == Token.Type.LBRACE) {
// parse body
TODO("parse body")
} else null
} else {
cc.previous()
null
}
// create class
val className = nameToken.value
@ -721,21 +732,45 @@ class Compiler(
val constructorCode = statement {
// constructor code is registered with class instance and is called over
// new `thisObj` already set by class to ObjInstance
// new `thisObj` already set by class to ObjInstance.instanceContext
thisObj as ObjInstance
// the context now is a "class creation context", we must use its args to initialize
// fields. Note that 'this' is already set by class
// parsedArgs?.let { pa ->
// pa.extractArgs { (def, value) ->
// val access = def.accessType ?: defaultAccess
// val visibility = def.visibility ?: defaultVisibility
// addItem(def.name, access.isMutable, value)
// }
//
thisObj
constructorArgsDeclaration?.let { cad ->
cad.assignToContext(this)
// note that accessors are created in ObjClass instance, not during instance
// initialization (not here)
}
thisObj
}
// inheritance must alter this code:
val newClass = ObjClass(className).apply {
instanceConstructor = constructorCode
constructorArgsDeclaration?.let { cad ->
// we need accessors for all fields:
for (f in cad.params) {
createField(
f.name,
statement {
val context = (thisObj as ObjInstance).instanceContext
println("called on $thisObj")
context[f.name]?.value ?: raiseError("field is not initialized: ${f.name}")
},
true,
// (f.accessType ?: defaultAccess).isMutable,
)
}
}
}
return statement {
// the main statement should create custom ObjClass instance with field
// accessors, constructor registration, etc.
addItem(className, false, newClass)
newClass
}
TODO()
}
@ -1063,8 +1098,11 @@ class Compiler(
throw ScriptError(t.pos, "Bad function definition: expected '(' after 'fn ${name}'")
val argsDeclaration = parseArgsDeclaration(tokens)
if( argsDeclaration == null || argsDeclaration.endTokenType != Token.Type.RPAREN)
throw ScriptError(t.pos, "Bad function definition: expected valid argument declaration or () after 'fn ${name}'")
if (argsDeclaration == null || argsDeclaration.endTokenType != Token.Type.RPAREN)
throw ScriptError(
t.pos,
"Bad function definition: expected valid argument declaration or () after 'fn ${name}'"
)
// Here we should be at open body
val fnStatements = parseBlock(tokens)

View File

@ -170,8 +170,7 @@ open class Obj {
val value = obj?.value
return when (value) {
is Statement -> {
// readonly property, important: call it on this
value.execute(context.copy(context.pos, newThisObj = this)).asReadonly
WithAccess(value.execute(context.copy(context.pos, newThisObj = this)), obj.isMutable)
}
// could be writable property naturally
null -> ObjNull.asReadonly

View File

@ -4,14 +4,10 @@ val ObjClassType by lazy { ObjClass("Class") }
class ObjClass(
val className: String,
val constructorArgs: List<ArgsDeclaration.Item> = emptyList(),
vararg val parents: ObjClass,
) : Obj() {
constructor(
className: String,
vararg parents: ObjClass,
) : this(className, emptyList(), *parents)
var instanceConstructor: Statement? = null
val allParentsSet: Set<ObjClass> = parents.flatMap {
listOf(it) + it.allParentsSet
@ -26,27 +22,28 @@ class ObjClass(
override suspend fun compareTo(context: Context, other: Obj): Int = if (other === this) 0 else -1
// private var initInstanceHandler: (suspend (Context, List<Obj>) -> Obj)? = null
override suspend fun callOn(context: Context): Obj {
println("callOn $this constructing....")
println("on context: $context")
val instance = ObjInstance(this)
instance.instanceContext = context.copy(newThisObj = instance,args = context.args)
if (instanceConstructor != null) {
instanceConstructor!!.execute(instance.instanceContext)
}
return instance
}
// suspend fun newInstance(context: Context, vararg args: Obj): Obj =
// initInstanceHandler?.invoke(context, args.toList())
// ?: context.raiseError("No initInstance handler for $this")
//
// fun buildInstance(f: suspend Context.(List<Obj>) -> Obj) {
// if (initInstanceHandler != null) throw IllegalStateException("initInstance already set")
// initInstanceHandler = f
// }
//
// fun addParent(context: Context, parent: Obj) {
// val self = context.thisObj
// self.parentInstances.add(parent)
// }
//
fun defaultInstance(): Obj = object : Obj() {
override val objClass: ObjClass = this@ObjClass
}
fun createField(name: String, initialValue: Obj, isMutable: Boolean = false, pos: Pos = Pos.builtIn) {
fun createField(
name: String,
initialValue: Obj,
isMutable: Boolean = false,
visibility: Compiler.Visibility = Compiler.Visibility.Public,
pos: Pos = Pos.builtIn
) {
if (name in members || allParentsSet.any { name in it.members } == true)
throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
members[name] = WithAccess(initialValue, isMutable)
@ -154,6 +151,7 @@ val ObjArray by lazy {
}
}
class ObjInstance(override val objClass: ObjClass): Obj() {
class ObjInstance(override val objClass: ObjClass) : Obj() {
internal lateinit var instanceContext: Context
}

View File

@ -156,6 +156,8 @@ class Script(
// interfaces
addConst("Iterable", ObjIterable)
addConst("Array", ObjArray)
addConst("Class", ObjClassType)
addConst("Object", Obj().objClass)
val pi = ObjReal(PI)
addConst("π", pi)

View File

@ -1383,14 +1383,36 @@ class ScriptTest {
}
@Test
fun simpleClassDelaration() = runTest {
eval( """
// class Vec2(x,y)
// println(Vec2(1,2)::class)
println("---------------------")
println(Int::class)
""".trimIndent()
)
fun testSimpleStruct() = runTest {
val c = Context()
c.eval("""
struct Point(x,y)
assert( Point::class is Class )
val p = Point(2,3)
assert(p is Point)
println(p)
println(p.x)
assert( p.x == 2 )
assert( p.y == 3 )
val p2 = Point(p.x+1,p.y+1)
p.x = 0
assertEquals( 0, p.x )
""".trimIndent())
}
@Test
fun testNonAssignalbeFieldInStruct() = runTest {
val c = Context()
c.eval("""
struct Point(x,y)
val p = Point("2",3)
assert(p is Point)
assert( p.x == "2" )
assert( p.y == 3 )
p.x = 0
assertEquals( 0, p.x )
""".trimIndent())
}
}