refs #35 Buffer is not mutable, MutableBuffer added (to cache in serialized form)

This commit is contained in:
Sergey Chernov 2025-07-16 11:47:23 +03:00
parent f3d766d1b1
commit 7aee25ffef
6 changed files with 120 additions and 44 deletions

View File

@ -1,7 +1,8 @@
# Binary `Buffer` # Binary `Buffer`
Buffers are effective unsigned byte arrays of fixed size. Buffers content is mutable, Buffers are effective unsigned byte arrays of fixed size. Buffers content is mutable,
unlike its size. Buffers are comparable and implement [Array], thus [Collection] and [Iterable]. Buffer iterators return its contents as unsigned bytes converted to `Int` unlike its size. Buffers are comparable and implement [Array], thus [Collection] and [Iterable]. Buffer iterators return
its contents as unsigned bytes converted to `Int`
Buffers needs to be imported with `import lyng.buffer`: Buffers needs to be imported with `import lyng.buffer`:
@ -10,6 +11,8 @@ Buffers needs to be imported with `import lyng.buffer`:
assertEquals(5, Buffer("Hello").size) assertEquals(5, Buffer("Hello").size)
>>> void >>> void
Buffer is _immutable_, there is a `MutableBuffer` with same interface but mutable.
## Constructing ## Constructing
There are a lo of ways to construct a buffer: There are a lo of ways to construct a buffer:
@ -36,15 +39,23 @@ There are a lo of ways to construct a buffer:
>>> void >>> void
## Accessing an modifying ## Accessing and modifying
Buffer implement [Array] and therefore can be accessed and modified with indexing: Buffer implement [Array] and therefore can be accessed, and `MutableBuffers` also modified:
import lyng.buffer import lyng.buffer
val b1 = Buffer( 1, 2, 3) val b1 = Buffer( 1, 2, 3)
assertEquals( 2, b1[1] ) assertEquals( 2, b1[1] )
b1[0] = 199
assertEquals(199, b1[0]) val b2 = b1.toMutable()
assertEquals( 2, b1[1] )
b2[1]++
b2[0] = 100
assertEquals( Buffer(100, 3, 3), b2)
// b2 is a mutable copy so b1 has not been changed:
assertEquals( Buffer(1, 2, 3), b1)
>>> void >>> void
Buffer provides concatenation with another Buffer: Buffer provides concatenation with another Buffer:
@ -58,12 +69,12 @@ Please note that indexed bytes are _readonly projection_, e.g. you can't modify
## Comparing ## Comparing
Buffers are comparable with other buffers: Buffers are comparable with other buffers (and notice there are _mutable_ buffers, bu default buffers ar _immutable_):
import lyng.buffer import lyng.buffer
val b1 = Buffer(1, 2, 3) val b1 = Buffer(1, 2, 3)
val b2 = Buffer(1, 2, 3) val b2 = Buffer(1, 2, 3)
val b3 = Buffer(b2) val b3 = MutableBuffer(b2)
b3[0] = 101 b3[0] = 101
@ -94,19 +105,17 @@ As with [List], it is possible to use ranges as indexes to slice a Buffer:
## Members ## Members
| name | meaning | type | | name | meaning | type |
|---------------------|--------------------------------------|-------| |---------------|------------------------------------|---------------|
| `size` | size | Int | | `size` | size | Int |
| `+=` | add one or more elements | Any | | `decodeUtf8` | decodee to String using UTF8 rules | Any |
| `+`, `union` | union sets | Any | | `+` | buffer concatenation | Any |
| `-`, `subtract` | subtract sets | Any | | `toMutable()` | create a mutable copy | MutableBuffer |
| `*`, `intersect` | subtract sets | Any |
| `remove(items...)` | remove one or more items | Range |
| `contains(element)` | check the element is in the list (1) | |
(1) (1)
: optimized implementation that override `Iterable` one : optimized implementation that override `Iterable` one
Also, it inherits methods from [Iterable]. Also, it inherits methods from [Iterable] and [Array].
[Range]: Range.md [Range]: Range.md
[Iterable]: Iterable.md

