org.antlr.v4.runtime.atn.ATNConfigSet Maven / Gradle / Ivy
/*
* Copyright (c) 2012 The ANTLR Project. All rights reserved.
* Use of this file is governed by the BSD-3-Clause license that
* can be found in the LICENSE.txt file in the project root.
*/
package org.antlr.v4.runtime.atn;
import org.antlr.v4.runtime.misc.NotNull;
import org.antlr.v4.runtime.misc.Nullable;
import org.antlr.v4.runtime.misc.Utils;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
/**
* Represents a set of ATN configurations (see {@link ATNConfig}). As
* configurations are added to the set, they are merged with other
* {@link ATNConfig} instances already in the set when possible using the
* graph-structured stack.
*
* An instance of this class represents the complete set of positions (with
* context) in an ATN which would be associated with a single DFA state. Its
* internal representation is more complex than traditional state used for NFA
* to DFA conversion due to performance requirements (both improving speed and
* reducing memory overhead) as well as supporting features such as semantic
* predicates and non-greedy operators in a form to support ANTLR's prediction
* algorithm.
*
* @author Sam Harwell
*/
public class ATNConfigSet implements Set {
/**
* This maps (state, alt) -> merged {@link ATNConfig}. The key does not account for
* the {@link ATNConfig#getSemanticContext} of the value, which is only a problem if a single
* {@code ATNConfigSet} contains two configs with the same state and alternative
* but different semantic contexts. When this case arises, the first config
* added to this map stays, and the remaining configs are placed in {@link #unmerged}.
*
* This map is only used for optimizing the process of adding configs to the set,
* and is {@code null} for read-only sets stored in the DFA.
*/
private final HashMap mergedConfigs;
/**
* This is an "overflow" list holding configs which cannot be merged with one
* of the configs in {@link #mergedConfigs} but have a colliding key. This
* occurs when two configs in the set have the same state and alternative but
* different semantic contexts.
*
* This list is only used for optimizing the process of adding configs to the set,
* and is {@code null} for read-only sets stored in the DFA.
*/
private final ArrayList unmerged;
/**
* This is a list of all configs in this set.
*/
private final ArrayList configs;
private int uniqueAlt;
private ConflictInfo conflictInfo;
// 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.
private boolean hasSemanticContext;
private boolean dipsIntoOuterContext;
/**
* When {@code true}, this config set represents configurations where the entire
* outer context has been consumed by the ATN interpreter. This prevents the
* {@link ParserATNSimulator#closure} from pursuing the global FOLLOW when a
* rule stop state is reached with an empty prediction context.
*
* Note: {@code outermostConfigSet} and {@link #dipsIntoOuterContext} should never
* be true at the same time.
*/
private boolean outermostConfigSet;
private int cachedHashCode = -1;
public ATNConfigSet() {
this.mergedConfigs = new HashMap();
this.unmerged = new ArrayList();
this.configs = new ArrayList();
this.uniqueAlt = ATN.INVALID_ALT_NUMBER;
}
@SuppressWarnings("unchecked")
protected ATNConfigSet(ATNConfigSet set, boolean readonly) {
if (readonly) {
this.mergedConfigs = null;
this.unmerged = null;
} else if (!set.isReadOnly()) {
this.mergedConfigs = (HashMap)set.mergedConfigs.clone();
this.unmerged = (ArrayList)set.unmerged.clone();
} else {
this.mergedConfigs = new HashMap(set.configs.size());
this.unmerged = new ArrayList();
}
this.configs = (ArrayList)set.configs.clone();
this.dipsIntoOuterContext = set.dipsIntoOuterContext;
this.hasSemanticContext = set.hasSemanticContext;
this.outermostConfigSet = set.outermostConfigSet;
if (readonly || !set.isReadOnly()) {
this.uniqueAlt = set.uniqueAlt;
this.conflictInfo = set.conflictInfo;
}
// if (!readonly && set.isReadOnly()) -> addAll is called from clone()
}
/**
* Get the set of all alternatives represented by configurations in this
* set.
*/
@NotNull
public BitSet getRepresentedAlternatives() {
if (conflictInfo != null) {
return (BitSet)conflictInfo.getConflictedAlts().clone();
}
BitSet alts = new BitSet();
for (ATNConfig config : this) {
alts.set(config.getAlt());
}
return alts;
}
public final boolean isReadOnly() {
return mergedConfigs == null;
}
public boolean isOutermostConfigSet() {
return outermostConfigSet;
}
public void setOutermostConfigSet(boolean outermostConfigSet) {
if (this.outermostConfigSet && !outermostConfigSet) {
throw new IllegalStateException();
}
assert !outermostConfigSet || !dipsIntoOuterContext;
this.outermostConfigSet = outermostConfigSet;
}
public Set getStates() {
Set states = new HashSet();
for (ATNConfig c : this.configs) {
states.add(c.getState());
}
return states;
}
public void optimizeConfigs(ATNSimulator interpreter) {
if (configs.isEmpty()) {
return;
}
for (int i = 0; i < configs.size(); i++) {
ATNConfig config = configs.get(i);
config.setContext(interpreter.atn.getCachedContext(config.getContext()));
}
}
public ATNConfigSet clone(boolean readonly) {
ATNConfigSet copy = new ATNConfigSet(this, readonly);
if (!readonly && this.isReadOnly()) {
copy.addAll(this.configs);
}
return copy;
}
@Override
public int size() {
return configs.size();
}
@Override
public boolean isEmpty() {
return configs.isEmpty();
}
@Override
public boolean contains(Object o) {
if (!(o instanceof ATNConfig)) {
return false;
}
ATNConfig config = (ATNConfig)o;
long configKey = getKey(config);
ATNConfig mergedConfig = mergedConfigs.get(configKey);
if (mergedConfig != null && canMerge(config, configKey, mergedConfig)) {
return mergedConfig.contains(config);
}
for (ATNConfig c : unmerged) {
if (c.contains(config)) {
return true;
}
}
return false;
}
@Override
public Iterator iterator() {
return new ATNConfigSetIterator();
}
@Override
public Object[] toArray() {
return configs.toArray();
}
@Override
public T[] toArray(T[] a) {
return configs.toArray(a);
}
@Override
public boolean add(ATNConfig e) {
return add(e, null);
}
public boolean add(ATNConfig e, @Nullable PredictionContextCache contextCache) {
ensureWritable();
assert !outermostConfigSet || !e.getReachesIntoOuterContext();
if (contextCache == null) {
contextCache = PredictionContextCache.UNCACHED;
}
boolean addKey;
long key = getKey(e);
ATNConfig mergedConfig = mergedConfigs.get(key);
addKey = (mergedConfig == null);
if (mergedConfig != null && canMerge(e, key, mergedConfig)) {
mergedConfig.setOuterContextDepth(Math.max(mergedConfig.getOuterContextDepth(), e.getOuterContextDepth()));
if (e.isPrecedenceFilterSuppressed()) {
mergedConfig.setPrecedenceFilterSuppressed(true);
}
PredictionContext joined = PredictionContext.join(mergedConfig.getContext(), e.getContext(), contextCache);
updatePropertiesForMergedConfig(e);
if (mergedConfig.getContext() == joined) {
return false;
}
mergedConfig.setContext(joined);
return true;
}
for (int i = 0; i < unmerged.size(); i++) {
ATNConfig unmergedConfig = unmerged.get(i);
if (canMerge(e, key, unmergedConfig)) {
unmergedConfig.setOuterContextDepth(Math.max(unmergedConfig.getOuterContextDepth(), e.getOuterContextDepth()));
if (e.isPrecedenceFilterSuppressed()) {
unmergedConfig.setPrecedenceFilterSuppressed(true);
}
PredictionContext joined = PredictionContext.join(unmergedConfig.getContext(), e.getContext(), contextCache);
updatePropertiesForMergedConfig(e);
if (unmergedConfig.getContext() == joined) {
return false;
}
unmergedConfig.setContext(joined);
if (addKey) {
mergedConfigs.put(key, unmergedConfig);
unmerged.remove(i);
}
return true;
}
}
configs.add(e);
if (addKey) {
mergedConfigs.put(key, e);
} else {
unmerged.add(e);
}
updatePropertiesForAddedConfig(e);
return true;
}
private void updatePropertiesForMergedConfig(ATNConfig config) {
// merged configs can't change the alt or semantic context
dipsIntoOuterContext |= config.getReachesIntoOuterContext();
assert !outermostConfigSet || !dipsIntoOuterContext;
}
private void updatePropertiesForAddedConfig(ATNConfig config) {
if (configs.size() == 1) {
uniqueAlt = config.getAlt();
} else if (uniqueAlt != config.getAlt()) {
uniqueAlt = ATN.INVALID_ALT_NUMBER;
}
hasSemanticContext |= !SemanticContext.NONE.equals(config.getSemanticContext());
dipsIntoOuterContext |= config.getReachesIntoOuterContext();
assert !outermostConfigSet || !dipsIntoOuterContext;
}
protected boolean canMerge(ATNConfig left, long leftKey, ATNConfig right) {
if (left.getState().stateNumber != right.getState().stateNumber) {
return false;
}
if (leftKey != getKey(right)) {
return false;
}
return left.getSemanticContext().equals(right.getSemanticContext());
}
protected long getKey(ATNConfig e) {
long key = e.getState().stateNumber;
key = (key << 12) | (e.getAlt() & 0xFFF);
return key;
}
@Override
public boolean remove(Object o) {
ensureWritable();
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public boolean containsAll(Collection> c) {
for (Object o : c) {
if (!(o instanceof ATNConfig)) {
return false;
}
if (!contains((ATNConfig)o)) {
return false;
}
}
return true;
}
@Override
public boolean addAll(Collection extends ATNConfig> c) {
return addAll(c, null);
}
public boolean addAll(Collection extends ATNConfig> c, PredictionContextCache contextCache) {
ensureWritable();
boolean changed = false;
for (ATNConfig group : c) {
changed |= add(group, contextCache);
}
return changed;
}
@Override
public boolean retainAll(Collection> c) {
ensureWritable();
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public boolean removeAll(Collection> c) {
ensureWritable();
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void clear() {
ensureWritable();
mergedConfigs.clear();
unmerged.clear();
configs.clear();
dipsIntoOuterContext = false;
hasSemanticContext = false;
uniqueAlt = ATN.INVALID_ALT_NUMBER;
conflictInfo = null;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof ATNConfigSet)) {
return false;
}
ATNConfigSet other = (ATNConfigSet)obj;
return this.outermostConfigSet == other.outermostConfigSet
&& Utils.equals(conflictInfo, other.conflictInfo)
&& configs.equals(other.configs);
}
@Override
public int hashCode() {
if (isReadOnly() && cachedHashCode != -1) {
return cachedHashCode;
}
int hashCode = 1;
hashCode = 5 * hashCode ^ (outermostConfigSet ? 1 : 0);
hashCode = 5 * hashCode ^ configs.hashCode();
if (isReadOnly()) {
cachedHashCode = hashCode;
}
return hashCode;
}
@Override
public String toString() {
return toString(false);
}
public String toString(boolean showContext) {
StringBuilder buf = new StringBuilder();
List sortedConfigs = new ArrayList(configs);
Collections.sort(sortedConfigs, new Comparator() {
@Override
public int compare(ATNConfig o1, ATNConfig o2) {
if (o1.getAlt() != o2.getAlt()) {
return o1.getAlt() - o2.getAlt();
}
else if (o1.getState().stateNumber != o2.getState().stateNumber) {
return o1.getState().stateNumber - o2.getState().stateNumber;
}
else {
return o1.getSemanticContext().toString().compareTo(o2.getSemanticContext().toString());
}
}
});
buf.append("[");
for (int i = 0; i < sortedConfigs.size(); i++) {
if (i > 0) {
buf.append(", ");
}
buf.append(sortedConfigs.get(i).toString(null, true, showContext));
}
buf.append("]");
if ( hasSemanticContext ) buf.append(",hasSemanticContext=").append(hasSemanticContext);
if ( uniqueAlt!=ATN.INVALID_ALT_NUMBER ) buf.append(",uniqueAlt=").append(uniqueAlt);
if ( conflictInfo!=null ) {
buf.append(",conflictingAlts=").append(conflictInfo.getConflictedAlts());
if (!conflictInfo.isExact()) {
buf.append("*");
}
}
if ( dipsIntoOuterContext ) buf.append(",dipsIntoOuterContext");
return buf.toString();
}
public int getUniqueAlt() {
return uniqueAlt;
}
public boolean hasSemanticContext() {
return hasSemanticContext;
}
public void clearExplicitSemanticContext() {
ensureWritable();
hasSemanticContext = false;
}
public void markExplicitSemanticContext() {
ensureWritable();
hasSemanticContext = true;
}
public ConflictInfo getConflictInfo() {
return conflictInfo;
}
public void setConflictInfo(ConflictInfo conflictInfo) {
ensureWritable();
this.conflictInfo = conflictInfo;
}
public BitSet getConflictingAlts() {
if (conflictInfo == null) {
return null;
}
return conflictInfo.getConflictedAlts();
}
public boolean isExactConflict() {
if (conflictInfo == null) {
return false;
}
return conflictInfo.isExact();
}
public boolean getDipsIntoOuterContext() {
return dipsIntoOuterContext;
}
public ATNConfig get(int index) {
return configs.get(index);
}
public void remove(int index) {
ensureWritable();
ATNConfig config = configs.get(index);
configs.remove(config);
long key = getKey(config);
if (mergedConfigs.get(key) == config) {
mergedConfigs.remove(key);
} else {
for (int i = 0; i < unmerged.size(); i++) {
if (unmerged.get(i) == config) {
unmerged.remove(i);
return;
}
}
}
}
protected final void ensureWritable() {
if (isReadOnly()) {
throw new IllegalStateException("This ATNConfigSet is read only.");
}
}
private final class ATNConfigSetIterator implements Iterator {
int index = -1;
boolean removed = false;
@Override
public boolean hasNext() {
return index + 1 < configs.size();
}
@Override
public ATNConfig next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
index++;
removed = false;
return configs.get(index);
}
@Override
public void remove() {
if (removed || index < 0 || index >= configs.size()) {
throw new IllegalStateException();
}
ATNConfigSet.this.remove(index);
removed = true;
}
}
}