OOP: structs with field access!
This commit is contained in:
parent
397dcaae92
commit
d3f23be7fe
36
docs/OOP.md
36
docs/OOP.md
@ -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)
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user