diff --git a/docs/Collection.md b/docs/Collection.md new file mode 100644 index 0000000..e4d28a0 --- /dev/null +++ b/docs/Collection.md @@ -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 \ No newline at end of file diff --git a/docs/Iterable.md b/docs/Iterable.md index b055745..46ec24c 100644 --- a/docs/Iterable.md +++ b/docs/Iterable.md @@ -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: diff --git a/docs/List.md b/docs/List.md index c2b2401..e8f0c8b 100644 --- a/docs/List.md +++ b/docs/List.md @@ -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 \ No newline at end of file +[Range]: Range.md +[Iterable]: Iterable.md \ No newline at end of file diff --git a/docs/Set.md b/docs/Set.md new file mode 100644 index 0000000..f489043 --- /dev/null +++ b/docs/Set.md @@ -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 \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index faffd1d..f13d407 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -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 ) } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Obj.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Obj.kt index 8659df8..940e545 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Obj.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Obj.kt @@ -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") } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjArray.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjArray.kt new file mode 100644 index 0000000..8484b43 --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjArray.kt @@ -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..() + if (self.nextIndex < self.lastIndex) { + self.array.invokeInstanceMethod(this, "getAt", (self.nextIndex++).toObj()) + } else raiseError(ObjIterationFinishedException(this)) + } + addFn("hasNext") { + val self = thisAs() + if (self.nextIndex < self.lastIndex) ObjTrue else ObjFalse + } + } + } + } +} \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjClass.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjClass.kt index 7f500fe..af5ddb1 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjClass.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjClass.kt @@ -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() - 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() - if (self.nextIndex < self.lastIndex) { - self.array.invokeInstanceMethod(this, "getAt", (self.nextIndex++).toObj()) - } else raiseError(ObjIterationFinishedException(this)) - } - addFn("hasNext") { - val self = thisAs() - 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..() + 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() + 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(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(0) + val result = mutableListOf() + 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() + ) + } + + } +} \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjIterator.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjIterator.kt new file mode 100644 index 0000000..6136868 --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjIterator.kt @@ -0,0 +1,3 @@ +package net.sergeych.lyng + +val ObjIterator by lazy { ObjClass("Iterator") } \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjKotlinIterator.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjKotlinIterator.kt new file mode 100644 index 0000000..ca60d28 --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjKotlinIterator.kt @@ -0,0 +1,14 @@ +package net.sergeych.lyng + +class ObjKotlinIterator(val iterator: Iterator): Obj() { + + override val objClass = type + + companion object { + val type = ObjClass("KotlinIterator", ObjIterator).apply { + addFn("next") { thisAs().iterator.next().toObj() } + addFn("hasNext") { thisAs().iterator.hasNext().toObj() } + } + + } +} \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjList.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjList.kt index 6720412..887b185 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjList.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjList.kt @@ -211,4 +211,6 @@ class ObjList(val list: MutableList = mutableListOf()) : Obj() { } } } -} \ No newline at end of file +} + + diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjSet.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjSet.kt new file mode 100644 index 0000000..2f901b3 --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjSet.kt @@ -0,0 +1,97 @@ +package net.sergeych.lyng + +class ObjSet(val set: MutableSet = 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().set.size.toObj() + } + addFn("intersect") { + thisAs().mul(this, args.firstAndOnly()) + } + addFn("iterator") { + thisAs().set.iterator().toObj() + } + addFn("union") { + thisAs().plus(this, args.firstAndOnly()) + } + addFn("subtract") { + thisAs().minus(this, args.firstAndOnly()) + } + addFn("remove") { + val set = thisAs().set + val n = set.size + for( x in args.list ) set -= x + if( n == set.size ) ObjFalse else ObjTrue + } + } + } +} \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt index 26d4900..a8d2f1b 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt @@ -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) diff --git a/lynglib/src/commonTest/kotlin/ScriptTest.kt b/lynglib/src/commonTest/kotlin/ScriptTest.kt index fba52d5..6ce1d29 100644 --- a/lynglib/src/commonTest/kotlin/ScriptTest.kt +++ b/lynglib/src/commonTest/kotlin/ScriptTest.kt @@ -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() + ) + } } \ No newline at end of file diff --git a/lynglib/src/jvmTest/kotlin/BookTest.kt b/lynglib/src/jvmTest/kotlin/BookTest.kt index d30f3bb..9d72774 100644 --- a/lynglib/src/jvmTest/kotlin/BookTest.kt +++ b/lynglib/src/jvmTest/kotlin/BookTest.kt @@ -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()) {