+collection functions (map, forEach, toList, toSet, isEmpty, etc,)
This commit is contained in:
Sergey Chernov 2025-06-15 17:26:32 +04:00
parent 8a4363bd84
commit a4448ab2ff
18 changed files with 467 additions and 146 deletions

13
docs/Collection.md Normal file
View File

@ -0,0 +1,13 @@
# Collection
Is a [Iterable] with known `size`, a finite [Iterable]:
class Collection : Iterable {
val size
}
See [List], [Set] and [Iterable]
[Iterable]: Iterable.md
[List]: List.md
[Set]: Set.md

View File

@ -1,13 +1,17 @@
# Iterable interface
The inteface which requires iterator to be implemented:
Iterable is a class that provides function that creates _the iterator_:
fun iterator(): Iterator
class Iterable {
abstract fun iterator()
}
Note that each call of `iterator()` must provide an independent iterator.
Iterator itself is a simple interface that should provide only to method:
interface Iterable {
fun hasNext(): Bool
class Iterator {
abstract fun hasNext(): Bool
fun next(): Obj
}
@ -15,19 +19,26 @@ Just remember at this stage typed declarations are not yet supported.
Having `Iterable` in base classes allows to use it in for loop. Also, each `Iterable` has some utility functions available:
## Instance methods
fun Iterable.toList(): List
fun Iterable.toSet(): Set
fun Iterable.indexOf(element): Int
fun Iterable.contains(element): Bool
fun Iterable.isEmpty(element): Bool
fun Iterable.forEach(block: (Any?)->Void ): Void
fun Iterable.map(block: (Any?)->Void ): List
## Abstract methods
fun iterator(): Iterator
## Instance methods
### toList()
Creates a list by iterating to the end. So, the Iterator should be finite to be used with it.
## Included in interfaces:
- Collection, Array, [List]
- [Collection], Array, [List]
## Implemented in classes:

View File

@ -116,6 +116,8 @@ Open end ranges remove head and tail elements:
: `+=` append either a single element, or all elements if the List or other Iterable
instance is appended. If you want to append an Iterable object itself, use `add` instead.
It inherits from [Iterable] too.
## Member inherited from Array
| name | meaning | type |
@ -130,8 +132,6 @@ instance is appended. If you want to append an Iterable object itself, use `add`
: end-inclisiveness allows to use negative indexes to, for exampe, remove several last elements, like
`list.removeRangeInclusive(-2, -1)` will remove two last elements.
# Notes
Could be rewritten using array as a class but List as the interface
[Range]: Range.md
[Range]: Range.md
[Iterable]: Iterable.md

94
docs/Set.md Normal file
View File

@ -0,0 +1,94 @@
# List 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.
val set = Set(1,2,3, "foo")
assert( 1 in set )
assert( "foo" in set)
assert( "bar" !in set)
>>> void
## Set is collection and therefore [Iterable]:
assert( Set(1,2) is Set)
assert( Set(1,2) is Iterable)
assert( Set(1,2) is Collection)
>>> void
So it supports all methods from [Iterable]; set is not, though, an [Array] and has
no indexing. Use [set.toList] as needed.
## Set operations
// Union
assertEquals( Set(1,2,3,4), Set(3, 1) + Set(2, 4))
// intersection
assertEquals( Set(1,4), Set(3, 1, 4).intersect(Set(2, 4, 1)) )
// or simple
assertEquals( Set(1,4), Set(3, 1, 4) * Set(2, 4, 1) )
// To find collection elements not present in another collection, use the
// subtract() or `-`:
assertEquals( Set( 1, 2), Set(1, 2, 4, 3) - Set(3, 4))
>>> void
## Adding elements
var s = Set()
s += 1
assertEquals( Set(1), s)
s += [3, 3, 4]
assertEquals( Set(3, 4, 1), s)
>>> void
## Removing elements
List is mutable, so it is possible to remove its contents. To remove a single element
by index use:
var s = Set(1,2,3)
s.remove(2)
assertEquals( s, Set(1,3) )
s = Set(1,2,3)
s.remove(2,1)
assertEquals( s, Set(3) )
>>> void
Note that `remove` returns true if at least one element was actually removed and false
if the set has not been changed.
## Comparisons and inclusion
Sets are only equal when contains exactly same elements, order, as was said, is not significant:
assert( Set(1, 2) == Set(2, 1) )
assert( Set(1, 2, 2) == Set(2, 1) )
assert( Set(1, 3) != Set(2, 1) )
assert( 1 in Set(5,1))
assert( 10 !in Set(5,1))
>>> void
## Members
| name | meaning | type |
|---------------------|--------------------------------------|-------|
| `size` | current 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

View File

@ -159,9 +159,8 @@ class Compiler(
cc.next()
isCall = true
val lambda =
parseExpression(cc) ?: throw ScriptError(t.pos, "expected valid lambda here")
parseLambdaExpression(cc)
println(cc.current())
cc.skipTokenOfType(Token.Type.RBRACE)
operand = Accessor { context ->
context.pos = next.pos
val v = left.getter(context).value
@ -172,7 +171,7 @@ class Compiler(
v.invokeInstanceMethod(
context,
next.value,
Arguments(listOf(lambda), true)
Arguments(listOf(lambda.getter(context).value), true)
), isMutable = false
)
}

View File

@ -265,6 +265,7 @@ open class Obj {
is Boolean -> ObjBool(obj)
Unit -> ObjVoid
null -> ObjNull
is Iterator<*> -> ObjKotlinIterator(obj)
else -> throw IllegalArgumentException("cannot convert to Obj: $obj")
}
}

View File

@ -0,0 +1,38 @@
package net.sergeych.lyng
val ObjArray by lazy {
/**
* Array abstract class is a [ObjCollection] with `getAt` method.
*/
ObjClass("Array", ObjCollection).apply {
// we can create iterators using size/getat:
addFn("iterator") {
ObjArrayIterator(thisObj).also { it.init(this) }
}
addFn("contains", isOpen = true) {
val obj = args.firstAndOnly()
for (i in 0..<thisObj.invokeInstanceMethod(this, "size").toInt()) {
if (thisObj.getAt(this, ObjInt(i.toLong())).compareTo(this, obj) == 0) return@addFn ObjTrue
}
ObjFalse
}
addFn("last") {
thisObj.invokeInstanceMethod(
this,
"getAt",
(thisObj.invokeInstanceMethod(this, "size").toInt() - 1).toObj()
)
}
addFn("lastIndex") { (thisObj.invokeInstanceMethod(this, "size").toInt() - 1).toObj() }
addFn("indices") {
ObjRange(0.toObj(), thisObj.invokeInstanceMethod(this, "size"), false)
}
}
}

View File

@ -0,0 +1,32 @@
package net.sergeych.lyng
class ObjArrayIterator(val array: Obj) : Obj() {
override val objClass: ObjClass by lazy { type }
private var nextIndex = 0
private var lastIndex = 0
suspend fun init(context: Context) {
nextIndex = 0
lastIndex = array.invokeInstanceMethod(context, "size").toInt()
ObjVoid
}
companion object {
val type by lazy {
ObjClass("ArrayIterator", ObjIterator).apply {
addFn("next") {
val self = thisAs<ObjArrayIterator>()
if (self.nextIndex < self.lastIndex) {
self.array.invokeInstanceMethod(this, "getAt", (self.nextIndex++).toObj())
} else raiseError(ObjIterationFinishedException(this))
}
addFn("hasNext") {
val self = thisAs<ObjArrayIterator>()
if (self.nextIndex < self.lastIndex) ObjTrue else ObjFalse
}
}
}
}
}

View File

@ -69,118 +69,4 @@ open class ObjClass(
?: throw ScriptError(atPos, "symbol doesn't exist: $name")
}
/**
* Abstract class that must provide `iterator` method that returns [ObjIterator] instance.
*/
val ObjIterable by lazy {
ObjClass("Iterable").apply {
addFn("toList") {
val result = mutableListOf<Obj>()
val iterator = thisObj.invokeInstanceMethod(this, "iterator")
while (iterator.invokeInstanceMethod(this, "hasNext").toBool())
result += iterator.invokeInstanceMethod(this, "next")
// val next = iterator.getMemberOrNull("next")!!
// val hasNext = iterator.getMemberOrNull("hasNext")!!
// while( hasNext.invoke(this, iterator).toBool() )
// result += next.invoke(this, iterator)
ObjList(result)
}
}
}
/**
* Collection is an iterator with `size`]
*/
val ObjCollection by lazy {
val i: ObjClass = ObjIterable
ObjClass("Collection", i).apply {
// it is not effective, but it is open:
addFn("contains", isOpen = true) {
val obj = args.firstAndOnly()
val it = thisObj.invokeInstanceMethod(this, "iterator")
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
if( obj.compareTo(this, it.invokeInstanceMethod(this, "next")) == 0 )
return@addFn ObjTrue
}
ObjFalse
}
}
}
val ObjIterator by lazy { ObjClass("Iterator") }
class ObjArrayIterator(val array: Obj) : Obj() {
override val objClass: ObjClass by lazy { type }
private var nextIndex = 0
private var lastIndex = 0
suspend fun init(context: Context) {
nextIndex = 0
lastIndex = array.invokeInstanceMethod(context, "size").toInt()
ObjVoid
}
companion object {
val type by lazy {
ObjClass("ArrayIterator", ObjIterator).apply {
addFn("next") {
val self = thisAs<ObjArrayIterator>()
if (self.nextIndex < self.lastIndex) {
self.array.invokeInstanceMethod(this, "getAt", (self.nextIndex++).toObj())
} else raiseError(ObjIterationFinishedException(this))
}
addFn("hasNext") {
val self = thisAs<ObjArrayIterator>()
if (self.nextIndex < self.lastIndex) ObjTrue else ObjFalse
}
}
}
}
}
val ObjArray by lazy {
/**
* Array abstract class is a [ObjCollection] with `getAt` method.
*/
ObjClass("Array", ObjCollection).apply {
// we can create iterators using size/getat:
addFn("iterator") {
ObjArrayIterator(thisObj).also { it.init(this) }
}
addFn("contains", isOpen = true) {
val obj = args.firstAndOnly()
for (i in 0..<thisObj.invokeInstanceMethod(this, "size").toInt()) {
if (thisObj.getAt(this, ObjInt(i.toLong())).compareTo(this, obj) == 0) return@addFn ObjTrue
}
ObjFalse
}
addFn("last") {
thisObj.invokeInstanceMethod(
this,
"getAt",
(thisObj.invokeInstanceMethod(this, "size").toInt() - 1).toObj()
)
}
addFn("lastIndex") { (thisObj.invokeInstanceMethod(this, "size").toInt() - 1).toObj() }
addFn("indices") {
ObjRange(0.toObj(), thisObj.invokeInstanceMethod(this, "size"), false)
}
}
}

