fix #33 minimal Buffer with docs, in a separate package
This commit is contained in:
parent
26282d3e22
commit
23006b5caa
110
docs/Buffer.md
Normal file
110
docs/Buffer.md
Normal file
@ -0,0 +1,110 @@
|
||||
# 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`
|
||||
|
||||
Buffers needs to be imported with `import lyng.buffer`:
|
||||
|
||||
import lyng.buffer
|
||||
|
||||
assertEquals(5, Buffer("Hello").size)
|
||||
>>> void
|
||||
|
||||
## Constructing
|
||||
|
||||
There are a lo of ways to construct a buffer:
|
||||
|
||||
import lyng.buffer
|
||||
|
||||
// from string using utf8 encoding:
|
||||
assertEquals( 5, Buffer("hello").size )
|
||||
|
||||
// from bytes, e.g. integers in range 0..255
|
||||
assertEquals( 255, Buffer(1,2,3,255).last() )
|
||||
|
||||
// from whatever iterable that produces bytes, e.g.
|
||||
// integers in 0..255 range:
|
||||
assertEquals( 129, Buffer([1,2,129]).last() )
|
||||
|
||||
// Empty buffer of fixed size:
|
||||
assertEquals(100, Buffer(100).size)
|
||||
assertEquals(0, Buffer(100)[0])
|
||||
|
||||
// Note that you can use list iteral to create buffer with 1 byte:
|
||||
assertEquals(1, Buffer([100]).size)
|
||||
assertEquals(100, Buffer([100])[0])
|
||||
|
||||
>>> void
|
||||
|
||||
## Accessing an modifying
|
||||
|
||||
Buffer implement [Array] and therefore can be accessed and modified with indexing:
|
||||
|
||||
import lyng.buffer
|
||||
val b1 = Buffer( 1, 2, 3)
|
||||
assertEquals( 2, b1[1] )
|
||||
b1[0] = 199
|
||||
assertEquals(199, b1[0])
|
||||
>>> void
|
||||
|
||||
Buffer provides concatenation with another Buffer:
|
||||
|
||||
import lyng.buffer
|
||||
val b = Buffer(101, 102)
|
||||
assertEquals( Buffer(101, 102, 1, 2), b + [1,2])
|
||||
>>> void
|
||||
|
||||
## Comparing
|
||||
|
||||
Buffers are comparable with other buffers:
|
||||
|
||||
import lyng.buffer
|
||||
val b1 = Buffer(1, 2, 3)
|
||||
val b2 = Buffer(1, 2, 3)
|
||||
val b3 = Buffer(b2)
|
||||
|
||||
b3[0] = 101
|
||||
|
||||
assert( b3 > b1 )
|
||||
assert( b2 == b1 )
|
||||
// longer string with same prefix is considered bigger:
|
||||
assert( b2 + "!".characters() > b1 )
|
||||
// note that characters() provide Iterable of characters that
|
||||
// can be concatenated to Buffer
|
||||
|
||||
>>> void
|
||||
|
||||
## Slicing
|
||||
|
||||
As with [List], it is possible to use ranges as indexes to slice a Buffer:
|
||||
|
||||
import lyng.buffer
|
||||
|
||||
val a = Buffer( 100, 101, 102, 103, 104, 105 )
|
||||
assertEquals( a[ 0..1 ], Buffer(100, 101) )
|
||||
assertEquals( a[ 0 ..< 2 ], Buffer(100, 101) )
|
||||
assertEquals( a[ ..< 2 ], Buffer(100, 101) )
|
||||
assertEquals( a[ 4.. ], Buffer(104, 105) )
|
||||
assertEquals( a[ 2..3 ], Buffer(102, 103) )
|
||||
|
||||
>>> void
|
||||
|
||||
## 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) | |
|
||||
|
||||
(1)
|
||||
: optimized implementation that override `Iterable` one
|
||||
|
||||
Also, it inherits methods from [Iterable].
|
||||
|
||||
|
||||
[Range]: Range.md
|
@ -3,6 +3,8 @@
|
||||
Map is a mutable collection of key-value pars, where keys are unique. Maps could be created with
|
||||
constructor or `.toMap` methods. When constructing from a list, each list item must be a [Collection] with exactly 2 elements, for example, a [List].
|
||||
|
||||
Important thing is that maps can't contain `null`: it is used to return from missing elements.
|
||||
|
||||
Constructed map instance is of class `Map` and implements `Collection` (and therefore `Iterable`)
|
||||
|
||||
val map = Map( ["foo", 1], ["bar", "buzz"] )
|
||||
@ -16,7 +18,7 @@ Map keys could be any objects (hashable, e.g. with reasonable hashCode, most of
|
||||
val map = Map( ["foo", 1], ["bar", "buzz"], [42, "answer"] )
|
||||
assert( map["bar"] == "buzz")
|
||||
assert( map[42] == "answer" )
|
||||
assertThrows { map["nonexistent"] }
|
||||
assertEquals( null, map["nonexisting"])
|
||||
assert( map.getOrNull(101) == null )
|
||||
assert( map.getOrPut(911) { "nine-eleven" } == "nine-eleven" )
|
||||
// now 91 entry is set:
|
||||
|
@ -530,7 +530,8 @@ The simplest way to concatenate lists is using `+` and `+=`:
|
||||
list += [2, 1]
|
||||
// or you can append a single element:
|
||||
list += "end"
|
||||
assert( list == [1, 2, 2, 1, "end"])
|
||||
assertEquals( list, [1, 2, 2, 1, "end"])
|
||||
void
|
||||
>>> void
|
||||
|
||||
***Important note***: the pitfall of using `+=` is that you can't append in [Iterable] instance as an object: it will always add all its contents. Use `list.add` to add a single iterable instance:
|
||||
@ -641,6 +642,11 @@ You can get ranges to extract a portion from a list:
|
||||
assertEquals( [2,3], list[1..<3])
|
||||
>>> void
|
||||
|
||||
# Buffers
|
||||
|
||||
[Buffer] is a special implementation of an [Array] of unsigned bytes, in the
|
||||
[separate file](Buffer.md).
|
||||
|
||||
# Sets
|
||||
|
||||
Set are unordered collection of unique elements, see [Set]. Sets are [Iterable] but have no indexing access.
|
||||
@ -740,13 +746,13 @@ You can thest that _when expression_ is _contained_, or not contained, in some o
|
||||
|
||||
Typical builtin types that are containers (e.g. support `conain`):
|
||||
|
||||
| class | notes |
|
||||
|------------|--------------------------------------------|
|
||||
| Collection | contains an element (1) |
|
||||
| Array | faster maybe that Collection's |
|
||||
| List | faster than Array's |
|
||||
| String | character in string or substring in string |
|
||||
| Range | object is included in the range (2) |
|
||||
| class | notes |
|
||||
|------------|------------------------------------------------|
|
||||
| Collection | contains an element (1) |
|
||||
| Array | faster maybe that Collection's |
|
||||
| List | faster than Array's |
|
||||
| String | character in string or substring in string (3) |
|
||||
| Range | object is included in the range (2) |
|
||||
|
||||
(1)
|
||||
: Iterable is not the container as it can be infinite
|
||||
@ -760,6 +766,9 @@ Typical builtin types that are containers (e.g. support `conain`):
|
||||
assert( "x" !in 'a'..'z') // string in character range: could be error
|
||||
>>> void
|
||||
|
||||
(3)
|
||||
: `String` also can provide array of characters directly with `str.characters()`, which is [Iterable] and [Array]. String itself is not iterable as otherwise it will interfere when adding strigns to lists (it will add _characters_ it it would be iterable).
|
||||
|
||||
So we recommend not to mix characters and string ranges; use `ch in str` that works
|
||||
as expected:
|
||||
|
||||
@ -1216,9 +1225,10 @@ Typical set of String functions includes:
|
||||
| s1 += s2 | self-modifying concatenation |
|
||||
| toReal() | attempts to parse string as a Real value |
|
||||
| toInt() | parse string to Int value |
|
||||
| | |
|
||||
|
||||
| characters() | create [List] of characters (1) |
|
||||
|
||||
(1)
|
||||
: List is mutable therefore a new copy is created on each call.
|
||||
|
||||
|
||||
|
||||
@ -1256,4 +1266,5 @@ See [math functions](math.md). Other general purpose functions are:
|
||||
[String]: String.md
|
||||
[string formatting]: https://github.com/sergeych/mp_stools?tab=readme-ov-file#sprintf-syntax-summary
|
||||
[Set]: Set.md
|
||||
[Map]: Map.md
|
||||
[Map]: Map.md
|
||||
[Buffer]: Buffer.md
|
@ -4,7 +4,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
group = "net.sergeych"
|
||||
version = "0.7.2-SNAPSHOT"
|
||||
version = "0.7.3-SNAPSHOT"
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
@ -20,7 +20,7 @@ plugins {
|
||||
alias(libs.plugins.kotlinMultiplatform)
|
||||
alias(libs.plugins.androidLibrary)
|
||||
// alias(libs.plugins.vanniktech.mavenPublish)
|
||||
kotlin("plugin.serialization") version "2.1.20"
|
||||
kotlin("plugin.serialization") version "2.2.0"
|
||||
id("com.codingfeline.buildkonfig") version "0.17.1"
|
||||
`maven-publish`
|
||||
}
|
||||
|
@ -274,9 +274,7 @@ class Compiler(
|
||||
if (x == ObjNull && isOptional) ObjNull.asReadonly
|
||||
else x.getAt(cxt, i).asMutable
|
||||
}) { cxt, newValue ->
|
||||
val i = (index.execute(cxt) as? ObjInt)?.value?.toInt()
|
||||
?: cxt.raiseError("index must be integer")
|
||||
left.getter(cxt).value.putAt(cxt, i, newValue)
|
||||
left.getter(cxt).value.putAt(cxt, index.execute(cxt), newValue)
|
||||
}
|
||||
} ?: run {
|
||||
// array literal
|
||||
|
@ -165,7 +165,6 @@ class CompilerContext(val tokens: List<Token>) {
|
||||
*/
|
||||
fun skipWsTokens(): Token {
|
||||
while( current().type in wstokens ) {
|
||||
println("skipws ${current()}")
|
||||
next()
|
||||
}
|
||||
return next()
|
||||
|
@ -220,7 +220,7 @@ open class Obj {
|
||||
|
||||
suspend fun getAt(scope: Scope, index: Int): Obj = getAt(scope, ObjInt(index.toLong()))
|
||||
|
||||
open suspend fun putAt(scope: Scope, index: Int, newValue: Obj) {
|
||||
open suspend fun putAt(scope: Scope, index: Obj, newValue: Obj) {
|
||||
scope.raiseNotImplemented("indexing")
|
||||
}
|
||||
|
||||
@ -280,7 +280,18 @@ open class Obj {
|
||||
args.firstAndOnly().callOn(copy(Arguments(thisObj)))
|
||||
thisObj
|
||||
}
|
||||
addFn("getAt") {
|
||||
requireExactCount(1)
|
||||
thisObj.getAt(this, requiredArg<Obj>(0))
|
||||
}
|
||||
addFn("putAt") {
|
||||
requireExactCount(2)
|
||||
val newValue = args[1]
|
||||
thisObj.putAt(this, requiredArg<Obj>(0), newValue)
|
||||
newValue
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
inline fun from(obj: Any?): Obj {
|
||||
@ -349,7 +360,7 @@ object ObjNull : Obj() {
|
||||
scope.raiseNPE()
|
||||
}
|
||||
|
||||
override suspend fun putAt(scope: Scope, index: Int, newValue: Obj) {
|
||||
override suspend fun putAt(scope: Scope, index: Obj, newValue: Obj) {
|
||||
scope.raiseNPE()
|
||||
}
|
||||
|
||||
|
145
lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjBuffer.kt
Normal file
145
lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjBuffer.kt
Normal file
@ -0,0 +1,145 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlin.math.min
|
||||
|
||||
class ObjBuffer(val byteArray: UByteArray) : Obj() {
|
||||
|
||||
override val objClass: ObjClass = type
|
||||
|
||||
fun checkIndex(scope: Scope, index: Obj): Int {
|
||||
if (index !is ObjInt)
|
||||
scope.raiseIllegalArgument("index must be Int")
|
||||
val i = index.value.toInt()
|
||||
if (i < 0) scope.raiseIllegalArgument("index must be positive")
|
||||
if (i >= byteArray.size)
|
||||
scope.raiseIndexOutOfBounds("index $i is out of bounds 0..<${byteArray.size}")
|
||||
return i
|
||||
}
|
||||
|
||||
override suspend fun getAt(scope: Scope, index: Obj): Obj {
|
||||
// notice: we create a copy if content, so we don't want it
|
||||
// to be treated as modifiable, or putAt will not be called:
|
||||
return if (index is ObjRange) {
|
||||
val start: Int = index.startInt(scope)
|
||||
val end: Int = index.exclusiveIntEnd(scope) ?: size
|
||||
ObjBuffer(byteArray.sliceArray(start..<end))
|
||||
}
|
||||
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 suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||
if (other !is ObjBuffer) return -1
|
||||
val limit = min(size, other.size)
|
||||
for (i in 0..<limit) {
|
||||
val own = byteArray[i]
|
||||
val their = other.byteArray[i]
|
||||
if (own < their) return -1
|
||||
else if (own > their) return 1
|
||||
}
|
||||
if (size < other.size) return -1
|
||||
if (size > other.size) return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
override suspend fun plus(scope: Scope, other: Obj): Obj {
|
||||
return if( other is ObjBuffer)
|
||||
ObjBuffer(byteArray + other.byteArray)
|
||||
else if( other.isInstanceOf(ObjIterable)) {
|
||||
ObjBuffer(
|
||||
byteArray + other.toFlow(scope).map { it.toLong().toUByte() }.toList().toTypedArray().toUByteArray()
|
||||
)
|
||||
} else scope.raiseIllegalArgument("can't concatenate buffer with ${other.inspect()}")
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "Buffer(${byteArray.toList()})"
|
||||
}
|
||||
|
||||
companion object {
|
||||
private suspend fun createBufferFrom(scope: Scope, obj: Obj): ObjBuffer =
|
||||
when (obj) {
|
||||
is ObjBuffer -> ObjBuffer(obj.byteArray.copyOf())
|
||||
is ObjInt -> {
|
||||
if (obj.value < 0)
|
||||
scope.raiseIllegalArgument("buffer size must be positive")
|
||||
val data = UByteArray(obj.value.toInt())
|
||||
ObjBuffer(data)
|
||||
}
|
||||
|
||||
is ObjString -> ObjBuffer(obj.value.encodeToByteArray().asUByteArray())
|
||||
else -> {
|
||||
if (obj.isInstanceOf(ObjIterable)) {
|
||||
ObjBuffer(
|
||||
obj.toFlow(scope).map { it.toLong().toUByte() }.toList().toTypedArray().toUByteArray()
|
||||
)
|
||||
} else
|
||||
scope.raiseIllegalArgument(
|
||||
"can't construct buffer from ${obj.inspect()}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val type = object : ObjClass("Buffer", ObjArray) {
|
||||
override suspend fun callOn(scope: Scope): Obj {
|
||||
val args = scope.args.list
|
||||
return when (args.size) {
|
||||
// empty buffer
|
||||
0 -> ObjBuffer(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
|
||||
}
|
||||
ObjBuffer(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}.apply {
|
||||
createField("size",
|
||||
statement {
|
||||
(thisObj as ObjBuffer).byteArray.size.toObj()
|
||||
}
|
||||
)
|
||||
addFn("decodeUtf8") {
|
||||
ObjString(
|
||||
thisAs<ObjBuffer>().byteArray.toByteArray().decodeToString()
|
||||
)
|
||||
}
|
||||
// )
|
||||
// addFn("getAt") {
|
||||
// requireExactCount(1)
|
||||
// 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
|
||||
// }
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
data class ObjInt(var value: Long) : Obj(), Numeric {
|
||||
class ObjInt(var value: Long,val isConst: Boolean = false) : Obj(), Numeric {
|
||||
override val asStr get() = ObjString(value.toString())
|
||||
override val longValue get() = value
|
||||
override val doubleValue get() = value.toDouble()
|
||||
@ -70,7 +70,7 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
|
||||
* assignment
|
||||
*/
|
||||
override suspend fun assign(scope: Scope, other: Obj): Obj? {
|
||||
return if (other is ObjInt) {
|
||||
return if (!isConst && other is ObjInt) {
|
||||
value = other.value
|
||||
this
|
||||
} else null
|
||||
@ -90,8 +90,8 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
|
||||
}
|
||||
|
||||
companion object {
|
||||
val Zero = ObjInt(0)
|
||||
val One = ObjInt(1)
|
||||
val Zero = ObjInt(0, true)
|
||||
val One = ObjInt(1, true)
|
||||
val type = ObjClass("Int")
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,6 @@ package net.sergeych.lyng
|
||||
|
||||
class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
||||
|
||||
init {
|
||||
for (p in objClass.parents)
|
||||
parentInstances.add(p.defaultInstance())
|
||||
}
|
||||
|
||||
override fun toString(): String = "[${
|
||||
list.joinToString(separator = ", ") { it.inspect() }
|
||||
}]"
|
||||
@ -51,9 +46,8 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun putAt(scope: Scope, index: Int, newValue: Obj) {
|
||||
val i = index
|
||||
list[i] = newValue
|
||||
override suspend fun putAt(scope: Scope, index: Obj, newValue: Obj) {
|
||||
list[index.toInt()] = newValue
|
||||
}
|
||||
|
||||
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||
@ -136,16 +130,6 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
||||
(thisObj as ObjList).list.size.toObj()
|
||||
}
|
||||
)
|
||||
addFn("getAt") {
|
||||
requireExactCount(1)
|
||||
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
|
||||
}
|
||||
createField("add",
|
||||
statement {
|
||||
val l = thisAs<ObjList>().list
|
||||
|
@ -39,7 +39,11 @@ class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
|
||||
override val objClass = type
|
||||
|
||||
override suspend fun getAt(scope: Scope, index: Obj): Obj =
|
||||
map.getOrElse(index) { scope.raiseNoSuchElement() }
|
||||
map.get(index) ?: ObjNull
|
||||
|
||||
override suspend fun putAt(scope: Scope, index: Obj, newValue: Obj) {
|
||||
map[index] = newValue
|
||||
}
|
||||
|
||||
override suspend fun contains(scope: Scope, other: Obj): Boolean {
|
||||
return other in map
|
||||
|
@ -15,6 +15,31 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob
|
||||
return result.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* IF end is open (null/ObjNull), returns null
|
||||
* Otherwise, return correct value for the exclusive end
|
||||
* raises [ObjIllegalArgumentException] if end is not ObjInt
|
||||
*/
|
||||
fun exclusiveIntEnd(scope: Scope): Int? =
|
||||
if (end == null || end is ObjNull) null
|
||||
else {
|
||||
if (end !is ObjInt) scope.raiseIllegalArgument("end is not int")
|
||||
if (isEndInclusive) end.value.toInt() + 1 else end.value.toInt()
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* If start is null/ObjNull, returns 0
|
||||
* if start is not ObjInt, raises [ObjIllegalArgumentException]
|
||||
* otherwise returns start.value.toInt()
|
||||
*/
|
||||
fun startInt(scope: Scope): Int =
|
||||
if( start == null || start is ObjNull ) 0
|
||||
else {
|
||||
if( start is ObjInt ) start.value.toInt()
|
||||
else scope.raiseIllegalArgument("start is not Int: ${start.inspect()}")
|
||||
}
|
||||
|
||||
suspend fun containsRange(scope: Scope, other: ObjRange): Boolean {
|
||||
if (start != null) {
|
||||
// our start is not -∞ so other start should be GTE or is not contained:
|
||||
|
@ -15,7 +15,6 @@ data class ObjString(val value: String) : Obj() {
|
||||
// return i
|
||||
// }
|
||||
|
||||
|
||||
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||
if (other !is ObjString) return -2
|
||||
return this.value.compareTo(other.value)
|
||||
@ -114,6 +113,11 @@ data class ObjString(val value: String) : Obj() {
|
||||
addFn("upper") {
|
||||
thisAs<ObjString>().value.uppercase().let(::ObjString)
|
||||
}
|
||||
addFn("characters") {
|
||||
ObjList(
|
||||
thisAs<ObjString>().value.map { ObjChar(it) }.toMutableList()
|
||||
)
|
||||
}
|
||||
addFn("size") { ObjInt(thisAs<ObjString>().value.length.toLong()) }
|
||||
addFn("toReal") { ObjReal(thisAs<ObjString>().value.toDouble())}
|
||||
}
|
||||
|
@ -174,7 +174,14 @@ class Script(
|
||||
}
|
||||
}
|
||||
|
||||
val defaultImportManager: ImportManager by lazy { ImportManager(rootScope, SecurityManager.allowAll) }
|
||||
val defaultImportManager: ImportManager by lazy {
|
||||
ImportManager(rootScope, SecurityManager.allowAll).apply {
|
||||
addPackage("lyng.buffer") {
|
||||
it.addConst("Buffer", ObjBuffer.type)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
package net.sergeych.lyng.pacman
|
||||
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import net.sergeych.lyng.*
|
||||
import net.sergeych.synctools.ProtectedOp
|
||||
import net.sergeych.synctools.withLock
|
||||
|
||||
/**
|
||||
* Import manager allow to register packages with builder lambdas and act as an
|
||||
@ -17,7 +17,7 @@ import net.sergeych.lyng.*
|
||||
class ImportManager(
|
||||
rootScope: Scope = Script.defaultImportManager.newModule(),
|
||||
securityManager: SecurityManager = SecurityManager.allowAll
|
||||
): ImportProvider(rootScope, securityManager) {
|
||||
) : ImportProvider(rootScope, securityManager) {
|
||||
|
||||
private inner class Entry(
|
||||
val packageName: String,
|
||||
@ -36,7 +36,7 @@ class ImportManager(
|
||||
|
||||
|
||||
/**
|
||||
* Inner provider does not lock [access], the only difference; it is meant to be used
|
||||
* Inner provider does not lock [op], the only difference; it is meant to be used
|
||||
* exclusively by the coroutine that starts actual import chain
|
||||
*/
|
||||
private inner class InternalProvider : ImportProvider(rootScope) {
|
||||
@ -52,7 +52,9 @@ class ImportManager(
|
||||
|
||||
|
||||
private val imports = mutableMapOf<String, Entry>()
|
||||
private val access = Mutex()
|
||||
|
||||
val op = ProtectedOp()
|
||||
|
||||
|
||||
/**
|
||||
* Register new package that can be imported. It is not possible to unregister or
|
||||
@ -65,8 +67,8 @@ class ImportManager(
|
||||
* @param name package name
|
||||
* @param builder lambda to create actual package using the given [ModuleScope]
|
||||
*/
|
||||
suspend fun addPackage(name: String, builder: suspend (ModuleScope) -> Unit) {
|
||||
access.withLock {
|
||||
fun addPackage(name: String, builder: suspend (ModuleScope) -> Unit) {
|
||||
op.withLock {
|
||||
if (name in imports)
|
||||
throw IllegalArgumentException("Package $name already exists")
|
||||
imports[name] = Entry(name, builder)
|
||||
@ -77,8 +79,8 @@ class ImportManager(
|
||||
* Bulk [addPackage] with slightly better performance
|
||||
*/
|
||||
@Suppress("unused")
|
||||
suspend fun addPackages(registrationData: List<Pair<String, suspend (ModuleScope) -> Unit>>) {
|
||||
access.withLock {
|
||||
fun addPackages(registrationData: List<Pair<String, suspend (ModuleScope) -> Unit>>) {
|
||||
op.withLock {
|
||||
for (pp in registrationData) {
|
||||
if (pp.first in imports)
|
||||
throw IllegalArgumentException("Package ${pp.first} already exists")
|
||||
@ -89,7 +91,7 @@ class ImportManager(
|
||||
|
||||
/**
|
||||
* Perform actual import or return ready scope. __It must only be called when
|
||||
* [access] is locked__, e.g. only internally
|
||||
* [op] is locked__, e.g. only internally
|
||||
*/
|
||||
private suspend fun doImport(packageName: String, pos: Pos): ModuleScope {
|
||||
val entry = imports[packageName] ?: throw ImportException(pos, "package not found: $packageName")
|
||||
@ -102,8 +104,8 @@ class ImportManager(
|
||||
/**
|
||||
* Add packages that only need to compile [Source].
|
||||
*/
|
||||
suspend fun addSourcePackages(vararg sources: Source) {
|
||||
for( s in sources) {
|
||||
fun addSourcePackages(vararg sources: Source) {
|
||||
for (s in sources) {
|
||||
addPackage(s.extractPackageName()) {
|
||||
it.eval(s)
|
||||
}
|
||||
@ -114,12 +116,12 @@ class ImportManager(
|
||||
/**
|
||||
* Add source packages using package name as [Source.fileName], for simplicity
|
||||
*/
|
||||
suspend fun addTextPackages(vararg sourceTexts: String) {
|
||||
for( s in sourceTexts) {
|
||||
fun addTextPackages(vararg sourceTexts: String) {
|
||||
for (s in sourceTexts) {
|
||||
var source = Source("tmp", s)
|
||||
val packageName = source.extractPackageName()
|
||||
source = Source(packageName, s)
|
||||
addPackage(packageName) { it.eval(source)}
|
||||
addPackage(packageName) { it.eval(source) }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2460,4 +2460,57 @@ class ScriptTest {
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMaps() = runTest {
|
||||
eval(
|
||||
"""
|
||||
val map = Map( "a" => 1, "b" => 2 )
|
||||
assertEquals( 1, map["a"] )
|
||||
assertEquals( 2, map["b"] )
|
||||
assertEquals( null, map["c"] )
|
||||
map["c"] = 3
|
||||
assertEquals( 3, map["c"] )
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBuffer() = runTest {
|
||||
eval("""
|
||||
import lyng.buffer
|
||||
|
||||
assertEquals( 0, Buffer().size )
|
||||
assertEquals( 3, Buffer(1, 2, 3).size )
|
||||
assertEquals( 5, Buffer("hello").size )
|
||||
|
||||
val buffer = Buffer("Hello")
|
||||
assertEquals( 5, buffer.size)
|
||||
assertEquals('l'.code, buffer[2] )
|
||||
assertEquals('l'.code, buffer[3] )
|
||||
assertEquals("Hello", buffer.decodeUtf8())
|
||||
|
||||
buffer[2] = 101
|
||||
assertEquals(101, buffer[2])
|
||||
assertEquals("Heelo", buffer.decodeUtf8())
|
||||
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBufferCompare() = runTest {
|
||||
eval("""
|
||||
import lyng.buffer
|
||||
|
||||
println("Hello".characters())
|
||||
val b1 = Buffer("Hello")
|
||||
val b2 = Buffer("Hello".characters())
|
||||
|
||||
assertEquals( b1, b2 )
|
||||
val b3 = b1 + Buffer("!")
|
||||
assertEquals( "Hello!", b3.decodeUtf8())
|
||||
assert( b3 > b1 )
|
||||
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
}
|
@ -265,6 +265,11 @@ class BookTest {
|
||||
runDocTests("../docs/Map.md")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBuffer() = runTest {
|
||||
runDocTests("../docs/Buffer.md")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSampleBooks() = runTest {
|
||||
for (bt in Files.list(Paths.get("../docs/samples")).toList()) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user