diff --git a/docs/Buffer.md b/docs/Buffer.md index f303411..8c755c5 100644 --- a/docs/Buffer.md +++ b/docs/Buffer.md @@ -1,7 +1,8 @@ # Binary `Buffer` 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`: @@ -10,6 +11,8 @@ Buffers needs to be imported with `import lyng.buffer`: assertEquals(5, Buffer("Hello").size) >>> void +Buffer is _immutable_, there is a `MutableBuffer` with same interface but mutable. + ## Constructing 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 -## 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 val b1 = Buffer( 1, 2, 3) 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 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 -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 val b1 = Buffer(1, 2, 3) val b2 = Buffer(1, 2, 3) - val b3 = Buffer(b2) + val b3 = MutableBuffer(b2) b3[0] = 101 @@ -93,20 +104,18 @@ As with [List], it is possible to use ranges as indexes to slice a Buffer: ## Members -| name | meaning | type | -|---------------------|--------------------------------------|-------| -| `size` | size | Int | -| `+=` | add one or more elements | Any | -| `+`, `union` | union sets | Any | -| `-`, `subtract` | subtract sets | Any | -| `*`, `intersect` | subtract sets | Any | -| `remove(items...)` | remove one or more items | Range | -| `contains(element)` | check the element is in the list (1) | | +| name | meaning | type | +|---------------|------------------------------------|---------------| +| `size` | size | Int | +| `decodeUtf8` | decodee to String using UTF8 rules | Any | +| `+` | buffer concatenation | Any | +| `toMutable()` | create a mutable copy | MutableBuffer | (1) : optimized implementation that override `Iterable` one -Also, it inherits methods from [Iterable]. +Also, it inherits methods from [Iterable] and [Array]. -[Range]: Range.md \ No newline at end of file +[Range]: Range.md +[Iterable]: Iterable.md \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt index 01909f2..961d311 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt @@ -82,6 +82,11 @@ open class Scope( } } + fun requireNoArgs() { + if( args.list.isNotEmpty()) + raiseError("This function does not accept any arguments") + } + inline fun thisAs(): T = (thisObj as? T) ?: raiseClassCastError("Cannot cast ${thisObj.objClass.className} to ${T::class.simpleName}") diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt index 45bd970..812b4ed 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt @@ -179,6 +179,7 @@ class Script( ImportManager(rootScope, SecurityManager.allowAll).apply { addPackage("lyng.buffer") { it.addConst("Buffer", ObjBuffer.type) + it.addConst("MutableBuffer", ObjMutableBuffer.type) } addPackage("lyng.time") { it.addConst("Instant", ObjInstant.type) 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 01c204e..413ba99 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBuffer.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBuffer.kt @@ -6,7 +6,7 @@ import net.sergeych.lyng.Scope import net.sergeych.lyng.statement import kotlin.math.min -class ObjBuffer(val byteArray: UByteArray) : Obj() { +open class ObjBuffer(val byteArray: UByteArray) : Obj() { override val objClass: ObjClass = type @@ -30,16 +30,6 @@ class ObjBuffer(val byteArray: UByteArray) : Obj() { } 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 override fun hashCode(): Int { @@ -144,18 +134,10 @@ class ObjBuffer(val byteArray: UByteArray) : Obj() { thisAs().byteArray.toByteArray().decodeToString() ) } -// ) -// addFn("getAt") { -// requireExactCount(1) -// thisAs().getAt(this, requiredArg(0)) -// } -// addFn("putAt") { -// requireExactCount(2) -// val newValue = args[1] -// thisAs().putAt(this, requiredArg(0).value.toInt(), newValue) -// newValue -// } - + addFn("toMutable") { + requireNoArgs() + ObjMutableBuffer(thisAs().byteArray.copyOf()) + } } } } \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMutableBuffer.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMutableBuffer.kt new file mode 100644 index 0000000..dd6ecf8 --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMutableBuffer.kt @@ -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) + } + } + } + } + } +} \ No newline at end of file diff --git a/lynglib/src/commonTest/kotlin/ScriptTest.kt b/lynglib/src/commonTest/kotlin/ScriptTest.kt index c8e1bc0..c28b124 100644 --- a/lynglib/src/commonTest/kotlin/ScriptTest.kt +++ b/lynglib/src/commonTest/kotlin/ScriptTest.kt @@ -2477,12 +2477,14 @@ class ScriptTest { assertEquals( 3, Buffer(1, 2, 3).size ) assertEquals( 5, Buffer("hello").size ) - val buffer = Buffer("Hello") + var buffer = Buffer("Hello") assertEquals( 5, buffer.size) assertEquals('l'.code, buffer[2] ) assertEquals('l'.code, buffer[3] ) assertEquals("Hello", buffer.decodeUtf8()) + buffer = buffer.toMutable() + buffer[2] = 101 assertEquals(101, buffer[2]) assertEquals("Heelo", buffer.decodeUtf8()) @@ -2503,6 +2505,12 @@ class ScriptTest { val b3 = b1 + Buffer("!") assertEquals( "Hello!", b3.decodeUtf8()) assert( b3 > b1 ) + assert( b1 !== b2) + + val map = Map( b1 => "foo") + assertEquals("foo", map[b1]) + assertEquals("foo", map[b2]) + assertEquals(null, map[b3]) """.trimIndent()) } @@ -2615,7 +2623,7 @@ class ScriptTest { import lyng.buffer - val b = Buffer(1,2,3) + val b = MutableBuffer(1,2,3) b[1]++ assert( b == Buffer(1,3,3) ) ++b[0] @@ -2633,7 +2641,7 @@ class ScriptTest { import lyng.buffer - val b = Buffer(1,2,3) + val b = Buffer(1,2,3).toMutable() b[1]-- assert( b == Buffer(1,1,3) ) --b[0]