View File

@ -82,6 +82,11 @@ open class Scope(
} }
} }
fun requireNoArgs() {
if( args.list.isNotEmpty())
raiseError("This function does not accept any arguments")
}
inline fun <reified T : Obj> thisAs(): T = (thisObj as? T) inline fun <reified T : Obj> thisAs(): T = (thisObj as? T)
?: raiseClassCastError("Cannot cast ${thisObj.objClass.className} to ${T::class.simpleName}") ?: raiseClassCastError("Cannot cast ${thisObj.objClass.className} to ${T::class.simpleName}")

View File

@ -179,6 +179,7 @@ class Script(
ImportManager(rootScope, SecurityManager.allowAll).apply { ImportManager(rootScope, SecurityManager.allowAll).apply {
addPackage("lyng.buffer") { addPackage("lyng.buffer") {
it.addConst("Buffer", ObjBuffer.type) it.addConst("Buffer", ObjBuffer.type)
it.addConst("MutableBuffer", ObjMutableBuffer.type)
} }
addPackage("lyng.time") { addPackage("lyng.time") {
it.addConst("Instant", ObjInstant.type) it.addConst("Instant", ObjInstant.type)

View File

@ -6,7 +6,7 @@ import net.sergeych.lyng.Scope
import net.sergeych.lyng.statement import net.sergeych.lyng.statement
import kotlin.math.min import kotlin.math.min
class ObjBuffer(val byteArray: UByteArray) : Obj() { open class ObjBuffer(val byteArray: UByteArray) : Obj() {
override val objClass: ObjClass = type override val objClass: ObjClass = type
@ -30,16 +30,6 @@ class ObjBuffer(val byteArray: UByteArray) : Obj() {
} else ObjInt(byteArray[checkIndex(scope, index)].toLong(), true) } else ObjInt(byteArray[checkIndex(scope, index)].toLong(), true)
} }
override suspend fun putAt(scope: Scope, index: Obj, newValue: Obj) {
byteArray[checkIndex(scope, index.toObj())] = when (newValue) {
is ObjInt -> newValue.value.toUByte()
is ObjChar -> newValue.value.code.toUByte()
else -> scope.raiseIllegalArgument(
"invalid byte value for buffer at index ${index.inspect()}: ${newValue.inspect()}"
)
}
}
val size by byteArray::size val size by byteArray::size
override fun hashCode(): Int { override fun hashCode(): Int {
@ -144,18 +134,10 @@ class ObjBuffer(val byteArray: UByteArray) : Obj() {
thisAs<ObjBuffer>().byteArray.toByteArray().decodeToString() thisAs<ObjBuffer>().byteArray.toByteArray().decodeToString()
) )
} }
// ) addFn("toMutable") {
// addFn("getAt") { requireNoArgs()
// requireExactCount(1) ObjMutableBuffer(thisAs<ObjBuffer>().byteArray.copyOf())
// thisAs<ObjList>().getAt(this, requiredArg<Obj>(0)) }
// }
// addFn("putAt") {
// requireExactCount(2)
// val newValue = args[1]
// thisAs<ObjList>().putAt(this, requiredArg<ObjInt>(0).value.toInt(), newValue)
// newValue
// }
} }
} }
} }

View File

