extension methids
This commit is contained in:
parent
a8067d0a6b
commit
eee6d75587
34
docs/OOP.md
34
docs/OOP.md
@ -160,6 +160,40 @@ For example, for our class Point:
|
||||
Point(1,1+1)
|
||||
>>> Point(x=1,y=2)
|
||||
|
||||
# Extending classes
|
||||
|
||||
It sometimes happen that the class is missing some particular functionality that can be _added to it_ without rewriting its inner logic and using its private state. In this case _extension methods_ could be used, for example. we want to create an extension method
|
||||
that would test if some object of unknown type contains something that can be interpreted
|
||||
as an integer. In this case we _extend_ class `Object`, as it is the parent class for any instance of any type:
|
||||
|
||||
fun Object.isInteger() {
|
||||
when(this) {
|
||||
// already Int?
|
||||
is Int -> true
|
||||
|
||||
// real, but with no declimal part?
|
||||
is Real -> toInt() == this
|
||||
|
||||
// string with int or real reuusig code above
|
||||
is String -> toReal().isInteger()
|
||||
|
||||
// otherwise, no:
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
// Let's test:
|
||||
assert( 12.isInteger() == true )
|
||||
assert( 12.1.isInteger() == false )
|
||||
assert( "5".isInteger() )
|
||||
assert( ! "5.2".isInteger() )
|
||||
>>> void
|
||||
|
||||
__Important note__ as for version 0.6.9, extensions are in __global scope__. It means, that once applied to a global type (Int in our sample), they will be available for _all_ contexts, even new created,
|
||||
as they are modifying the type, not the context.
|
||||
|
||||
Beware of it. We might need to reconsider it later.
|
||||
|
||||
# Theory
|
||||
|
||||
## Basic principles:
|
||||
|
16
docs/Real.md
16
docs/Real.md
@ -15,11 +15,11 @@ you can use it's class to ensure type:
|
||||
|
||||
## Member functions
|
||||
|
||||
| name | meaning | type |
|
||||
|-----------------|------------------------------------|------|
|
||||
| `.roundToInt()` | round to nearest int like round(x) | Int |
|
||||
| | | |
|
||||
| | | |
|
||||
| | | |
|
||||
| | | |
|
||||
| | | |
|
||||
| name | meaning | type |
|
||||
|-----------------|-------------------------------------------------------------|------|
|
||||
| `.roundToInt()` | round to nearest int like round(x) | Int |
|
||||
| `.toInt()` | convert integer part of real to `Int` dropping decimal part | Int |
|
||||
| | | |
|
||||
| | | |
|
||||
| | | |
|
||||
| | | |
|
||||
|
@ -1214,6 +1214,9 @@ Typical set of String functions includes:
|
||||
| [Range] | substring at range |
|
||||
| s1 + s2 | concatenation |
|
||||
| s1 += s2 | self-modifying concatenation |
|
||||
| toReal() | attempts to parse string as a Real value |
|
||||
| | |
|
||||
| | |
|
||||
|
||||
|
||||
|
||||
|
@ -4,7 +4,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
group = "net.sergeych"
|
||||
version = "0.6.8-SNAPSHOT"
|
||||
version = "0.6.9-SNAPSHOT"
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
|
@ -1432,11 +1432,22 @@ class Compiler(
|
||||
): Statement {
|
||||
var t = cc.next()
|
||||
val start = t.pos
|
||||
val name = if (t.type != Token.Type.ID)
|
||||
throw ScriptError(t.pos, "Expected identifier after 'fn'")
|
||||
var extTypeName: String? = null
|
||||
var name = if (t.type != Token.Type.ID)
|
||||
throw ScriptError(t.pos, "Expected identifier after 'fun'")
|
||||
else t.value
|
||||
|
||||
t = cc.next()
|
||||
// Is extension?
|
||||
if( t.type == Token.Type.DOT) {
|
||||
extTypeName = name
|
||||
t = cc.next()
|
||||
if( t.type != Token.Type.ID)
|
||||
throw ScriptError(t.pos, "illegal extension format: expected function name")
|
||||
name = t.value
|
||||
t = cc.next()
|
||||
}
|
||||
|
||||
if (t.type != Token.Type.LPAREN)
|
||||
throw ScriptError(t.pos, "Bad function definition: expected '(' after 'fn ${name}'")
|
||||
|
||||
@ -1465,13 +1476,25 @@ class Compiler(
|
||||
|
||||
// load params from caller context
|
||||
argsDeclaration.assignToContext(context, callerContext.args, defaultAccessType = AccessType.Val)
|
||||
if( extTypeName != null ) {
|
||||
context.thisObj = callerContext.thisObj
|
||||
}
|
||||
fnStatements.execute(context)
|
||||
}
|
||||
return statement(start) { context ->
|
||||
// we added fn in the context. now we must save closure
|
||||
// for the function
|
||||
closure = context
|
||||
context.addItem(name, false, fnBody, visibility)
|
||||
extTypeName?.let { typeName ->
|
||||
// class extension method
|
||||
val type = context.get(typeName)?.value ?: context.raiseSymbolNotFound("class $typeName not found")
|
||||
if( type !is ObjClass ) context.raiseClassCastError("$typeName is not the class instance")
|
||||
type.addFn( name, isOpen = true) {
|
||||
fnBody.execute(this)
|
||||
}
|
||||
}
|
||||
// regular function/method
|
||||
?: context.addItem(name, false, fnBody, visibility)
|
||||
// as the function can be called from anywhere, we have
|
||||
// saved the proper context in the closure
|
||||
fnBody
|
||||
|
@ -72,6 +72,7 @@ open class Context(
|
||||
else {
|
||||
objects[name]
|
||||
?: parent?.get(name)
|
||||
?: thisObj.objClass.getInstanceMemberOrNull(name)
|
||||
}
|
||||
|
||||
fun copy(pos: Pos, args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Context =
|
||||
|
@ -64,6 +64,9 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
|
||||
(it.thisObj as ObjReal).value.roundToLong().toObj()
|
||||
},
|
||||
)
|
||||
addFn("toInt") {
|
||||
ObjInt(thisAs<ObjReal>().value.toLong())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -112,6 +112,7 @@ data class ObjString(val value: String) : Obj() {
|
||||
thisAs<ObjString>().value.uppercase().let(::ObjString)
|
||||
}
|
||||
addFn("size") { ObjInt(thisAs<ObjString>().value.length.toLong()) }
|
||||
addFn("toReal") { ObjReal(thisAs<ObjString>().value.toDouble())}
|
||||
}
|
||||
}
|
||||
}
|
@ -2284,4 +2284,31 @@ class ScriptTest {
|
||||
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testExtend() = runTest() {
|
||||
eval("""
|
||||
|
||||
fun Int.isEven() {
|
||||
this % 2 == 0
|
||||
}
|
||||
|
||||
fun Object.isInteger() {
|
||||
when(this) {
|
||||
is Int -> true
|
||||
is Real -> toInt() == this
|
||||
is String -> toReal().isInteger()
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
assert( 4.isEven() )
|
||||
assert( !5.isEven() )
|
||||
|
||||
assert( 12.isInteger() == true )
|
||||
assert( 12.1.isInteger() == false )
|
||||
assert( "5".isInteger() )
|
||||
assert( ! "5.2".isInteger() )
|
||||
""".trimIndent())
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user