# # Copyright (c) 2012-2017 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. #/ from io import StringIO from antlr4.error.Errors import IllegalStateException from antlr4.RuleContext import RuleContext from antlr4.atn.ATN import ATN from antlr4.atn.ATNState import ATNState class PredictionContext(object): # Represents {@code $} in local context prediction, which means wildcard. # {@code#+x =#}. #/ EMPTY = None # Represents {@code $} in an array in full context mode, when {@code $} # doesn't mean wildcard: {@code $ + x = [$,x]}. Here, # {@code $} = {@link #EMPTY_RETURN_STATE}. #/ EMPTY_RETURN_STATE = 0x7FFFFFFF globalNodeCount = 1 id = globalNodeCount # Stores the computed hash code of this {@link PredictionContext}. The hash # code is computed in parts to match the following reference algorithm. # #
# private int referenceHashCode() { # int hash = {@link MurmurHash#initialize MurmurHash.initialize}({@link #INITIAL_HASH}); # # for (int i = 0; i < {@link #size()}; i++) { # hash = {@link MurmurHash#update MurmurHash.update}(hash, {@link #getParent getParent}(i)); # } # # for (int i = 0; i < {@link #size()}; i++) { # hash = {@link MurmurHash#update MurmurHash.update}(hash, {@link #getReturnState getReturnState}(i)); # } # # hash = {@link MurmurHash#finish MurmurHash.finish}(hash, 2# {@link #size()}); # return hash; # } ##/ def __init__(self, cachedHashCode:int): self.cachedHashCode = cachedHashCode def __len__(self): return 0 # This means only the {@link #EMPTY} context is in set. def isEmpty(self): return self is self.EMPTY def hasEmptyPath(self): return self.getReturnState(len(self) - 1) == self.EMPTY_RETURN_STATE def getReturnState(self, index:int): raise IllegalStateException("illegal!") def __hash__(self): return self.cachedHashCode def calculateHashCode(parent:PredictionContext, returnState:int): return hash("") if parent is None else hash((hash(parent), returnState)) def calculateListsHashCode(parents:[], returnStates:[] ): h = 0 for parent, returnState in zip(parents, returnStates): h = hash((h, calculateHashCode(parent, returnState))) return h # Used to cache {@link PredictionContext} objects. Its used for the shared # context cash associated with contexts in DFA states. This cache # can be used for both lexers and parsers. class PredictionContextCache(object): def __init__(self): self.cache = dict() # Add a context to the cache and return it. If the context already exists, # return that one instead and do not add a new context to the cache. # Protect shared cache from unsafe thread access. # def add(self, ctx:PredictionContext): if ctx==PredictionContext.EMPTY: return PredictionContext.EMPTY existing = self.cache.get(ctx, None) if existing is not None: return existing self.cache[ctx] = ctx return ctx def get(self, ctx:PredictionContext): return self.cache.get(ctx, None) def __len__(self): return len(self.cache) class SingletonPredictionContext(PredictionContext): @staticmethod def create(parent:PredictionContext , returnState:int ): if returnState == PredictionContext.EMPTY_RETURN_STATE and parent is None: # someone can pass in the bits of an array ctx that mean $ return SingletonPredictionContext.EMPTY else: return SingletonPredictionContext(parent, returnState) def __init__(self, parent:PredictionContext, returnState:int): hashCode = calculateHashCode(parent, returnState) super().__init__(hashCode) self.parentCtx = parent self.returnState = returnState def __len__(self): return 1 def getParent(self, index:int): return self.parentCtx def getReturnState(self, index:int): return self.returnState def __eq__(self, other): if self is other: return True elif other is None: return False elif not isinstance(other, SingletonPredictionContext): return False else: return self.returnState == other.returnState and self.parentCtx == other.parentCtx def __hash__(self): return self.cachedHashCode def __str__(self): up = "" if self.parentCtx is None else str(self.parentCtx) if len(up)==0: if self.returnState == self.EMPTY_RETURN_STATE: return "$" else: return str(self.returnState) else: return str(self.returnState) + " " + up class EmptyPredictionContext(SingletonPredictionContext): def __init__(self): super().__init__(None, self.EMPTY_RETURN_STATE) def isEmpty(self): return True def __eq__(self, other): return self is other def __hash__(self): return self.cachedHashCode def __str__(self): return "$" PredictionContext.EMPTY = EmptyPredictionContext() class ArrayPredictionContext(PredictionContext): # Parent can be null only if full ctx mode and we make an array # from {@link #EMPTY} and non-empty. We merge {@link #EMPTY} by using null parent and # returnState == {@link #EMPTY_RETURN_STATE}. def __init__(self, parents:list, returnStates:list): super().__init__(calculateListsHashCode(parents, returnStates)) self.parents = parents self.returnStates = returnStates def isEmpty(self): # since EMPTY_RETURN_STATE can only appear in the last position, we # don't need to verify that size==1 return self.returnStates[0]==PredictionContext.EMPTY_RETURN_STATE def __len__(self): return len(self.returnStates) def getParent(self, index:int): return self.parents[index] def getReturnState(self, index:int): return self.returnStates[index] def __eq__(self, other): if self is other: return True elif not isinstance(other, ArrayPredictionContext): return False elif hash(self) != hash(other): return False # can't be same if hash is different else: return self.returnStates==other.returnStates and self.parents==other.parents def __str__(self): if self.isEmpty(): return "[]" with StringIO() as buf: buf.write("[") for i in range(0,len(self.returnStates)): if i>0: buf.write(", ") if self.returnStates[i]==PredictionContext.EMPTY_RETURN_STATE: buf.write("$") continue buf.write(str(self.returnStates[i])) if self.parents[i] is not None: buf.write(' ') buf.write(str(self.parents[i])) else: buf.write("null") buf.write("]") return buf.getvalue() def __hash__(self): return self.cachedHashCode # Convert a {@link RuleContext} tree to a {@link PredictionContext} graph. # Return {@link #EMPTY} if {@code outerContext} is empty or null. #/ def PredictionContextFromRuleContext(atn:ATN, outerContext:RuleContext=None): if outerContext is None: outerContext = RuleContext.EMPTY # if we are in RuleContext of start rule, s, then PredictionContext # is EMPTY. Nobody called us. (if we are empty, return empty) if outerContext.parentCtx is None or outerContext is RuleContext.EMPTY: return PredictionContext.EMPTY # If we have a parent, convert it to a PredictionContext graph parent = PredictionContextFromRuleContext(atn, outerContext.parentCtx) state = atn.states[outerContext.invokingState] transition = state.transitions[0] return SingletonPredictionContext.create(parent, transition.followState.stateNumber) def merge(a:PredictionContext, b:PredictionContext, rootIsWildcard:bool, mergeCache:dict): # share same graph if both same if a==b: return a if isinstance(a, SingletonPredictionContext) and isinstance(b, SingletonPredictionContext): return mergeSingletons(a, b, rootIsWildcard, mergeCache) # At least one of a or b is array # If one is $ and rootIsWildcard, return $ as# wildcard if rootIsWildcard: if isinstance( a, EmptyPredictionContext ): return a if isinstance( b, EmptyPredictionContext ): return b # convert singleton so both are arrays to normalize if isinstance( a, SingletonPredictionContext ): a = ArrayPredictionContext([a.parentCtx], [a.returnState]) if isinstance( b, SingletonPredictionContext): b = ArrayPredictionContext([b.parentCtx], [b.returnState]) return mergeArrays(a, b, rootIsWildcard, mergeCache) # # Merge two {@link SingletonPredictionContext} instances. # #
Stack tops equal, parents merge is same; return left graph.
#
Same stack top, parents differ; merge parents giving array node, then
# remainders of those graphs. A new root node is created to point to the
# merged parents.
#
Different stack tops pointing to same parent. Make array node for the
# root where both element in the root point to the same (original)
# parent.
#
Different stack tops pointing to different parents. Make array node for
# the root where each element points to the corresponding original
# parent.
#
These local-context merge operations are used when {@code rootIsWildcard} # is true.
# #{@link #EMPTY} is superset of any graph; return {@link #EMPTY}.
#
{@link #EMPTY} and anything is {@code #EMPTY}, so merged parent is
# {@code #EMPTY}; return left graph.
#
Special case of last merge if local context.
#
These full-context merge operations are used when {@code rootIsWildcard} # is false.
# # # #Must keep all contexts; {@link #EMPTY} in array is a special value (and
# null parent).
#
Different tops, different parents.
#
Shared top, same parents.
#
Shared top, different parents.
#
Shared top, all shared parents.
#
Equal tops, merge parents and reduce top to
# {@link SingletonPredictionContext}.
#