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(1,1+1)
 | 
				
			||||||
    >>> Point(x=1,y=2)
 | 
					    >>> 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
 | 
					# Theory
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Basic principles:
 | 
					## Basic principles:
 | 
				
			||||||
 | 
				
			|||||||
@ -16,9 +16,9 @@ you can use it's class to ensure type:
 | 
				
			|||||||
## Member functions
 | 
					## Member functions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| name            | meaning                                                     | type |
 | 
					| name            | meaning                                                     | type |
 | 
				
			||||||
|-----------------|------------------------------------|------|
 | 
					|-----------------|-------------------------------------------------------------|------|
 | 
				
			||||||
| `.roundToInt()` | round to nearest int like round(x)                          | Int  |
 | 
					| `.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                                         |
 | 
					| [Range]            | substring at range                                         |
 | 
				
			||||||
| s1 + s2            | concatenation                                              |
 | 
					| s1 + s2            | concatenation                                              |
 | 
				
			||||||
| s1 += s2           | self-modifying 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
 | 
					import org.jetbrains.kotlin.gradle.dsl.JvmTarget
 | 
				
			||||||
 | 
					
 | 
				
			||||||
group = "net.sergeych"
 | 
					group = "net.sergeych"
 | 
				
			||||||
version = "0.6.8-SNAPSHOT"
 | 
					version = "0.6.9-SNAPSHOT"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
buildscript {
 | 
					buildscript {
 | 
				
			||||||
    repositories {
 | 
					    repositories {
 | 
				
			||||||
 | 
				
			|||||||
@ -1432,11 +1432,22 @@ class Compiler(
 | 
				
			|||||||
    ): Statement {
 | 
					    ): Statement {
 | 
				
			||||||
        var t = cc.next()
 | 
					        var t = cc.next()
 | 
				
			||||||
        val start = t.pos
 | 
					        val start = t.pos
 | 
				
			||||||
        val name = if (t.type != Token.Type.ID)
 | 
					        var extTypeName: String? = null
 | 
				
			||||||
            throw ScriptError(t.pos, "Expected identifier after 'fn'")
 | 
					        var name = if (t.type != Token.Type.ID)
 | 
				
			||||||
 | 
					            throw ScriptError(t.pos, "Expected identifier after 'fun'")
 | 
				
			||||||
        else t.value
 | 
					        else t.value
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        t = cc.next()
 | 
					        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)
 | 
					        if (t.type != Token.Type.LPAREN)
 | 
				
			||||||
            throw ScriptError(t.pos, "Bad function definition: expected '(' after 'fn ${name}'")
 | 
					            throw ScriptError(t.pos, "Bad function definition: expected '(' after 'fn ${name}'")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1465,13 +1476,25 @@ class Compiler(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            // load params from caller context
 | 
					            // load params from caller context
 | 
				
			||||||
            argsDeclaration.assignToContext(context, callerContext.args, defaultAccessType = AccessType.Val)
 | 
					            argsDeclaration.assignToContext(context, callerContext.args, defaultAccessType = AccessType.Val)
 | 
				
			||||||
 | 
					            if( extTypeName != null ) {
 | 
				
			||||||
 | 
					                context.thisObj = callerContext.thisObj
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            fnStatements.execute(context)
 | 
					            fnStatements.execute(context)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return statement(start) { context ->
 | 
					        return statement(start) { context ->
 | 
				
			||||||
            // 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, 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
 | 
					            // 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
 | 
				
			||||||
 | 
				
			|||||||
@ -72,6 +72,7 @@ open class Context(
 | 
				
			|||||||
        else {
 | 
					        else {
 | 
				
			||||||
            objects[name]
 | 
					            objects[name]
 | 
				
			||||||
                ?: parent?.get(name)
 | 
					                ?: parent?.get(name)
 | 
				
			||||||
 | 
					                ?: thisObj.objClass.getInstanceMemberOrNull(name)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun copy(pos: Pos, args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Context =
 | 
					    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()
 | 
					                    (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)
 | 
					                thisAs<ObjString>().value.uppercase().let(::ObjString)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            addFn("size") { ObjInt(thisAs<ObjString>().value.length.toLong()) }
 | 
					            addFn("size") { ObjInt(thisAs<ObjString>().value.length.toLong()) }
 | 
				
			||||||
 | 
					            addFn("toReal") { ObjReal(thisAs<ObjString>().value.toDouble())}
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -2284,4 +2284,31 @@ class ScriptTest {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        """.trimIndent())
 | 
					        """.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