From 3948283481f187841bfb972ad19fad23e45df340 Mon Sep 17 00:00:00 2001 From: sergeych Date: Sat, 9 Aug 2025 22:50:48 +0300 Subject: [PATCH] 0.8.4: stadlib in Lyng! added universal Iterable functions --- docs/Iterable.md | 17 +++- docs/RingBuffer.md | 52 +++++++++++ docs/parallelism.md | 16 ++++ docs/tutorial.md | 7 +- lynglib/build.gradle.kts | 2 +- .../kotlin/net/sergeych/lyng/Scope.kt | 7 +- .../kotlin/net/sergeych/lyng/Script.kt | 11 ++- .../sergeych/lyng/obj/ObjKotlinIterator.kt | 4 +- .../net/sergeych/lyng/obj/ObjMutableBuffer.kt | 2 +- .../net/sergeych/lyng/obj/ObjRingBuffer.kt | 91 +++++++++++++++++++ .../sergeych/lyng/pacman/ImportProvider.kt | 10 ++ .../lyng/stdlib_included/root_lyng.kt | 57 +++++++++++- .../src/commonTest/kotlin/CoroutinesTest.kt | 3 +- lynglib/src/commonTest/kotlin/StdlibTest.kt | 65 +++++++++++++ lynglib/src/jvmTest/kotlin/BookTest.kt | 9 +- 15 files changed, 338 insertions(+), 15 deletions(-) create mode 100644 docs/RingBuffer.md create mode 100644 lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRingBuffer.kt create mode 100644 lynglib/src/commonTest/kotlin/StdlibTest.kt diff --git a/docs/Iterable.md b/docs/Iterable.md index 8be8a2f..3db09c2 100644 --- a/docs/Iterable.md +++ b/docs/Iterable.md @@ -17,7 +17,14 @@ Iterator itself is a simple interface that should provide only to method: 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: +Having `Iterable` in base classes allows to use it in for loop. Also, each `Iterable` has some utility functions available, for example + + val r = 1..10 // Range is Iterable! + assertEquals( [9,10] r.takeLast(2) ) + assertEquals( [1,2,3] r.take(3) ) + assertEquals( [9,10] r.drop(8) ) + assertEquals( [1,2] r.dropLast(8) ) + >>> void ## Instance methods @@ -33,7 +40,15 @@ Having `Iterable` in base classes allows to use it in for loop. Also, each `Iter | map(f) | create a list of values returned by `f` called for each element of the iterable | | indexOf(i) | return index if the first encounter of i or a negative value if not found | | associateBy(kf) | create a map where keys are returned by kf that will be called for each element | +| first | first element (1) | +| last | last element (1) | +| take(n) | return [Iterable] of up to n first elements | +| taleLast(n) | return [Iterable] of up to n last elements | +| drop(n) | return new [Iterable] without first n elements | +| dropLast(n) | return new [Iterable] without last n elements | +(1) +: throws `NoSuchElementException` if there is no such element fun Iterable.toList(): List fun Iterable.toSet(): Set diff --git a/docs/RingBuffer.md b/docs/RingBuffer.md new file mode 100644 index 0000000..0a300b6 --- /dev/null +++ b/docs/RingBuffer.md @@ -0,0 +1,52 @@ +# RingBuffer + +This is a fixed size buffer that allow to store N last elements with _O(1)_ effectiveness (no data shifting). + +Here is the sample: + + val r = RingBuffer(3) + assert( r is RingBuffer ) + assertEquals(0, r.size) + assertEquals(3, r.capacity) + + r += 10 + assertEquals(1, r.size) + assertEquals(10, r.first) + + r += 20 + assertEquals(2, r.size) + assertEquals( [10, 20], r.toList() ) + + r += 30 + assertEquals(3, r.size) + assertEquals( [10, 20, 30], r.toList() ) + + // now first value is lost: + r += 40 + assertEquals(3, r.size) + assertEquals( [20, 30, 40], r.toList() ) + assertEquals(3, r.capacity) + + >>> void + +Ring buffer implements [Iterable], so any of its methods are available for `RingBuffer`, e.g. `first`, `last`, `toList`, +`take`, `drop`, `takelast`, `dropLast`, etc. + +## Constructor + + RinbBuffer(capacity: Int) + +## Instance methods + +| method | description | remarks | +|-------------|------------------------|---------| +| capacity | max size of the buffer | | +| size | current size | (1) | +| operator += | add new item | (1) | +| add(item) | add new item | (1) | +| iterator() | return iterator | (1) | + +(1) +: Ringbuffer is not threadsafe, protect it with a mutex to avoid RC where necessary. + +[Iterable]: Iterable.md \ No newline at end of file diff --git a/docs/parallelism.md b/docs/parallelism.md index ab14c8c..8b4e245 100644 --- a/docs/parallelism.md +++ b/docs/parallelism.md @@ -189,3 +189,19 @@ Important difference from the channels or like, every time you collect the flow, Notice that flow's lambda is not called until actual collection is started. Cold flows are better in terms of resource consumption. + +Flows allow easy transforming of any [Iterable]. See how the standard Lyng library functions use it: + + fun Iterable.filter(predicate) { + val list = this + flow { + for( item in list ) { + if( predicate(item) ) { + emit(item) + } + } + } + } + + +[Iterable]: Iterable.md \ No newline at end of file diff --git a/docs/tutorial.md b/docs/tutorial.md index 65b6c82..da148ad 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -14,8 +14,9 @@ __Other documents to read__ maybe after this one: - [Advanced topics](advanced_topics.md), [declaring arguments](declaring_arguments.md) - [OOP notes](OOP.md), [exception handling](exceptions_handling.md) - [math in Lyng](math.md) +- [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) +- Some class references: [List], [Set], [Map], [Real], [Range], [Iterable], [Iterator], [time manipulation](time.md), [RingBuffer], [Buffer]. - Some samples: [combinatorics](samples/combinatorics.lyng.md), national vars and loops: [сумма ряда](samples/сумма_ряда.lyng.md). More at [samples folder](samples) @@ -1304,4 +1305,6 @@ See [math functions](math.md). Other general purpose functions are: [Buffer]: Buffer.md -[parallelism]: parallelism.md \ No newline at end of file +[parallelism]: parallelism.md + +[RingBuffer]: RingBuffer.md \ No newline at end of file diff --git a/lynglib/build.gradle.kts b/lynglib/build.gradle.kts index de93484..76bf224 100644 --- a/lynglib/build.gradle.kts +++ b/lynglib/build.gradle.kts @@ -4,7 +4,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.dsl.JvmTarget group = "net.sergeych" -version = "0.8.3-SNAPSHOT" +version = "0.8.4-SNAPSHOT" buildscript { repositories { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt index ca53530..1f5ca77 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt @@ -163,13 +163,14 @@ open class Scope( fun addConst(name: String, value: Obj) = addItem(name, false, value) suspend fun eval(code: String): Obj = - Compiler.compile(code.toSource(), currentImportProvider).execute(this) + eval(code.toSource()) - suspend fun eval(source: Source): Obj = - Compiler.compile( + suspend fun eval(source: Source): Obj { + return Compiler.compile( source, currentImportProvider ).execute(this) + } fun containsLocal(name: String): Boolean = name in objects diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt index 4a06c9a..ae5602c 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt @@ -4,6 +4,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.yield import net.sergeych.lyng.obj.* import net.sergeych.lyng.pacman.ImportManager +import net.sergeych.lyng.stdlib_included.rootLyng import net.sergeych.lynon.ObjLynonClass import net.sergeych.mp_tools.globalDefer import kotlin.math.* @@ -22,10 +23,14 @@ class Script( return lastResult } - suspend fun execute() = execute(defaultImportManager.newModule()) + suspend fun execute() = execute( + defaultImportManager.newStdScope() + ) companion object { + suspend fun newScope(pos: Pos = Pos.builtIn) = defaultImportManager.newStdScope(pos) + internal val rootScope: Scope = Scope(null).apply { ObjException.addExceptionsToContext(this) addFn("print") { @@ -202,6 +207,7 @@ class Script( addConst("Iterable", ObjIterable) addConst("Collection", ObjCollection) addConst("Array", ObjArray) + addConst("RingBuffer", ObjRingBuffer.type) addConst("Class", ObjClassType) addConst("Deferred", ObjDeferred.type) @@ -235,6 +241,9 @@ class Script( val defaultImportManager: ImportManager by lazy { ImportManager(rootScope, SecurityManager.allowAll).apply { + addTextPackages( + rootLyng + ) addPackage("lyng.buffer") { it.addConst("Buffer", ObjBuffer.type) it.addConst("MutableBuffer", ObjMutableBuffer.type) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjKotlinIterator.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjKotlinIterator.kt index 3543580..cfde3a6 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjKotlinIterator.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjKotlinIterator.kt @@ -36,7 +36,9 @@ class ObjKotlinObjIterator(val iterator: Iterator) : Obj() { addFn("next") { thisAs().iterator.next() } - addFn("hasNext") { thisAs().iterator.hasNext().toObj() } + addFn("hasNext") { + thisAs().iterator.hasNext().toObj() + } } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMutableBuffer.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMutableBuffer.kt index dd6ecf8..247bd40 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMutableBuffer.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMutableBuffer.kt @@ -68,4 +68,4 @@ class ObjMutableBuffer(byteArray: UByteArray) : ObjBuffer(byteArray) { } } } -} \ No newline at end of file +} diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRingBuffer.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRingBuffer.kt new file mode 100644 index 0000000..ad9a2c9 --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRingBuffer.kt @@ -0,0 +1,91 @@ +package net.sergeych.lyng.obj + +import net.sergeych.lyng.Scope + +class RingBuffer(val maxSize: Int) : Iterable { + private val data = arrayOfNulls(maxSize) + + var size = 0 + private set + + private var start = 0 + + init { + check(maxSize > 0) { "Max size should be a positive number: $maxSize" } + } + + fun add(item: T) { + if (size < maxSize) + size++ + else + start = (start + 1) % maxSize + data[(start + size - 1) % maxSize] = item + } + + @Suppress("unused") + fun addAll(vararg items: T) { + for (i in items) add(i) + } + + @Suppress("unused") + fun addAll(elements: Iterable) { + elements.forEach { add(it) } + } + + @Suppress("unused") + fun clear() { + start = 0 + size = 0 + for (i in data.indices) { + data[i] = null + } + } + + override fun iterator(): Iterator = + object : Iterator { + private var i = 0 + + override fun hasNext(): Boolean = i < size + + override fun next(): T { + if (!hasNext()) throw NoSuchElementException() + + @Suppress("UNCHECKED_CAST") + return data[(start + i++) % maxSize] as T + } + } +} + + +class ObjRingBuffer(val capacity: Int) : Obj() { + val buffer = RingBuffer(capacity) + + override val objClass: ObjClass = type + + override suspend fun plusAssign(scope: Scope, other: Obj): Obj { + buffer.add(other.byValueCopy()) + return this + } + + companion object { + val type = object : ObjClass("RingBuffer", ObjIterable) { + override suspend fun callOn(scope: Scope): Obj { + return ObjRingBuffer(scope.requireOnlyArg().toInt()) + } + }.apply { + addFn("capacity") { + thisAs().capacity.toObj() + } + addFn("size") { + thisAs().buffer.size.toObj() + } + addFn("iterator") { + val buffer = thisAs().buffer + ObjKotlinObjIterator(buffer.iterator()) + } + addFn("add") { + thisAs().apply { buffer.add(requireOnlyArg()) } + } + } + } +} \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/pacman/ImportProvider.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/pacman/ImportProvider.kt index 8b6ad47..c2a8f04 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/pacman/ImportProvider.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/pacman/ImportProvider.kt @@ -1,6 +1,7 @@ package net.sergeych.lyng.pacman import net.sergeych.lyng.* +import net.sergeych.mptools.CachedExpression /** * Package manager INTERFACE (abstract class). Performs import routines @@ -45,6 +46,15 @@ abstract class ImportProvider( fun newModuleAt(pos: Pos): ModuleScope = ModuleScope(this, pos, "unknown") + + private var cachedStdScope = CachedExpression() + + suspend fun newStdScope(pos: Pos = Pos.builtIn): Scope = + cachedStdScope.get { + newModuleAt(pos).also { + it.eval("import lyng.stdlib\n") + } + }.copy() } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/stdlib_included/root_lyng.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/stdlib_included/root_lyng.kt index e1e4f1a..2e6ada3 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/stdlib_included/root_lyng.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/stdlib_included/root_lyng.kt @@ -1,5 +1,58 @@ package net.sergeych.lyng.stdlib_included - internal val rootLyng = """ +package lyng.stdlib -""".trimIndent() \ No newline at end of file +fun Iterable.filter(predicate) { + val list = this + flow { + for( item in list ) { + if( predicate(item) ) { + emit(item) + } + } + } +} + +fun Iterable.drop(n) { + var cnt = 0 + filter { cnt++ >= n } +} + +fun Iterable.first() { + val i = iterator() + if( !i.hasNext() ) throw NoSuchElementException() + i.next() +} + +fun Iterable.last() { + var found = false + var element = null + for( i in this ) { + element = i + found = true + } + if( !found ) throw NoSuchElementException() + element +} + +fun Iterable.dropLast(n) { + val list = this + val buffer = RingBuffer(n) + flow { + for( item in list ) { + if( buffer.size == n ) + emit( buffer.first() ) + buffer += item + } + } +} + +fun Iterable.takeLast(n) { + val list = this + val buffer = RingBuffer(n) + for( item in list ) buffer += item + buffer +} + +""".trimIndent() + diff --git a/lynglib/src/commonTest/kotlin/CoroutinesTest.kt b/lynglib/src/commonTest/kotlin/CoroutinesTest.kt index fe3bf40..b0c3082 100644 --- a/lynglib/src/commonTest/kotlin/CoroutinesTest.kt +++ b/lynglib/src/commonTest/kotlin/CoroutinesTest.kt @@ -29,7 +29,7 @@ class TestCoroutines { val done = CompletableDeferred() launch { - delay(10) + delay(30) done.complete("ok") } @@ -124,7 +124,6 @@ class TestCoroutines { """.trimIndent()) } - @Test fun testFilterFlow() = runTest { eval(""" diff --git a/lynglib/src/commonTest/kotlin/StdlibTest.kt b/lynglib/src/commonTest/kotlin/StdlibTest.kt new file mode 100644 index 0000000..b77eabc --- /dev/null +++ b/lynglib/src/commonTest/kotlin/StdlibTest.kt @@ -0,0 +1,65 @@ +import kotlinx.coroutines.test.runTest +import net.sergeych.lyng.eval +import kotlin.test.Test + +class StdlibTest { + @Test + fun testIterableFilter() = runTest { + eval(""" + assertEquals([1,3,5,7], (1..8).filter{ it % 2 == 1 }.toList() ) + assertEquals([2,4,6,8], (1..8).filter{ it % 2 == 0 }.toList() ) + """.trimIndent()) + + } + + @Test + fun testFirstLast() = runTest { + eval(""" + assertEquals(1, (1..8).first ) + assertEquals(8, (1..8).last ) + """.trimIndent()) + } + + @Test + fun testTake() = runTest { + eval(""" + assertEquals([1,2,3], (1..8).take(3).toList() ) + assertEquals([7,8], (1..8).takeLast(2).toList() ) + """.trimIndent()) + } + + @Test + fun testRingBuffer() = runTest { + eval(""" + val r = RingBuffer(3) + assert( r is RingBuffer ) + assertEquals(0, r.size) + assertEquals(3, r.capacity) + + r += 10 + assertEquals(1, r.size) + assertEquals(10, r.first) + + r += 20 + assertEquals(2, r.size) + assertEquals( [10, 20], r.toList() ) + + r += 30 + assertEquals(3, r.size) + assertEquals( [10, 20, 30], r.toList() ) + + r += 40 + assertEquals(3, r.size) + assertEquals( [20, 30, 40], r.toList() ) + + """.trimIndent()) + } + + @Test + fun testDrop() = runTest { + eval(""" + assertEquals([7,8], (1..8).drop(6).toList() ) + assertEquals([1,2], (1..8).dropLast(6).toList() ) + """.trimIndent()) + } +} \ No newline at end of file diff --git a/lynglib/src/jvmTest/kotlin/BookTest.kt b/lynglib/src/jvmTest/kotlin/BookTest.kt index 0c7304c..3617060 100644 --- a/lynglib/src/jvmTest/kotlin/BookTest.kt +++ b/lynglib/src/jvmTest/kotlin/BookTest.kt @@ -5,6 +5,7 @@ import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import net.sergeych.lyng.Scope +import net.sergeych.lyng.Script import net.sergeych.lyng.obj.ObjVoid import java.nio.file.Files import java.nio.file.Files.readAllLines @@ -163,9 +164,10 @@ fun parseDocTests(fileName: String, bookMode: Boolean = false): Flow = } .flowOn(Dispatchers.IO) -suspend fun DocTest.test(scope: Scope = Scope()) { +suspend fun DocTest.test(_scope: Scope? = null) { val collectedOutput = StringBuilder() val currentTest = this + val scope = _scope ?: Script.newScope() scope.apply { addFn("println") { if( bookMode ) { @@ -304,4 +306,9 @@ class BookTest { runDocTests("../docs/parallelism.md") } + @Test + fun testRingBuffer() = runBlocking { + runDocTests("../docs/RingBuffer.md") + } + } \ No newline at end of file