fix #28 basic map supports (still no iterators)
This commit is contained in:
		
							parent
							
								
									a4448ab2ff
								
							
						
					
					
						commit
						c002204420
					
				
							
								
								
									
										63
									
								
								docs/Map.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								docs/Map.md
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,63 @@
 | 
			
		||||
# Map
 | 
			
		||||
 | 
			
		||||
Map is a mutable collection of key-value pars, where keys are unique. Maps could be created with
 | 
			
		||||
constructor or `.toMap` methods. When constructing from a list, each list item must be a [Collection] with exactly 2 elements, for example, a [List].
 | 
			
		||||
 | 
			
		||||
Constructed map instance is of class `Map` and implements `Collection` (and therefore `Iterable`)
 | 
			
		||||
 | 
			
		||||
    val map = Map( ["foo", 1], ["bar", "buzz"] )
 | 
			
		||||
    assert(map is Map)
 | 
			
		||||
    assert(map.size == 2)
 | 
			
		||||
    assert(map is Iterable)
 | 
			
		||||
    >>> void
 | 
			
		||||
 | 
			
		||||
Map keys could be any objects (hashable, e.g. with reasonable hashCode, most of standard types are). You can access elements with indexing operator:
 | 
			
		||||
 | 
			
		||||
    val map = Map( ["foo", 1], ["bar", "buzz"], [42, "answer"] )
 | 
			
		||||
    assert( map["bar"] == "buzz")
 | 
			
		||||
    assert( map[42] == "answer" )
 | 
			
		||||
    assertThrows { map["nonexistent"] }
 | 
			
		||||
    assert( map.getOrNull(101) == null )
 | 
			
		||||
    assert( map.getOrPut(911) { "nine-eleven" } == "nine-eleven" )
 | 
			
		||||
    // now 91 entry is set:
 | 
			
		||||
    assert( map[911] == "nine-eleven" )
 | 
			
		||||
    map["foo"] = -1
 | 
			
		||||
    assert( map["foo"] == -1)
 | 
			
		||||
    >>> void
 | 
			
		||||
 | 
			
		||||
To remove item from the collection. use `remove`. It returns last removed item or null. Be careful if you 
 | 
			
		||||
hold nulls in the map - this is not a recommended practice when using `remove` returned value. `clear()` 
 | 
			
		||||
removes all.
 | 
			
		||||
 | 
			
		||||
    val map = Map( ["foo", 1], ["bar", "buzz"], [42, "answer"] )
 | 
			
		||||
    assertEquals( 1, map.remove("foo") )
 | 
			
		||||
    assert( map.getOrNull("foo") == null)
 | 
			
		||||
    assert( map.size == 2 )
 | 
			
		||||
    map.clear()
 | 
			
		||||
    assert( map.size == 0 )
 | 
			
		||||
    >>> void
 | 
			
		||||
 | 
			
		||||
Map implements [contains] method that checks _the presence of the key_ in the map:
 | 
			
		||||
 | 
			
		||||
    val map = Map( ["foo", 1], ["bar", "buzz"], [42, "answer"] )
 | 
			
		||||
    assert( "foo" in map )
 | 
			
		||||
    assert( "answer" !in map )
 | 
			
		||||
    >>> void
 | 
			
		||||
 | 
			
		||||
