/* * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ @file:OptIn(ExperimentalJsCollectionsApi::class) package kotlin.collections import kotlin.js.collections.* private class JsArrayView : JsArray() private fun UNSUPPORTED_OPERATION() { throw UnsupportedOperationException() } internal fun createJsReadonlyArrayViewFrom(list: List): JsReadonlyArray = createJsArrayViewWith( listSize = { list.size }, listGet = { i -> list[i] }, listSet = ::UNSUPPORTED_OPERATION.asDynamic(), listDecreaseSize = ::UNSUPPORTED_OPERATION.asDynamic(), listIncreaseSize = ::UNSUPPORTED_OPERATION.asDynamic() ) internal fun createJsArrayViewFrom(list: MutableList): JsArray = createJsArrayViewWith( listSize = { list.size }, listGet = { i -> list[i] }, listSet = { i, v -> list[i] = v }, listDecreaseSize = { size -> list.subList(list.size - size, list.size).clear() }, listIncreaseSize = ::UNSUPPORTED_OPERATION.asDynamic() ) @Suppress("UNUSED_VARIABLE", "UNUSED_PARAMETER") private fun createJsArrayViewWith( listSize: () -> Int, listGet: (Int) -> E, listSet: (Int, E) -> Unit, listDecreaseSize: (Int) -> Unit, listIncreaseSize: (Int) -> Unit, ): dynamic { val arrayView = objectCreate>() return js(""" new Proxy(arrayView, { get: function(target, prop, receiver) { if (prop === "length") return listSize(); var type = typeof prop var index = type === "string" || type === "number" ? +prop : undefined if (!isNaN(index)) return listGet(index); return target[prop] }, has: function(target, key) { return !isNaN(key) && key < listSize() }, set: function(obj, prop, value) { if (prop === "length") { var size = listSize(); var newSize = type === "string" || type === "number" ? +prop : undefined if (isNaN(newSize)) throw new RangeError("invalid array length") if (newSize < size) listDecreaseSize(size - newSize) else listIncreaseSize(newSize - size) return true } var type = typeof prop; var index = type === "string" || type === "number" ? +prop : undefined; if (isNaN(index)) return false; listSet(index, value) return true }, }) """) } private class JsSetView : JsSet() internal fun createJsReadonlySetViewFrom(set: Set): JsReadonlySet = createJsSetViewWith( setSize = { set.size }, setAdd = ::UNSUPPORTED_OPERATION.asDynamic(), setRemove = ::UNSUPPORTED_OPERATION.asDynamic(), setClear = ::UNSUPPORTED_OPERATION.asDynamic(), setContains = { v -> set.contains(v) }, valuesIterator = { createJsIteratorFrom(set.iterator()) }, entriesIterator = { createJsIteratorFrom(set.iterator()) { arrayOf(it, it) } }, forEach = { cb, t -> forEach(cb, t) } ) internal fun createJsSetViewFrom(set: MutableSet): JsSet = createJsSetViewWith( setSize = { set.size }, setAdd = { v -> set.add(v) }, setRemove = { v -> set.remove(v) }, setClear = { set.clear() }, setContains = { v -> set.contains(v) }, valuesIterator = { createJsIteratorFrom(set.iterator()) }, entriesIterator = { createJsIteratorFrom(set.iterator()) { arrayOf(it, it) } }, forEach = { cb, t -> forEach(cb, t) } ) @Suppress("UNUSED_VARIABLE", "UNUSED_PARAMETER") private fun createJsSetViewWith( setSize: () -> Int, setAdd: (E) -> Unit, setRemove: (E) -> Boolean, setClear: () -> Unit, setContains: (E) -> Boolean, valuesIterator: () -> dynamic, entriesIterator: () -> dynamic, forEach: (dynamic, dynamic) -> Unit, ): dynamic { val setView = objectCreate>().also { js("it[Symbol.iterator] = valuesIterator") defineProp(it, "size", setSize, VOID) } return js(""" Object.assign(setView, { add: function(value) { setAdd(value); return this }, 'delete': setRemove, clear: setClear, has: setContains, keys: valuesIterator, values: valuesIterator, entries: entriesIterator, forEach: function (cb, thisArg) { forEach(cb, thisArg || setView) } }) """) } private class JsMapView : JsMap() internal fun createJsReadonlyMapViewFrom(map: Map): JsReadonlyMap = createJsMapViewWith( mapSize = { map.size }, mapGet = { k -> map[k] }, mapContains = { k -> map.containsKey(k) }, mapPut = ::UNSUPPORTED_OPERATION.asDynamic(), mapRemove = ::UNSUPPORTED_OPERATION.asDynamic(), mapClear = ::UNSUPPORTED_OPERATION.asDynamic(), keysIterator = { createJsIteratorFrom(map.keys.iterator()) }, valuesIterator = { createJsIteratorFrom(map.values.iterator()) }, entriesIterator = { createJsIteratorFrom(map.entries.iterator()) { arrayOf(it.key, it.value) } }, forEach = { cb, t -> forEach(cb, t) } ) internal fun createJsMapViewFrom(map: MutableMap): JsMap = createJsMapViewWith( mapSize = { map.size }, mapGet = { k -> map[k] }, mapContains = { k -> map.containsKey(k) }, mapPut = { k, v -> map.put(k, v) }, mapRemove = { k -> map.remove(k) }, mapClear = { map.clear() }, keysIterator = { createJsIteratorFrom(map.keys.iterator()) }, valuesIterator = { createJsIteratorFrom(map.values.iterator()) }, entriesIterator = { createJsIteratorFrom(map.entries.iterator()) { arrayOf(it.key, it.value) } }, forEach = { cb, t -> forEach(cb, t) } ) @Suppress("UNUSED_VARIABLE", "UNUSED_PARAMETER") private fun createJsMapViewWith( mapSize: () -> Int, mapGet: (K) -> V?, mapContains: (K) -> Boolean, mapPut: (K, V) -> Unit, mapRemove: (K) -> Unit, mapClear: () -> Unit, keysIterator: () -> dynamic, valuesIterator: () -> dynamic, entriesIterator: () -> dynamic, forEach: (dynamic, dynamic) -> Unit, ): dynamic { val mapView = objectCreate>().also { js("it[Symbol.iterator] = entriesIterator") defineProp(it, "size", mapSize, VOID) } return js(""" Object.assign(mapView, { get: mapGet, set: function(key, value) { mapPut(key, value); return this }, 'delete': mapRemove, clear: mapClear, has: mapContains, keys: valuesIterator, values: valuesIterator, entries: entriesIterator, forEach: function (cb, thisArg) { forEach(cb, thisArg || mapView) } }) """) } @Suppress("UNUSED_VARIABLE", "UNUSED_PARAMETER") private fun createJsIteratorFrom(iterator: Iterator, transform: (T) -> dynamic = { it }): dynamic { val iteratorNext = { iterator.next() } val iteratorHasNext = { iterator.hasNext() } return js("""{ next: function() { var result = { done: !iteratorHasNext() }; if (!result.done) result.value = transform(iteratorNext()); return result; } }""") } private fun forEach(cb: (dynamic, dynamic, dynamic) -> Unit, thisArg: dynamic) { val iterator = thisArg.entries() var result = iterator.next() while (!result.done) { val value = result.value cb(value[0], value[1], thisArg) result = iterator.next() } }