View File

@ -0,0 +1,8 @@
package net.sergeych.lyng
/**
* Collection is an iterator with `size`]
*/
val ObjCollection = ObjClass("Collection", ObjIterable).apply {
}

View File

@ -0,0 +1,80 @@
package net.sergeych.lyng
/**
* Abstract class that must provide `iterator` method that returns [ObjIterator] instance.
*/
val ObjIterable by lazy {
ObjClass("Iterable").apply {
addFn("toList") {
val result = mutableListOf<Obj>()
val iterator = thisObj.invokeInstanceMethod(this, "iterator")
while (iterator.invokeInstanceMethod(this, "hasNext").toBool())
result += iterator.invokeInstanceMethod(this, "next")
ObjList(result)
}
// it is not effective, but it is open:
addFn("contains", isOpen = true) {
val obj = args.firstAndOnly()
val it = thisObj.invokeInstanceMethod(this, "iterator")
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
if (obj.compareTo(this, it.invokeInstanceMethod(this, "next")) == 0)
return@addFn ObjTrue
}
ObjFalse
}
addFn("indexOf", isOpen = true) {
val obj = args.firstAndOnly()
var index = 0
val it = thisObj.invokeInstanceMethod(this, "iterator")
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
if (obj.compareTo(this, it.invokeInstanceMethod(this, "next")) == 0)
return@addFn ObjInt(index.toLong())
index++
}
ObjInt(-1L)
}
addFn("toSet") {
val result = mutableSetOf<Obj>()
val it = thisObj.invokeInstanceMethod(this, "iterator")
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
result += it.invokeInstanceMethod(this, "next")
}
ObjSet(result)
}
addFn("forEach", isOpen = true) {
val it = thisObj.invokeInstanceMethod(this, "iterator")
val fn = requiredArg<Statement>(0)
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
val x = it.invokeInstanceMethod(this, "next")
fn.execute(this.copy(Arguments(listOf(x))))
}
ObjVoid
}
addFn("map", isOpen = true) {
val it = thisObj.invokeInstanceMethod(this, "iterator")
val fn = requiredArg<Statement>(0)
val result = mutableListOf<Obj>()
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
val x = it.invokeInstanceMethod(this, "next")
result += fn.execute(this.copy(Arguments(listOf(x))))
}
ObjList(result)
}
addFn("isEmpty") {
ObjBool(
thisObj.invokeInstanceMethod(this, "iterator")
.invokeInstanceMethod(this, "hasNext").toBool()
.not()
)
}
}
}