To iterate maps it is convenient to use `keys` method that returns [Set] of keys (keys are unique:
 | 
			
		||||
 | 
			
		||||
    val map = Map( ["foo", 1], ["bar", "buzz"], [42, "answer"] )
 | 
			
		||||
    for( k in map.keys ) println(map[k])
 | 
			
		||||
    >>> 1
 | 
			
		||||
    >>> buzz
 | 
			
		||||
    >>> answer
 | 
			
		||||
    >>> 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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[Collection](Collection.md)
 | 
			
		||||
@ -56,7 +56,7 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
 | 
			
		||||
                    else -> {
 | 
			
		||||
                        println("callArgs: ${callArgs.joinToString()}")
 | 
			
		||||
                        println("tailBlockMode: ${arguments.tailBlockMode}")
 | 
			
		||||
                        context.raiseArgumentError("too few arguments for the call")
 | 
			
		||||
                        context.raiseIllegalArgument("too few arguments for the call")
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                assign(a, value)
 | 
			
		||||
@ -77,7 +77,7 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    a.defaultValue != null -> a.defaultValue.execute(context)
 | 
			
		||||
                    else -> context.raiseArgumentError("too few arguments for the call")
 | 
			
		||||
                    else -> context.raiseIllegalArgument("too few arguments for the call")
 | 
			
		||||
                }
 | 
			
		||||
                assign(a, value)
 | 
			
		||||
                i--
 | 
			
		||||
@ -98,7 +98,7 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
 | 
			
		||||
            processEllipsis(leftIndex, end)
 | 
			
		||||
        } else {
 | 
			
		||||
            if (leftIndex < callArgs.size)
 | 
			
		||||
                context.raiseArgumentError("too many arguments for the call")
 | 
			
		||||
                context.raiseIllegalArgument("too many arguments for the call")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -23,7 +23,11 @@ class Context(
 | 
			
		||||
        raiseError(ObjIndexOutOfBoundsException(this, message))
 | 
			
		||||
 | 
			
		||||
    @Suppress("unused")
 | 
			
		||||
    fun raiseArgumentError(message: String = "Illegal argument error"): Nothing =
 | 
			
		||||
    fun raiseIllegalArgument(message: String = "Illegal argument error"): Nothing =
 | 
			
		||||
        raiseError(ObjIllegalArgumentException(this, message))
 | 
			
		||||
 | 
			
		||||
    @Suppress("unused")
 | 
			
		||||
    fun raiseNoSuchElement(message: String = "No such element"): Nothing =
 | 
			
		||||
        raiseError(ObjIllegalArgumentException(this, message))
 | 
			
		||||
 | 
			
		||||
    fun raiseClassCastError(msg: String): Nothing = raiseError(ObjClassCastException(this, msg))
 | 
			
		||||
 | 
			
		||||
@ -254,6 +254,7 @@ open class Obj {
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        inline fun <reified T> from(obj: T): Obj {
 | 
			
		||||
            @Suppress("UNCHECKED_CAST")
 | 
			
		||||
            return when (obj) {
 | 
			
		||||
                is Obj -> obj
 | 
			
		||||
                is Double -> ObjReal(obj)
 | 
			
		||||
@ -263,6 +264,7 @@ open class Obj {
 | 
			
		||||
                is String -> ObjString(obj)
 | 
			
		||||
                is CharSequence -> ObjString(obj.toString())
 | 
			
		||||
                is Boolean -> ObjBool(obj)
 | 
			
		||||
                is Set<*> -> ObjSet((obj as Set<Obj>).toMutableSet())
 | 
			
		||||
                Unit -> ObjVoid
 | 
			
		||||
                null -> ObjNull
 | 
			
		||||
                is Iterator<*> -> ObjKotlinIterator(obj)
 | 
			
		||||
@ -423,6 +425,7 @@ open class ObjException(exceptionClass: ExceptionClass, val context: Context, va
 | 
			
		||||
                "ClassCastException",
 | 
			
		||||
                "IndexOutOfBoundsException",
 | 
			
		||||
                "IllegalArgumentException",
 | 
			
		||||
                "NoSuchElementException",
 | 
			
		||||
                "IllegalAssignmentException",
 | 
			
		||||
                "SymbolNotDefinedException",
 | 
			
		||||
                "IterationEndException",
 | 
			
		||||
@ -447,8 +450,11 @@ class ObjIndexOutOfBoundsException(context: Context, message: String = "index ou
 | 
			
		||||
class ObjIllegalArgumentException(context: Context, message: String = "illegal argument") :
 | 
			
		||||
    ObjException("IllegalArgumentException", context, message)
 | 
			
		||||
 | 
			
		||||
class ObjNoSuchElementException(context: Context, message: String = "no such element") :
 | 
			
		||||
    ObjException("IllegalArgumentException", context, message)
 | 
			
		||||
 | 
			
		||||
class ObjIllegalAssignmentException(context: Context, message: String = "illegal assignment") :
 | 
			
		||||
    ObjException("IllegalAssignmentException", context, message)
 | 
			
		||||
    ObjException("NoSuchElementException", context, message)
 | 
			
		||||
 | 
			
		||||
class ObjSymbolNotDefinedException(context: Context, message: String = "symbol is not defined") :
 | 
			
		||||
    ObjException("SymbolNotDefinedException", context, message)
 | 
			
		||||
 | 
			
		||||
@ -9,6 +9,10 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
 | 
			
		||||
 | 
			
		||||
    override fun byValueCopy(): Obj = ObjInt(value)
 | 
			
		||||
 | 
			
		||||
    override fun hashCode(): Int {
 | 
			
		||||
        return value.hashCode()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun getAndIncrement(context: Context): Obj {
 | 
			
		||||
        return ObjInt(value).also { value++ }
 | 
			
		||||
    }
 | 
			
		||||
@ -77,6 +81,8 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        val Zero = ObjInt(0)
 | 
			
		||||
        val One = ObjInt(1)
 | 
			
		||||
        val type = ObjClass("Int")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -47,7 +47,7 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            else -> context.raiseArgumentError("Illegal index object for a list: ${index.inspect()}")
 | 
			
		||||
            else -> context.raiseIllegalArgument("Illegal index object for a list: ${index.inspect()}")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -114,6 +114,11 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
 | 
			
		||||
        return list.map { it.toKotlin(context) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun hashCode(): Int {
 | 
			
		||||
        // check?
 | 
			
		||||
        return list.hashCode()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        val type = ObjClass("List", ObjArray).apply {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										71
									
								
								lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjMap.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjMap.kt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,71 @@
 | 
			
		||||
package net.sergeych.lyng
 | 
			
		||||
 | 
			
		||||
class ObjMap(val map: MutableMap<Obj,Obj>): Obj() {
 | 
			
		||||
 | 
			
		||||
    override val objClass = type
 | 
			
		||||
 | 
			
		||||
    override suspend fun getAt(context: Context, index: Obj): Obj =
 | 
			
		||||
        map.getOrElse(index) { context.raiseNoSuchElement() }
 | 
			
		||||
 | 
			
		||||
    override suspend fun contains(context: Context, other: Obj): Boolean {
 | 
			
		||||
        return other in map
 | 
			
		||||
    }
 | 
			
		||||
    override fun toString(): String = map.toString()
 | 
			
		||||
    companion object {
 | 
			
		||||
 | 
			
		||||
        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)
 | 
			
		||||
                    context.raiseIllegalArgument(
 | 
			
		||||
                        "list to construct map entry should exactly be 2 element Array like [key,value], got $list"
 | 
			
		||||
                    )
 | 
			
		||||
            } else context.raiseIllegalArgument("first element of map list be a Collection of 2 elements; got $first")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            list.forEach {
 | 
			
		||||
                map[it.getAt(context,ObjInt.Zero)] = it.getAt(context,ObjInt.One)
 | 
			
		||||
            }
 | 
			
		||||
            return map
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        val type = object: ObjClass("Map", ObjCollection) {
 | 
			
		||||
            override suspend fun callOn(context: Context): Obj {
 | 
			
		||||
                return ObjMap(listToMap(context, context.args.list))
 | 
			
		||||
            }
 | 
			
		||||
        }.apply {
 | 
			
		||||
            addFn("getOrNull") {
 | 
			
		||||
                val key = args.firstAndOnly(pos)
 | 
			
		||||
                thisAs<ObjMap>().map.getOrElse(key) { ObjNull }
 | 
			
		||||
            }
 | 
			
		||||
            addFn("getOrPut") {
 | 
			
		||||
                val key = requiredArg<Obj>(0)
 | 
			
		||||
                thisAs<ObjMap>().map.getOrPut(key) {
 | 
			
		||||
                    val lambda = requiredArg<Statement>(1)
 | 
			
		||||
                    lambda.execute(this)
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            addFn("size") {
 | 
			
		||||
                thisAs<ObjMap>().map.size.toObj()
 | 
			
		||||
            }
 | 
			
		||||
            addFn("remove") {
 | 
			
		||||
                thisAs<ObjMap>().map.remove(requiredArg<Obj>(0))?.toObj() ?: ObjNull
 | 
			
		||||
            }
 | 
			
		||||
            addFn("clear") {
 | 
			
		||||
                thisAs<ObjMap>().map.clear()
 | 
			
		||||
                thisObj
 | 
			
		||||
            }
 | 
			
		||||
            addFn("keys") {
 | 
			
		||||
                thisAs<ObjMap>().map.keys.toObj()
 | 
			
		||||
            }
 | 
			
		||||
            addFn("values") {
 | 
			
		||||
                ObjList(thisAs<ObjMap>().map.values.toMutableList())
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -10,6 +10,8 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
 | 
			
		||||
    override val toObjInt: ObjInt by lazy { ObjInt(longValue) }
 | 
			
		||||
    override val toObjReal: ObjReal by lazy { ObjReal(value) }
 | 
			
		||||
 | 
			
		||||
    override val objClass: ObjClass = type
 | 
			
		||||
 | 
			
		||||
    override fun byValueCopy(): Obj = ObjReal(value)
 | 
			
		||||
 | 
			
		||||
    override suspend fun compareTo(context: Context, other: Obj): Int {
 | 
			
		||||
@ -19,7 +21,9 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
 | 
			
		||||
 | 
			
		||||
    override fun toString(): String = value.toString()
 | 
			
		||||
 | 
			
		||||
    override val objClass: ObjClass = type
 | 
			
		||||
    override fun hashCode(): Int {
 | 
			
		||||
        return value.hashCode()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun plus(context: Context, other: Obj): Obj =
 | 
			
		||||
        ObjReal(this.value + other.toDouble())
 | 
			
		||||
 | 
			
		||||
@ -44,12 +44,12 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
 | 
			
		||||
        return if (other is ObjSet) {
 | 
			
		||||
            ObjSet(set.intersect(other.set).toMutableSet())
 | 
			
		||||
        } else
 | 
			
		||||
            context.raiseArgumentError("set operator * requires another set")
 | 
			
		||||
            context.raiseIllegalArgument("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")
 | 
			
		||||
            context.raiseIllegalArgument("set operator - requires another set")
 | 
			
		||||
        return ObjSet(set.minus(other.set).toMutableSet())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -66,6 +66,8 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        val type = object : ObjClass("Set", ObjCollection) {
 | 
			
		||||
            override suspend fun callOn(context: Context): Obj {
 | 
			
		||||
                return ObjSet(context.args.list.toMutableSet())
 | 
			
		||||
 | 
			
		||||
@ -46,7 +46,11 @@ data class ObjString(val value: String) : Obj() {
 | 
			
		||||
            }
 | 
			
		||||
            return ObjString(value.substring(start, end))
 | 
			
		||||
        }
 | 
			
		||||
        context.raiseArgumentError("String index must be Int or Range")
 | 
			
		||||
        context.raiseIllegalArgument("String index must be Int or Range")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun hashCode(): Int {
 | 
			
		||||
        return value.hashCode()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun callOn(context: Context): Obj {
 | 
			
		||||
@ -58,7 +62,7 @@ data class ObjString(val value: String) : Obj() {
 | 
			
		||||
            value.contains(other.value)
 | 
			
		||||
        else if (other is ObjChar)
 | 
			
		||||
            value.contains(other.value)
 | 
			
		||||
        else context.raiseArgumentError("String.contains can't take $other")
 | 
			
		||||
        else context.raiseIllegalArgument("String.contains can't take $other")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
 | 
			
		||||
@ -154,6 +154,7 @@ class Script(
 | 
			
		||||
            addConst("List", ObjList.type)
 | 
			
		||||
            addConst("Set", ObjSet.type)
 | 
			
		||||
            addConst("Range", ObjRange.type)
 | 
			
		||||
            addConst("Map", ObjMap.type)
 | 
			
		||||
            @Suppress("RemoveRedundantQualifierName")
 | 
			
		||||
            addConst("Callable", Statement.type)
 | 
			
		||||
            // interfaces
 | 
			
		||||
 | 
			
		||||
@ -1890,7 +1890,7 @@ class ScriptTest {
 | 
			
		||||
    fun testThrowFromKotlin() = runTest {
 | 
			
		||||
        val c = Context()
 | 
			
		||||
        c.addFn("callThrow") {
 | 
			
		||||
            raiseArgumentError("fromKotlin")
 | 
			
		||||
            raiseIllegalArgument("fromKotlin")
 | 
			
		||||
        }
 | 
			
		||||
        c.eval(
 | 
			
		||||
            """
 | 
			
		||||
 | 
			
		||||
@ -260,6 +260,11 @@ class BookTest {
 | 
			
		||||
        runDocTests("../docs/Set.md")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testMap() = runTest {
 | 
			
		||||
        runDocTests("../docs/Map.md")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testSampleBooks() = runTest {
 | 
			
		||||
        for (bt in Files.list(Paths.get("../docs/samples")).toList()) {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user