fix #29 mapentries, map iterators, => operator and .toMap()
This commit is contained in:
		
							parent
							
								
									c002204420
								
							
						
					
					
						commit
						f9416105ec
					
				
							
								
								
									
										46
									
								
								docs/Map.md
									
									
									
									
									
								
							
							
						
						
									
										46
									
								
								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)
 | 
			
		||||
@ -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
 | 
			
		||||
@ -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 {
 | 
			
		||||
 | 
			
		||||
@ -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) },
 | 
			
		||||
 | 
			
		||||
@ -253,7 +253,7 @@ open class Obj {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        inline fun <reified T> 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<Obj, Obj>
 | 
			
		||||
                    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)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
@ -47,6 +47,15 @@ val ObjIterable by lazy {
 | 
			
		||||
            ObjSet(result)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        addFn("toMap") {
 | 
			
		||||
            val result = mutableListOf<Obj>()
 | 
			
		||||
            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<Statement>(0)
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,12 @@
 | 
			
		||||
@file:Suppress("unused")
 | 
			
		||||
 | 
			
		||||
package net.sergeych.lyng
 | 
			
		||||
 | 
			
		||||
class ObjKotlinIterator(val iterator: Iterator<Any?>): 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<Any?>) : Obj() {
 | 
			
		||||
 | 
			
		||||
    override val objClass = type
 | 
			
		||||
 | 
			
		||||
@ -10,5 +16,24 @@ class ObjKotlinIterator(val iterator: Iterator<Any?>): Obj() {
 | 
			
		||||
            addFn("hasNext") { thisAs<ObjKotlinIterator>().iterator.hasNext().toObj() }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Propagate kotlin iterator that already produces Lyng objects, no conversion
 | 
			
		||||
 * is applied
 | 
			
		||||
 */
 | 
			
		||||
class ObjKotlinObjIterator(val iterator: Iterator<Obj>) : Obj() {
 | 
			
		||||
 | 
			
		||||
    override val objClass = type
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        val type = ObjClass("KotlinIterator", ObjIterator).apply {
 | 
			
		||||
            addFn("next") {
 | 
			
		||||
                thisAs<ObjKotlinObjIterator>().iterator.next()
 | 
			
		||||
            }
 | 
			
		||||
            addFn("hasNext") { thisAs<ObjKotlinIterator>().iterator.hasNext().toObj() }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -119,6 +119,15 @@ class ObjList(val list: MutableList<Obj> = 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 {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,40 @@
 | 
			
		||||
package net.sergeych.lyng
 | 
			
		||||
 | 
			
		||||
class ObjMap(val map: MutableMap<Obj,Obj>): 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<Obj>(0), context.requiredArg<Obj>(1))
 | 
			
		||||
            }
 | 
			
		||||
        }.apply {
 | 
			
		||||
            addFn("key") { thisAs<ObjMapEntry>().key }
 | 
			
		||||
            addFn("value") { thisAs<ObjMapEntry>().value }
 | 
			
		||||
            addFn("size") { 2.toObj() }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class ObjMap(val map: MutableMap<Obj, Obj>) : Obj() {
 | 
			
		||||
 | 
			
		||||
    override val objClass = type
 | 
			
		||||
 | 
			
		||||
@ -10,16 +44,22 @@ class ObjMap(val map: MutableMap<Obj,Obj>): 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<Obj>): MutableMap<Obj,Obj> {
 | 
			
		||||
        suspend fun listToMap(context: Context, list: List<Obj>): MutableMap<Obj, Obj> {
 | 
			
		||||
            val map = mutableMapOf<Obj, Obj>()
 | 
			
		||||
            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,Obj>): 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,Obj>): Obj() {
 | 
			
		||||
            addFn("values") {
 | 
			
		||||
                ObjList(thisAs<ObjMap>().map.values.toMutableList())
 | 
			
		||||
            }
 | 
			
		||||
            addFn("iterator") {
 | 
			
		||||
                ObjKotlinIterator(thisAs<ObjMap>().map.entries.iterator())
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -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(
 | 
			
		||||
 | 
			
		||||
@ -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") {
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user