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