Skip to content

Commit

Permalink
feat: implement IdentityHashMap for the JS target
Browse files Browse the repository at this point in the history
  • Loading branch information
lppedd authored and ftomassetti committed Aug 29, 2024
1 parent 75a71f6 commit 4a5fdbc
Show file tree
Hide file tree
Showing 10 changed files with 457 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import org.antlr.v4.kotlinruntime.misc.MurmurHash

@Suppress("SimplifyBooleanWithConstants")
public actual class BitSet actual constructor(size: Int) {
private val bits = newArray(size)
private val bits = newArray<Boolean>(size)

init {
require(size >= 0) {
Expand Down Expand Up @@ -158,8 +158,4 @@ public actual class BitSet actual constructor(size: Int) {

return -1
}

@Suppress("UNUSED_PARAMETER")
private fun newArray(size: Int): Array<Boolean> =
js("Array(size)").unsafeCast<Array<Boolean>>()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright 2017-present Strumenta and contributors, licensed under Apache 2.0.
// Copyright 2024-present Strumenta and contributors, licensed under BSD 3-Clause.
package com.strumenta.antlrkotlin.runtime

import js.collections.JsMap
import kotlin.collections.MutableMap.MutableEntry as ME

internal class IdentityEntriesView<K, V>(private val jsMap: JsMap<K, V>) : AbstractMutableSet<ME<K, V>>() {
override val size: Int
get() = jsMap.size

override fun isEmpty(): Boolean =
jsMap.size == 0

override fun clear(): Unit =
jsMap.clear()

override fun add(element: ME<K, V>): Boolean =
throw UnsupportedOperationException("Adding is not supported on entries")

override fun remove(element: ME<K, V>): Boolean {
val (key, value) = element
return remove(key, value)
}

override fun removeAll(elements: Collection<ME<K, V>>): Boolean {
var removed = false

for (element in elements) {
removed = remove(element) || removed
}

return removed
}

override fun contains(element: ME<K, V>): Boolean {
val k = undefinedToNull(element.key)
return jsMap.has(k) && jsMap[k] === element.value
}

override fun iterator(): MutableIterator<ME<K, V>> {
val iterator = jsMap.iterator()
return object : MutableIterator<ME<K, V>> {
var lastEntry: IdentityEntriesView<K, V>.IdentityEntry? = null

override fun hasNext(): Boolean =
iterator.hasNext()

override fun next(): ME<K, V> {
val (key, value) = iterator.next()
val entry = IdentityEntry(key, value)
lastEntry = entry
return entry
}

override fun remove() {
val lastEntry = checkNotNull(this.lastEntry)
remove(lastEntry.key, lastEntry.value)
}
}
}

private fun remove(key: K, value: V): Boolean {
val k = undefinedToNull(key)

if (jsMap.has(k)) {
// IMPORTANT: notice that we use reference/strict equality
if (jsMap[k] === value) {
return jsMap.delete(k)
}
}

return false
}

private inner class IdentityEntry(override val key: K, override var value: V) : ME<K, V> {
override fun setValue(newValue: V): V {
val oldValue = value
check(oldValue === jsMap[key])

jsMap[key] = newValue
value = newValue
return oldValue
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright 2017-present Strumenta and contributors, licensed under Apache 2.0.
// Copyright 2024-present Strumenta and contributors, licensed under BSD 3-Clause.
package com.strumenta.antlrkotlin.runtime

import js.collections.JsMap
import kotlin.collections.MutableMap.MutableEntry as ME

public actual class IdentityHashMap<K, V> : MutableMap<K, V> {
private val jsMap = JsMap<K, V>()

actual override val size: Int
get() = jsMap.size

actual override val keys: MutableSet<K>
get() = IdentityKeysView(this)

actual override val values: MutableCollection<V>
get() = IdentityValuesView(this)

actual override val entries: MutableSet<ME<K, V>>
get() = IdentityEntriesView(jsMap)

actual override fun isEmpty(): Boolean =
jsMap.size == 0

actual override fun clear(): Unit =
jsMap.clear()

actual override fun get(key: K): V? {
val k = undefinedToNull(key)
return undefinedToNull(jsMap[k])
}

actual override fun put(key: K, value: V): V? {
val k = undefinedToNull(key)
val previousValue = jsMap[k]
jsMap[k] = value
return undefinedToNull(previousValue)
}

actual override fun remove(key: K): V? {
val k = undefinedToNull(key)
val removedValue = jsMap[k]
jsMap.delete(k)
return undefinedToNull(removedValue)
}

actual override fun putAll(from: Map<out K, V>) {
for ((key, value) in from) {
val k = undefinedToNull(key)
jsMap[k] = value
}
}

actual override fun containsKey(key: K): Boolean {
val k = undefinedToNull(key)
return jsMap.has(k)
}

actual override fun containsValue(value: V): Boolean {
for (v in jsMap.values()) {
// IMPORTANT: notice that we use reference/strict equality
if (v === value) {
return true
}
}

return false
}

/**
* Same as `remove(key)`, but returns a boolean value.
*/
internal fun removeBoolean(key: K): Boolean {
val k = undefinedToNull(key)
return jsMap.delete(k)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright 2017-present Strumenta and contributors, licensed under Apache 2.0.
// Copyright 2024-present Strumenta and contributors, licensed under BSD 3-Clause.
package com.strumenta.antlrkotlin.runtime

internal class IdentityKeysView<K>(private val map: IdentityHashMap<K, *>) : AbstractMutableSet<K>() {
override val size: Int
get() = map.size

override fun isEmpty(): Boolean =
map.isEmpty()

override fun clear(): Unit =
map.clear()

override fun contains(element: K): Boolean =
map.containsKey(element)

override fun add(element: K): Boolean =
throw UnsupportedOperationException("Adding is not supported on keys")

override fun remove(element: K): Boolean =
map.removeBoolean(element)

override fun removeAll(elements: Collection<K>): Boolean {
var removed = false

for (element in elements) {
removed = remove(element) || removed
}

return removed
}

override fun iterator(): MutableIterator<K> {
val entriesIterator = map.entries.iterator()
return object : MutableIterator<K> {
override fun hasNext(): Boolean =
entriesIterator.hasNext()

override fun next(): K =
entriesIterator.next().key

override fun remove(): Unit =
entriesIterator.remove()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright 2017-present Strumenta and contributors, licensed under Apache 2.0.
// Copyright 2024-present Strumenta and contributors, licensed under BSD 3-Clause.
package com.strumenta.antlrkotlin.runtime

internal class IdentityValuesView<V>(private val map: IdentityHashMap<*, V>) : AbstractMutableCollection<V>() {
override val size: Int
get() = map.size

override fun isEmpty(): Boolean =
map.isEmpty()

override fun clear(): Unit =
map.clear()

override fun add(element: V): Boolean =
throw UnsupportedOperationException("Adding is not supported on values")

override fun remove(element: V): Boolean {
val iterator = iterator()

while (iterator.hasNext()) {
// IMPORTANT: notice that we use reference/strict equality
if (iterator.next() === element) {
iterator.remove()
return true
}
}

return false
}

override fun removeAll(elements: Collection<V>): Boolean {
var removed = false

for (element in elements) {
removed = remove(element) || removed
}

return removed
}

override operator fun contains(element: V): Boolean =
map.containsValue(element)

override operator fun iterator(): MutableIterator<V> {
val entriesIterator = map.entries.iterator()
return object : MutableIterator<V> {
override fun hasNext(): Boolean =
entriesIterator.hasNext()

override fun next(): V =
entriesIterator.next().value

override fun remove(): Unit =
entriesIterator.remove()
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
// Copyright 2017-present Strumenta and contributors, licensed under Apache 2.0.
// Copyright 2024-present Strumenta and contributors, licensed under BSD 3-Clause.
@file:Suppress("NOTHING_TO_INLINE", "UnsafeCastFromDynamic")

package com.strumenta.antlrkotlin.runtime

/**
* Normalizes potentially `undefined` values to `null`.
*/
internal inline fun <T> undefinedToNull(value: T): T =
if (value === js("undefined")) js("null") else value

/**
* Creates a new generic array with an initial [size],
* without requiring the initialization of the elements.
*/
@Suppress("UNUSED_PARAMETER")
internal inline fun <T> newArray(size: Int): Array<T> =
js("Array(size)")

/**
* Returns whether we are running on Node.js, or not.
*/
Expand All @@ -16,4 +32,4 @@ internal fun isNodeJs(): Boolean =
&& window.process.versions != null
&& window.process.versions.node != null)
"""
).unsafeCast<Boolean>()
)
Loading

0 comments on commit 4a5fdbc

Please sign in to comment.