fix #11 and private visibility for constructor params, fields and methods.
This commit is contained in:
parent
fffa3d31bb
commit
20c81dbf2e
1
.gitignore
vendored
1
.gitignore
vendored
@ -13,3 +13,4 @@ xcuserdata
|
|||||||
*.gpg
|
*.gpg
|
||||||
.gigaide
|
.gigaide
|
||||||
/kotlin-js-store/yarn.lock
|
/kotlin-js-store/yarn.lock
|
||||||
|
/test.lyng
|
||||||
|
117
docs/OOP.md
117
docs/OOP.md
@ -1,12 +1,19 @@
|
|||||||
# OO implementation in Lyng
|
# OO implementation in Lyng
|
||||||
|
|
||||||
Short introduction
|
## Declaration
|
||||||
|
|
||||||
|
The class clause looks like
|
||||||
|
|
||||||
|
class Point(x,y)
|
||||||
|
assert( Point is Class )
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
It creates new `Class` with two fields. Here is the more practical sample:
|
||||||
|
|
||||||
class Point(x,y) {
|
class Point(x,y) {
|
||||||
fun length() { sqrt(x*x + y*y) }
|
fun length() { sqrt(x*x + y*y) }
|
||||||
}
|
}
|
||||||
|
|
||||||
assert( Point is Class )
|
|
||||||
val p = Point(3,4)
|
val p = Point(3,4)
|
||||||
assert(p is Point)
|
assert(p is Point)
|
||||||
assertEquals(5, p.length())
|
assertEquals(5, p.length())
|
||||||
@ -32,7 +39,111 @@ Form now on `Point` is a class, it's type is `Class`, and we can create instance
|
|||||||
example above.
|
example above.
|
||||||
|
|
||||||
Class point has a _method_, or a _member function_ `length()` that uses its _fields_ `x` and `y` to
|
Class point has a _method_, or a _member function_ `length()` that uses its _fields_ `x` and `y` to
|
||||||
calculate the magnitude.
|
calculate the magnitude. Length is called
|
||||||
|
|
||||||
|
### default values in constructor
|
||||||
|
|
||||||
|
Constructor arguments are the same as function arguments except visibility
|
||||||
|
statements discussed later, there could be default values, ellipsis, etc.
|
||||||
|
|
||||||
|
class Point(x=0,y=0)
|
||||||
|
val p = Point()
|
||||||
|
assert( p.x == 0 && p.y == 0 )
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
## Methods
|
||||||
|
|
||||||
|
Functions defined inside a class body are methods, and unless declared
|
||||||
|
`private` are available to be called from outside the class:
|
||||||
|
|
||||||
|
class Point(x,y) {
|
||||||
|
// public method declaration:
|
||||||
|
fun length() { sqrt(d2()) }
|
||||||
|
|
||||||
|
// private method:
|
||||||
|
private fun d2() {x*x + y*y}
|
||||||
|
}
|
||||||
|
val p = Point(3,4)
|
||||||
|
// private called from inside public: OK
|
||||||
|
assertEquals( 5, p.length() )
|
||||||
|
// but us not available directly
|
||||||
|
assertThrows() { p.d2() }
|
||||||
|
void
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
## fields and visibility
|
||||||
|
|
||||||
|
It is possible to add non-constructor fields:
|
||||||
|
|
||||||
|
class Point(x,y) {
|
||||||
|
fun length() { sqrt(x*x + y*y) }
|
||||||
|
|
||||||
|
// set at construction time:
|
||||||
|
val initialLength = length()
|
||||||
|
}
|
||||||
|
val p = Point(3,4)
|
||||||
|
p.x = 3
|
||||||
|
p.y = 0
|
||||||
|
assertEquals( 3, p.length() )
|
||||||
|
// but initial length could not be changed after as declard val:
|
||||||
|
assert( p.initialLength == 5 )
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
### Mutable fields
|
||||||
|
|
||||||
|
Are declared with var
|
||||||
|
|
||||||
|
class Point(x,y) {
|
||||||
|
var isSpecial = false
|
||||||
|
}
|
||||||
|
val p = Point(0,0)
|
||||||
|
assert( p.isSpecial == false )
|
||||||
|
|
||||||
|
p.isSpecial = true
|
||||||
|
assert( p.isSpecial == true )
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
### Private fields
|
||||||
|
|
||||||
|
Private fields are visible only _inside the class instance_:
|
||||||
|
|
||||||
|
class SecretCounter {
|
||||||
|
private var count = 0
|
||||||
|
|
||||||
|
fun increment() {
|
||||||
|
count++
|
||||||
|
void // hide counter
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isEnough() {
|
||||||
|
count > 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val c = SecretCounter()
|
||||||
|
assert( c.isEnough() == false )
|
||||||
|
assert( c.increment() == void )
|
||||||
|
for( i in 0..10 ) c.increment()
|
||||||
|
assert( c.isEnough() )
|
||||||
|
|
||||||
|
// but the count is not available outside:
|
||||||
|
assertThrows() { c.count }
|
||||||
|
void
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
It is possible to provide private constructor parameters so they can be
|
||||||
|
set at construction but not available outside the class:
|
||||||
|
|
||||||
|
class SecretCounter(private var count = 0) {
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
val c = SecretCounter(10)
|
||||||
|
assertThrows() { c.count }
|
||||||
|
void
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Theory
|
||||||
|
|
||||||
## Basic principles:
|
## Basic principles:
|
||||||
|
|
||||||
|
@ -23,10 +23,12 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
|||||||
suspend fun assignToContext(
|
suspend fun assignToContext(
|
||||||
context: Context,
|
context: Context,
|
||||||
fromArgs: Arguments = context.args,
|
fromArgs: Arguments = context.args,
|
||||||
defaultAccessType: Compiler.AccessType = Compiler.AccessType.Var
|
defaultAccessType: Compiler.AccessType = Compiler.AccessType.Var,
|
||||||
|
defaultVisibility: Compiler.Visibility = Compiler.Visibility.Public
|
||||||
) {
|
) {
|
||||||
fun assign(a: Item, value: Obj) {
|
fun assign(a: Item, value: Obj) {
|
||||||
context.addItem(a.name, (a.accessType ?: defaultAccessType).isMutable, value)
|
context.addItem(a.name, (a.accessType ?: defaultAccessType).isMutable, value,
|
||||||
|
a.visibility ?: defaultVisibility)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun processHead(index: Int): Int {
|
suspend fun processHead(index: Int): Int {
|
||||||
|
@ -37,6 +37,13 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Token.Type.PRIVATE, Token.Type.PROTECTED -> {
|
||||||
|
if(cc.nextIdValue() in setOf("var", "val", "class", "fun", "fn")) {
|
||||||
|
continue
|
||||||
|
} else
|
||||||
|
throw ScriptError(t.pos, "unexpected keyword ${t.value}")
|
||||||
|
}
|
||||||
|
|
||||||
Token.Type.PLUS2, Token.Type.MINUS2 -> {
|
Token.Type.PLUS2, Token.Type.MINUS2 -> {
|
||||||
cc.previous()
|
cc.previous()
|
||||||
parseExpression(cc)
|
parseExpression(cc)
|
||||||
@ -408,7 +415,7 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum class Visibility {
|
enum class Visibility {
|
||||||
Public, Private, Protected, Internal
|
Public, Private, Protected//, Internal
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -429,43 +436,16 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Token.Type.NEWLINE -> {}
|
Token.Type.NEWLINE -> {}
|
||||||
|
Token.Type.PROTECTED, Token.Type.PRIVATE -> {
|
||||||
|
if (!isClassDeclaration) {
|
||||||
|
cc.restorePos(startPos); return null
|
||||||
|
}
|
||||||
|
}
|
||||||
Token.Type.ID -> {
|
Token.Type.ID -> {
|
||||||
// visibility
|
// visibility
|
||||||
val visibility = when (t.value) {
|
val visibility = if( isClassDeclaration )
|
||||||
"private" -> {
|
cc.getVisibility(Visibility.Public)
|
||||||
if (!isClassDeclaration) {
|
else Visibility.Public
|
||||||
cc.restorePos(startPos); return null
|
|
||||||
}
|
|
||||||
t = cc.next()
|
|
||||||
Visibility.Private
|
|
||||||
}
|
|
||||||
|
|
||||||
"protected" -> {
|
|
||||||
if (!isClassDeclaration) {
|
|
||||||
cc.restorePos(startPos); return null
|
|
||||||
}
|
|
||||||
t = cc.next()
|
|
||||||
Visibility.Protected
|
|
||||||
}
|
|
||||||
|
|
||||||
"internal" -> {
|
|
||||||
if (!isClassDeclaration) {
|
|
||||||
cc.restorePos(startPos); return null
|
|
||||||
}
|
|
||||||
t = cc.next()
|
|
||||||
Visibility.Internal
|
|
||||||
}
|
|
||||||
|
|
||||||
"public" -> {
|
|
||||||
if (!isClassDeclaration) {
|
|
||||||
cc.restorePos(startPos); return null
|
|
||||||
}
|
|
||||||
t = cc.next()
|
|
||||||
Visibility.Public
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
// val/var?
|
// val/var?
|
||||||
val access = when (t.value) {
|
val access = when (t.value) {
|
||||||
"val" -> {
|
"val" -> {
|
||||||
@ -703,7 +683,7 @@ class Compiler(
|
|||||||
val nameToken = cc.requireToken(Token.Type.ID)
|
val nameToken = cc.requireToken(Token.Type.ID)
|
||||||
val constructorArgsDeclaration =
|
val constructorArgsDeclaration =
|
||||||
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true))
|
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true))
|
||||||
parseArgsDeclaration(cc)
|
parseArgsDeclaration(cc, isClassDeclaration = true)
|
||||||
else null
|
else null
|
||||||
|
|
||||||
if (constructorArgsDeclaration != null && constructorArgsDeclaration.endTokenType != Token.Type.RPAREN)
|
if (constructorArgsDeclaration != null && constructorArgsDeclaration.endTokenType != Token.Type.RPAREN)
|
||||||
@ -752,7 +732,9 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
Visibility.Protected ->
|
Visibility.Protected ->
|
||||||
thisObj.protectedFields += name
|
thisObj.protectedFields += name
|
||||||
else -> {
|
|
||||||
|
Visibility.Private -> {
|
||||||
|
//println("private field: $name")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -762,20 +744,6 @@ class Compiler(
|
|||||||
// inheritance must alter this code:
|
// inheritance must alter this code:
|
||||||
val newClass = ObjClass(className).apply {
|
val newClass = ObjClass(className).apply {
|
||||||
instanceConstructor = constructorCode
|
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
|
|
||||||
context[f.name]?.value ?: raiseError("field is not initialized: ${f.name}")
|
|
||||||
},
|
|
||||||
true,
|
|
||||||
// (f.accessType ?: defaultAccess).isMutable,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return statement {
|
return statement {
|
||||||
@ -1100,6 +1068,8 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun parseFunctionDeclaration(tokens: CompilerContext): Statement {
|
private fun parseFunctionDeclaration(tokens: CompilerContext): Statement {
|
||||||
|
val visibility = tokens.getVisibility()
|
||||||
|
|
||||||
var t = tokens.next()
|
var t = tokens.next()
|
||||||
val start = t.pos
|
val start = t.pos
|
||||||
val name = if (t.type != Token.Type.ID)
|
val name = if (t.type != Token.Type.ID)
|
||||||
@ -1136,7 +1106,7 @@ class Compiler(
|
|||||||
// we added fn in the context. now we must save closure
|
// we added fn in the context. now we must save closure
|
||||||
// for the function
|
// for the function
|
||||||
closure = context
|
closure = context
|
||||||
context.addItem(name, false, fnBody)
|
context.addItem(name, false, fnBody, visibility)
|
||||||
// as the function can be called from anywhere, we have
|
// as the function can be called from anywhere, we have
|
||||||
// saved the proper context in the closure
|
// saved the proper context in the closure
|
||||||
fnBody
|
fnBody
|
||||||
@ -1162,6 +1132,14 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun parseVarDeclaration(kind: String, mutable: Boolean, tokens: CompilerContext): Statement {
|
private fun parseVarDeclaration(kind: String, mutable: Boolean, tokens: CompilerContext): Statement {
|
||||||
|
// we are just after var/val, visibility if exists is 2 steps behind
|
||||||
|
val visibility = when( tokens.atOffset(-2)?.type ) {
|
||||||
|
Token.Type.PRIVATE ->
|
||||||
|
Visibility.Private
|
||||||
|
Token.Type.PROTECTED -> Visibility.Protected
|
||||||
|
else -> Visibility.Public
|
||||||
|
}
|
||||||
|
|
||||||
val nameToken = tokens.next()
|
val nameToken = tokens.next()
|
||||||
val start = nameToken.pos
|
val start = nameToken.pos
|
||||||
if (nameToken.type != Token.Type.ID)
|
if (nameToken.type != Token.Type.ID)
|
||||||
@ -1190,7 +1168,7 @@ class Compiler(
|
|||||||
// create a separate copy:
|
// create a separate copy:
|
||||||
val initValue = initialExpression?.execute(context)?.byValueCopy() ?: ObjNull
|
val initValue = initialExpression?.execute(context)?.byValueCopy() ?: ObjNull
|
||||||
|
|
||||||
context.addItem(name, mutable, initValue)
|
context.addItem(name, mutable, initValue, visibility)
|
||||||
ObjVoid
|
ObjVoid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ internal class CompilerContext(val tokens: List<Token>) {
|
|||||||
var loopLevel = 0
|
var loopLevel = 0
|
||||||
private set
|
private set
|
||||||
|
|
||||||
inline fun <T> parseLoop(f: () -> T): Pair<Boolean,T> {
|
inline fun <T> parseLoop(f: () -> T): Pair<Boolean, T> {
|
||||||
if (++loopLevel == 0) breakFound = false
|
if (++loopLevel == 0) breakFound = false
|
||||||
val result = f()
|
val result = f()
|
||||||
return Pair(breakFound, result).also {
|
return Pair(breakFound, result).also {
|
||||||
@ -98,4 +98,40 @@ internal class CompilerContext(val tokens: List<Token>) {
|
|||||||
breakFound = true
|
breakFound = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return value of the next token if it is an identifier, null otherwise.
|
||||||
|
* Does not change position.
|
||||||
|
*/
|
||||||
|
fun nextIdValue(): String? {
|
||||||
|
return if (hasNext()) {
|
||||||
|
val nt = tokens[currentIndex]
|
||||||
|
if (nt.type == Token.Type.ID)
|
||||||
|
nt.value
|
||||||
|
else null
|
||||||
|
} else null
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun current(): Token = tokens[currentIndex]
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the token at current position plus offset (could be negative) exists, returns it, otherwise returns null.
|
||||||
|
*/
|
||||||
|
fun atOffset(offset: Int): Token? =
|
||||||
|
if (currentIndex + offset in tokens.indices) tokens[currentIndex + offset] else null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan backwards as deep as specified looking for visibility token. Does not change position.
|
||||||
|
*/
|
||||||
|
fun getVisibility(default: Compiler.Visibility = Compiler.Visibility.Public, depths: Int = 2): Compiler.Visibility {
|
||||||
|
for( i in -depths .. -1) {
|
||||||
|
when( atOffset(i)?.type) {
|
||||||
|
Token.Type.PROTECTED -> return Compiler.Visibility.Protected
|
||||||
|
Token.Type.PRIVATE -> return Compiler.Visibility.Private
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return default
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -28,7 +28,8 @@ class Context(
|
|||||||
fun raiseClassCastError(msg: String): Nothing = raiseError(ObjClassCastError(this, msg))
|
fun raiseClassCastError(msg: String): Nothing = raiseError(ObjClassCastError(this, msg))
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun raiseSymbolNotFound(name: String): Nothing = raiseError(ObjSymbolNotDefinedError(this, "symbol is not defined: $name"))
|
fun raiseSymbolNotFound(name: String): Nothing =
|
||||||
|
raiseError(ObjSymbolNotDefinedError(this, "symbol is not defined: $name"))
|
||||||
|
|
||||||
fun raiseError(message: String): Nothing {
|
fun raiseError(message: String): Nothing {
|
||||||
throw ExecutionError(ObjError(this, message))
|
throw ExecutionError(ObjError(this, message))
|
||||||
@ -73,8 +74,13 @@ class Context(
|
|||||||
|
|
||||||
fun copy() = Context(this, args, pos, thisObj)
|
fun copy() = Context(this, args, pos, thisObj)
|
||||||
|
|
||||||
fun addItem(name: String, isMutable: Boolean, value: Obj): ObjRecord {
|
fun addItem(
|
||||||
return ObjRecord(value, isMutable).also { objects.put(name, it) }
|
name: String,
|
||||||
|
isMutable: Boolean,
|
||||||
|
value: Obj,
|
||||||
|
visibility: Compiler.Visibility = Compiler.Visibility.Public
|
||||||
|
): ObjRecord {
|
||||||
|
return ObjRecord(value, isMutable, visibility).also { objects.put(name, it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getOrCreateNamespace(name: String): ObjClass {
|
fun getOrCreateNamespace(name: String): ObjClass {
|
||||||
|
@ -280,6 +280,8 @@ private class Parser(fromPos: Pos) {
|
|||||||
when (text) {
|
when (text) {
|
||||||
"in" -> Token("in", from, Token.Type.IN)
|
"in" -> Token("in", from, Token.Type.IN)
|
||||||
"is" -> Token("is", from, Token.Type.IS)
|
"is" -> Token("is", from, Token.Type.IS)
|
||||||
|
"protected" -> Token("protected", from, Token.Type.PROTECTED)
|
||||||
|
"private" -> Token("private", from, Token.Type.PRIVATE)
|
||||||
else -> Token(text, from, Token.Type.ID)
|
else -> Token(text, from, Token.Type.ID)
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
|
@ -17,6 +17,8 @@ data class Token(val value: String, val pos: Pos, val type: Type) {
|
|||||||
AND, BITAND, OR, BITOR, NOT, BITNOT, DOT, ARROW, QUESTION, COLONCOLON,
|
AND, BITAND, OR, BITOR, NOT, BITNOT, DOT, ARROW, QUESTION, COLONCOLON,
|
||||||
SINLGE_LINE_COMMENT, MULTILINE_COMMENT,
|
SINLGE_LINE_COMMENT, MULTILINE_COMMENT,
|
||||||
LABEL, ATLABEL, // label@ at@label
|
LABEL, ATLABEL, // label@ at@label
|
||||||
|
PRIVATE, PROTECTED,
|
||||||
|
//PUBLIC, PROTECTED, INTERNAL, EXPORT, OPEN, INLINE, OVERRIDE, ABSTRACT, SEALED, EXTERNAL, VAL, VAR, CONST, TYPE, FUN, CLASS, INTERFACE, ENUM, OBJECT, TRAIT, THIS,
|
||||||
ELLIPSIS, DOTDOT, DOTDOTLT,
|
ELLIPSIS, DOTDOT, DOTDOTLT,
|
||||||
NEWLINE,
|
NEWLINE,
|
||||||
EOF,
|
EOF,
|
||||||
|
@ -1453,4 +1453,15 @@ class ScriptTest {
|
|||||||
assertEquals(sqrt(109), p.length())
|
assertEquals(sqrt(109), p.length())
|
||||||
""".trimIndent())
|
""".trimIndent())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testPrivateConstructorParams() = runTest {
|
||||||
|
val c = Context()
|
||||||
|
c.eval("""
|
||||||
|
class Point(private var x,y)
|
||||||
|
val p = Point(1,2)
|
||||||
|
p.y = 101
|
||||||
|
assertThrows() { p.x = 10 }
|
||||||
|
""")
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user