Add immutable collections hierarchy with runtime/compiler/docs/tests
This commit is contained in:
parent
9417f8f0cc
commit
8b5e6ee993
@ -1,8 +1,8 @@
|
||||
# Array
|
||||
|
||||
It's an interface if the [Collection] that provides indexing access, like `array[3] = 0`.
|
||||
Array therefore implements [Iterable] too. The well known implementatino of the `Array` is
|
||||
[List].
|
||||
Array therefore implements [Iterable] too. Well known implementations of `Array` are
|
||||
[List] and [ImmutableList].
|
||||
|
||||
Array adds the following methods:
|
||||
|
||||
@ -35,3 +35,4 @@ To pre-sort and array use `Iterable.sorted*` or in-place `List.sort*` families,
|
||||
[Collection]: Collection.md
|
||||
[Iterable]: Iterable.md
|
||||
[List]: List.md
|
||||
[ImmutableList]: ImmutableList.md
|
||||
|
||||
@ -6,6 +6,12 @@ Is a [Iterable] with known `size`, a finite [Iterable]:
|
||||
val size
|
||||
}
|
||||
|
||||
`Collection` is a read/traversal contract shared by mutable and immutable collections.
|
||||
Concrete collection classes:
|
||||
|
||||
- Mutable: [List], [Set], [Map]
|
||||
- Immutable: [ImmutableList], [ImmutableSet], [ImmutableMap]
|
||||
|
||||
| name | description |
|
||||
|------------------------|------------------------------------------------------|
|
||||
|
||||
@ -17,3 +23,7 @@ See [List], [Set], [Iterable] and [Efficient Iterables in Kotlin Interop](Effici
|
||||
[Iterable]: Iterable.md
|
||||
[List]: List.md
|
||||
[Set]: Set.md
|
||||
[Map]: Map.md
|
||||
[ImmutableList]: ImmutableList.md
|
||||
[ImmutableSet]: ImmutableSet.md
|
||||
[ImmutableMap]: ImmutableMap.md
|
||||
|
||||
37
docs/ImmutableList.md
Normal file
37
docs/ImmutableList.md
Normal file
@ -0,0 +1,37 @@
|
||||
# ImmutableList built-in class
|
||||
|
||||
`ImmutableList` is an immutable, indexable list value.
|
||||
It implements [Array], therefore [Collection] and [Iterable].
|
||||
|
||||
Use it when API contracts require a list that cannot be mutated through aliases.
|
||||
|
||||
## Creating
|
||||
|
||||
val a = ImmutableList(1,2,3)
|
||||
val b = [1,2,3].toImmutable()
|
||||
val c = (1..3).toImmutableList()
|
||||
>>> void
|
||||
|
||||
## Converting
|
||||
|
||||
val i = ImmutableList(1,2,3)
|
||||
val m = i.toMutable()
|
||||
m += 4
|
||||
assertEquals( ImmutableList(1,2,3), i )
|
||||
assertEquals( [1,2,3,4], m )
|
||||
>>> void
|
||||
|
||||
## Members
|
||||
|
||||
| name | meaning |
|
||||
|---------------|-----------------------------------------|
|
||||
| `size` | number of elements |
|
||||
| `[index]` | element access by index |
|
||||
| `[Range]` | immutable slice |
|
||||
| `+` | append element(s), returns new immutable list |
|
||||
| `-` | remove element(s), returns new immutable list |
|
||||
| `toMutable()` | create mutable copy |
|
||||
|
||||
[Array]: Array.md
|
||||
[Collection]: Collection.md
|
||||
[Iterable]: Iterable.md
|
||||
36
docs/ImmutableMap.md
Normal file
36
docs/ImmutableMap.md
Normal file
@ -0,0 +1,36 @@
|
||||
# ImmutableMap built-in class
|
||||
|
||||
`ImmutableMap` is an immutable map of key-value pairs.
|
||||
It implements [Collection] and [Iterable] of [MapEntry].
|
||||
|
||||
## Creating
|
||||
|
||||
val a = ImmutableMap("a" => 1, "b" => 2)
|
||||
val b = Map("a" => 1, "b" => 2).toImmutable()
|
||||
val c = ["a" => 1, "b" => 2].toImmutableMap
|
||||
>>> void
|
||||
|
||||
## Converting
|
||||
|
||||
val i = ImmutableMap("a" => 1)
|
||||
val m = i.toMutable()
|
||||
m["a"] = 2
|
||||
assertEquals( 1, i["a"] )
|
||||
assertEquals( 2, m["a"] )
|
||||
>>> void
|
||||
|
||||
## Members
|
||||
|
||||
| name | meaning |
|
||||
|-----------------|------------------------------------------|
|
||||
| `size` | number of entries |
|
||||
| `[key]` | get value by key, or `null` if absent |
|
||||
| `getOrNull(key)`| same as `[key]` |
|
||||
| `keys` | list of keys |
|
||||
| `values` | list of values |
|
||||
| `+` | merge (rightmost wins), returns new immutable map |
|
||||
| `toMutable()` | create mutable copy |
|
||||
|
||||
[Collection]: Collection.md
|
||||
[Iterable]: Iterable.md
|
||||
[MapEntry]: Map.md
|
||||
34
docs/ImmutableSet.md
Normal file
34
docs/ImmutableSet.md
Normal file
@ -0,0 +1,34 @@
|
||||
# ImmutableSet built-in class
|
||||
|
||||
`ImmutableSet` is an immutable set of unique elements.
|
||||
It implements [Collection] and [Iterable].
|
||||
|
||||
## Creating
|
||||
|
||||
val a = ImmutableSet(1,2,3)
|
||||
val b = Set(1,2,3).toImmutable()
|
||||
val c = [1,2,3].toImmutableSet
|
||||
>>> void
|
||||
|
||||
## Converting
|
||||
|
||||
val i = ImmutableSet(1,2,3)
|
||||
val m = i.toMutable()
|
||||
m += 4
|
||||
assertEquals( ImmutableSet(1,2,3), i )
|
||||
assertEquals( Set(1,2,3,4), m )
|
||||
>>> void
|
||||
|
||||
## Members
|
||||
|
||||
| name | meaning |
|
||||
|---------------|-----------------------------------------------------|
|
||||
| `size` | number of elements |
|
||||
| `contains(x)` | membership test |
|
||||
| `+`, `union` | union, returns new immutable set |
|
||||
| `-`, `subtract` | subtraction, returns new immutable set |
|
||||
| `*`, `intersect` | intersection, returns new immutable set |
|
||||
| `toMutable()` | create mutable copy |
|
||||
|
||||
[Collection]: Collection.md
|
||||
[Iterable]: Iterable.md
|
||||
@ -147,12 +147,15 @@ Search for the first element that satisfies the given predicate:
|
||||
| fun/method | description |
|
||||
|------------------------|---------------------------------------------------------------------------------|
|
||||
| toList() | create a list from iterable |
|
||||
| toImmutableList() | create an immutable list from iterable |
|
||||
| toSet() | create a set from iterable |
|
||||
| toImmutableSet | create an immutable set from iterable |
|
||||
| contains(i) | check that iterable contains `i` |
|
||||
| `i in iterable` | same as `contains(i)` |
|
||||
| isEmpty() | check iterable is empty |
|
||||
| forEach(f) | call f for each element |
|
||||
| toMap() | create a map from list of key-value pairs (arrays of 2 items or like) |
|
||||
| toImmutableMap | create an immutable map from list of key-value pairs |
|
||||
| any(p) | true if any element matches predicate `p` |
|
||||
| all(p) | true if all elements match predicate `p` |
|
||||
| map(f) | create a list of values returned by `f` called for each element of the iterable |
|
||||
@ -206,16 +209,20 @@ For high-performance Kotlin-side interop and custom iterable implementation deta
|
||||
|
||||
## Implemented in classes:
|
||||
|
||||
- [List], [Range], [Buffer](Buffer.md), [BitBuffer], [Buffer], [Set], [RingBuffer]
|
||||
- [List], [ImmutableList], [Range], [Buffer](Buffer.md), [BitBuffer], [Buffer], [Set], [ImmutableSet], [Map], [ImmutableMap], [RingBuffer]
|
||||
|
||||
[Collection]: Collection.md
|
||||
|
||||
[List]: List.md
|
||||
[ImmutableList]: ImmutableList.md
|
||||
|
||||
[Flow]: parallelism.md#flow
|
||||
|
||||
[Range]: Range.md
|
||||
|
||||
[Set]: Set.md
|
||||
[ImmutableSet]: ImmutableSet.md
|
||||
[Map]: Map.md
|
||||
[ImmutableMap]: ImmutableMap.md
|
||||
|
||||
[RingBuffer]: RingBuffer.md
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
# List built-in class
|
||||
|
||||
Mutable list of any objects.
|
||||
For immutable list values, see [ImmutableList].
|
||||
|
||||
It's class in Lyng is `List`:
|
||||
|
||||
@ -197,3 +198,4 @@ It inherits from [Iterable] too and thus all iterable methods are applicable to
|
||||
[Range]: Range.md
|
||||
|
||||
[Iterable]: Iterable.md
|
||||
[ImmutableList]: ImmutableList.md
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
Map is a mutable collection of key-value pairs, where keys are unique. You can create maps in two ways:
|
||||
- with the constructor `Map(...)` or `.toMap()` helpers; and
|
||||
- with map literals using braces: `{ "key": value, id: expr, id: }`.
|
||||
For immutable map values, see [ImmutableMap].
|
||||
|
||||
When constructing from a list, each list item must be a [Collection] with exactly 2 elements, for example, a [List].
|
||||
|
||||
@ -177,3 +178,4 @@ Notes:
|
||||
- When you need computed or non-string keys, use the constructor form `Map(...)`, map literals with computed keys (if supported), or build entries with `=>` and then merge.
|
||||
|
||||
[Collection](Collection.md)
|
||||
[ImmutableMap]: ImmutableMap.md
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
# List built-in class
|
||||
# Set built-in class
|
||||
|
||||
Mutable set of any objects: a group of different objects, no repetitions.
|
||||
Sets are not ordered, order of appearance does not matter.
|
||||
For immutable set values, see [ImmutableSet].
|
||||
|
||||
val set = Set(1,2,3, "foo")
|
||||
assert( 1 in set )
|
||||
@ -92,3 +93,4 @@ Also, it inherits methods from [Iterable].
|
||||
|
||||
|
||||
[Range]: Range.md
|
||||
[ImmutableSet]: ImmutableSet.md
|
||||
|
||||
@ -14,7 +14,7 @@ __Other documents to read__ maybe after this one:
|
||||
- [time](time.md) and [parallelism](parallelism.md)
|
||||
- [parallelism] - multithreaded code, coroutines, etc.
|
||||
- Some class
|
||||
references: [List], [Set], [Map], [Real], [Range], [Iterable], [Iterator], [time manipulation](time.md), [Array], [RingBuffer], [Buffer].
|
||||
references: [List], [ImmutableList], [Set], [ImmutableSet], [Map], [ImmutableMap], [Real], [Range], [Iterable], [Iterator], [time manipulation](time.md), [Array], [RingBuffer], [Buffer].
|
||||
- Some samples: [combinatorics](samples/combinatorics.lyng.md), national vars and
|
||||
loops: [сумма ряда](samples/сумма_ряда.lyng.md). More at [samples folder](samples)
|
||||
|
||||
@ -785,6 +785,7 @@ Lyng has built-in mutable array class `List` with simple literals:
|
||||
|
||||
[List] is an implementation of the type `Array`, and through it `Collection` and [Iterable]. Please read [Iterable],
|
||||
many collection based methods are implemented there.
|
||||
For immutable list values, use `list.toImmutable()` and [ImmutableList].
|
||||
|
||||
Lists can contain any type of objects, lists too:
|
||||
|
||||
@ -967,6 +968,7 @@ Set are unordered collection of unique elements, see [Set]. Sets are [Iterable]
|
||||
>>> void
|
||||
|
||||
Please see [Set] for detailed description.
|
||||
For immutable set values, use `set.toImmutable()` and [ImmutableSet].
|
||||
|
||||
# Maps
|
||||
|
||||
@ -1027,6 +1029,7 @@ Notes:
|
||||
- When you need computed (expression) keys or non-string keys, use `Map(...)` constructor with entries, e.g. `Map( ("a" + "b") => 1 )`, then merge with a literal if needed: `{ base: } + (computedKey => 42)`.
|
||||
|
||||
Please see the [Map] reference for a deeper guide.
|
||||
For immutable map values, use `map.toImmutable()` and [ImmutableMap].
|
||||
|
||||
# Flow control operators
|
||||
|
||||
@ -1750,6 +1753,7 @@ Lambda avoid unnecessary execution if assertion is not failed. for example:
|
||||
| π | See [math](math.md) |
|
||||
|
||||
[List]: List.md
|
||||
[ImmutableList]: ImmutableList.md
|
||||
|
||||
[Testing]: Testing.md
|
||||
|
||||
@ -1766,8 +1770,10 @@ Lambda avoid unnecessary execution if assertion is not failed. for example:
|
||||
[string formatting]: https://github.com/sergeych/mp_stools?tab=readme-ov-file#sprintf-syntax-summary
|
||||
|
||||
[Set]: Set.md
|
||||
[ImmutableSet]: ImmutableSet.md
|
||||
|
||||
[Map]: Map.md
|
||||
[ImmutableMap]: ImmutableMap.md
|
||||
|
||||
[Buffer]: Buffer.md
|
||||
|
||||
|
||||
@ -4339,7 +4339,7 @@ class Compiler(
|
||||
val mapType = inferTypeDeclFromRef(entry.ref) ?: return TypeDecl.TypeAny to TypeDecl.TypeAny
|
||||
if (mapType is TypeDecl.Generic) {
|
||||
val base = mapType.name.substringAfterLast('.')
|
||||
if (base == "Map") {
|
||||
if (base == "Map" || base == "ImmutableMap") {
|
||||
val k = mapType.args.getOrNull(0) ?: TypeDecl.TypeAny
|
||||
val v = mapType.args.getOrNull(1) ?: TypeDecl.TypeAny
|
||||
addKey(k)
|
||||
@ -4374,7 +4374,7 @@ class Compiler(
|
||||
if (listType == TypeDecl.TypeAny || listType == TypeDecl.TypeNullableAny) return listType
|
||||
if (listType is TypeDecl.Generic) {
|
||||
val base = listType.name.substringAfterLast('.')
|
||||
if (base == "List" || base == "Array" || base == "Iterable") {
|
||||
if (base == "List" || base == "ImmutableList" || base == "Array" || base == "Iterable") {
|
||||
return listType.args.firstOrNull() ?: TypeDecl.TypeAny
|
||||
}
|
||||
}
|
||||
@ -4385,7 +4385,7 @@ class Compiler(
|
||||
val generic = typeDecl as? TypeDecl.Generic ?: return null
|
||||
val base = generic.name.substringAfterLast('.')
|
||||
return when (base) {
|
||||
"Set", "List", "Iterable", "Collection", "Array" -> generic.args.firstOrNull()
|
||||
"Set", "ImmutableSet", "List", "ImmutableList", "Iterable", "Collection", "Array" -> generic.args.firstOrNull()
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
@ -4669,6 +4669,58 @@ class Compiler(
|
||||
name == "next" && receiver is TypeDecl.Generic && base == "Iterator" -> {
|
||||
receiver.args.firstOrNull()
|
||||
}
|
||||
name == "toImmutableList" && receiver is TypeDecl.Generic && (base == "Iterable" || base == "Collection" || base == "Array" || base == "List" || base == "ImmutableList") -> {
|
||||
val arg = receiver.args.firstOrNull() ?: TypeDecl.TypeAny
|
||||
TypeDecl.Generic("ImmutableList", listOf(arg), false)
|
||||
}
|
||||
name == "toList" && receiver is TypeDecl.Generic && (base == "ImmutableList" || base == "List") -> {
|
||||
val arg = receiver.args.firstOrNull() ?: TypeDecl.TypeAny
|
||||
TypeDecl.Generic("List", listOf(arg), false)
|
||||
}
|
||||
name == "toMutable" && receiver is TypeDecl.Generic && base == "ImmutableList" -> {
|
||||
val arg = receiver.args.firstOrNull() ?: TypeDecl.TypeAny
|
||||
TypeDecl.Generic("List", listOf(arg), false)
|
||||
}
|
||||
name == "toImmutable" && receiver is TypeDecl.Generic && base == "List" -> {
|
||||
val arg = receiver.args.firstOrNull() ?: TypeDecl.TypeAny
|
||||
TypeDecl.Generic("ImmutableList", listOf(arg), false)
|
||||
}
|
||||
name == "toImmutableSet" && receiver is TypeDecl.Generic && (base == "Iterable" || base == "Collection" || base == "Set" || base == "ImmutableSet") -> {
|
||||
val arg = receiver.args.firstOrNull() ?: TypeDecl.TypeAny
|
||||
TypeDecl.Generic("ImmutableSet", listOf(arg), false)
|
||||
}
|
||||
name == "toSet" && receiver is TypeDecl.Generic && (base == "Iterable" || base == "Collection" || base == "Set" || base == "ImmutableSet") -> {
|
||||
val arg = receiver.args.firstOrNull() ?: TypeDecl.TypeAny
|
||||
TypeDecl.Generic("Set", listOf(arg), false)
|
||||
}
|
||||
name == "toMutable" && receiver is TypeDecl.Generic && base == "ImmutableSet" -> {
|
||||
val arg = receiver.args.firstOrNull() ?: TypeDecl.TypeAny
|
||||
TypeDecl.Generic("Set", listOf(arg), false)
|
||||
}
|
||||
name == "toImmutable" && receiver is TypeDecl.Generic && base == "Set" -> {
|
||||
val arg = receiver.args.firstOrNull() ?: TypeDecl.TypeAny
|
||||
TypeDecl.Generic("ImmutableSet", listOf(arg), false)
|
||||
}
|
||||
name == "toImmutableMap" && receiver is TypeDecl.Generic && base == "Iterable" -> {
|
||||
TypeDecl.Generic("ImmutableMap", listOf(TypeDecl.TypeAny, TypeDecl.TypeAny), false)
|
||||
}
|
||||
name == "toMap" && receiver is TypeDecl.Generic && base == "Iterable" -> {
|
||||
TypeDecl.Generic("Map", listOf(TypeDecl.TypeAny, TypeDecl.TypeAny), false)
|
||||
}
|
||||
name == "toMutable" && receiver is TypeDecl.Generic && base == "ImmutableMap" -> {
|
||||
val args = receiver.args.ifEmpty { listOf(TypeDecl.TypeAny, TypeDecl.TypeAny) }
|
||||
TypeDecl.Generic("Map", args, false)
|
||||
}
|
||||
name == "toImmutable" && receiver is TypeDecl.Generic && base == "Map" -> {
|
||||
val args = receiver.args.ifEmpty { listOf(TypeDecl.TypeAny, TypeDecl.TypeAny) }
|
||||
TypeDecl.Generic("ImmutableMap", args, false)
|
||||
}
|
||||
name == "toImmutable" && base == "List" -> TypeDecl.Simple("ImmutableList", false)
|
||||
name == "toMutable" && base == "ImmutableList" -> TypeDecl.Simple("List", false)
|
||||
name == "toImmutable" && base == "Set" -> TypeDecl.Simple("ImmutableSet", false)
|
||||
name == "toMutable" && base == "ImmutableSet" -> TypeDecl.Simple("Set", false)
|
||||
name == "toImmutable" && base == "Map" -> TypeDecl.Simple("ImmutableMap", false)
|
||||
name == "toMutable" && base == "ImmutableMap" -> TypeDecl.Simple("Map", false)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
@ -4739,6 +4791,10 @@ class Compiler(
|
||||
"matches" -> ObjBool.type
|
||||
"toInt",
|
||||
"toEpochSeconds" -> ObjInt.type
|
||||
"toImmutableList" -> ObjImmutableList.type
|
||||
"toImmutableSet" -> ObjImmutableSet.type
|
||||
"toImmutableMap" -> ObjImmutableMap.type
|
||||
"toImmutable" -> Obj.rootObjectType
|
||||
"toMutable" -> ObjMutableBuffer.type
|
||||
"seq" -> ObjFlow.type
|
||||
"encode" -> ObjBitBuffer.type
|
||||
@ -8248,8 +8304,11 @@ class Compiler(
|
||||
"Bool" -> ObjBool.type
|
||||
"Char" -> ObjChar.type
|
||||
"List" -> ObjList.type
|
||||
"ImmutableList" -> ObjImmutableList.type
|
||||
"Map" -> ObjMap.type
|
||||
"ImmutableMap" -> ObjImmutableMap.type
|
||||
"Set" -> ObjSet.type
|
||||
"ImmutableSet" -> ObjImmutableSet.type
|
||||
"Range", "IntRange" -> ObjRange.type
|
||||
"Iterator" -> ObjIterator
|
||||
"Iterable" -> ObjIterable
|
||||
|
||||
@ -522,9 +522,12 @@ class Script(
|
||||
addConst("Bool", ObjBool.type)
|
||||
addConst("Char", ObjChar.type)
|
||||
addConst("List", ObjList.type)
|
||||
addConst("ImmutableList", ObjImmutableList.type)
|
||||
addConst("Set", ObjSet.type)
|
||||
addConst("ImmutableSet", ObjImmutableSet.type)
|
||||
addConst("Range", ObjRange.type)
|
||||
addConst("Map", ObjMap.type)
|
||||
addConst("ImmutableMap", ObjImmutableMap.type)
|
||||
addConst("MapEntry", ObjMapEntry.type)
|
||||
@Suppress("RemoveRedundantQualifierName")
|
||||
addConst("Callable", Statement.type)
|
||||
|
||||
@ -4439,7 +4439,11 @@ class BytecodeCompiler(
|
||||
}
|
||||
val initClass = when (localTarget?.name) {
|
||||
"List" -> ObjList.type
|
||||
"ImmutableList" -> ObjImmutableList.type
|
||||
"Map" -> ObjMap.type
|
||||
"ImmutableMap" -> ObjImmutableMap.type
|
||||
"Set" -> ObjSet.type
|
||||
"ImmutableSet" -> ObjImmutableSet.type
|
||||
else -> null
|
||||
}
|
||||
val callee = compileRefWithFallback(ref.target, null, refPosOrCurrent(ref.target)) ?: return null
|
||||
@ -7304,8 +7308,11 @@ class BytecodeCompiler(
|
||||
"Bool" -> ObjBool.type
|
||||
"Char" -> ObjChar.type
|
||||
"List" -> ObjList.type
|
||||
"ImmutableList" -> ObjImmutableList.type
|
||||
"Map" -> ObjMap.type
|
||||
"ImmutableMap" -> ObjImmutableMap.type
|
||||
"Set" -> ObjSet.type
|
||||
"ImmutableSet" -> ObjImmutableSet.type
|
||||
"Range", "IntRange" -> ObjRange.type
|
||||
"Iterator" -> ObjIterator
|
||||
"Iterable" -> ObjIterable
|
||||
@ -7379,7 +7386,9 @@ class BytecodeCompiler(
|
||||
"iterator" -> ObjIterator
|
||||
"count" -> ObjInt.type
|
||||
"toSet" -> ObjSet.type
|
||||
"toImmutableSet" -> ObjImmutableSet.type
|
||||
"toMap" -> ObjMap.type
|
||||
"toImmutableMap" -> ObjImmutableMap.type
|
||||
"joinToString" -> ObjString.type
|
||||
"now",
|
||||
"truncateToSecond",
|
||||
@ -7406,6 +7415,8 @@ class BytecodeCompiler(
|
||||
"matches" -> ObjBool.type
|
||||
"toInt",
|
||||
"toEpochSeconds" -> ObjInt.type
|
||||
"toImmutableList" -> ObjImmutableList.type
|
||||
"toImmutable" -> Obj.rootObjectType
|
||||
"toMutable" -> ObjMutableBuffer.type
|
||||
"seq" -> ObjFlow.type
|
||||
"encode" -> ObjBitBuffer.type
|
||||
|
||||
@ -37,8 +37,16 @@ object StdlibDocsBootstrap {
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
val _list = net.sergeych.lyng.obj.ObjList.type
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
val _immutableList = net.sergeych.lyng.obj.ObjImmutableList.type
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
val _map = net.sergeych.lyng.obj.ObjMap.type
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
val _immutableMap = net.sergeych.lyng.obj.ObjImmutableMap.type
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
val _set = net.sergeych.lyng.obj.ObjSet.type
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
val _immutableSet = net.sergeych.lyng.obj.ObjImmutableSet.type
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
val _int = net.sergeych.lyng.obj.ObjInt.type
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
val _real = net.sergeych.lyng.obj.ObjReal.type
|
||||
|
||||
@ -0,0 +1,197 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
import net.sergeych.lynon.LynonDecoder
|
||||
import net.sergeych.lynon.LynonEncoder
|
||||
import net.sergeych.lynon.LynonType
|
||||
|
||||
class ObjImmutableList(items: List<Obj> = emptyList()) : Obj() {
|
||||
private val data: List<Obj> = items.toList()
|
||||
|
||||
override val objClass: ObjClass
|
||||
get() = type
|
||||
|
||||
override suspend fun equals(scope: Scope, other: Obj): Boolean {
|
||||
if (this === other) return true
|
||||
return when (other) {
|
||||
is ObjImmutableList -> data.size == other.data.size && data.indices.all { i -> data[i].equals(scope, other.data[i]) }
|
||||
else -> {
|
||||
if (other.isInstanceOf(ObjIterable)) compareTo(scope, other) == 0 else false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||
if (other is ObjImmutableList) {
|
||||
val mySize = data.size
|
||||
val otherSize = other.data.size
|
||||
val commonSize = minOf(mySize, otherSize)
|
||||
for (i in 0..<commonSize) {
|
||||
val d = data[i].compareTo(scope, other.data[i])
|
||||
if (d != 0) return d
|
||||
}
|
||||
return mySize.compareTo(otherSize)
|
||||
}
|
||||
if (other.isInstanceOf(ObjIterable)) {
|
||||
val it1 = data.iterator()
|
||||
val it2 = other.invokeInstanceMethod(scope, "iterator")
|
||||
val hasNext2 = it2.getInstanceMethod(scope, "hasNext")
|
||||
val next2 = it2.getInstanceMethod(scope, "next")
|
||||
while (it1.hasNext()) {
|
||||
if (!hasNext2.invoke(scope, it2).toBool()) return 1
|
||||
val d = it1.next().compareTo(scope, next2.invoke(scope, it2))
|
||||
if (d != 0) return d
|
||||
}
|
||||
return if (hasNext2.invoke(scope, it2).toBool()) -1 else 0
|
||||
}
|
||||
return -2
|
||||
}
|
||||
|
||||
override suspend fun getAt(scope: Scope, index: Obj): Obj {
|
||||
return when (index) {
|
||||
is ObjInt -> data[index.toInt()]
|
||||
is ObjRange -> {
|
||||
when {
|
||||
index.start is ObjInt && index.end is ObjInt -> {
|
||||
if (index.isEndInclusive)
|
||||
ObjImmutableList(data.subList(index.start.toInt(), index.end.toInt() + 1))
|
||||
else
|
||||
ObjImmutableList(data.subList(index.start.toInt(), index.end.toInt()))
|
||||
}
|
||||
index.isOpenStart && !index.isOpenEnd -> {
|
||||
if (index.isEndInclusive)
|
||||
ObjImmutableList(data.subList(0, index.end!!.toInt() + 1))
|
||||
else
|
||||
ObjImmutableList(data.subList(0, index.end!!.toInt()))
|
||||
}
|
||||
index.isOpenEnd && !index.isOpenStart -> ObjImmutableList(data.subList(index.start!!.toInt(), data.size))
|
||||
index.isOpenStart && index.isOpenEnd -> ObjImmutableList(data)
|
||||
else -> throw RuntimeException("Can't apply range for index: $index")
|
||||
}
|
||||
}
|
||||
else -> scope.raiseIllegalArgument("Illegal index object for immutable list: ${index.inspect(scope)}")
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun contains(scope: Scope, other: Obj): Boolean {
|
||||
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS && other is ObjInt) {
|
||||
var i = 0
|
||||
val sz = data.size
|
||||
while (i < sz) {
|
||||
val v = data[i]
|
||||
if (v is ObjInt && v.value == other.value) return true
|
||||
i++
|
||||
}
|
||||
return false
|
||||
}
|
||||
return data.contains(other)
|
||||
}
|
||||
|
||||
override suspend fun enumerate(scope: Scope, callback: suspend (Obj) -> Boolean) {
|
||||
for (item in data) {
|
||||
if (!callback(item)) break
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun plus(scope: Scope, other: Obj): Obj {
|
||||
return when {
|
||||
other is ObjImmutableList -> ObjImmutableList(data + other.data)
|
||||
other is ObjList -> ObjImmutableList(data + other.list)
|
||||
other.isInstanceOf(ObjIterable) && other !is ObjString && other !is ObjBuffer -> {
|
||||
val l = other.callMethod<ObjList>(scope, "toList")
|
||||
ObjImmutableList(data + l.list)
|
||||
}
|
||||
else -> ObjImmutableList(data + other)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun minus(scope: Scope, other: Obj): Obj {
|
||||
if (other !is ObjString && other !is ObjBuffer && other.isInstanceOf(ObjIterable)) {
|
||||
val toRemove = mutableSetOf<Obj>()
|
||||
other.enumerate(scope) {
|
||||
toRemove += it
|
||||
true
|
||||
}
|
||||
return ObjImmutableList(data.filterNot { toRemove.contains(it) })
|
||||
}
|
||||
val out = data.toMutableList()
|
||||
out.remove(other)
|
||||
return ObjImmutableList(out)
|
||||
}
|
||||
|
||||
override suspend fun serialize(scope: Scope, encoder: LynonEncoder, lynonType: LynonType?) {
|
||||
encoder.encodeAnyList(scope, data)
|
||||
}
|
||||
|
||||
override suspend fun lynonType(): LynonType = LynonType.List
|
||||
|
||||
override suspend fun defaultToString(scope: Scope): ObjString {
|
||||
return ObjString(buildString {
|
||||
append("ImmutableList(")
|
||||
var first = true
|
||||
for (v in data) {
|
||||
if (first) first = false else append(",")
|
||||
append(v.toString(scope).value)
|
||||
}
|
||||
append(")")
|
||||
})
|
||||
}
|
||||
|
||||
fun toMutableList(): MutableList<Obj> = data.toMutableList()
|
||||
|
||||
companion object {
|
||||
val type = object : ObjClass("ImmutableList", ObjArray) {
|
||||
override suspend fun callOn(scope: Scope): Obj {
|
||||
return ObjImmutableList(scope.args.list)
|
||||
}
|
||||
|
||||
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {
|
||||
return ObjImmutableList(decoder.decodeAnyList(scope))
|
||||
}
|
||||
}.apply {
|
||||
addPropertyDoc(
|
||||
name = "size",
|
||||
doc = "Number of elements in this immutable list.",
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { (this.thisObj as ObjImmutableList).data.size.toObj() }
|
||||
)
|
||||
addFnDoc(
|
||||
name = "toMutable",
|
||||
doc = "Create a mutable copy of this immutable list.",
|
||||
returns = type("lyng.List"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
ObjList(thisAs<ObjImmutableList>().toMutableList())
|
||||
}
|
||||
addFnDoc(
|
||||
name = "toImmutable",
|
||||
doc = "Return this immutable list.",
|
||||
returns = type("lyng.ImmutableList"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisObj
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.miniast.ParamDoc
|
||||
import net.sergeych.lyng.miniast.TypeGenericDoc
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
import net.sergeych.lynon.LynonDecoder
|
||||
import net.sergeych.lynon.LynonEncoder
|
||||
import net.sergeych.lynon.LynonType
|
||||
|
||||
class ObjImmutableMap(entries: Map<Obj, Obj> = emptyMap()) : Obj() {
|
||||
val map: Map<Obj, Obj> = LinkedHashMap(entries)
|
||||
|
||||
override val objClass get() = type
|
||||
|
||||
override suspend fun equals(scope: Scope, other: Obj): Boolean {
|
||||
if (this === other) return true
|
||||
val otherMap = when (other) {
|
||||
is ObjImmutableMap -> other.map
|
||||
is ObjMap -> other.map
|
||||
else -> return false
|
||||
}
|
||||
if (map.size != otherMap.size) return false
|
||||
for ((k, v) in map) {
|
||||
val ov = other.getAt(scope, k)
|
||||
if (ov === ObjNull && !other.contains(scope, k)) return false
|
||||
if (!v.equals(scope, ov)) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||
val otherMap = when (other) {
|
||||
is ObjImmutableMap -> other.map
|
||||
is ObjMap -> other.map
|
||||
else -> return -1
|
||||
}
|
||||
if (map == otherMap) return 0
|
||||
if (map.size != otherMap.size) return map.size.compareTo(otherMap.size)
|
||||
return map.toString().compareTo(otherMap.toString())
|
||||
}
|
||||
|
||||
override suspend fun getAt(scope: Scope, index: Obj): Obj = map[index] ?: ObjNull
|
||||
|
||||
override suspend fun contains(scope: Scope, other: Obj): Boolean = other in map
|
||||
|
||||
override suspend fun defaultToString(scope: Scope): ObjString {
|
||||
val rendered = buildString {
|
||||
append("ImmutableMap(")
|
||||
var first = true
|
||||
for ((k, v) in map) {
|
||||
if (!first) append(",")
|
||||
append(k.inspect(scope))
|
||||
append(" => ")
|
||||
append(v.toString(scope).value)
|
||||
first = false
|
||||
}
|
||||
append(")")
|
||||
}
|
||||
return ObjString(rendered)
|
||||
}
|
||||
|
||||
override suspend fun lynonType(): LynonType = LynonType.Map
|
||||
|
||||
override suspend fun serialize(scope: Scope, encoder: LynonEncoder, lynonType: LynonType?) {
|
||||
val keys = map.keys.map { it.toObj() }
|
||||
val values = map.values.map { it.toObj() }
|
||||
encoder.encodeAnyList(scope, keys)
|
||||
encoder.encodeAnyList(scope, values, fixedSize = true)
|
||||
}
|
||||
|
||||
override suspend fun toJson(scope: Scope): JsonElement {
|
||||
return JsonObject(map.map { it.key.toString(scope).value to it.value.toJson(scope) }.toMap())
|
||||
}
|
||||
|
||||
override suspend fun plus(scope: Scope, other: Obj): Obj {
|
||||
val out = LinkedHashMap(map)
|
||||
mergeIn(scope, out, other)
|
||||
return ObjImmutableMap(out)
|
||||
}
|
||||
|
||||
private suspend fun mergeIn(scope: Scope, out: MutableMap<Obj, Obj>, other: Obj) {
|
||||
when (other) {
|
||||
is ObjImmutableMap -> out.putAll(other.map)
|
||||
is ObjMap -> out.putAll(other.map)
|
||||
is ObjMapEntry -> out[other.key] = other.value
|
||||
is ObjList -> {
|
||||
for (e in other.list) {
|
||||
when (e) {
|
||||
is ObjMapEntry -> out[e.key] = e.value
|
||||
else -> {
|
||||
if (e.isInstanceOf(ObjArray)) {
|
||||
if (e.invokeInstanceMethod(scope, "size").toInt() != 2)
|
||||
scope.raiseIllegalArgument("Array element to merge into map must have 2 elements, got $e")
|
||||
out[e.getAt(scope, 0)] = e.getAt(scope, 1)
|
||||
} else {
|
||||
scope.raiseIllegalArgument("map can only be merged with MapEntry elements; got $e")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> scope.raiseIllegalArgument("map can only be merged with Map, ImmutableMap, MapEntry, or List<MapEntry>")
|
||||
}
|
||||
}
|
||||
|
||||
fun toMutableMapCopy(): MutableMap<Obj, Obj> = LinkedHashMap(map)
|
||||
|
||||
companion object {
|
||||
val type = object : ObjClass("ImmutableMap", ObjCollection) {
|
||||
override suspend fun callOn(scope: Scope): Obj {
|
||||
return ObjImmutableMap(ObjMap.listToMap(scope, scope.args.list))
|
||||
}
|
||||
|
||||
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj {
|
||||
val keys = decoder.decodeAnyList(scope)
|
||||
val values = decoder.decodeAnyList(scope, fixedSize = keys.size)
|
||||
if (keys.size != values.size) scope.raiseIllegalArgument("map keys and values should be same size")
|
||||
return ObjImmutableMap(keys.zip(values).toMap())
|
||||
}
|
||||
}.apply {
|
||||
addFnDoc(
|
||||
name = "getOrNull",
|
||||
doc = "Get value by key or return null if the key is absent.",
|
||||
params = listOf(ParamDoc("key")),
|
||||
returns = type("lyng.Any", nullable = true),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjImmutableMap>().map[args.firstAndOnly(pos)] ?: ObjNull
|
||||
}
|
||||
addPropertyDoc(
|
||||
name = "size",
|
||||
doc = "Number of entries in the immutable map.",
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjImmutableMap>().map.size.toObj() }
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "keys",
|
||||
doc = "List of keys in this immutable map.",
|
||||
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Any"))),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjImmutableMap>().map.keys.toObj() }
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "values",
|
||||
doc = "List of values in this immutable map.",
|
||||
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Any"))),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { ObjList(thisAs<ObjImmutableMap>().map.values.toMutableList()) }
|
||||
)
|
||||
addFnDoc(
|
||||
name = "iterator",
|
||||
doc = "Iterator over map entries as MapEntry objects.",
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
ObjKotlinIterator(thisAs<ObjImmutableMap>().map.entries.iterator())
|
||||
}
|
||||
addFnDoc(
|
||||
name = "toMutable",
|
||||
doc = "Create a mutable copy of this immutable map.",
|
||||
returns = type("lyng.Map"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
ObjMap(thisAs<ObjImmutableMap>().toMutableMapCopy())
|
||||
}
|
||||
addFnDoc(
|
||||
name = "toImmutable",
|
||||
doc = "Return this immutable map.",
|
||||
returns = type("lyng.ImmutableMap"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) { thisObj }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.miniast.ParamDoc
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
import net.sergeych.lynon.LynonDecoder
|
||||
import net.sergeych.lynon.LynonEncoder
|
||||
import net.sergeych.lynon.LynonType
|
||||
|
||||
class ObjImmutableSet(items: Collection<Obj> = emptyList()) : Obj() {
|
||||
private val data: Set<Obj> = LinkedHashSet(items)
|
||||
|
||||
override val objClass get() = type
|
||||
|
||||
override suspend fun equals(scope: Scope, other: Obj): Boolean {
|
||||
if (this === other) return true
|
||||
val otherSet = when (other) {
|
||||
is ObjImmutableSet -> other.data
|
||||
is ObjSet -> other.set
|
||||
else -> return false
|
||||
}
|
||||
if (data.size != otherSet.size) return false
|
||||
for (e in data) {
|
||||
if (!other.contains(scope, e)) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||
val otherSet = when (other) {
|
||||
is ObjImmutableSet -> other.data
|
||||
is ObjSet -> other.set
|
||||
else -> return -2
|
||||
}
|
||||
if (data == otherSet) return 0
|
||||
if (data.size != otherSet.size) return data.size.compareTo(otherSet.size)
|
||||
return data.toString().compareTo(otherSet.toString())
|
||||
}
|
||||
|
||||
override suspend fun contains(scope: Scope, other: Obj): Boolean = data.contains(other)
|
||||
|
||||
override suspend fun enumerate(scope: Scope, callback: suspend (Obj) -> Boolean) {
|
||||
for (item in data) {
|
||||
if (!callback(item)) break
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun plus(scope: Scope, other: Obj): Obj {
|
||||
val merged = LinkedHashSet(data)
|
||||
when {
|
||||
other is ObjImmutableSet -> merged.addAll(other.data)
|
||||
other is ObjSet -> merged.addAll(other.set)
|
||||
other is ObjString || other is ObjBuffer || !other.isInstanceOf(ObjIterable) -> merged.add(other)
|
||||
else -> other.enumerate(scope) { merged += it; true }
|
||||
}
|
||||
return ObjImmutableSet(merged)
|
||||
}
|
||||
|
||||
override suspend fun minus(scope: Scope, other: Obj): Obj {
|
||||
val out = LinkedHashSet(data)
|
||||
when {
|
||||
other is ObjImmutableSet -> out.removeAll(other.data)
|
||||
other is ObjSet -> out.removeAll(other.set)
|
||||
other is ObjString || other is ObjBuffer || !other.isInstanceOf(ObjIterable) -> out.remove(other)
|
||||
else -> other.enumerate(scope) { out.remove(it); true }
|
||||
}
|
||||
return ObjImmutableSet(out)
|
||||
}
|
||||
|
||||
override suspend fun mul(scope: Scope, other: Obj): Obj {
|
||||
val right = when (other) {
|
||||
is ObjImmutableSet -> other.data
|
||||
is ObjSet -> other.set
|
||||
else -> scope.raiseIllegalArgument("set operator * requires another set")
|
||||
}
|
||||
return ObjImmutableSet(data.intersect(right))
|
||||
}
|
||||
|
||||
override suspend fun lynonType(): LynonType = LynonType.Set
|
||||
|
||||
override suspend fun serialize(scope: Scope, encoder: LynonEncoder, lynonType: LynonType?) {
|
||||
encoder.encodeAnyList(scope, data.toList())
|
||||
}
|
||||
|
||||
fun toMutableSet(): MutableSet<Obj> = LinkedHashSet(data)
|
||||
|
||||
companion object {
|
||||
val type: ObjClass = object : ObjClass("ImmutableSet", ObjCollection) {
|
||||
override suspend fun callOn(scope: Scope): Obj {
|
||||
return ObjImmutableSet(scope.args.list)
|
||||
}
|
||||
|
||||
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj =
|
||||
ObjImmutableSet(decoder.decodeAnyList(scope))
|
||||
}.apply {
|
||||
addFnDoc(
|
||||
name = "size",
|
||||
doc = "Number of elements in this immutable set.",
|
||||
returns = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjImmutableSet>().data.size.toObj()
|
||||
}
|
||||
addFnDoc(
|
||||
name = "intersect",
|
||||
doc = "Intersection with another set. Returns a new immutable set.",
|
||||
params = listOf(ParamDoc("other")),
|
||||
returns = type("lyng.ImmutableSet"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjImmutableSet>().mul(requireScope(), args.firstAndOnly())
|
||||
}
|
||||
addFnDoc(
|
||||
name = "iterator",
|
||||
doc = "Iterator over elements of this immutable set.",
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
ObjKotlinObjIterator(thisAs<ObjImmutableSet>().data.iterator())
|
||||
}
|
||||
addFnDoc(
|
||||
name = "union",
|
||||
doc = "Union with another set or iterable. Returns a new immutable set.",
|
||||
params = listOf(ParamDoc("other")),
|
||||
returns = type("lyng.ImmutableSet"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjImmutableSet>().plus(requireScope(), args.firstAndOnly())
|
||||
}
|
||||
addFnDoc(
|
||||
name = "subtract",
|
||||
doc = "Subtract another set or iterable from this set. Returns a new immutable set.",
|
||||
params = listOf(ParamDoc("other")),
|
||||
returns = type("lyng.ImmutableSet"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjImmutableSet>().minus(requireScope(), args.firstAndOnly())
|
||||
}
|
||||
addFnDoc(
|
||||
name = "toMutable",
|
||||
doc = "Create a mutable copy of this immutable set.",
|
||||
returns = type("lyng.Set"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
ObjSet(thisAs<ObjImmutableSet>().toMutableSet())
|
||||
}
|
||||
addFnDoc(
|
||||
name = "toImmutable",
|
||||
doc = "Return this immutable set.",
|
||||
returns = type("lyng.ImmutableSet"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) { thisObj }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -50,6 +50,21 @@ val ObjIterable by lazy {
|
||||
ObjList(result)
|
||||
}
|
||||
|
||||
addFnDoc(
|
||||
name = "toImmutableList",
|
||||
doc = "Collect elements of this iterable into a new immutable list.",
|
||||
returns = type("lyng.ImmutableList"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val scope = requireScope()
|
||||
val result = mutableListOf<Obj>()
|
||||
val it = thisObj.invokeInstanceMethod(scope, "iterator")
|
||||
while (it.invokeInstanceMethod(scope, "hasNext").toBool()) {
|
||||
result.add(it.invokeInstanceMethod(scope, "next"))
|
||||
}
|
||||
ObjImmutableList(result)
|
||||
}
|
||||
|
||||
// it is not effective, but it is open:
|
||||
addFnDoc(
|
||||
name = "contains",
|
||||
@ -109,6 +124,28 @@ val ObjIterable by lazy {
|
||||
}
|
||||
)
|
||||
|
||||
addPropertyDoc(
|
||||
name = "toImmutableSet",
|
||||
doc = "Collect elements of this iterable into a new immutable set.",
|
||||
type = type("lyng.ImmutableSet"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
when (val self = this.thisObj) {
|
||||
is ObjImmutableSet -> self
|
||||
is ObjSet -> ObjImmutableSet(self.set)
|
||||
else -> {
|
||||
val result = mutableSetOf<Obj>()
|
||||
val scope = requireScope()
|
||||
val it = self.invokeInstanceMethod(scope, "iterator")
|
||||
while (it.invokeInstanceMethod(scope, "hasNext").toBool()) {
|
||||
result.add(it.invokeInstanceMethod(scope, "next"))
|
||||
}
|
||||
ObjImmutableSet(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
addPropertyDoc(
|
||||
name = "toMap",
|
||||
doc = "Collect pairs into a map using [0] as key and [1] as value for each element.",
|
||||
@ -128,6 +165,25 @@ val ObjIterable by lazy {
|
||||
}
|
||||
)
|
||||
|
||||
addPropertyDoc(
|
||||
name = "toImmutableMap",
|
||||
doc = "Collect pairs into an immutable map using [0] as key and [1] as value for each element.",
|
||||
type = type("lyng.ImmutableMap"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
val result = linkedMapOf<Obj, Obj>()
|
||||
val scope = requireScope()
|
||||
this.thisObj.enumerate(scope) { pair ->
|
||||
when (pair) {
|
||||
is ObjMapEntry -> result[pair.key] = pair.value
|
||||
else -> result[pair.getAt(scope, 0)] = pair.getAt(scope, 1)
|
||||
}
|
||||
true
|
||||
}
|
||||
ObjImmutableMap(result)
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
name = "associateBy",
|
||||
doc = "Build a map from elements using the lambda result as key.",
|
||||
|
||||
@ -562,6 +562,14 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
||||
}
|
||||
ObjInt((-1).toLong())
|
||||
}
|
||||
addFnDoc(
|
||||
name = "toImmutable",
|
||||
doc = "Create an immutable snapshot of this list.",
|
||||
returns = type("lyng.ImmutableList"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
ObjImmutableList(thisAs<ObjList>().list)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -107,8 +107,12 @@ class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
|
||||
|
||||
override suspend fun equals(scope: Scope, other: Obj): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is ObjMap) return false
|
||||
if (map.size != other.map.size) return false
|
||||
val otherSize = when (other) {
|
||||
is ObjMap -> other.map.size
|
||||
is ObjImmutableMap -> other.map.size
|
||||
else -> return false
|
||||
}
|
||||
if (map.size != otherSize) return false
|
||||
for ((k, v) in map) {
|
||||
val otherV = other.getAt(scope, k)
|
||||
if (otherV === ObjNull && !other.contains(scope, k)) return false
|
||||
@ -131,14 +135,16 @@ class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
|
||||
}
|
||||
|
||||
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||
if (other is ObjMap) {
|
||||
if (map == other.map) return 0
|
||||
if (map.size != other.map.size) return map.size.compareTo(other.map.size)
|
||||
val otherMap = when (other) {
|
||||
is ObjMap -> other.map
|
||||
is ObjImmutableMap -> other.map
|
||||
else -> return -1
|
||||
}
|
||||
if (map == otherMap) return 0
|
||||
if (map.size != otherMap.size) return map.size.compareTo(otherMap.size)
|
||||
// for same size, if they are not equal, we don't have a stable order
|
||||
// but let's try to be consistent
|
||||
return map.toString().compareTo(other.map.toString())
|
||||
}
|
||||
return -1
|
||||
return map.toString().compareTo(otherMap.toString())
|
||||
}
|
||||
|
||||
override suspend fun defaultToString(scope: Scope): ObjString {
|
||||
@ -311,6 +317,14 @@ class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
|
||||
) {
|
||||
ObjKotlinIterator(thisAs<ObjMap>().map.entries.iterator())
|
||||
}
|
||||
addFnDoc(
|
||||
name = "toImmutable",
|
||||
doc = "Create an immutable snapshot of this map.",
|
||||
returns = type("lyng.ImmutableMap"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
ObjImmutableMap(thisAs<ObjMap>().map)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -334,6 +348,11 @@ class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
|
||||
map[k] = v
|
||||
}
|
||||
}
|
||||
is ObjImmutableMap -> {
|
||||
for ((k, v) in other.map) {
|
||||
map[k] = v
|
||||
}
|
||||
}
|
||||
is ObjMapEntry -> {
|
||||
map[other.key] = other.value
|
||||
}
|
||||
|
||||
@ -40,8 +40,12 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
|
||||
|
||||
override suspend fun equals(scope: Scope, other: Obj): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is ObjSet) return false
|
||||
if (set.size != other.set.size) return false
|
||||
val otherSet = when (other) {
|
||||
is ObjSet -> other.set
|
||||
is ObjImmutableSet -> other.toMutableSet()
|
||||
else -> return false
|
||||
}
|
||||
if (set.size != otherSet.size) return false
|
||||
// Sets are equal if all my elements are in other and vice versa
|
||||
// contains() in ObjSet uses equals(scope, ...), so we need to be careful
|
||||
for (e in set) {
|
||||
@ -115,10 +119,11 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
|
||||
}
|
||||
|
||||
override suspend fun mul(scope: Scope, other: Obj): Obj {
|
||||
return if (other is ObjSet) {
|
||||
ObjSet(set.intersect(other.set).toMutableSet())
|
||||
} else
|
||||
scope.raiseIllegalArgument("set operator * requires another set")
|
||||
return when (other) {
|
||||
is ObjSet -> ObjSet(set.intersect(other.set).toMutableSet())
|
||||
is ObjImmutableSet -> ObjSet(set.intersect(other.toMutableSet()).toMutableSet())
|
||||
else -> scope.raiseIllegalArgument("set operator * requires another set")
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun minus(scope: Scope, other: Obj): Obj {
|
||||
@ -144,12 +149,14 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
|
||||
}
|
||||
|
||||
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||
if (other is ObjSet) {
|
||||
if (set == other.set) return 0
|
||||
if (set.size != other.set.size) return set.size.compareTo(other.set.size)
|
||||
return set.toString().compareTo(other.set.toString())
|
||||
val otherSet = when (other) {
|
||||
is ObjSet -> other.set
|
||||
is ObjImmutableSet -> other.toMutableSet()
|
||||
else -> return -2
|
||||
}
|
||||
return -2
|
||||
if (set == otherSet) return 0
|
||||
if (set.size != otherSet.size) return set.size.compareTo(otherSet.size)
|
||||
return set.toString().compareTo(otherSet.toString())
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
@ -233,6 +240,14 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
|
||||
for( x in args.list ) set -= x
|
||||
if( n == set.size ) ObjFalse else ObjTrue
|
||||
}
|
||||
addFnDoc(
|
||||
name = "toImmutable",
|
||||
doc = "Create an immutable snapshot of this set.",
|
||||
returns = type("lyng.ImmutableSet"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
ObjImmutableSet(thisAs<ObjSet>().set)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
107
lynglib/src/commonTest/kotlin/ImmutableCollectionsTest.kt
Normal file
107
lynglib/src/commonTest/kotlin/ImmutableCollectionsTest.kt
Normal file
@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2026 Sergey S. Chernov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import net.sergeych.lyng.eval
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertFails
|
||||
|
||||
class ImmutableCollectionsTest {
|
||||
@Test
|
||||
fun immutableListSnapshotAndConversion() = runTest {
|
||||
eval(
|
||||
"""
|
||||
val src = [1,2,3]
|
||||
val imm = src.toImmutable()
|
||||
assert(imm is ImmutableList<Int>)
|
||||
assert(imm is Array<Int>)
|
||||
assert(imm is Collection<Int>)
|
||||
assert(imm is Iterable<Int>)
|
||||
|
||||
src += 4
|
||||
assertEquals(3, imm.size)
|
||||
assertEquals([1,2,3], imm.toMutable())
|
||||
assertEquals([1,2,3], (1..3).toImmutableList().toMutable())
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun immutableSetSnapshotAndConversion() = runTest {
|
||||
eval(
|
||||
"""
|
||||
val src = Set(1,2,3)
|
||||
val imm = src.toImmutable()
|
||||
assert(imm is ImmutableSet<Int>)
|
||||
assert(imm is Collection<Int>)
|
||||
assert(imm is Iterable<Int>)
|
||||
src += 4
|
||||
assertEquals(3, imm.size)
|
||||
assertEquals(Set(1,2,3), imm.toMutable())
|
||||
assertEquals(Set(1,2,3), [1,2,3].toImmutableSet.toMutable())
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun immutableMapSnapshotAndConversion() = runTest {
|
||||
eval(
|
||||
"""
|
||||
val src = Map("a" => 1, "b" => 2)
|
||||
val imm = src.toImmutable()
|
||||
assert(imm is ImmutableMap<String,Int>)
|
||||
assert(imm is Collection<MapEntry<String,Int>>)
|
||||
assert(imm is Iterable<MapEntry<String,Int>>)
|
||||
src["a"] = 100
|
||||
assertEquals(1, imm["a"])
|
||||
assertEquals(Map("a" => 1, "b" => 2), imm.toMutable())
|
||||
|
||||
val imm2 = ["x" => 10, "y" => 20].toImmutableMap
|
||||
assertEquals(10, imm2["x"])
|
||||
assertEquals(Map("x" => 10, "y" => 20), imm2.toMutable())
|
||||
"""
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun immutableCollectionsRejectMutationOps() = runTest {
|
||||
assertFails {
|
||||
eval(
|
||||
"""
|
||||
val xs = ImmutableList(1,2,3)
|
||||
xs += 4
|
||||
"""
|
||||
)
|
||||
}
|
||||
assertFails {
|
||||
eval(
|
||||
"""
|
||||
val s = ImmutableSet(1,2,3)
|
||||
s += 4
|
||||
"""
|
||||
)
|
||||
}
|
||||
assertFails {
|
||||
eval(
|
||||
"""
|
||||
val m = ImmutableMap("a" => 1)
|
||||
m["a"] = 10
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -12,6 +12,11 @@ extern class Iterable<T> {
|
||||
fun forEach(action: (T)->Void): Void
|
||||
fun map<R>(transform: (T)->R): List<R>
|
||||
fun toList(): List<T>
|
||||
fun toImmutableList(): ImmutableList<T>
|
||||
val toSet: Set<T>
|
||||
val toImmutableSet: ImmutableSet<T>
|
||||
val toMap: Map<Object,Object>
|
||||
val toImmutableMap: ImmutableMap<Object,Object>
|
||||
}
|
||||
|
||||
extern class Iterator<T> {
|
||||
@ -28,13 +33,19 @@ class KotlinIterator<T> : Iterator<T> {
|
||||
}
|
||||
|
||||
extern class Collection<T> : Iterable<T> {
|
||||
val size: Int
|
||||
}
|
||||
|
||||
extern class Array<T> : Collection<T> {
|
||||
}
|
||||
|
||||
extern class ImmutableList<T> : Array<T> {
|
||||
fun toMutable(): List<T>
|
||||
}
|
||||
|
||||
extern class List<T> : Array<T> {
|
||||
fun add(value: T, more...): Void
|
||||
fun toImmutable(): ImmutableList<T>
|
||||
}
|
||||
|
||||
extern class RingBuffer<T> : Iterable<T> {
|
||||
@ -44,12 +55,26 @@ extern class RingBuffer<T> : Iterable<T> {
|
||||
}
|
||||
|
||||
extern class Set<T> : Collection<T> {
|
||||
fun toImmutable(): ImmutableSet<T>
|
||||
}
|
||||
|
||||
extern class Map<K,V> {
|
||||
extern class ImmutableSet<T> : Collection<T> {
|
||||
fun toMutable(): Set<T>
|
||||
}
|
||||
|
||||
extern class MapEntry<K,V>
|
||||
extern class Map<K,V> : Collection<MapEntry<K,V>> {
|
||||
fun toImmutable(): ImmutableMap<K,V>
|
||||
}
|
||||
|
||||
extern class ImmutableMap<K,V> : Collection<MapEntry<K,V>> {
|
||||
fun getOrNull(key: K): V?
|
||||
fun toMutable(): Map<K,V>
|
||||
}
|
||||
|
||||
extern class MapEntry<K,V> : Array<Object> {
|
||||
val key: K
|
||||
val value: V
|
||||
}
|
||||
|
||||
// Built-in math helpers (implemented in host runtime).
|
||||
extern fun abs(x: Object): Real
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user