commonMain.org.antlr.v4.kotlinruntime.atn.ATNConfigSet.kt Maven / Gradle / Ivy
The newest version!
// Copyright 2017-present Strumenta and contributors, licensed under Apache 2.0.
// Copyright 2024-present Strumenta and contributors, licensed under BSD 3-Clause.
package org.antlr.v4.kotlinruntime.atn
import com.strumenta.antlrkotlin.runtime.BitSet
import org.antlr.v4.kotlinruntime.misc.AbstractEqualityComparator
import org.antlr.v4.kotlinruntime.misc.Array2DHashSet
import org.antlr.v4.kotlinruntime.misc.DoubleKeyMap
import kotlin.math.max
/**
* Specialized [Set]`<`[ATNConfig]`>` that can track
* info about the set, with support for combining similar configurations using a
* graph-structured stack.
*
* @param fullCtx Indicates that this configuration set is part of a full
* context LL prediction. It will be used to determine how to merge `$`.
* With SLL it's a wildcard whereas it is not for LL context merge
*/
public open class ATNConfigSet(public val fullCtx: Boolean = true) : MutableSet {
/**
* The reason that we need this is that we don't want the hash map to use
* the standard hash code and equals. We need all configurations with the same
* `(s,i,_,semctx)` to be equal. Unfortunately, this key effectively doubles
* the number of objects associated with ATNConfigs. The other solution is to
* use a hash table that lets us specify the equals/hashcode operation.
*/
public class ConfigHashSet : AbstractConfigHashSet(ConfigEqualityComparator)
public object ConfigEqualityComparator : AbstractEqualityComparator() {
override fun hashCode(obj: ATNConfig): Int {
var hashCode = 7
hashCode = 31 * hashCode + obj.state.stateNumber
hashCode = 31 * hashCode + obj.alt
hashCode = 31 * hashCode + obj.semanticContext.hashCode()
return hashCode
}
override fun equals(a: ATNConfig?, b: ATNConfig?): Boolean {
if (a === b) {
return true
}
if (a == null || b == null) {
return false
}
return a.state.stateNumber == b.state.stateNumber
&& a.alt == b.alt
&& a.semanticContext == b.semanticContext
}
}
/**
* All configs but hashed by (s, i, _, pi) not including context.
*
* Wiped out when we go readonly as this set becomes a DFA state.
*/
public var configLookup: AbstractConfigHashSet? = ConfigHashSet()
/**
* Track the elements as they are added to the set; supports get(i).
*/
public val configs: MutableList = ArrayList(7)
// TODO: these fields make me pretty uncomfortable but nice to pack up info together, saves re-computation
// TODO: can we track conflicts as they are added to save scanning configs later?
public var uniqueAlt: Int = 0
/**
* Currently this is only used when we detect SLL conflict; this does
* not necessarily represent the ambiguous alternatives. In fact,
* I should also point out that this seems to include predicated alternatives
* that have predicates that evaluate to false. Computed in computeTargetState().
*/
public var conflictingAlts: BitSet? = null
// Used in parser and lexer. In lexer, it indicates we hit a pred
// while computing a closure operation. Don't make a DFA state from this.
public var hasSemanticContext: Boolean = false
public var dipsIntoOuterContext: Boolean = false
private var cachedHashCode = -1
public val states: Set
get() {
val states = HashSet()
for (c in configs) {
states.add(c.state)
}
return states
}
/**
* The complete set of represented alternatives for the configuration set.
*
* @since 4.3
*/
public val alts: BitSet
get() {
val alts = BitSet()
for (config in configs) {
alts.set(config.alt)
}
return alts
}
public val predicates: List
get() {
val preds = ArrayList()
for (c in configs) {
if (c.semanticContext !== SemanticContext.Empty) {
preds.add(c.semanticContext)
}
}
return preds
}
/**
* Indicates that the set of configurations is read-only. Do not
* allow any code to manipulate the set; DFA states will point at
* the sets, and they must not change. This does not protect the other
* fields; in particular, conflictingAlts is set after
* we've made this readonly.
*/
public var isReadonly: Boolean = false
set(value) {
field = value
// Can't mod, no need for lookup cache
configLookup = null
}
public constructor(old: ATNConfigSet) : this(old.fullCtx) {
@Suppress("LeakingThis")
addAll(old)
uniqueAlt = old.uniqueAlt
conflictingAlts = old.conflictingAlts
hasSemanticContext = old.hasSemanticContext
dipsIntoOuterContext = old.dipsIntoOuterContext
}
override fun add(element: ATNConfig): Boolean =
add(element, null)
/**
* Adding a new config means merging contexts with existing configs for
* `(s, i, pi, _)`, where `s` is the [ATNConfig.state],`i` is the [ATNConfig.alt],
* and `pi` is the [ATNConfig.semanticContext].
*
* We use `(s,i,pi)` as key.
*
* This method updates [dipsIntoOuterContext] and [hasSemanticContext] when necessary.
*/
public fun add(
config: ATNConfig,
mergeCache: DoubleKeyMap?,
): Boolean {
if (isReadonly) {
throw IllegalStateException("This set is readonly")
}
if (config.semanticContext !== SemanticContext.Empty) {
hasSemanticContext = true
}
if (config.outerContextDepth > 0) {
dipsIntoOuterContext = true
}
val existing = configLookup!!.getOrAdd(config)
if (existing === config) { // we added this new one
cachedHashCode = -1
configs.add(config) // track order here
return true
}
// A previous (s,i,pi,_), merge with it and save result
val rootIsWildcard = !fullCtx
val merged = PredictionContext.merge(existing.context, config.context, rootIsWildcard, mergeCache)
// No need to check for existing.context, config.context in cache
// since only way to create new graphs is "call rule" and here. We
// cache at both places
existing.reachesIntoOuterContext = max(existing.reachesIntoOuterContext, config.reachesIntoOuterContext)
// Make sure to preserve the precedence filter suppression during the merge
if (config.isPrecedenceFilterSuppressed) {
existing.isPrecedenceFilterSuppressed = true
}
existing.context = merged // replace context; no need to alt mapping
return true
}
/**
* Return a list holding list of configs.
*/
public fun elements(): List =
configs
public operator fun get(i: Int): ATNConfig =
configs[i]
public fun optimizeConfigs(interpreter: ATNSimulator) {
if (isReadonly) {
throw IllegalStateException("This set is readonly")
}
if (configLookup!!.isEmpty()) {
return
}
for (config in configs) {
config.context = interpreter.getCachedContext(config.context!!)
}
}
public override fun addAll(elements: Collection): Boolean {
for (c in elements) {
add(c)
}
return false
}
override fun equals(other: Any?): Boolean {
if (other === this) {
return true
}
if (other !is ATNConfigSet) {
return false
}
return configs == other.configs && // includes stack context
fullCtx == other.fullCtx &&
uniqueAlt == other.uniqueAlt &&
conflictingAlts === other.conflictingAlts &&
hasSemanticContext == other.hasSemanticContext &&
dipsIntoOuterContext == other.dipsIntoOuterContext
}
override fun hashCode(): Int {
if (isReadonly) {
if (cachedHashCode == -1) {
cachedHashCode = configs.hashCode()
}
return cachedHashCode
}
return configs.hashCode()
}
override val size: Int
get() = configs.size
override fun isEmpty(): Boolean =
configs.isEmpty()
override fun contains(element: ATNConfig): Boolean {
if (configLookup == null) {
throw UnsupportedOperationException("This method is not implemented for readonly sets.")
}
return configLookup!!.contains(element)
}
public fun containsFast(obj: ATNConfig): Boolean {
if (configLookup == null) {
throw UnsupportedOperationException("This method is not implemented for readonly sets.")
}
return configLookup!!.containsFast(obj)
}
override fun iterator(): MutableIterator =
configs.iterator()
public override fun clear() {
if (isReadonly) {
throw IllegalStateException("This set is readonly")
}
configs.clear()
cachedHashCode = -1
configLookup!!.clear()
}
override fun toString(): String {
val buf = StringBuilder()
buf.append(elements().toString())
if (hasSemanticContext) {
buf.append(",hasSemanticContext=")
buf.append(hasSemanticContext)
}
if (uniqueAlt != ATN.INVALID_ALT_NUMBER) {
buf.append(",uniqueAlt=")
buf.append(uniqueAlt)
}
if (conflictingAlts != null) {
buf.append(",conflictingAlts=")
buf.append(conflictingAlts)
}
if (dipsIntoOuterContext) {
buf.append(",dipsIntoOuterContext")
}
return buf.toString()
}
public fun toArray(): Array =
configLookup!!.toArray()
public fun toArray(a: Array): Array =
configLookup!!.toArray(a)
override fun remove(element: ATNConfig): Boolean =
throw UnsupportedOperationException()
override fun containsAll(elements: Collection): Boolean =
throw UnsupportedOperationException()
override fun retainAll(elements: Collection): Boolean =
throw UnsupportedOperationException()
override fun removeAll(elements: Collection): Boolean =
throw UnsupportedOperationException()
public abstract class AbstractConfigHashSet(
comparator: AbstractEqualityComparator,
initialCapacity: Int = 16,
initialBucketCapacity: Int = 2,
) : Array2DHashSet(comparator, initialCapacity, initialBucketCapacity) {
protected override fun asElementType(o: Any?): ATNConfig? =
if (o !is ATNConfig) null else o
protected override fun createBuckets(capacity: Int): Array?> =
arrayOfNulls(capacity)
protected override fun createBucket(capacity: Int): Array =
arrayOfNulls(capacity)
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy