added "dynamic" fields (get/set/call fields by name using dynamic standard function

This commit is contained in:
Sergey Chernov 2025-08-12 00:15:45 +03:00
parent e916d9805a
commit 6d8eed7b8c
5 changed files with 170 additions and 13 deletions

View File

@ -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

View File

@ -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:

View File

@ -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 ) {

View File

@ -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") {}
}
}

View File

@ -44,20 +44,49 @@ class OOTest {
""".trimIndent())
}
// @Test
fun testDynamic() = runTest {
@Test
fun testDynamicGet() = runTest {
eval("""
println("0")
class DynamicTest : Dynamic {
fun getDynamic(name) {
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())
}
}