more Buffer encodings

This commit is contained in:
Sergey Chernov 2025-08-17 19:26:15 +03:00
parent 2ac92a1d09
commit eefecae7b4
6 changed files with 83 additions and 14 deletions

View File

@ -102,20 +102,46 @@ As with [List], it is possible to use ranges as indexes to slice a Buffer:
>>> void >>> 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 ## Members
| name | meaning | type | | name | meaning | type |
|---------------|------------------------------------|---------------| |---------------------------|-----------------------------------|---------------|
| `size` | size | Int | | `size` | size | Int |
| `decodeUtf8` | decodee to String using UTF8 rules | Any | | `decodeUtf8` | decode to String using UTF8 rules | Any |
| `+` | buffer concatenation | Any | | `+` | buffer concatenation | Any |
| `toMutable()` | create a mutable copy | MutableBuffer | | `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) (1)
: optimized implementation that override `Iterable` one : 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]. Also, it inherits methods from [Iterable] and [Array].
[Range]: Range.md [Range]: Range.md
[Iterable]: Iterable.md [Iterable]: Iterable.md

View File

@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.JvmTarget
group = "net.sergeych" group = "net.sergeych"
version = "0.8.11-SNAPSHOT" version = "0.8.12-SNAPSHOT"
buildscript { buildscript {
repositories { repositories {

View File

@ -288,6 +288,9 @@ open class Obj {
addFn("toString") { addFn("toString") {
thisObj.asStr thisObj.asStr
} }
addFn("inspect", true) {
thisObj.inspect().toObj()
}
addFn("contains") { addFn("contains") {
ObjBool(thisObj.contains(this, args.firstAndOnly())) ObjBool(thisObj.contains(this, args.firstAndOnly()))
} }

View File

@ -19,6 +19,7 @@ package net.sergeych.lyng.obj
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.toList import kotlinx.coroutines.flow.toList
import net.sergeych.bintools.decodeHex
import net.sergeych.bintools.encodeToHex import net.sergeych.bintools.encodeToHex
import net.sergeych.bintools.toDump import net.sergeych.bintools.toDump
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
@ -26,12 +27,17 @@ import net.sergeych.lyng.statement
import net.sergeych.lynon.LynonDecoder import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonEncoder import net.sergeych.lynon.LynonEncoder
import net.sergeych.lynon.LynonType import net.sergeych.lynon.LynonType
import net.sergeych.mp_tools.decodeBase64Url
import net.sergeych.mp_tools.encodeToBase64Url
import kotlin.math.min import kotlin.math.min
open class ObjBuffer(val byteArray: UByteArray) : Obj() { open class ObjBuffer(val byteArray: UByteArray) : Obj() {
override val objClass: ObjClass = type 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 { fun checkIndex(scope: Scope, index: Obj): Int {
if (index !is ObjInt) if (index !is ObjInt)
scope.raiseIllegalArgument("index must be Int") 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()}") } else scope.raiseIllegalArgument("can't concatenate buffer with ${other.inspect()}")
} }
override fun toString(): String { override fun toString(): String = base64
return "Buffer(${byteArray.encodeToHex()})"
}
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
@ -102,6 +106,8 @@ open class ObjBuffer(val byteArray: UByteArray) : Obj() {
encoder.encodeCachedBytes(byteArray.asByteArray()) encoder.encodeCachedBytes(byteArray.asByteArray())
} }
override fun inspect(): String = "Buf($base64)"
companion object { companion object {
private suspend fun createBufferFrom(scope: Scope, obj: Obj): ObjBuffer = private suspend fun createBufferFrom(scope: Scope, obj: Obj): ObjBuffer =
when (obj) { when (obj) {
@ -158,11 +164,27 @@ open class ObjBuffer(val byteArray: UByteArray) : Obj() {
}) })
}.apply { }.apply {
addClassFn("decodeBase64") {
ObjBuffer(requireOnlyArg<Obj>().toString().decodeBase64Url().asUByteArray())
}
addClassFn("decodeHex") {
ObjBuffer(requireOnlyArg<Obj>().toString().decodeHex().asUByteArray())
}
createField("size", createField("size",
statement { statement {
(thisObj as ObjBuffer).byteArray.size.toObj() (thisObj as ObjBuffer).byteArray.size.toObj()
} }
) )
createField("hex",
statement {
thisAs<ObjBuffer>().hex.toObj()
}
)
createField("base64",
statement {
thisAs<ObjBuffer>().base64.toObj()
}
)
addFn("decodeUtf8") { addFn("decodeUtf8") {
ObjString( ObjString(
thisAs<ObjBuffer>().byteArray.toByteArray().decodeToString() thisAs<ObjBuffer>().byteArray.toByteArray().decodeToString()

View File

@ -95,10 +95,8 @@ fun Iterable.joinToString(prefix=" ", transformer=null) {
fun Iterable.any(predicate): Bool { fun Iterable.any(predicate): Bool {
for( i in this ) { for( i in this ) {
if( predicate(i) ) { if( predicate(i) )
break true break true
// todo: add cancelIteration() in for loop!
}
} else false } else false
} }

View File

@ -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 @Test
fun testBufferCompare() = runTest { fun testBufferCompare() = runTest {
eval( eval(