@ -0,0 +1,71 @@
package net.sergeych.lyng.obj
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.toList
import net.sergeych.lyng.Scope
class ObjMutableBuffer(byteArray: UByteArray) : ObjBuffer(byteArray) {
override suspend fun putAt(scope: Scope, index: Obj, newValue: Obj) {
byteArray[checkIndex(scope, index.toObj())] = when (newValue) {
is ObjInt -> newValue.value.toUByte()
is ObjChar -> newValue.value.code.toUByte()
else -> scope.raiseIllegalArgument(
"invalid byte value for buffer at index ${index.inspect()}: ${newValue.inspect()}"
)
}
}
companion object {
private suspend fun createBufferFrom(scope: Scope, obj: Obj): ObjBuffer =
when (obj) {
is ObjBuffer -> ObjMutableBuffer(obj.byteArray.copyOf())
is ObjInt -> {
if (obj.value < 0)
scope.raiseIllegalArgument("buffer size must be positive")
val data = UByteArray(obj.value.toInt())
ObjMutableBuffer(data)
}
is ObjString -> ObjMutableBuffer(obj.value.encodeToByteArray().asUByteArray())
else -> {
if (obj.isInstanceOf(ObjIterable)) {
ObjMutableBuffer(
obj.toFlow(scope).map { it.toLong().toUByte() }.toList().toTypedArray()
.toUByteArray()
)
} else
scope.raiseIllegalArgument(
"can't construct buffer from ${obj.inspect()}"
)
}
}
val type = object : ObjClass("MutableBuffer", ObjBuffer.type) {
override suspend fun callOn(scope: Scope): Obj {
val args = scope.args.list
return when (args.size) {
// empty buffer
0 -> ObjMutableBuffer(ubyteArrayOf())
1 -> createBufferFrom(scope, args[0])
else -> {
// create buffer from array, each argument should be a byte then:
val data = UByteArray(args.size)
for ((i, b) in args.withIndex()) {
val code = when (b) {
is ObjChar -> b.value.code.toUByte()
is ObjInt -> b.value.toUByte()
else -> scope.raiseIllegalArgument(
"invalid byte value for buffer constructor at index $i: ${b.inspect()}"
)
}
data[i] = code
}
ObjMutableBuffer(data)
}
}
}
}
}
}

View File

@ -2477,12 +2477,14 @@ class ScriptTest {
assertEquals( 3, Buffer(1, 2, 3).size ) assertEquals( 3, Buffer(1, 2, 3).size )
assertEquals( 5, Buffer("hello").size ) assertEquals( 5, Buffer("hello").size )
val buffer = Buffer("Hello") var buffer = Buffer("Hello")
assertEquals( 5, buffer.size) assertEquals( 5, buffer.size)
assertEquals('l'.code, buffer[2] ) assertEquals('l'.code, buffer[2] )
assertEquals('l'.code, buffer[3] ) assertEquals('l'.code, buffer[3] )
assertEquals("Hello", buffer.decodeUtf8()) assertEquals("Hello", buffer.decodeUtf8())
buffer = buffer.toMutable()
buffer[2] = 101 buffer[2] = 101
assertEquals(101, buffer[2]) assertEquals(101, buffer[2])
assertEquals("Heelo", buffer.decodeUtf8()) assertEquals("Heelo", buffer.decodeUtf8())
@ -2503,6 +2505,12 @@ class ScriptTest {
val b3 = b1 + Buffer("!") val b3 = b1 + Buffer("!")
assertEquals( "Hello!", b3.decodeUtf8()) assertEquals( "Hello!", b3.decodeUtf8())
assert( b3 > b1 ) assert( b3 > b1 )
assert( b1 !== b2)
val map = Map( b1 => "foo")
assertEquals("foo", map[b1])
assertEquals("foo", map[b2])
assertEquals(null, map[b3])
""".trimIndent()) """.trimIndent())
} }
@ -2615,7 +2623,7 @@ class ScriptTest {
import lyng.buffer import lyng.buffer
val b = Buffer(1,2,3) val b = MutableBuffer(1,2,3)
b[1]++ b[1]++
assert( b == Buffer(1,3,3) ) assert( b == Buffer(1,3,3) )
++b[0] ++b[0]
@ -2633,7 +2641,7 @@ class ScriptTest {
import lyng.buffer import lyng.buffer
val b = Buffer(1,2,3) val b = Buffer(1,2,3).toMutable()
b[1]-- b[1]--
assert( b == Buffer(1,1,3) ) assert( b == Buffer(1,1,3) )
--b[0] --b[0]