added "dynamic" fields (get/set/call fields by name using dynamic standard function
				
					
				
			This commit is contained in:
		
							parent
							
								
									e916d9805a
								
							
						
					
					
						commit
						6d8eed7b8c
					
				@ -156,8 +156,9 @@ Ready features:
 | 
			
		||||
- [x] multiline strings
 | 
			
		||||
- [x] typesafe bit-effective serialization
 | 
			
		||||
- [x] compression/decompression (integrated in serialization)
 | 
			
		||||
- 
 | 
			
		||||
  Under way: 
 | 
			
		||||
- [x] dynamic fields
 | 
			
		||||
 | 
			
		||||
###  Under way: 
 | 
			
		||||
 | 
			
		||||
- [ ] regular exceptions
 | 
			
		||||
- [ ] multiple inheritance for user classes
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										57
									
								
								docs/OOP.md
									
									
									
									
									
								
							
							
						
						
									
										57
									
								
								docs/OOP.md
									
									
									
									
									
								
							@ -235,6 +235,63 @@ as they are modifying the type, not the context.
 | 
			
		||||
 | 
			
		||||
Beware of it. We might need to reconsider it later.
 | 
			
		||||
 | 
			
		||||
## dynamic symbols
 | 
			
		||||
 | 
			
		||||
Sometimes it is convenient to provide methods and variables whose names are not known at compile time. For example, it could be external interfaces not known to library code, user-defined data fields, etc. You can use `dynamic` function to create such:
 | 
			
		||||
 | 
			
		||||
    // val only dynamic object
 | 
			
		||||
    val accessor = dynamic {
 | 
			
		||||
        // all symbol reads are redirected here:
 | 
			
		||||
        get { name ->
 | 
			
		||||
            // lets provide one dynamic symbol:
 | 
			
		||||
            if( name == "foo" ) "bar" else null
 | 
			
		||||
            // consider also throw SymbolNotDefinedException
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // now we can access dynamic "fields" of accessor:
 | 
			
		||||
    assertEquals("bar", accessor.foo)
 | 
			
		||||
    assertEquals(null, accessor.bar)
 | 
			
		||||
    >>> void
 | 
			
		||||
 | 
			
		||||
The same we can provide writable dynamic fields (var-type), adding set method:
 | 
			
		||||
 | 
			
		||||
    // store one dynamic field here
 | 
			
		||||
    var storedValueForBar = null
 | 
			
		||||
    
 | 
			
		||||
    // create dynamic object with 2 fields:
 | 
			
		||||
    val accessor = dynamic {
 | 
			
		||||
        get { name ->
 | 
			
		||||
            when(name) {
 | 
			
		||||
                // constant field
 | 
			
		||||
                "foo" -> "bar"
 | 
			
		||||
                // mutable field
 | 
			
		||||
                "bar" -> setValueForBar 
 | 
			
		||||
 | 
			
		||||
                else -> throw SymbolNotFoundException()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        set { name, value ->
 | 
			
		||||
            // only 'bar' is mutable:
 | 
			
		||||
            if( name == "bar" )
 | 
			
		||||
                storedValueForBar = value
 | 
			
		||||
            // the rest is immotable. consider throw also
 | 
			
		||||
            // SymbolNotFoundException when needed.
 | 
			
		||||
            else throw IllegalAssignmentException("Can't assign "+name)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    assertEquals("bar", accessor.foo)
 | 
			
		||||
    assertEquals(null, accessor.bar)
 | 
			
		||||
    accessor.bar = "buzz"
 | 
			
		||||
    assertEquals("buzz", accessor.bar)
 | 
			
		||||
    
 | 
			
		||||
    assertThrows {
 | 
			
		||||
        accessor.bad = "!23"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
Of course, you can return any object from dynamic fields; returning lambdas let create _dynamic methods_ - the callable method. It is very convenient to implement libraries with dynamic remote interfaces, etc.
 | 
			
		||||
 | 
			
		||||
# Theory
 | 
			
		||||
 | 
			
		||||
## Basic principles:
 | 
			
		||||
 | 
			
		||||
@ -170,6 +170,11 @@ class Script(
 | 
			
		||||
                }
 | 
			
		||||
                result ?: raiseError(ObjAssertionFailedException(this,"Expected exception but nothing was thrown"))
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            addFn("dynamic") {
 | 
			
		||||
                ObjDynamic.create(this, requireOnlyArg())
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            addFn("require") {
 | 
			
		||||
                val condition = requiredArg<ObjBool>(0)
 | 
			
		||||
                if( !condition.value ) {
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,65 @@
 | 
			
		||||
package net.sergeych.lyng.obj
 | 
			
		||||
 | 
			
		||||
import net.sergeych.lyng.Arguments
 | 
			
		||||
import net.sergeych.lyng.Scope
 | 
			
		||||
import net.sergeych.lyng.Statement
 | 
			
		||||
 | 
			
		||||
class ObjDynamicContext(val delegate: ObjDynamic) : Obj() {
 | 
			
		||||
    override val objClass: ObjClass = type
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        val type = ObjClass("DelegateContext").apply {
 | 
			
		||||
            addFn("get") {
 | 
			
		||||
                val d = thisAs<ObjDynamicContext>().delegate
 | 
			
		||||
                if (d.readCallback != null)
 | 
			
		||||
                    raiseIllegalState("get already defined")
 | 
			
		||||
                d.readCallback = requireOnlyArg()
 | 
			
		||||
                ObjVoid
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            addFn("set") {
 | 
			
		||||
                val d = thisAs<ObjDynamicContext>().delegate
 | 
			
		||||
                if (d.writeCallback != null)
 | 
			
		||||
                    raiseIllegalState("set already defined")
 | 
			
		||||
                d.writeCallback = requireOnlyArg()
 | 
			
		||||
                ObjVoid
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ObjDynamic : Obj() {
 | 
			
		||||
 | 
			
		||||
    internal var readCallback: Statement? = null
 | 
			
		||||
    internal var writeCallback: Statement? = null
 | 
			
		||||
 | 
			
		||||
    override suspend fun readField(scope: Scope, name: String): ObjRecord {
 | 
			
		||||
        return readCallback?.execute(scope.copy(Arguments(ObjString(name))))?.let {
 | 
			
		||||
            if (writeCallback != null)
 | 
			
		||||
                it.asMutable
 | 
			
		||||
            else
 | 
			
		||||
                it.asReadonly
 | 
			
		||||
        }
 | 
			
		||||
            ?: super.readField(scope, name)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun writeField(scope: Scope, name: String, newValue: Obj) {
 | 
			
		||||
        writeCallback?.execute(scope.copy(Arguments(ObjString(name), newValue)))
 | 
			
		||||
            ?: super.writeField(scope, name, newValue)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
 | 
			
		||||
        suspend fun create(scope: Scope, builder: Statement): ObjDynamic {
 | 
			
		||||
            val delegate = ObjDynamic()
 | 
			
		||||
            val context = ObjDynamicContext(delegate)
 | 
			
		||||
            builder.execute(scope.copy(newThisObj = context))
 | 
			
		||||
            return delegate
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val type = object : ObjClass("Delegate") {}
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -44,20 +44,49 @@ class OOTest {
 | 
			
		||||
        """.trimIndent())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
//    @Test
 | 
			
		||||
    fun testDynamic() = runTest {
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testDynamicGet() = runTest {
 | 
			
		||||
        eval("""
 | 
			
		||||
            println("0")
 | 
			
		||||
            class DynamicTest : Dynamic {
 | 
			
		||||
            
 | 
			
		||||
                fun getDynamic(name) {
 | 
			
		||||
                    if (name == "foo") "bar" else null
 | 
			
		||||
            val accessor = dynamic {
 | 
			
		||||
                get { name ->
 | 
			
		||||
                    if( name == "foo" ) "bar" else null
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            println("1")
 | 
			
		||||
            val d = DynamicTest()
 | 
			
		||||
            println(d)
 | 
			
		||||
            println("2")
 | 
			
		||||
           
 | 
			
		||||
            println("-- " + accessor.foo)
 | 
			
		||||
            assertEquals("bar", accessor.foo)
 | 
			
		||||
            assertEquals(null, accessor.bar)
 | 
			
		||||
            
 | 
			
		||||
        """.trimIndent())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testDelegateSet() = runTest {
 | 
			
		||||
        eval("""
 | 
			
		||||
            var setValueForBar = null
 | 
			
		||||
            val accessor = dynamic {
 | 
			
		||||
                get { name ->
 | 
			
		||||
                    when(name) {
 | 
			
		||||
                        "foo" -> "bar"
 | 
			
		||||
                        "bar" -> setValueForBar 
 | 
			
		||||
                        else -> null
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                set { name, value ->
 | 
			
		||||
                    if( name == "bar" )
 | 
			
		||||
                        setValueForBar = value
 | 
			
		||||
                    else throw IllegalAssignmentException("Can't assign "+name)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
           
 | 
			
		||||
            assertEquals("bar", accessor.foo)
 | 
			
		||||
            assertEquals(null, accessor.bar)
 | 
			
		||||
            accessor.bar = "buzz"
 | 
			
		||||
            assertEquals("buzz", accessor.bar)
 | 
			
		||||
            
 | 
			
		||||
            assertThrows {
 | 
			
		||||
                accessor.bad = "!23"
 | 
			
		||||
            }
 | 
			
		||||
        """.trimIndent())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user