View File

@ -0,0 +1,3 @@
package net.sergeych.lyng
val ObjIterator by lazy { ObjClass("Iterator") }

View File

@ -0,0 +1,14 @@
package net.sergeych.lyng
class ObjKotlinIterator(val iterator: Iterator<Any?>): Obj() {
override val objClass = type
companion object {
val type = ObjClass("KotlinIterator", ObjIterator).apply {
addFn("next") { thisAs<ObjKotlinIterator>().iterator.next().toObj() }
addFn("hasNext") { thisAs<ObjKotlinIterator>().iterator.hasNext().toObj() }
}
}
}

View File

@ -211,4 +211,6 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
}
}
}
}
}

View File

@ -0,0 +1,97 @@
package net.sergeych.lyng
class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
override val objClass = type
override suspend fun contains(context: Context, other: Obj): Boolean {
return set.contains(other)
}
override suspend fun plus(context: Context, other: Obj): Obj {
return ObjSet(
if (other is ObjSet)
(set + other.set).toMutableSet()
else
(set + other).toMutableSet()
)
}
override suspend fun plusAssign(context: Context, other: Obj): Obj {
when (other) {
is ObjSet -> {
set += other.set
}
is ObjList -> {
set += other.list
}
else -> {
if (other.isInstanceOf(ObjIterable)) {
val i = other.invokeInstanceMethod(context, "iterable")
while (i.invokeInstanceMethod(context, "hasNext").toBool()) {
set += i.invokeInstanceMethod(context, "next")
}
}
set += other
}
}
return this
}
override suspend fun mul(context: Context, other: Obj): Obj {
return if (other is ObjSet) {
ObjSet(set.intersect(other.set).toMutableSet())
} else
context.raiseArgumentError("set operator * requires another set")
}
override suspend fun minus(context: Context, other: Obj): Obj {
if (other !is ObjSet)
context.raiseArgumentError("set operator - requires another set")
return ObjSet(set.minus(other.set).toMutableSet())
}
override fun toString(): String {
return "Set(${set.joinToString(", ")})"
}
override suspend fun compareTo(context: Context, other: Obj): Int {
return if (other !is ObjSet) -1
else {
if (set == other.set) 0
else -1
}
}
companion object {
val type = object : ObjClass("Set", ObjCollection) {
override suspend fun callOn(context: Context): Obj {
return ObjSet(context.args.list.toMutableSet())
}
}.apply {
addFn("size") {
thisAs<ObjSet>().set.size.toObj()
}
addFn("intersect") {
thisAs<ObjSet>().mul(this, args.firstAndOnly())
}
addFn("iterator") {
thisAs<ObjSet>().set.iterator().toObj()
}
addFn("union") {
thisAs<ObjSet>().plus(this, args.firstAndOnly())
}
addFn("subtract") {
thisAs<ObjSet>().minus(this, args.firstAndOnly())
}
addFn("remove") {
val set = thisAs<ObjSet>().set
val n = set.size
for( x in args.list ) set -= x
if( n == set.size ) ObjFalse else ObjTrue
}
}
}
}

