Skip to content

Commit

Permalink
Switch to thread safe regex match caching (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
wingio committed Dec 3, 2023
1 parent e5f5941 commit 8fdff23
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import xyz.wingio.syntakts.parser.addTextRule
import xyz.wingio.syntakts.style.StyledTextBuilder
import xyz.wingio.syntakts.util.Logger
import xyz.wingio.syntakts.util.LoggerImpl
import xyz.wingio.syntakts.util.SynchronizedCache
import xyz.wingio.syntakts.util.Stack
import xyz.wingio.syntakts.util.firstMapOrNull

Expand Down Expand Up @@ -237,7 +238,7 @@ public class Syntakts<C> internal constructor(
if(debugOptions.enableLogging) debugOptions.logger.debug(message)
}

private val cache: MutableMap<String, MatchResult?> = mutableMapOf()
private val cache: SynchronizedCache<String, MatchResult> = SynchronizedCache()

/**
* Parse an input using the specified [rules]
Expand Down Expand Up @@ -273,11 +274,11 @@ public class Syntakts<C> internal constructor(
rules.firstMapOrNull { rule ->
val key = "${rule.regex}-$inspectionSource-$lastCapture"

val matchResult = if(cache.containsKey(key))
val matchResult = if(cache.hasKey(key))
cache[key]
else
rule.match(inspectionSource, lastCapture).apply {
if(cache.size > 10_000) cache.remove(cache.keys.first())
if(cache.size > 10_000) cache.removeFirst()
cache[key] = this
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package xyz.wingio.syntakts.util

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock

/**
* Thread safe cache store, all writes are restricted with a [Mutex] to prevent [ConcurrentModificationException]s
*/
public class SynchronizedCache<K, V> {

private val scope: CoroutineScope = CoroutineScope(Dispatchers.Main.immediate)
private val mutex: Mutex = Mutex(false)
private val cache: MutableMap<K, V?> = mutableMapOf()

/**
* Number of cached entities
*/
public val size: Int get() = cache.size

public operator fun set(key: K, value: V?) {
scope.launch {
mutex.withLock {
cache[key] = value
}
}
}

public operator fun get(key: K): V? {
return cache[key]
}

/**
* Returns true if this cache already contains an element with the given [key]
*/
public fun hasKey(key: K): Boolean {
return cache.containsKey(key)
}

/**
* Removes the first element of this cache
*/
public fun removeFirst() {
scope.launch {
mutex.withLock {
cache.remove(cache.keys.firstOrNull())
}
}
}

}

0 comments on commit 8fdff23

Please sign in to comment.