ATNConfigSet.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. #
  2. # Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
  3. # Use of this file is governed by the BSD 3-clause license that
  4. # can be found in the LICENSE.txt file in the project root.
  5. #
  6. # Specialized {@link Set}{@code <}{@link ATNConfig}{@code >} that can track
  7. # info about the set, with support for combining similar configurations using a
  8. # graph-structured stack.
  9. #/
  10. from io import StringIO
  11. from functools import reduce
  12. from antlr4.PredictionContext import PredictionContext, merge
  13. from antlr4.Utils import str_list
  14. from antlr4.atn.ATN import ATN
  15. from antlr4.atn.ATNConfig import ATNConfig
  16. from antlr4.atn.SemanticContext import SemanticContext
  17. from antlr4.error.Errors import UnsupportedOperationException, IllegalStateException
  18. ATNSimulator = None
  19. class ATNConfigSet(object):
  20. __slots__ = (
  21. 'configLookup', 'fullCtx', 'readonly', 'configs', 'uniqueAlt',
  22. 'conflictingAlts', 'hasSemanticContext', 'dipsIntoOuterContext',
  23. 'cachedHashCode'
  24. )
  25. #
  26. # The reason that we need this is because we don't want the hash map to use
  27. # the standard hash code and equals. We need all configurations with the same
  28. # {@code (s,i,_,semctx)} to be equal. Unfortunately, this key effectively doubles
  29. # the number of objects associated with ATNConfigs. The other solution is to
  30. # use a hash table that lets us specify the equals/hashcode operation.
  31. def __init__(self, fullCtx:bool=True):
  32. # All configs but hashed by (s, i, _, pi) not including context. Wiped out
  33. # when we go readonly as this set becomes a DFA state.
  34. self.configLookup = dict()
  35. # Indicates that this configuration set is part of a full context
  36. # LL prediction. It will be used to determine how to merge $. With SLL
  37. # it's a wildcard whereas it is not for LL context merge.
  38. self.fullCtx = fullCtx
  39. # Indicates that the set of configurations is read-only. Do not
  40. # allow any code to manipulate the set; DFA states will point at
  41. # the sets and they must not change. This does not protect the other
  42. # fields; in particular, conflictingAlts is set after
  43. # we've made this readonly.
  44. self.readonly = False
  45. # Track the elements as they are added to the set; supports get(i)#/
  46. self.configs = []
  47. # TODO: these fields make me pretty uncomfortable but nice to pack up info together, saves recomputation
  48. # TODO: can we track conflicts as they are added to save scanning configs later?
  49. self.uniqueAlt = 0
  50. self.conflictingAlts = None
  51. # Used in parser and lexer. In lexer, it indicates we hit a pred
  52. # while computing a closure operation. Don't make a DFA state from this.
  53. self.hasSemanticContext = False
  54. self.dipsIntoOuterContext = False
  55. self.cachedHashCode = -1
  56. def __iter__(self):
  57. return self.configs.__iter__()
  58. # Adding a new config means merging contexts with existing configs for
  59. # {@code (s, i, pi, _)}, where {@code s} is the
  60. # {@link ATNConfig#state}, {@code i} is the {@link ATNConfig#alt}, and
  61. # {@code pi} is the {@link ATNConfig#semanticContext}. We use
  62. # {@code (s,i,pi)} as key.
  63. #
  64. # <p>This method updates {@link #dipsIntoOuterContext} and
  65. # {@link #hasSemanticContext} when necessary.</p>
  66. #/
  67. def add(self, config:ATNConfig, mergeCache=None):
  68. if self.readonly:
  69. raise Exception("This set is readonly")
  70. if config.semanticContext is not SemanticContext.NONE:
  71. self.hasSemanticContext = True
  72. if config.reachesIntoOuterContext > 0:
  73. self.dipsIntoOuterContext = True
  74. existing = self.getOrAdd(config)
  75. if existing is config:
  76. self.cachedHashCode = -1
  77. self.configs.append(config) # track order here
  78. return True
  79. # a previous (s,i,pi,_), merge with it and save result
  80. rootIsWildcard = not self.fullCtx
  81. merged = merge(existing.context, config.context, rootIsWildcard, mergeCache)
  82. # no need to check for existing.context, config.context in cache
  83. # since only way to create new graphs is "call rule" and here.
  84. # We cache at both places.
  85. existing.reachesIntoOuterContext = max(existing.reachesIntoOuterContext, config.reachesIntoOuterContext)
  86. # make sure to preserve the precedence filter suppression during the merge
  87. if config.precedenceFilterSuppressed:
  88. existing.precedenceFilterSuppressed = True
  89. existing.context = merged # replace context; no need to alt mapping
  90. return True
  91. def getOrAdd(self, config:ATNConfig):
  92. h = config.hashCodeForConfigSet()
  93. l = self.configLookup.get(h, None)
  94. if l is not None:
  95. r = next((cfg for cfg in l if config.equalsForConfigSet(cfg)), None)
  96. if r is not None:
  97. return r
  98. if l is None:
  99. l = [config]
  100. self.configLookup[h] = l
  101. else:
  102. l.append(config)
  103. return config
  104. def getStates(self):
  105. return set(c.state for c in self.configs)
  106. def getPredicates(self):
  107. return list(cfg.semanticContext for cfg in self.configs if cfg.semanticContext!=SemanticContext.NONE)
  108. def get(self, i:int):
  109. return self.configs[i]
  110. def optimizeConfigs(self, interpreter:ATNSimulator):
  111. if self.readonly:
  112. raise IllegalStateException("This set is readonly")
  113. if len(self.configs)==0:
  114. return
  115. for config in self.configs:
  116. config.context = interpreter.getCachedContext(config.context)
  117. def addAll(self, coll:list):
  118. for c in coll:
  119. self.add(c)
  120. return False
  121. def __eq__(self, other):
  122. if self is other:
  123. return True
  124. elif not isinstance(other, ATNConfigSet):
  125. return False
  126. same = self.configs is not None and \
  127. self.configs==other.configs and \
  128. self.fullCtx == other.fullCtx and \
  129. self.uniqueAlt == other.uniqueAlt and \
  130. self.conflictingAlts == other.conflictingAlts and \
  131. self.hasSemanticContext == other.hasSemanticContext and \
  132. self.dipsIntoOuterContext == other.dipsIntoOuterContext
  133. return same
  134. def __hash__(self):
  135. if self.readonly:
  136. if self.cachedHashCode == -1:
  137. self.cachedHashCode = self.hashConfigs()
  138. return self.cachedHashCode
  139. return self.hashConfigs()
  140. def hashConfigs(self):
  141. return reduce(lambda h, cfg: hash((h, cfg)), self.configs, 0)
  142. def __len__(self):
  143. return len(self.configs)
  144. def isEmpty(self):
  145. return len(self.configs)==0
  146. def __contains__(self, config):
  147. if self.configLookup is None:
  148. raise UnsupportedOperationException("This method is not implemented for readonly sets.")
  149. h = config.hashCodeForConfigSet()
  150. l = self.configLookup.get(h, None)
  151. if l is not None:
  152. for c in l:
  153. if config.equalsForConfigSet(c):
  154. return True
  155. return False
  156. def clear(self):
  157. if self.readonly:
  158. raise IllegalStateException("This set is readonly")
  159. self.configs.clear()
  160. self.cachedHashCode = -1
  161. self.configLookup.clear()
  162. def setReadonly(self, readonly:bool):
  163. self.readonly = readonly
  164. self.configLookup = None # can't mod, no need for lookup cache
  165. def __str__(self):
  166. with StringIO() as buf:
  167. buf.write(str_list(self.configs))
  168. if self.hasSemanticContext:
  169. buf.write(",hasSemanticContext=")
  170. buf.write(str(self.hasSemanticContext))
  171. if self.uniqueAlt!=ATN.INVALID_ALT_NUMBER:
  172. buf.write(",uniqueAlt=")
  173. buf.write(str(self.uniqueAlt))
  174. if self.conflictingAlts is not None:
  175. buf.write(",conflictingAlts=")
  176. buf.write(str(self.conflictingAlts))
  177. if self.dipsIntoOuterContext:
  178. buf.write(",dipsIntoOuterContext")
  179. return buf.getvalue()
  180. class OrderedATNConfigSet(ATNConfigSet):
  181. def __init__(self):
  182. super().__init__()