View File

@ -152,11 +152,13 @@ class Script(
addConst("Bool", ObjBool.type)
addConst("Char", ObjChar.type)
addConst("List", ObjList.type)
addConst("Set", ObjSet.type)
addConst("Range", ObjRange.type)
@Suppress("RemoveRedundantQualifierName")
addConst("Callable", Statement.type)
// interfaces
addConst("Iterable", ObjIterable)
addConst("Collection", ObjCollection)
addConst("Array", ObjArray)
addConst("Class", ObjClassType)
addConst("Object", Obj().objClass)

View File

@ -1221,21 +1221,6 @@ class ScriptTest {
println(a)
}
@Test
fun iterableList() = runTest {
// 473
eval(
"""
for( i in 0..<1024 ) {
val list = (1..1024).toList()
assert(list.size == 1024)
assert(list[0] == 1)
assert(list.last == 1024)
}
""".trimIndent()
)
}
@Test
fun testLambdaWithIt1() = runTest {
eval(
@ -2206,4 +2191,55 @@ class ScriptTest {
)
}
@Test
fun testCollectionStructure() = runTest {
eval(
"""
val list = [1,2,3]
assert( 1 in list )
assert( list.indexOf(3) == 2 )
assert( list.indexOf(5) == -1 )
assert( list is List )
assert( list is Array )
assert( list is Iterable )
assert( list is Collection )
val other = []
list.forEach { other += it }
assertEquals( list, other )
assert( list.isEmpty() == false )
assertEquals( [10, 20, 30], list.map { it * 10 } )
assertEquals( [10, 20, 30], (1..3).map { it * 10 } )
""".trimIndent()
)
}
@Test
fun testSet() = runTest {
eval(
"""
val set = Set(1,2,3)
assert( set.contains(1) )
assert( 1 in set )
assert(set is Set)
assert(set is Iterable)
assert(set is Collection)
println(set)
for( x in set ) println(x)
assert([1,2,3] == set.toList())
set += 4
assert(set.toList() == [1,2,3,4])
assert(set == Set(1,2,3,4))
val s1 = [1, 2].toSet()
assertEquals( Set(1,2), s1 * set)
""".trimIndent()
)
}
}

View File

@ -255,6 +255,11 @@ class BookTest {
runDocTests("../docs/Range.md")
}
@Test
fun testSet() = runTest {
runDocTests("../docs/Set.md")
}
@Test
fun testSampleBooks() = runTest {
for (bt in Files.list(Paths.get("../docs/samples")).toList()) {