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] multiline strings
|
||||||
- [x] typesafe bit-effective serialization
|
- [x] typesafe bit-effective serialization
|
||||||
- [x] compression/decompression (integrated in serialization)
|
- [x] compression/decompression (integrated in serialization)
|
||||||
-
|
- [x] dynamic fields
|
||||||
Under way:
|
|
||||||
|
### Under way:
|
||||||
|
|
||||||
- [ ] regular exceptions
|
- [ ] regular exceptions
|
||||||
- [ ] multiple inheritance for user classes
|
- [ ] 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.
|
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
|
# Theory
|
||||||
|
|
||||||
## Basic principles:
|
## Basic principles:
|
||||||
|
@ -170,6 +170,11 @@ class Script(
|
|||||||
}
|
}
|
||||||
result ?: raiseError(ObjAssertionFailedException(this,"Expected exception but nothing was thrown"))
|
result ?: raiseError(ObjAssertionFailedException(this,"Expected exception but nothing was thrown"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addFn("dynamic") {
|
||||||
|
ObjDynamic.create(this, requireOnlyArg())
|
||||||
|
}
|
||||||
|
|
||||||
addFn("require") {
|
addFn("require") {
|
||||||
val condition = requiredArg<ObjBool>(0)
|
val condition = requiredArg<ObjBool>(0)
|
||||||
if( !condition.value ) {
|
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())
|
""".trimIndent())
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Test
|
@Test
|
||||||
fun testDynamic() = runTest {
|
fun testDynamicGet() = runTest {
|
||||||
eval("""
|
eval("""
|
||||||
println("0")
|
val accessor = dynamic {
|
||||||
class DynamicTest : Dynamic {
|
get { name ->
|
||||||
|
if( name == "foo" ) "bar" else null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getDynamic(name) {
|
println("-- " + accessor.foo)
|
||||||
if (name == "foo") "bar" else null
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
println("1")
|
set { name, value ->
|
||||||
val d = DynamicTest()
|
if( name == "bar" )
|
||||||
println(d)
|
setValueForBar = value
|
||||||
println("2")
|
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())
|
""".trimIndent())
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user