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
|
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].
|
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`)
|
Constructed map instance is of class `Map` and implements `Collection` (and therefore `Iterable`)
|
||||||
|
|
||||||
val map = Map( ["foo", 1], ["bar", "buzz"] )
|
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"] )
|
val map = Map( ["foo", 1], ["bar", "buzz"], [42, "answer"] )
|
||||||
assert( map["bar"] == "buzz")
|
assert( map["bar"] == "buzz")
|
||||||
assert( map[42] == "answer" )
|
assert( map[42] == "answer" )
|
||||||
assertThrows { map["nonexistent"] }
|
assertEquals( null, map["nonexisting"])
|
||||||
assert( map.getOrNull(101) == null )
|
assert( map.getOrNull(101) == null )
|
||||||
assert( map.getOrPut(911) { "nine-eleven" } == "nine-eleven" )
|
assert( map.getOrPut(911) { "nine-eleven" } == "nine-eleven" )
|
||||||
// now 91 entry is set:
|
// now 91 entry is set:
|
||||||
|
@ -530,7 +530,8 @@ The simplest way to concatenate lists is using `+` and `+=`:
|
|||||||
list += [2, 1]
|
list += [2, 1]
|
||||||
// or you can append a single element:
|
// or you can append a single element:
|
||||||
list += "end"
|
list += "end"
|
||||||
assert( list == [1, 2, 2, 1, "end"])
|
assertEquals( list, [1, 2, 2, 1, "end"])
|
||||||
|
void
|
||||||
>>> 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:
|
***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])
|
assertEquals( [2,3], list[1..<3])
|
||||||
>>> void
|
>>> void
|
||||||
|
|
||||||
|
# Buffers
|
||||||
|
|
||||||
|
[Buffer] is a special implementation of an [Array] of unsigned bytes, in the
|
||||||
|
[separate file](Buffer.md).
|
||||||
|
|
||||||
# Sets
|
# Sets
|
||||||
|
|
||||||
Set are unordered collection of unique elements, see [Set]. Sets are [Iterable] but have no indexing access.
|
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`):
|
Typical builtin types that are containers (e.g. support `conain`):
|
||||||
|
|
||||||
| class | notes |
|
| class | notes |
|
||||||
|------------|--------------------------------------------|
|
|------------|------------------------------------------------|
|
||||||
| Collection | contains an element (1) |
|
| Collection | contains an element (1) |
|
||||||
| Array | faster maybe that Collection's |
|
| Array | faster maybe that Collection's |
|
||||||
| List | faster than Array's |
|
| List | faster than Array's |
|
||||||
| String | character in string or substring in string |
|
| String | character in string or substring in string (3) |
|
||||||
| Range | object is included in the range (2) |
|
| Range | object is included in the range (2) |
|
||||||
|
|
||||||
(1)
|
(1)
|
||||||
: Iterable is not the container as it can be infinite
|
: 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
|
assert( "x" !in 'a'..'z') // string in character range: could be error
|
||||||
>>> void
|
>>> 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
|
So we recommend not to mix characters and string ranges; use `ch in str` that works
|
||||||
as expected:
|
as expected:
|
||||||
|
|
||||||
@ -1216,9 +1225,10 @@ Typical set of String functions includes:
|
|||||||
| s1 += s2 | self-modifying concatenation |
|
| s1 += s2 | self-modifying concatenation |
|
||||||
| toReal() | attempts to parse string as a Real value |
|
| toReal() | attempts to parse string as a Real value |
|
||||||
| toInt() | parse string to Int 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]: String.md
|
||||||
[string formatting]: https://github.com/sergeych/mp_stools?tab=readme-ov-file#sprintf-syntax-summary
|
[string formatting]: https://github.com/sergeych/mp_stools?tab=readme-ov-file#sprintf-syntax-summary
|
||||||
[Set]: Set.md
|
[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
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
group = "net.sergeych"
|
group = "net.sergeych"
|
||||||
version = "0.7.2-SNAPSHOT"
|
version = "0.7.3-SNAPSHOT"
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
@ -20,7 +20,7 @@ plugins {
|
|||||||
alias(libs.plugins.kotlinMultiplatform)
|
alias(libs.plugins.kotlinMultiplatform)
|
||||||
alias(libs.plugins.androidLibrary)
|
alias(libs.plugins.androidLibrary)
|
||||||
// alias(libs.plugins.vanniktech.mavenPublish)
|
// 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"
|
id("com.codingfeline.buildkonfig") version "0.17.1"
|
||||||
`maven-publish`
|
`maven-publish`
|
||||||
}
|
}
|
||||||
|
@ -274,9 +274,7 @@ class Compiler(
|
|||||||
if (x == ObjNull && isOptional) ObjNull.asReadonly
|
if (x == ObjNull && isOptional) ObjNull.asReadonly
|
||||||
else x.getAt(cxt, i).asMutable
|
else x.getAt(cxt, i).asMutable
|
||||||
}) { cxt, newValue ->
|
}) { cxt, newValue ->
|
||||||
val i = (index.execute(cxt) as? ObjInt)?.value?.toInt()
|
left.getter(cxt).value.putAt(cxt, index.execute(cxt), newValue)
|
||||||
?: cxt.raiseError("index must be integer")
|
|
||||||
left.getter(cxt).value.putAt(cxt, i, newValue)
|
|
||||||
}
|
}
|
||||||
} ?: run {
|
} ?: run {
|
||||||
// array literal
|
// array literal
|
||||||
|
@ -165,7 +165,6 @@ class CompilerContext(val tokens: List<Token>) {
|
|||||||
*/
|
*/
|
||||||
fun skipWsTokens(): Token {
|
fun skipWsTokens(): Token {
|
||||||
while( current().type in wstokens ) {
|
while( current().type in wstokens ) {
|
||||||
println("skipws ${current()}")
|
|
||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
return next()
|
return next()
|
||||||
|
@ -220,7 +220,7 @@ open class Obj {
|
|||||||
|
|
||||||
suspend fun getAt(scope: Scope, index: Int): Obj = getAt(scope, ObjInt(index.toLong()))
|
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")
|
scope.raiseNotImplemented("indexing")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,7 +280,18 @@ open class Obj {
|
|||||||
args.firstAndOnly().callOn(copy(Arguments(thisObj)))
|
args.firstAndOnly().callOn(copy(Arguments(thisObj)))
|
||||||
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 {
|
inline fun from(obj: Any?): Obj {
|
||||||
@ -349,7 +360,7 @@ object ObjNull : Obj() {
|
|||||||
scope.raiseNPE()
|
scope.raiseNPE()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun putAt(scope: Scope, index: Int, newValue: Obj) {
|
override suspend fun putAt(scope: Scope, index: Obj, newValue: Obj) {
|
||||||
scope.raiseNPE()
|
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
|
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 asStr get() = ObjString(value.toString())
|
||||||
override val longValue get() = value
|
override val longValue get() = value
|
||||||
override val doubleValue get() = value.toDouble()
|
override val doubleValue get() = value.toDouble()
|
||||||
@ -70,7 +70,7 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
|
|||||||
* assignment
|
* assignment
|
||||||
*/
|
*/
|
||||||
override suspend fun assign(scope: Scope, other: Obj): Obj? {
|
override suspend fun assign(scope: Scope, other: Obj): Obj? {
|
||||||
return if (other is ObjInt) {
|
return if (!isConst && other is ObjInt) {
|
||||||
value = other.value
|
value = other.value
|
||||||
this
|
this
|
||||||
} else null
|
} else null
|
||||||
@ -90,8 +90,8 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val Zero = ObjInt(0)
|
val Zero = ObjInt(0, true)
|
||||||
val One = ObjInt(1)
|
val One = ObjInt(1, true)
|
||||||
val type = ObjClass("Int")
|
val type = ObjClass("Int")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,6 @@ package net.sergeych.lyng
|
|||||||
|
|
||||||
class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
||||||
|
|
||||||
init {
|
|
||||||
for (p in objClass.parents)
|
|
||||||
parentInstances.add(p.defaultInstance())
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString(): String = "[${
|
override fun toString(): String = "[${
|
||||||
list.joinToString(separator = ", ") { it.inspect() }
|
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) {
|
override suspend fun putAt(scope: Scope, index: Obj, newValue: Obj) {
|
||||||
val i = index
|
list[index.toInt()] = newValue
|
||||||
list[i] = newValue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
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()
|
(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",
|
createField("add",
|
||||||
statement {
|
statement {
|
||||||
val l = thisAs<ObjList>().list
|
val l = thisAs<ObjList>().list
|
||||||
|
@ -39,7 +39,11 @@ class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
|
|||||||
override val objClass = type
|
override val objClass = type
|
||||||
|
|
||||||
override suspend fun getAt(scope: Scope, index: Obj): Obj =
|
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 {
|
override suspend fun contains(scope: Scope, other: Obj): Boolean {
|
||||||
return other in map
|
return other in map
|
||||||
|
@ -15,6 +15,31 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob
|
|||||||
return result.toString()
|
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 {
|
suspend fun containsRange(scope: Scope, other: ObjRange): Boolean {
|
||||||
if (start != null) {
|
if (start != null) {
|
||||||
// our start is not -∞ so other start should be GTE or is not contained:
|
// 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
|
// return i
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||||
if (other !is ObjString) return -2
|
if (other !is ObjString) return -2
|
||||||
return this.value.compareTo(other.value)
|
return this.value.compareTo(other.value)
|
||||||
@ -114,6 +113,11 @@ data class ObjString(val value: String) : Obj() {
|
|||||||
addFn("upper") {
|
addFn("upper") {
|
||||||
thisAs<ObjString>().value.uppercase().let(::ObjString)
|
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("size") { ObjInt(thisAs<ObjString>().value.length.toLong()) }
|
||||||
addFn("toReal") { ObjReal(thisAs<ObjString>().value.toDouble())}
|
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
|
package net.sergeych.lyng.pacman
|
||||||
|
|
||||||
import kotlinx.coroutines.sync.Mutex
|
|
||||||
import kotlinx.coroutines.sync.withLock
|
|
||||||
import net.sergeych.lyng.*
|
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
|
* Import manager allow to register packages with builder lambdas and act as an
|
||||||
@ -17,7 +17,7 @@ import net.sergeych.lyng.*
|
|||||||
class ImportManager(
|
class ImportManager(
|
||||||
rootScope: Scope = Script.defaultImportManager.newModule(),
|
rootScope: Scope = Script.defaultImportManager.newModule(),
|
||||||
securityManager: SecurityManager = SecurityManager.allowAll
|
securityManager: SecurityManager = SecurityManager.allowAll
|
||||||
): ImportProvider(rootScope, securityManager) {
|
) : ImportProvider(rootScope, securityManager) {
|
||||||
|
|
||||||
private inner class Entry(
|
private inner class Entry(
|
||||||
val packageName: String,
|
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
|
* exclusively by the coroutine that starts actual import chain
|
||||||
*/
|
*/
|
||||||
private inner class InternalProvider : ImportProvider(rootScope) {
|
private inner class InternalProvider : ImportProvider(rootScope) {
|
||||||
@ -52,7 +52,9 @@ class ImportManager(
|
|||||||
|
|
||||||
|
|
||||||
private val imports = mutableMapOf<String, Entry>()
|
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
|
* 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 name package name
|
||||||
* @param builder lambda to create actual package using the given [ModuleScope]
|
* @param builder lambda to create actual package using the given [ModuleScope]
|
||||||
*/
|
*/
|
||||||
suspend fun addPackage(name: String, builder: suspend (ModuleScope) -> Unit) {
|
fun addPackage(name: String, builder: suspend (ModuleScope) -> Unit) {
|
||||||
access.withLock {
|
op.withLock {
|
||||||
if (name in imports)
|
if (name in imports)
|
||||||
throw IllegalArgumentException("Package $name already exists")
|
throw IllegalArgumentException("Package $name already exists")
|
||||||
imports[name] = Entry(name, builder)
|
imports[name] = Entry(name, builder)
|
||||||
@ -77,8 +79,8 @@ class ImportManager(
|
|||||||
* Bulk [addPackage] with slightly better performance
|
* Bulk [addPackage] with slightly better performance
|
||||||
*/
|
*/
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
suspend fun addPackages(registrationData: List<Pair<String, suspend (ModuleScope) -> Unit>>) {
|
fun addPackages(registrationData: List<Pair<String, suspend (ModuleScope) -> Unit>>) {
|
||||||
access.withLock {
|
op.withLock {
|
||||||
for (pp in registrationData) {
|
for (pp in registrationData) {
|
||||||
if (pp.first in imports)
|
if (pp.first in imports)
|
||||||
throw IllegalArgumentException("Package ${pp.first} already exists")
|
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
|
* 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 {
|
private suspend fun doImport(packageName: String, pos: Pos): ModuleScope {
|
||||||
val entry = imports[packageName] ?: throw ImportException(pos, "package not found: $packageName")
|
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].
|
* Add packages that only need to compile [Source].
|
||||||
*/
|
*/
|
||||||
suspend fun addSourcePackages(vararg sources: Source) {
|
fun addSourcePackages(vararg sources: Source) {
|
||||||
for( s in sources) {
|
for (s in sources) {
|
||||||
addPackage(s.extractPackageName()) {
|
addPackage(s.extractPackageName()) {
|
||||||
it.eval(s)
|
it.eval(s)
|
||||||
}
|
}
|
||||||
@ -114,12 +116,12 @@ class ImportManager(
|
|||||||
/**
|
/**
|
||||||
* Add source packages using package name as [Source.fileName], for simplicity
|
* Add source packages using package name as [Source.fileName], for simplicity
|
||||||
*/
|
*/
|
||||||
suspend fun addTextPackages(vararg sourceTexts: String) {
|
fun addTextPackages(vararg sourceTexts: String) {
|
||||||
for( s in sourceTexts) {
|
for (s in sourceTexts) {
|
||||||
var source = Source("tmp", s)
|
var source = Source("tmp", s)
|
||||||
val packageName = source.extractPackageName()
|
val packageName = source.extractPackageName()
|
||||||
source = Source(packageName, s)
|
source = Source(packageName, s)
|
||||||
addPackage(packageName) { it.eval(source)}
|
addPackage(packageName) { it.eval(source) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2460,4 +2460,57 @@ class ScriptTest {
|
|||||||
""".trimIndent())
|
""".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")
|
runDocTests("../docs/Map.md")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBuffer() = runTest {
|
||||||
|
runDocTests("../docs/Buffer.md")
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testSampleBooks() = runTest {
|
fun testSampleBooks() = runTest {
|
||||||
for (bt in Files.list(Paths.get("../docs/samples")).toList()) {
|
for (bt in Files.list(Paths.get("../docs/samples")).toList()) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user