From eefecae7b4442ec6bbc9c1136725dcee8462c86b Mon Sep 17 00:00:00 2001 From: sergeych Date: Sun, 17 Aug 2025 19:26:15 +0300 Subject: [PATCH] more Buffer encodings --- docs/Buffer.md | 38 ++++++++++++++++--- lynglib/build.gradle.kts | 2 +- .../kotlin/net/sergeych/lyng/obj/Obj.kt | 3 ++ .../kotlin/net/sergeych/lyng/obj/ObjBuffer.kt | 28 ++++++++++++-- .../lyng/stdlib_included/root_lyng.kt | 6 +-- lynglib/src/commonTest/kotlin/ScriptTest.kt | 20 ++++++++++ 6 files changed, 83 insertions(+), 14 deletions(-) diff --git a/docs/Buffer.md b/docs/Buffer.md index 8c755c5..d236211 100644 --- a/docs/Buffer.md +++ b/docs/Buffer.md @@ -102,20 +102,46 @@ As with [List], it is possible to use ranges as indexes to slice a Buffer: >>> void +## Encoding + +You can encode `String` to buffer using buffer constructor, as was shown. Also, buffer supports out of the box base64 ( +which is used in `toString`) and hex encoding: + + import lyng.buffer + + // to UTF8 and back: + val b = Buffer("hello") + assertEquals( "hello", b.decodeUtf8() ) + + // to base64 and back: + assertEquals( b, Buffer.decodeBase64(b.base64) ) + assertEquals( b, Buffer.decodeHex(b.hex) ) + >>> void + ## Members -| name | meaning | type | -|---------------|------------------------------------|---------------| -| `size` | size | Int | -| `decodeUtf8` | decodee to String using UTF8 rules | Any | -| `+` | buffer concatenation | Any | -| `toMutable()` | create a mutable copy | MutableBuffer | +| 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 | (1) : optimized implementation that override `Iterable` one +(2) +: base64url alphabet is used without trailing '=', which allows string to be used in URI without escaping. Note that +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). + Also, it inherits methods from [Iterable] and [Array]. [Range]: Range.md + [Iterable]: Iterable.md \ No newline at end of file diff --git a/lynglib/build.gradle.kts b/lynglib/build.gradle.kts index 6d8e8c1..29e9e2f 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.11-SNAPSHOT" +version = "0.8.12-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 55d146b..6fac847 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt @@ -288,6 +288,9 @@ open class Obj { addFn("toString") { thisObj.asStr } + addFn("inspect", true) { + thisObj.inspect().toObj() + } addFn("contains") { ObjBool(thisObj.contains(this, args.firstAndOnly())) } 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 8556f67..227cc80 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBuffer.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBuffer.kt @@ -19,6 +19,7 @@ package net.sergeych.lyng.obj import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.toList +import net.sergeych.bintools.decodeHex import net.sergeych.bintools.encodeToHex import net.sergeych.bintools.toDump import net.sergeych.lyng.Scope @@ -26,12 +27,17 @@ import net.sergeych.lyng.statement import net.sergeych.lynon.LynonDecoder import net.sergeych.lynon.LynonEncoder import net.sergeych.lynon.LynonType +import net.sergeych.mp_tools.decodeBase64Url +import net.sergeych.mp_tools.encodeToBase64Url import kotlin.math.min open class ObjBuffer(val byteArray: UByteArray) : Obj() { override val objClass: ObjClass = type + val hex by lazy { byteArray.encodeToHex("")} + val base64 by lazy { byteArray.toByteArray().encodeToBase64Url()} + fun checkIndex(scope: Scope, index: Obj): Int { if (index !is ObjInt) scope.raiseIllegalArgument("index must be Int") @@ -83,9 +89,7 @@ open class ObjBuffer(val byteArray: UByteArray) : Obj() { } else scope.raiseIllegalArgument("can't concatenate buffer with ${other.inspect()}") } - override fun toString(): String { - return "Buffer(${byteArray.encodeToHex()})" - } + override fun toString(): String = base64 override fun equals(other: Any?): Boolean { if (this === other) return true @@ -102,6 +106,8 @@ open class ObjBuffer(val byteArray: UByteArray) : Obj() { encoder.encodeCachedBytes(byteArray.asByteArray()) } + override fun inspect(): String = "Buf($base64)" + companion object { private suspend fun createBufferFrom(scope: Scope, obj: Obj): ObjBuffer = when (obj) { @@ -158,11 +164,27 @@ open class ObjBuffer(val byteArray: UByteArray) : Obj() { }) }.apply { + addClassFn("decodeBase64") { + ObjBuffer(requireOnlyArg().toString().decodeBase64Url().asUByteArray()) + } + addClassFn("decodeHex") { + ObjBuffer(requireOnlyArg().toString().decodeHex().asUByteArray()) + } createField("size", statement { (thisObj as ObjBuffer).byteArray.size.toObj() } ) + createField("hex", + statement { + thisAs().hex.toObj() + } + ) + createField("base64", + statement { + thisAs().base64.toObj() + } + ) addFn("decodeUtf8") { ObjString( thisAs().byteArray.toByteArray().decodeToString() diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/stdlib_included/root_lyng.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/stdlib_included/root_lyng.kt index 00cbea5..54c2338 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/stdlib_included/root_lyng.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/stdlib_included/root_lyng.kt @@ -95,10 +95,8 @@ fun Iterable.joinToString(prefix=" ", transformer=null) { fun Iterable.any(predicate): Bool { for( i in this ) { - if( predicate(i) ) { - break true - // todo: add cancelIteration() in for loop! - } + if( predicate(i) ) + break true } else false } diff --git a/lynglib/src/commonTest/kotlin/ScriptTest.kt b/lynglib/src/commonTest/kotlin/ScriptTest.kt index 4e375e4..d67d7a9 100644 --- a/lynglib/src/commonTest/kotlin/ScriptTest.kt +++ b/lynglib/src/commonTest/kotlin/ScriptTest.kt @@ -2630,6 +2630,26 @@ class ScriptTest { ) } + @Test + fun testBufferEncodings() = runTest { + eval(""" + import lyng.buffer + + val b = Buffer("hello") + println(b.toDump()) + assertEquals( "hello", b.decodeUtf8() ) + + println(b.base64) + println(b.hex) + + assertEquals( b, Buffer.decodeBase64(b.base64) ) + assertEquals( b, Buffer.decodeHex(b.hex) ) + + println(b.inspect()) + + """.trimIndent()) + } + @Test fun testBufferCompare() = runTest { eval(