OOP: structs with field access!
This commit is contained in:
parent
397dcaae92
commit
d3f23be7fe
34
docs/OOP.md
34
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,
|
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:
|
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)
|
||||||
|
|
||||||
|
// is is of the newly created type:
|
||||||
|
assert( p1 is Point )
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
|
||||||
class Vec2(x,y)
|
|
||||||
Vec2(10,20)
|
|
||||||
>> eee
|
|
||||||
|
|
||||||
TBD
|
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,
|
* List of argument declarations in the __definition__ of the lambda, class constructor,
|
||||||
* function, etc. It is created by [Compiler.parseArgsDeclaration]
|
* 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 {
|
init {
|
||||||
val i = args.count { it.isEllipsis }
|
val i = params.count { it.isEllipsis }
|
||||||
if (i > 1) throw ScriptError(args[i].pos, "there can be only one argument")
|
if (i > 1) throw ScriptError(params[i].pos, "there can be only one argument")
|
||||||
val start = args.indexOfFirst { it.defaultValue != null }
|
val start = params.indexOfFirst { it.defaultValue != null }
|
||||||
if (start >= 0)
|
if (start >= 0)
|
||||||
for (j in start + 1 until args.size)
|
for (j in start + 1 until params.size)
|
||||||
if (args[j].defaultValue == null) throw ScriptError(
|
if (params[j].defaultValue == null) throw ScriptError(
|
||||||
args[j].pos,
|
params[j].pos,
|
||||||
"required argument can't follow default one"
|
"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 {
|
suspend fun processHead(index: Int): Int {
|
||||||
var i = index
|
var i = index
|
||||||
while (i != args.size) {
|
while (i != params.size) {
|
||||||
val a = args[i]
|
val a = params[i]
|
||||||
if (a.isEllipsis) break
|
if (a.isEllipsis) break
|
||||||
val value = when {
|
val value = when {
|
||||||
i < fromArgs.size -> fromArgs[i]
|
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 {
|
suspend fun processTail(index: Int): Int {
|
||||||
var i = args.size - 1
|
var i = params.size - 1
|
||||||
var j = fromArgs.size - 1
|
var j = fromArgs.size - 1
|
||||||
while (i > index) {
|
while (i > index) {
|
||||||
val a = args[i]
|
val a = params[i]
|
||||||
if (a.isEllipsis) break
|
if (a.isEllipsis) break
|
||||||
val value = when {
|
val value = when {
|
||||||
j >= index -> {
|
j >= index -> {
|
||||||
@ -66,14 +66,14 @@ data class ArgsDeclaration(val args: List<Item>, val endTokenType: Token.Type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun processEllipsis(index: Int, toFromIndex: Int) {
|
fun processEllipsis(index: Int, toFromIndex: Int) {
|
||||||
val a = args[index]
|
val a = params[index]
|
||||||
val l = if (index > toFromIndex) ObjList()
|
val l = if (index > toFromIndex) ObjList()
|
||||||
else ObjList(fromArgs.values.subList(index, toFromIndex + 1).toMutableList())
|
else ObjList(fromArgs.values.subList(index, toFromIndex + 1).toMutableList())
|
||||||
assign(a, l)
|
assign(a, l)
|
||||||
}
|
}
|
||||||
|
|
||||||
val leftIndex = processHead(0)
|
val leftIndex = processHead(0)
|
||||||
if (leftIndex < args.size) {
|
if (leftIndex < params.size) {
|
||||||
val end = processTail(leftIndex)
|
val end = processTail(leftIndex)
|
||||||
processEllipsis(leftIndex, end)
|
processEllipsis(leftIndex, end)
|
||||||
} else {
|
} else {
|
||||||
|
@ -141,8 +141,10 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!isCall) {
|
if (!isCall) {
|
||||||
operand = Accessor { context ->
|
operand = Accessor({ context ->
|
||||||
left.getter(context).value.readField(context, next.value)
|
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")
|
} ?: throw ScriptError(t.pos, "Expecting expression before dot")
|
||||||
@ -695,10 +697,16 @@ class Compiler(
|
|||||||
|
|
||||||
private fun parseClassDeclaration(cc: CompilerContext, isStruct: Boolean): Statement {
|
private fun parseClassDeclaration(cc: CompilerContext, isStruct: Boolean): Statement {
|
||||||
val nameToken = cc.requireToken(Token.Type.ID)
|
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)
|
if (constructorArgsDeclaration != null && constructorArgsDeclaration.endTokenType != Token.Type.RPAREN)
|
||||||
throw ScriptError(nameToken.pos, "Bad class declaration: expected ')' at the end of the primary constructor")
|
throw ScriptError(
|
||||||
|
nameToken.pos,
|
||||||
|
"Bad class declaration: expected ')' at the end of the primary constructor"
|
||||||
|
)
|
||||||
|
|
||||||
cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
|
cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true)
|
||||||
val t = cc.next()
|
val t = cc.next()
|
||||||
@ -707,7 +715,10 @@ class Compiler(
|
|||||||
val bodyInit: Statement? = if (t.type == Token.Type.LBRACE) {
|
val bodyInit: Statement? = if (t.type == Token.Type.LBRACE) {
|
||||||
// parse body
|
// parse body
|
||||||
TODO("parse body")
|
TODO("parse body")
|
||||||
} else null
|
} else {
|
||||||
|
cc.previous()
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
// create class
|
// create class
|
||||||
val className = nameToken.value
|
val className = nameToken.value
|
||||||
@ -721,21 +732,45 @@ class Compiler(
|
|||||||
|
|
||||||
val constructorCode = statement {
|
val constructorCode = statement {
|
||||||
// constructor code is registered with class instance and is called over
|
// 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
|
thisObj as ObjInstance
|
||||||
|
|
||||||
// the context now is a "class creation context", we must use its args to initialize
|
// the context now is a "class creation context", we must use its args to initialize
|
||||||
// fields. Note that 'this' is already set by class
|
// fields. Note that 'this' is already set by class
|
||||||
// parsedArgs?.let { pa ->
|
constructorArgsDeclaration?.let { cad ->
|
||||||
// pa.extractArgs { (def, value) ->
|
cad.assignToContext(this)
|
||||||
// val access = def.accessType ?: defaultAccess
|
// note that accessors are created in ObjClass instance, not during instance
|
||||||
// val visibility = def.visibility ?: defaultVisibility
|
// initialization (not here)
|
||||||
// addItem(def.name, access.isMutable, value)
|
}
|
||||||
// }
|
|
||||||
//
|
thisObj
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1064,7 +1099,10 @@ class Compiler(
|
|||||||
|
|
||||||
val argsDeclaration = parseArgsDeclaration(tokens)
|
val argsDeclaration = parseArgsDeclaration(tokens)
|
||||||
if (argsDeclaration == null || argsDeclaration.endTokenType != Token.Type.RPAREN)
|
if (argsDeclaration == null || argsDeclaration.endTokenType != Token.Type.RPAREN)
|
||||||
throw ScriptError(t.pos, "Bad function definition: expected valid argument declaration or () after 'fn ${name}'")
|
throw ScriptError(
|
||||||
|
t.pos,
|
||||||
|
"Bad function definition: expected valid argument declaration or () after 'fn ${name}'"
|
||||||
|
)
|
||||||
|
|
||||||
// Here we should be at open body
|
// Here we should be at open body
|
||||||
val fnStatements = parseBlock(tokens)
|
val fnStatements = parseBlock(tokens)
|
||||||
|
@ -170,8 +170,7 @@ open class Obj {
|
|||||||
val value = obj?.value
|
val value = obj?.value
|
||||||
return when (value) {
|
return when (value) {
|
||||||
is Statement -> {
|
is Statement -> {
|
||||||
// readonly property, important: call it on this
|
WithAccess(value.execute(context.copy(context.pos, newThisObj = this)), obj.isMutable)
|
||||||
value.execute(context.copy(context.pos, newThisObj = this)).asReadonly
|
|
||||||
}
|
}
|
||||||
// could be writable property naturally
|
// could be writable property naturally
|
||||||
null -> ObjNull.asReadonly
|
null -> ObjNull.asReadonly
|
||||||
|
@ -4,14 +4,10 @@ val ObjClassType by lazy { ObjClass("Class") }
|
|||||||
|
|
||||||
class ObjClass(
|
class ObjClass(
|
||||||
val className: String,
|
val className: String,
|
||||||
val constructorArgs: List<ArgsDeclaration.Item> = emptyList(),
|
|
||||||
vararg val parents: ObjClass,
|
vararg val parents: ObjClass,
|
||||||
) : Obj() {
|
) : Obj() {
|
||||||
constructor(
|
|
||||||
className: String,
|
|
||||||
vararg parents: ObjClass,
|
|
||||||
) : this(className, emptyList(), *parents)
|
|
||||||
|
|
||||||
|
var instanceConstructor: Statement? = null
|
||||||
|
|
||||||
val allParentsSet: Set<ObjClass> = parents.flatMap {
|
val allParentsSet: Set<ObjClass> = parents.flatMap {
|
||||||
listOf(it) + it.allParentsSet
|
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
|
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() {
|
fun defaultInstance(): Obj = object : Obj() {
|
||||||
override val objClass: ObjClass = this@ObjClass
|
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)
|
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")
|
throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
|
||||||
members[name] = WithAccess(initialValue, isMutable)
|
members[name] = WithAccess(initialValue, isMutable)
|
||||||
@ -156,4 +153,5 @@ 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
|
// interfaces
|
||||||
addConst("Iterable", ObjIterable)
|
addConst("Iterable", ObjIterable)
|
||||||
addConst("Array", ObjArray)
|
addConst("Array", ObjArray)
|
||||||
|
addConst("Class", ObjClassType)
|
||||||
|
addConst("Object", Obj().objClass)
|
||||||
|
|
||||||
val pi = ObjReal(PI)
|
val pi = ObjReal(PI)
|
||||||
addConst("π", pi)
|
addConst("π", pi)
|
||||||
|
@ -1383,14 +1383,36 @@ class ScriptTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun simpleClassDelaration() = runTest {
|
fun testSimpleStruct() = runTest {
|
||||||
eval( """
|
val c = Context()
|
||||||
// class Vec2(x,y)
|
c.eval("""
|
||||||
// println(Vec2(1,2)::class)
|
struct Point(x,y)
|
||||||
println("---------------------")
|
assert( Point::class is Class )
|
||||||
println(Int::class)
|
val p = Point(2,3)
|
||||||
""".trimIndent()
|
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