From f9416105ec44817348721b42ffbf008f90e9be24 Mon Sep 17 00:00:00 2001 From: sergeych Date: Mon, 16 Jun 2025 02:11:47 +0400 Subject: [PATCH] fix #29 mapentries, map iterators, => operator and .toMap() --- docs/Map.md | 46 ++++++++++++++++ docs/tutorial.md | 26 ++++++++- lynglib/build.gradle.kts | 2 +- .../kotlin/net/sergeych/lyng/Compiler.kt | 2 + .../kotlin/net/sergeych/lyng/Obj.kt | 7 ++- .../kotlin/net/sergeych/lyng/ObjInt.kt | 9 ++++ .../kotlin/net/sergeych/lyng/ObjIterable.kt | 9 ++++ .../net/sergeych/lyng/ObjKotlinIterator.kt | 27 +++++++++- .../kotlin/net/sergeych/lyng/ObjList.kt | 9 ++++ .../kotlin/net/sergeych/lyng/ObjMap.kt | 53 +++++++++++++++++-- .../kotlin/net/sergeych/lyng/ObjReal.kt | 9 ++++ .../kotlin/net/sergeych/lyng/ObjString.kt | 9 ++++ .../kotlin/net/sergeych/lyng/Parser.kt | 18 ++++--- .../kotlin/net/sergeych/lyng/Script.kt | 1 + .../kotlin/net/sergeych/lyng/Token.kt | 2 +- 15 files changed, 213 insertions(+), 16 deletions(-) diff --git a/docs/Map.md b/docs/Map.md index df5f7c8..266231e 100644 --- a/docs/Map.md +++ b/docs/Map.md @@ -53,11 +53,57 @@ To iterate maps it is convenient to use `keys` method that returns [Set] of keys >>> answer >>> void +Or iterate its key-value pairs that are instances of [MapEntry] class: + + val map = Map( ["foo", 1], ["bar", "buzz"], [42, "answer"] ) + for( entry in map ) { + println("map[%s] = %s"(entry.key, entry.value)) + } + void + >>> map[foo] = 1 + >>> map[bar] = buzz + >>> map[42] = answer + >>> void + +There is a shortcut to use `MapEntry` to create maps: operator `=>` which creates `MapEntry`: + + val entry = "answer" => 42 + assert( entry is MapEntry ) + >>> void + +And you can use it to construct maps: + + val map = Map( "foo" => 1, "bar" => 22) + assertEquals(1, map["foo"]) + assertEquals(22, map["bar"]) + >>> void + +Or use `.toMap` on anything that implements [Iterable] and which elements implements [Array] with 2 elements size, for example, `MapEntry`: + + val map = ["foo" => 1, "bar" => 22].toMap() + assert( map is Map ) + assertEquals(1, map["foo"]) + assertEquals(22, map["bar"]) + >>> void + + It is possible also to get values as [List] (values are not unique): val map = Map( ["foo", 1], ["bar", "buzz"], [42, "answer"] ) assertEquals(map.values, [1, "buzz", "answer"] ) >>> void +Map could be tested to be equal: when all it key-value pairs are equal, the map +is equal. + + val m1 = Map(["foo", 1]) + val m2 = Map(["foo", 1]) + val m3 = Map(["foo", 2]) + assert( m1 == m2 ) + // but the references are different: + assert( m1 !== m2 ) + // different maps: + assert( m1 != m3 ) + >>> void [Collection](Collection.md) \ No newline at end of file diff --git a/docs/tutorial.md b/docs/tutorial.md index 25ac917..c417791 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -14,7 +14,7 @@ __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) -- Some class references: [List], [Real], [Range], [Iterable], [Iterator] +- Some class references: [List], [Set], [Map], [Real], [Range], [Iterable], [Iterator] - Some samples: [combinatorics](samples/combinatorics.lyng.md), national vars and loops: [сумма ряда](samples/сумма_ряда.lyng.md). More at [samples folder](samples) # Expressions @@ -583,6 +583,28 @@ You can get ranges to extract a portion from a list: assertEquals( [2,3], list[1..<3]) >>> void +# Sets + +Set are unordered collection of unique elements, see [Set]. Sets are [Iterable] but have no indexing access. + + assertEquals( Set(3, 2, 1), Set( 1, 2, 3)) + assert( 5 !in Set(1, 2, 6) ) + >>> void + +Please see [Set] for detailed description. + +# Maps + +Maps are unordered collection of key-value pairs, where keys are unique. See [Map] for details. Map also +are [Iterable]: + + val m = Map( "foo" => 77, "bar" => "buzz" ) + assertEquals( m["foo"], 77 ) + >>> void + +Please see [Map] reference for detailed description on using Maps. + + # Flow control operators ## if-then-else @@ -1172,3 +1194,5 @@ See [math functions](math.md). Other general purpose functions are: [Range]: Range.md [String]: String.md [string formatting]: https://github.com/sergeych/mp_stools?tab=readme-ov-file#sprintf-syntax-summary +[Set]: Set.md +[Map]: Map.md \ No newline at end of file diff --git a/lynglib/build.gradle.kts b/lynglib/build.gradle.kts index 9cc72d4..bc5c196 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.6.4-SNAPSHOT" +version = "0.6.5-SNAPSHOT" buildscript { repositories { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index f13d407..bf08fd8 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -1616,6 +1616,8 @@ class Compiler( // bitwise or 2 // bitwise and 3 // equality/ne 4 + Operator.simple(Token.Type.EQARROW, ++lastPrty) { c, a, b -> ObjMapEntry(a,b) }, + // Operator.simple(Token.Type.EQ, ++lastPrty) { c, a, b -> ObjBool(a.compareTo(c, b) == 0) }, Operator.simple(Token.Type.NEQ, lastPrty) { c, a, b -> ObjBool(a.compareTo(c, b) != 0) }, Operator.simple(Token.Type.REF_EQ, lastPrty) { _, a, b -> ObjBool(a === b) }, diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Obj.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Obj.kt index 0b6249d..0c1cabe 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Obj.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Obj.kt @@ -253,7 +253,7 @@ open class Obj { companion object { - inline fun from(obj: T): Obj { + inline fun from(obj: Any?): Obj { @Suppress("UNCHECKED_CAST") return when (obj) { is Obj -> obj @@ -268,6 +268,10 @@ open class Obj { Unit -> ObjVoid null -> ObjNull is Iterator<*> -> ObjKotlinIterator(obj) + is Map.Entry<*, *> -> { + obj as MutableMap.MutableEntry + ObjMapEntry(obj.key, obj.value) + } else -> throw IllegalArgumentException("cannot convert to Obj: $obj") } } @@ -450,6 +454,7 @@ class ObjIndexOutOfBoundsException(context: Context, message: String = "index ou class ObjIllegalArgumentException(context: Context, message: String = "illegal argument") : ObjException("IllegalArgumentException", context, message) +@Suppress("unused") class ObjNoSuchElementException(context: Context, message: String = "no such element") : ObjException("IllegalArgumentException", context, message) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjInt.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjInt.kt index 9d02248..83aca70 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjInt.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjInt.kt @@ -80,6 +80,15 @@ data class ObjInt(var value: Long) : Obj(), Numeric { return value } + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + + other as ObjInt + + return value == other.value + } + companion object { val Zero = ObjInt(0) val One = ObjInt(1) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjIterable.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjIterable.kt index e4d5565..c99c78b 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjIterable.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjIterable.kt @@ -47,6 +47,15 @@ val ObjIterable by lazy { ObjSet(result) } + addFn("toMap") { + val result = mutableListOf() + val it = thisObj.invokeInstanceMethod(this, "iterator") + while (it.invokeInstanceMethod(this, "hasNext").toBool()) { + result += it.invokeInstanceMethod(this, "next") + } + ObjMap(ObjMap.listToMap(this, result)) + } + addFn("forEach", isOpen = true) { val it = thisObj.invokeInstanceMethod(this, "iterator") val fn = requiredArg(0) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjKotlinIterator.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjKotlinIterator.kt index ca60d28..1b300c0 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjKotlinIterator.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjKotlinIterator.kt @@ -1,6 +1,12 @@ +@file:Suppress("unused") + package net.sergeych.lyng -class ObjKotlinIterator(val iterator: Iterator): Obj() { +/** + * Iterator wrapper to allow Kotlin collections to be returned from Lyng objects; + * each object is converted to a Lyng object. + */ +class ObjKotlinIterator(val iterator: Iterator) : Obj() { override val objClass = type @@ -10,5 +16,24 @@ class ObjKotlinIterator(val iterator: Iterator): Obj() { addFn("hasNext") { thisAs().iterator.hasNext().toObj() } } + } +} + +/** + * Propagate kotlin iterator that already produces Lyng objects, no conversion + * is applied + */ +class ObjKotlinObjIterator(val iterator: Iterator) : Obj() { + + override val objClass = type + + companion object { + val type = ObjClass("KotlinIterator", ObjIterator).apply { + addFn("next") { + thisAs().iterator.next() + } + 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 62bc5ca..eee5820 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjList.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjList.kt @@ -119,6 +119,15 @@ class ObjList(val list: MutableList = mutableListOf()) : Obj() { return list.hashCode() } + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + + other as ObjList + + return list == other.list + } + companion object { val type = ObjClass("List", ObjArray).apply { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjMap.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjMap.kt index 63b3593..b110c82 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjMap.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjMap.kt @@ -1,6 +1,40 @@ package net.sergeych.lyng -class ObjMap(val map: MutableMap): Obj() { +class ObjMapEntry(val key: Obj, val value: Obj) : Obj() { + + override suspend fun compareTo(context: Context, other: Obj): Int { + if (other !is ObjMapEntry) return -1 + val c = key.compareTo(context, other.key) + if (c != 0) return c + return value.compareTo(context, other.value) + } + + override suspend fun getAt(context: Context, index: Obj): Obj = when (index.toInt()) { + 0 -> key + 1 -> value + else -> context.raiseIndexOutOfBounds() + } + + override fun toString(): String { + return "$key=>$value" + } + + override val objClass = type + + companion object { + val type = object : ObjClass("MapEntry", ObjArray) { + override suspend fun callOn(context: Context): Obj { + return ObjMapEntry(context.requiredArg(0), context.requiredArg(1)) + } + }.apply { + addFn("key") { thisAs().key } + addFn("value") { thisAs().value } + addFn("size") { 2.toObj() } + } + } +} + +class ObjMap(val map: MutableMap) : Obj() { override val objClass = type @@ -10,16 +44,22 @@ class ObjMap(val map: MutableMap): Obj() { override suspend fun contains(context: Context, other: Obj): Boolean { return other in map } + + override suspend fun compareTo(context: Context, other: Obj): Int { + if( other is ObjMap && other.map == map) return 0 + return -1 + } override fun toString(): String = map.toString() + companion object { - suspend fun listToMap(context: Context, list: List): MutableMap { + suspend fun listToMap(context: Context, list: List): MutableMap { val map = mutableMapOf() if (list.isEmpty()) return map val first = list.first() if (first.isInstanceOf(ObjArray)) { - if( first.invokeInstanceMethod(context,"size").toInt() != 2) + if (first.invokeInstanceMethod(context, "size").toInt() != 2) context.raiseIllegalArgument( "list to construct map entry should exactly be 2 element Array like [key,value], got $list" ) @@ -28,13 +68,13 @@ class ObjMap(val map: MutableMap): Obj() { list.forEach { - map[it.getAt(context,ObjInt.Zero)] = it.getAt(context,ObjInt.One) + map[it.getAt(context, ObjInt.Zero)] = it.getAt(context, ObjInt.One) } return map } - val type = object: ObjClass("Map", ObjCollection) { + val type = object : ObjClass("Map", ObjCollection) { override suspend fun callOn(context: Context): Obj { return ObjMap(listToMap(context, context.args.list)) } @@ -66,6 +106,9 @@ class ObjMap(val map: MutableMap): Obj() { addFn("values") { ObjList(thisAs().map.values.toMutableList()) } + addFn("iterator") { + ObjKotlinIterator(thisAs().map.entries.iterator()) + } } } } \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjReal.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjReal.kt index 4e5b50a..7d8fcf4 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjReal.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjReal.kt @@ -47,6 +47,15 @@ data class ObjReal(val value: Double) : Obj(), Numeric { return value } + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + + other as ObjReal + + return value == other.value + } + companion object { val type: ObjClass = ObjClass("Real").apply { createField( diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjString.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjString.kt index 3533d42..d5d6456 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjString.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjString.kt @@ -65,6 +65,15 @@ data class ObjString(val value: String) : Obj() { else context.raiseIllegalArgument("String.contains can't take $other") } + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || this::class != other::class) return false + + other as ObjString + + return value == other.value + } + companion object { val type = ObjClass("String").apply { addFn("startsWith") { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Parser.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Parser.kt index 423a9ff..2f6f1f8 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Parser.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Parser.kt @@ -45,12 +45,18 @@ private class Parser(fromPos: Pos) { '=' -> { if (pos.currentChar == '=') { pos.advance() - if (currentChar == '=') { - pos.advance() - Token("===", from, Token.Type.REF_EQ) - } else - Token("==", from, Token.Type.EQ) - } else + when (currentChar) { + '=' -> { + pos.advance() + Token("===", from, Token.Type.REF_EQ) + } + else -> Token("==", from, Token.Type.EQ) + } + } else if( currentChar == '>' ) { + pos.advance() + Token("=>", from, Token.Type.EQARROW) + } + else Token("=", from, Token.Type.ASSIGN) } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt index af210a7..8f11941 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt @@ -155,6 +155,7 @@ class Script( addConst("Set", ObjSet.type) addConst("Range", ObjRange.type) addConst("Map", ObjMap.type) + addConst("MapEntry", ObjMapEntry.type) @Suppress("RemoveRedundantQualifierName") addConst("Callable", Statement.type) // interfaces diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Token.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Token.kt index aceaee4..b1252c6 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Token.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Token.kt @@ -14,7 +14,7 @@ data class Token(val value: String, val pos: Pos, val type: Type) { IN, NOTIN, IS, NOTIS, EQ, NEQ, LT, LTE, GT, GTE, REF_EQ, REF_NEQ, SHUTTLE, - AND, BITAND, OR, BITOR, NOT, BITNOT, DOT, ARROW, QUESTION, COLONCOLON, + AND, BITAND, OR, BITOR, NOT, BITNOT, DOT, ARROW, EQARROW, QUESTION, COLONCOLON, SINLGE_LINE_COMMENT, MULTILINE_COMMENT, LABEL, ATLABEL, // label@ at@label //PUBLIC, PROTECTED, INTERNAL, EXPORT, OPEN, INLINE, OVERRIDE, ABSTRACT, SEALED, EXTERNAL, VAL, VAR, CONST, TYPE, FUN, CLASS, INTERFACE, ENUM, OBJECT, TRAIT, THIS,