first OO features: x::class, x.method(1,2,3) builting Real.roundToInt
This commit is contained in:
parent
361c1d5b13
commit
d21544ca5d
57
docs/OOP.md
Normal file
57
docs/OOP.md
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# OO implementation in Ling
|
||||||
|
|
||||||
|
Basic principles:
|
||||||
|
|
||||||
|
- Everything is an instance of some class
|
||||||
|
- Every class except Obj has at least one parent
|
||||||
|
- Obj has no parents and is the root of the hierarchy
|
||||||
|
- instance has member fields and member functions
|
||||||
|
- Every class has hclass members and class functions, or companion ones, are these of the base class.
|
||||||
|
- every class has _type_ which is an instances of ObjClass
|
||||||
|
- ObjClass sole parent is Obj
|
||||||
|
- ObjClass contains code for instance methods, class fields, hierarchy information.
|
||||||
|
- Class information is also scoped.
|
||||||
|
- We acoid imported classes duplication using packages and import caching, so the same imported module is the same object in all its classes.
|
||||||
|
|
||||||
|
## Instances
|
||||||
|
|
||||||
|
Result of executing of any expression or statement in the Ling is the object that
|
||||||
|
inherits `Obj`, but is not `Obj`. For example it could be Int, void, null, real, string, bool, etc.
|
||||||
|
|
||||||
|
This means whatever expression returns or the variable holds, is the first-class
|
||||||
|
object, no differenes. For example:
|
||||||
|
|
||||||
|
1.67.roundToInt()
|
||||||
|
1>>> 2
|
||||||
|
|
||||||
|
Here, instance method of the real object, created from literal `1.67` is called.
|
||||||
|
|
||||||
|
## Instance class
|
||||||
|
|
||||||
|
Everything can be classified, and classes could be tested for equivalence:
|
||||||
|
|
||||||
|
3.14::class
|
||||||
|
1>>> Real
|
||||||
|
|
||||||
|
Class is the object, naturally, with class:
|
||||||
|
|
||||||
|
3.14::class::class
|
||||||
|
1>>> Class
|
||||||
|
|
||||||
|
Classes can be compared:
|
||||||
|
|
||||||
|
println(3.14::class == 2.21::class)
|
||||||
|
println(3.14::class == 1::class)
|
||||||
|
println(π::class)
|
||||||
|
>>> true
|
||||||
|
>>> false
|
||||||
|
>>> Real
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
### Methods in-depth
|
||||||
|
|
||||||
|
Regular methods are called on instances as usual `instance.method()`. The method resolution order is
|
||||||
|
|
||||||
|
1. this instance methods;
|
||||||
|
2. parents method: no guarantee but we enumerate parents in order of appearance;
|
||||||
|
3. possible extension methods (scoped)
|
@ -1,6 +1,6 @@
|
|||||||
package net.sergeych.ling
|
package net.sergeych.ling
|
||||||
|
|
||||||
data class Arguments(val callerPos: Pos,val list: List<Info>): Iterable<Obj> {
|
data class Arguments(val list: List<Info>): Iterable<Obj> {
|
||||||
|
|
||||||
data class Info(val value: Obj,val pos: Pos)
|
data class Info(val value: Obj,val pos: Pos)
|
||||||
|
|
||||||
@ -14,10 +14,12 @@ data class Arguments(val callerPos: Pos,val list: List<Info>): Iterable<Obj> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val EMPTY = Arguments("".toSource().startPos,emptyList())
|
val EMPTY = Arguments(emptyList())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun iterator(): Iterator<Obj> {
|
override fun iterator(): Iterator<Obj> {
|
||||||
return list.map { it.value }.iterator()
|
return list.map { it.value }.iterator()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun List<Arguments.Info>.toArguments() = Arguments(this )
|
@ -89,6 +89,17 @@ class CompilerContext(val tokens: List<Token>) : ListIterator<Token> by tokens.l
|
|||||||
} else true
|
} else true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun ifNextIs(typeId: Token.Type, f: (Token) -> Unit): IfScope {
|
||||||
|
val t = next()
|
||||||
|
return if (t.type == typeId) {
|
||||||
|
f(t)
|
||||||
|
IfScope(true)
|
||||||
|
} else {
|
||||||
|
previous()
|
||||||
|
IfScope(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -264,9 +275,30 @@ class Compiler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Token.Type.DOT -> {
|
Token.Type.DOT -> {
|
||||||
if (operand == null)
|
operand?.let { left ->
|
||||||
throw ScriptError(t.pos, "Expecting expression before dot")
|
// dotcall: calling method on the operand, if next is ID, "("
|
||||||
continue
|
cc.ifNextIs(Token.Type.ID) { methodToken ->
|
||||||
|
cc.ifNextIs(Token.Type.LPAREN) {
|
||||||
|
// instance method call
|
||||||
|
val args = parseArgs(cc)
|
||||||
|
operand = Accessor { context ->
|
||||||
|
context.pos = methodToken.pos
|
||||||
|
val v = left.getter(context)
|
||||||
|
v.callInstanceMethod(
|
||||||
|
context,
|
||||||
|
methodToken.value,
|
||||||
|
args.toArguments()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.otherwise {
|
||||||
|
TODO("implement member access")
|
||||||
|
}
|
||||||
|
} ?: throw ScriptError(t.pos, "Expecting expression before dot")
|
||||||
|
}
|
||||||
|
|
||||||
|
Token.Type.COLONCOLON -> {
|
||||||
|
operand = parseScopeOperator(operand,cc)
|
||||||
}
|
}
|
||||||
|
|
||||||
Token.Type.LPAREN -> {
|
Token.Type.LPAREN -> {
|
||||||
@ -275,7 +307,6 @@ class Compiler {
|
|||||||
operand = parseFunctionCall(
|
operand = parseFunctionCall(
|
||||||
cc,
|
cc,
|
||||||
left,
|
left,
|
||||||
thisObj = null,
|
|
||||||
)
|
)
|
||||||
} ?: run {
|
} ?: run {
|
||||||
// Expression in parentheses
|
// Expression in parentheses
|
||||||
@ -371,8 +402,20 @@ class Compiler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun parseFunctionCall(cc: CompilerContext, left: Accessor, thisObj: Statement?): Accessor {
|
private fun parseScopeOperator(operand: Accessor?, cc: CompilerContext): Accessor {
|
||||||
// insofar, functions always return lvalue
|
// implement global scope maybe?
|
||||||
|
if( operand == null ) throw ScriptError(cc.next().pos, "Expecting expression before ::")
|
||||||
|
val t = cc.next()
|
||||||
|
if( t.type != Token.Type.ID ) throw ScriptError(t.pos, "Expecting ID after ::")
|
||||||
|
return when(t.value) {
|
||||||
|
"class" -> Accessor {
|
||||||
|
operand.getter(it).objClass
|
||||||
|
}
|
||||||
|
else -> throw ScriptError(t.pos, "Unknown scope operation: ${t.value}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseArgs(cc: CompilerContext): List<Arguments.Info> {
|
||||||
val args = mutableListOf<Arguments.Info>()
|
val args = mutableListOf<Arguments.Info>()
|
||||||
do {
|
do {
|
||||||
val t = cc.next()
|
val t = cc.next()
|
||||||
@ -385,13 +428,19 @@ class Compiler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while (t.type != Token.Type.RPAREN)
|
} while (t.type != Token.Type.RPAREN)
|
||||||
|
return args
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun parseFunctionCall(cc: CompilerContext, left: Accessor): Accessor {
|
||||||
|
// insofar, functions always return lvalue
|
||||||
|
val args = parseArgs(cc)
|
||||||
|
|
||||||
return Accessor { context ->
|
return Accessor { context ->
|
||||||
val v = left.getter(context)
|
val v = left.getter(context)
|
||||||
v.callOn(context.copy(
|
v.callOn(context.copy(
|
||||||
context.pos,
|
context.pos,
|
||||||
Arguments(
|
Arguments(
|
||||||
context.pos,
|
|
||||||
args.map { Arguments.Info((it.value as Statement).execute(context), it.pos) }
|
args.map { Arguments.Info((it.value as Statement).execute(context), it.pos) }
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@ -851,7 +900,7 @@ class Compiler {
|
|||||||
false,
|
false,
|
||||||
d.defaultValue?.execute(context)
|
d.defaultValue?.execute(context)
|
||||||
?: throw ScriptError(
|
?: throw ScriptError(
|
||||||
context.args.callerPos,
|
context.pos,
|
||||||
"missing required argument #${1 + i}: ${d.name}"
|
"missing required argument #${1 + i}: ${d.name}"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -3,11 +3,12 @@ package net.sergeych.ling
|
|||||||
class Context(
|
class Context(
|
||||||
val parent: Context?,
|
val parent: Context?,
|
||||||
val args: Arguments = Arguments.EMPTY,
|
val args: Arguments = Arguments.EMPTY,
|
||||||
var pos: Pos = Pos.builtIn
|
var pos: Pos = Pos.builtIn,
|
||||||
|
val thisObj: Obj = ObjVoid
|
||||||
) {
|
) {
|
||||||
constructor(
|
constructor(
|
||||||
args: Arguments = Arguments.EMPTY,
|
args: Arguments = Arguments.EMPTY,
|
||||||
pos: Pos = Pos.builtIn
|
pos: Pos = Pos.builtIn,
|
||||||
)
|
)
|
||||||
: this(Script.defaultContext, args, pos)
|
: this(Script.defaultContext, args, pos)
|
||||||
|
|
||||||
@ -29,7 +30,8 @@ class Context(
|
|||||||
objects[name]
|
objects[name]
|
||||||
?: parent?.get(name)
|
?: parent?.get(name)
|
||||||
|
|
||||||
fun copy(pos: Pos, args: Arguments = Arguments.EMPTY): Context = Context(this, args, pos)
|
fun copy(pos: Pos, args: Arguments = Arguments.EMPTY,newThisObj: Obj? = null): Context =
|
||||||
|
Context(this, args, pos, newThisObj ?: thisObj)
|
||||||
|
|
||||||
fun addItem(name: String, isMutable: Boolean, value: Obj?) {
|
fun addItem(name: String, isMutable: Boolean, value: Obj?) {
|
||||||
objects.put(name, StoredObj(value, isMutable))
|
objects.put(name, StoredObj(value, isMutable))
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
package net.sergeych.ling
|
||||||
|
|
||||||
|
class IfScope(val isTrue: Boolean) {
|
||||||
|
|
||||||
|
fun otherwise(f: ()->Unit): Boolean {
|
||||||
|
if( !isTrue ) f()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,7 @@ import kotlinx.coroutines.sync.withLock
|
|||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
|
import kotlin.math.roundToLong
|
||||||
|
|
||||||
typealias InstanceMethod = (Context, Obj) -> Obj
|
typealias InstanceMethod = (Context, Obj) -> Obj
|
||||||
|
|
||||||
@ -19,50 +20,36 @@ data class Accessor(
|
|||||||
fun setter(pos: Pos) = setterOrNull ?: throw ScriptError(pos,"can't assign value")
|
fun setter(pos: Pos) = setterOrNull ?: throw ScriptError(pos,"can't assign value")
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class ClassDef(
|
|
||||||
val className: String
|
|
||||||
) {
|
|
||||||
val baseClasses: List<ClassDef> get() = emptyList()
|
|
||||||
protected val instanceMembers: MutableMap<String, WithAccess<Obj>> = mutableMapOf()
|
|
||||||
private val monitor = Mutex()
|
|
||||||
|
|
||||||
|
|
||||||
suspend fun addInstanceMethod(
|
|
||||||
context: Context,
|
|
||||||
name: String,
|
|
||||||
isOpen: Boolean = false,
|
|
||||||
body: Obj
|
|
||||||
) {
|
|
||||||
monitor.withLock {
|
|
||||||
instanceMembers[name]?.let {
|
|
||||||
if (!it.isMutable)
|
|
||||||
context.raiseError("method $name is not open and can't be overridden")
|
|
||||||
it.value = body
|
|
||||||
} ?: instanceMembers.put(name, WithAccess(body, isOpen))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getInstanceMethodOrNull(name: String): Obj? =
|
|
||||||
monitor.withLock { instanceMembers[name]?.value }
|
|
||||||
|
|
||||||
suspend fun getInstanceMethod(context: Context, name: String): Obj =
|
|
||||||
getInstanceMethodOrNull(name) ?: context.raiseError("no method found: $name")
|
|
||||||
|
|
||||||
// suspend fun callInstanceMethod(context: Context, name: String, self: Obj,args: Arguments): Obj {
|
|
||||||
// getInstanceMethod(context, name).invoke(context, self,args)
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
object ObjClassDef : ClassDef("Obj")
|
|
||||||
|
|
||||||
sealed class Obj {
|
sealed class Obj {
|
||||||
open val classDef: ClassDef = ObjClassDef
|
|
||||||
var isFrozen: Boolean = false
|
var isFrozen: Boolean = false
|
||||||
|
|
||||||
protected val instanceMethods: Map<String, WithAccess<InstanceMethod>> = mutableMapOf()
|
|
||||||
private val monitor = Mutex()
|
private val monitor = Mutex()
|
||||||
|
|
||||||
|
// members: fields most often
|
||||||
|
internal val members = mutableMapOf<String, WithAccess<Obj>>()
|
||||||
|
private val parentInstances = listOf<Obj>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get instance member traversing the hierarchy if needed. Its meaning is different for different objects.
|
||||||
|
*/
|
||||||
|
fun getInstanceMemberOrNull(name: String): Obj? {
|
||||||
|
members[name]?.let { return it.value }
|
||||||
|
parentInstances.forEach { parent -> parent.getInstanceMemberOrNull(name)?.let { return it } }
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getInstanceMember(atPos: Pos, name: String): Obj = getInstanceMemberOrNull(name)
|
||||||
|
?: throw ScriptError(atPos,"symbol doesn't exist: $name")
|
||||||
|
|
||||||
|
suspend fun callInstanceMethod(context: Context, name: String,args: Arguments): Obj {
|
||||||
|
// instance _methods_ are our ObjClass instance:
|
||||||
|
// note that getInstanceMember traverses the hierarchy
|
||||||
|
return objClass.getInstanceMember(context.pos,name).invoke(context, this, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// methods that to override
|
||||||
|
|
||||||
open suspend fun compareTo(context: Context, other: Obj): Int {
|
open suspend fun compareTo(context: Context, other: Obj): Int {
|
||||||
context.raiseNotImplemented()
|
context.raiseNotImplemented()
|
||||||
}
|
}
|
||||||
@ -71,7 +58,11 @@ sealed class Obj {
|
|||||||
if (this is ObjString) this else ObjString(this.toString())
|
if (this is ObjString) this else ObjString(this.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
open val definition: ClassDef = ObjClassDef
|
/**
|
||||||
|
* Class of the object: definition of member functions (top-level), etc.
|
||||||
|
* Note that using lazy allows to avoid endless recursion here
|
||||||
|
*/
|
||||||
|
open val objClass: ObjClass by lazy { ObjClass("Obj") }
|
||||||
|
|
||||||
open fun plus(context: Context, other: Obj): Obj {
|
open fun plus(context: Context, other: Obj): Obj {
|
||||||
context.raiseNotImplemented()
|
context.raiseNotImplemented()
|
||||||
@ -106,8 +97,6 @@ sealed class Obj {
|
|||||||
if (isFrozen) context.raiseError("attempt to mutate frozen object")
|
if (isFrozen) context.raiseError("attempt to mutate frozen object")
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getInstanceMember(context: Context, name: String): Obj? = definition.getInstanceMethodOrNull(name)
|
|
||||||
|
|
||||||
suspend fun <T> sync(block: () -> T): T = monitor.withLock { block() }
|
suspend fun <T> sync(block: () -> T): T = monitor.withLock { block() }
|
||||||
|
|
||||||
open suspend fun readField(context: Context, name: String): Obj {
|
open suspend fun readField(context: Context, name: String): Obj {
|
||||||
@ -122,6 +111,13 @@ sealed class Obj {
|
|||||||
context.raiseNotImplemented()
|
context.raiseNotImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun invoke(context: Context, thisObj: Obj,args: Arguments): Obj =
|
||||||
|
callOn(context.copy(context.pos,args = args, newThisObj = thisObj))
|
||||||
|
|
||||||
|
suspend fun invoke(context: Context,atPos: Pos, thisObj: Obj,args: Arguments): Obj =
|
||||||
|
callOn(context.copy(atPos,args = args,newThisObj = thisObj))
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
inline fun <reified T> from(obj: T): Obj {
|
inline fun <reified T> from(obj: T): Obj {
|
||||||
return when (obj) {
|
return when (obj) {
|
||||||
@ -206,8 +202,7 @@ fun Obj.toBool(): Boolean =
|
|||||||
(this as? ObjBool)?.value ?: throw IllegalArgumentException("cannot convert to boolean $this")
|
(this as? ObjBool)?.value ?: throw IllegalArgumentException("cannot convert to boolean $this")
|
||||||
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@SerialName("real")
|
|
||||||
data class ObjReal(val value: Double) : Obj(), Numeric {
|
data class ObjReal(val value: Double) : Obj(), Numeric {
|
||||||
override val asStr by lazy { ObjString(value.toString()) }
|
override val asStr by lazy { ObjString(value.toString()) }
|
||||||
override val longValue: Long by lazy { floor(value).toLong() }
|
override val longValue: Long by lazy { floor(value).toLong() }
|
||||||
@ -221,10 +216,21 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String = value.toString()
|
override fun toString(): String = value.toString()
|
||||||
|
|
||||||
|
override val objClass: ObjClass = type
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val type: ObjClass = ObjClass("Real").apply {
|
||||||
|
members["roundToInt"] = WithAccess(
|
||||||
|
statement(Pos.builtIn) {
|
||||||
|
(it.thisObj as ObjReal).value.roundToLong().toObj()
|
||||||
|
},
|
||||||
|
false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@SerialName("int")
|
|
||||||
data class ObjInt(var value: Long) : Obj(), Numeric {
|
data class ObjInt(var value: Long) : Obj(), Numeric {
|
||||||
override val asStr get() = ObjString(value.toString())
|
override val asStr get() = ObjString(value.toString())
|
||||||
override val longValue get() = value
|
override val longValue get() = value
|
||||||
@ -285,14 +291,3 @@ open class ObjError(val context: Context, val message: String) : Obj() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ObjNullPointerError(context: Context) : ObjError(context, "object is null")
|
class ObjNullPointerError(context: Context) : ObjError(context, "object is null")
|
||||||
|
|
||||||
class ObjClass(override val definition: ClassDef) : Obj() {
|
|
||||||
|
|
||||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
|
||||||
// definition.callInstanceMethod(":compareTo", context, other)?.let {
|
|
||||||
// it(context, this)
|
|
||||||
// }
|
|
||||||
TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
20
library/src/commonMain/kotlin/net/sergeych/ling/ObjClass.kt
Normal file
20
library/src/commonMain/kotlin/net/sergeych/ling/ObjClass.kt
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package net.sergeych.ling
|
||||||
|
|
||||||
|
val ObjClassType by lazy { ObjClass("Class") }
|
||||||
|
|
||||||
|
class ObjClass(
|
||||||
|
val className: String
|
||||||
|
): Obj() {
|
||||||
|
|
||||||
|
override val objClass: ObjClass by lazy { ObjClassType }
|
||||||
|
|
||||||
|
override fun toString(): String = className
|
||||||
|
|
||||||
|
override suspend fun compareTo(context: Context, other: Obj): Int = if( other === this ) 0 else -1
|
||||||
|
|
||||||
|
// val parents: List<ObjClass> get() = emptyList()
|
||||||
|
|
||||||
|
// suspend fun callInstanceMethod(context: Context, name: String, self: Obj,args: Arguments): Obj {
|
||||||
|
// getInstanceMethod(context, name).invoke(context, self,args)
|
||||||
|
// }
|
||||||
|
}
|
@ -133,6 +133,15 @@ private class Parser(fromPos: Pos) {
|
|||||||
}
|
}
|
||||||
'\n' -> Token("\n", from, Token.Type.NEWLINE)
|
'\n' -> Token("\n", from, Token.Type.NEWLINE)
|
||||||
|
|
||||||
|
':' -> {
|
||||||
|
if( currentChar == ':') {
|
||||||
|
advance()
|
||||||
|
Token("::", from, Token.Type.COLONCOLON)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
Token(":", from, Token.Type.COLON)
|
||||||
|
}
|
||||||
|
|
||||||
'"' -> loadStringToken()
|
'"' -> loadStringToken()
|
||||||
in digitsSet -> {
|
in digitsSet -> {
|
||||||
pos.back()
|
pos.back()
|
||||||
|
@ -47,6 +47,8 @@ class Script(
|
|||||||
sin(args.firstAndOnly().toDouble())
|
sin(args.firstAndOnly().toDouble())
|
||||||
}
|
}
|
||||||
val pi = ObjReal(PI)
|
val pi = ObjReal(PI)
|
||||||
|
val z = pi.objClass
|
||||||
|
println("PI class $z")
|
||||||
addConst(pi, "π")
|
addConst(pi, "π")
|
||||||
getOrCreateNamespace("Math").also { ns ->
|
getOrCreateNamespace("Math").also { ns ->
|
||||||
ns.addConst(pi, "PI")
|
ns.addConst(pi, "PI")
|
||||||
|
@ -564,4 +564,14 @@ class ScriptTest {
|
|||||||
eval(src)
|
eval(src)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testCallable1() = runTest {
|
||||||
|
val src = """
|
||||||
|
val callable = {
|
||||||
|
println("called")
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
println(eval(src).toString())
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -187,4 +187,9 @@ class BookTest {
|
|||||||
fun testsFromAdvanced() = runTest {
|
fun testsFromAdvanced() = runTest {
|
||||||
runDocTests("../docs/advanced_topics.md")
|
runDocTests("../docs/advanced_topics.md")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testsFromOOPrinciples() = runTest {
|
||||||
|
runDocTests("../docs/OOP.md")
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user