diff --git a/docs/Buffer.md b/docs/Buffer.md index d236211..fdad4bf 100644 --- a/docs/Buffer.md +++ b/docs/Buffer.md @@ -120,16 +120,17 @@ which is used in `toString`) and hex encoding: ## Members -| name | meaning | type | -|---------------------------|-----------------------------------|---------------| -| `size` | size | Int | -| `decodeUtf8` | decode to String using UTF8 rules | Any | -| `+` | buffer concatenation | Any | -| `toMutable()` | create a mutable copy | MutableBuffer | -| `hex` | encode to hex strign | String | -| `Buffer.decodeHex(hexStr) | decode hex string | Buffer | -| `base64` | encode to base64 (url flavor) (2) | String | -| `Buffer.decodeBase64` | decode base64 to new Buffer (2) | Buffer | +| name | meaning | type | +|----------------------------|-----------------------------------------|---------------| +| `size` | size | Int | +| `decodeUtf8` | decode to String using UTF8 rules | Any | +| `+` | buffer concatenation | Any | +| `toMutable()` | create a mutable copy | MutableBuffer | +| `hex` | encode to hex strign | String | +| `Buffer.decodeHex(hexStr) | decode hex string | Buffer | +| `base64` | encode to base64 (url flavor) (2) | String | +| `Buffer.decodeBase64(str)` | decode base64 to new Buffer (2) | Buffer | +| `toBitInput()` | create bit input from a byte buffer (3) | | (1) : optimized implementation that override `Iterable` one @@ -139,6 +140,9 @@ which is used in `toString`) and hex encoding: decoding supports both traditional and URL alphabets automatically, and ignores filling `=` characters. Base64URL is well known and mentioned in the internet, for example, [here](https://base64.guru/standards/base64url). +(3) +: `BitInput` is a bit buffer that is used, for example, in [Lynon.decode](serialization.md) + Also, it inherits methods from [Iterable] and [Array]. diff --git a/docs/serialization.md b/docs/serialization.md new file mode 100644 index 0000000..dd192ca --- /dev/null +++ b/docs/serialization.md @@ -0,0 +1,46 @@ +# Lyng serialization + +Lyng has builting binary bit-effective serialization format, called Lynon for LYng Object Notation. It is typed, binary, implements caching, automatic compression, variable-length ints, one-bit Booleans an many nice features. + +It is as simple as: + + import lyng.serialization + + val text = " + We hold these truths to be self-evident, that all men are created equal, + that they are endowed by their Creator with certain unalienable Rights, + that among these are Life, Liberty and the pursuit of Happiness. + " + val encodedBits = Lynon.encode(text) + + // decode bits source: + assertEquals( text, Lynon.decode(encodedBits) ) + + // compression was used automatically + assert( text.length > encodedBits.toBuffer().size ) + >>> void + +Any class you create is serializable by default; lynon serializes first constructor fields, then any `var` member fields: + + import lyng.serialization + + class Point(x,y) + + val p = Lynon.decode( Lynon.encode( Point(5,6) ) ) + + assertEquals( 5, p.x ) + assertEquals( 6, p.y ) + >>> void + + +just as expected. + +Important is to understand that normally `Lynon.decode` wants [BitBuffer], as `Lynon.encode` produces. If you have the regular [Buffer], be sure to convert it: + + buffer.toBitInput() + +this possibly creates extra zero bits at the end, as bit content could be shorter than byte-grained but for the Lynon format it does not make sense. Note that when you serialize [BitBuffer], exact number of bits is written. To convert bit buffer to bytes: + + Lynon.encode("hello").toBuffer() + +(topic is incomplete and under construction) diff --git a/lynglib/build.gradle.kts b/lynglib/build.gradle.kts index 29e9e2f..1d47702 100644 --- a/lynglib/build.gradle.kts +++ b/lynglib/build.gradle.kts @@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.dsl.JvmTarget group = "net.sergeych" -version = "0.8.12-SNAPSHOT" +version = "0.8.14-SNAPSHOT" buildscript { repositories { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt index 6fac847..a8795e9 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt @@ -85,13 +85,15 @@ open class Obj { name: String, args: Arguments = Arguments.EMPTY, onNotFoundResult: Obj?=null - ): Obj = - objClass.getInstanceMemberOrNull(name)?.value?.invoke( + ): Obj { + return objClass.getInstanceMemberOrNull(name)?.value?.invoke( scope, this, - args) + args + ) ?: onNotFoundResult ?: scope.raiseSymbolNotFound(name) + } open suspend fun getInstanceMethod( scope: Scope, diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBuffer.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBuffer.kt index 227cc80..ffa37fc 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBuffer.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBuffer.kt @@ -24,6 +24,7 @@ import net.sergeych.bintools.encodeToHex import net.sergeych.bintools.toDump import net.sergeych.lyng.Scope import net.sergeych.lyng.statement +import net.sergeych.lynon.BitArray import net.sergeych.lynon.LynonDecoder import net.sergeych.lynon.LynonEncoder import net.sergeych.lynon.LynonType @@ -200,6 +201,9 @@ open class ObjBuffer(val byteArray: UByteArray) : Obj() { thisAs().byteArray.toByteArray().toDump() ) } + addFn("toBitInput") { + ObjBitBuffer(BitArray(thisAs().byteArray, 8)) + } } } } \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInt.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInt.kt index 2306860..2664de6 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInt.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInt.kt @@ -62,7 +62,7 @@ class ObjInt(var value: Long, override val isConst: Boolean = false) : Obj(), Nu override fun toString(): String = value.toString() - override val objClass: ObjClass = type + override val objClass: ObjClass by lazy { type } override suspend fun plus(scope: Scope, other: Obj): Obj = if (other is ObjInt) diff --git a/lynglib/src/jvmTest/kotlin/BookTest.kt b/lynglib/src/jvmTest/kotlin/BookTest.kt index 333c1bf..9eb991c 100644 --- a/lynglib/src/jvmTest/kotlin/BookTest.kt +++ b/lynglib/src/jvmTest/kotlin/BookTest.kt @@ -322,4 +322,9 @@ class BookTest { runDocTests("../docs/Iterable.md") } + @Test + fun testSerialization() = runBlocking { + runDocTests("../docs/serialization.md") + } + } \ No newline at end of file diff --git a/lynglib/src/jvmTest/kotlin/LynonTests.kt b/lynglib/src/jvmTest/kotlin/LynonTests.kt index 2e6dc7e..ae5f6c8 100644 --- a/lynglib/src/jvmTest/kotlin/LynonTests.kt +++ b/lynglib/src/jvmTest/kotlin/LynonTests.kt @@ -649,6 +649,48 @@ class LynonTests { """.trimIndent()) } + @Test + fun testTokenFailure() = runTest { + val s = Scope() + val t = s.eval( + """ + import lyng.serialization + import lyng.buffer + import lyng.time + +class Wallet( id, ownerKey, balance=0, createdAt=Instant.now().truncateToSecond(), isBlocked=false) { + + /* + Create new key for the given owner multikey + */ + static fun new( ownerKey ) { + + // this is per-contract unique id generated and approved by the network. + // it is not random, but it _is_ unique, and we use it as a walletId. + val newId = Buffer("testid") + + val w = Wallet(newId, ownerKey) + println(w) + println(w.balance) + + val x = Lynon.encode(Wallet(newId, ownerKey) ).toBuffer() + val t = Lynon.decode(x.toBitInput()) + println(x) + println(t) + assertEquals(w.balance, t.balance) + w + } + +} + Wallet.new(Buffer("1234")) + + """.trimIndent()) + println(t) +// val bb = lynonEncodeAny(s, t) +// val t2 = lynonDecodeAny(s, bb) +// println(t2.readField(s, "balance")) + } + }