This repo contains code to mirror other repos. It also contains the code that is getting mirrored.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

311 lines
14 KiB

  1. """This module implements an scanerless Earley parser.
  2. The core Earley algorithm used here is based on Elizabeth Scott's implementation, here:
  3. https://www.sciencedirect.com/science/article/pii/S1571066108001497
  4. That is probably the best reference for understanding the algorithm here.
  5. The Earley parser outputs an SPPF-tree as per that document. The SPPF tree format
  6. is better documented here:
  7. http://www.bramvandersanden.com/post/2014/06/shared-packed-parse-forest/
  8. """
  9. # Author: Erez Shinan (2017)
  10. # Email : erezshin@gmail.com
  11. from ..visitors import Transformer_InPlace, v_args
  12. from ..exceptions import ParseError, UnexpectedToken
  13. from .grammar_analysis import GrammarAnalyzer
  14. from ..grammar import NonTerminal
  15. from .earley_common import Item, TransitiveItem
  16. from .earley_forest import ForestToTreeVisitor, ForestSumVisitor, SymbolNode
  17. from collections import deque, defaultdict
  18. class Parser:
  19. def __init__(self, parser_conf, term_matcher, resolve_ambiguity=True, forest_sum_visitor = ForestSumVisitor):
  20. analysis = GrammarAnalyzer(parser_conf)
  21. self.parser_conf = parser_conf
  22. self.resolve_ambiguity = resolve_ambiguity
  23. self.FIRST = analysis.FIRST
  24. self.NULLABLE = analysis.NULLABLE
  25. self.callbacks = {}
  26. self.predictions = {}
  27. ## These could be moved to the grammar analyzer. Pre-computing these is *much* faster than
  28. # the slow 'isupper' in is_terminal.
  29. self.TERMINALS = { sym for r in parser_conf.rules for sym in r.expansion if sym.is_term }
  30. self.NON_TERMINALS = { sym for r in parser_conf.rules for sym in r.expansion if not sym.is_term }
  31. for rule in parser_conf.rules:
  32. self.callbacks[rule] = rule.alias if callable(rule.alias) else getattr(parser_conf.callback, rule.alias)
  33. self.predictions[rule.origin] = [x.rule for x in analysis.expand_rule(rule.origin)]
  34. self.forest_tree_visitor = ForestToTreeVisitor(forest_sum_visitor, self.callbacks)
  35. self.term_matcher = term_matcher
  36. def parse(self, stream, start_symbol=None):
  37. # Define parser functions
  38. start_symbol = NonTerminal(start_symbol or self.parser_conf.start)
  39. match = self.term_matcher
  40. # Held Completions (H in E.Scotts paper).
  41. held_completions = {}
  42. # Cache for nodes & tokens created in a particular parse step.
  43. node_cache = {}
  44. token_cache = {}
  45. columns = []
  46. transitives = []
  47. def is_quasi_complete(item):
  48. if item.is_complete:
  49. return True
  50. quasi = item.advance()
  51. while not quasi.is_complete:
  52. symbol = quasi.expect
  53. if symbol not in self.NULLABLE:
  54. return False
  55. if quasi.rule.origin == start_symbol and symbol == start_symbol:
  56. return False
  57. quasi = quasi.advance()
  58. return True
  59. def create_leo_transitives(item, trule, previous, visited = None):
  60. if visited is None:
  61. visited = set()
  62. if item.rule.origin in transitives[item.start]:
  63. previous = trule = transitives[item.start][item.rule.origin]
  64. return trule, previous
  65. is_empty_rule = not self.FIRST[item.rule.origin]
  66. if is_empty_rule:
  67. return trule, previous
  68. originator = None
  69. for key in columns[item.start]:
  70. if key.expect is not None and key.expect == item.rule.origin:
  71. if originator is not None:
  72. return trule, previous
  73. originator = key
  74. if originator is None:
  75. return trule, previous
  76. if originator in visited:
  77. return trule, previous
  78. visited.add(originator)
  79. if not is_quasi_complete(originator):
  80. return trule, previous
  81. trule = originator.advance()
  82. if originator.start != item.start:
  83. visited.clear()
  84. trule, previous = create_leo_transitives(originator, trule, previous, visited)
  85. if trule is None:
  86. return trule, previous
  87. titem = None
  88. if previous is not None:
  89. titem = TransitiveItem(item.rule.origin, trule, originator, previous.column)
  90. previous.next_titem = titem
  91. else:
  92. titem = TransitiveItem(item.rule.origin, trule, originator, item.start)
  93. previous = transitives[item.start][item.rule.origin] = titem
  94. return trule, previous
  95. def predict_and_complete(i, to_scan):
  96. """The core Earley Predictor and Completer.
  97. At each stage of the input, we handling any completed items (things
  98. that matched on the last cycle) and use those to predict what should
  99. come next in the input stream. The completions and any predicted
  100. non-terminals are recursively processed until we reach a set of,
  101. which can be added to the scan list for the next scanner cycle."""
  102. held_completions.clear()
  103. column = columns[i]
  104. # R (items) = Ei (column.items)
  105. items = deque(column)
  106. while items:
  107. item = items.pop() # remove an element, A say, from R
  108. ### The Earley completer
  109. if item.is_complete: ### (item.s == string)
  110. if item.node is None:
  111. label = (item.s, item.start, i)
  112. item.node = node_cache[label] if label in node_cache else node_cache.setdefault(label, SymbolNode(*label))
  113. item.node.add_family(item.s, item.rule, item.start, None, None)
  114. create_leo_transitives(item, None, None)
  115. ###R Joop Leo right recursion Completer
  116. if item.rule.origin in transitives[item.start]:
  117. transitive = transitives[item.start][item.s]
  118. if transitive.previous in transitives[transitive.column]:
  119. root_transitive = transitives[transitive.column][transitive.previous]
  120. else:
  121. root_transitive = transitive
  122. label = (root_transitive.s, root_transitive.start, i)
  123. node = vn = node_cache[label] if label in node_cache else node_cache.setdefault(label, SymbolNode(*label))
  124. vn.add_path(root_transitive, item.node)
  125. new_item = Item(transitive.rule, transitive.ptr, transitive.start)
  126. new_item.node = vn
  127. if new_item.expect in self.TERMINALS:
  128. # Add (B :: aC.B, h, y) to Q
  129. to_scan.add(new_item)
  130. elif new_item not in column:
  131. # Add (B :: aC.B, h, y) to Ei and R
  132. column.add(new_item)
  133. items.append(new_item)
  134. ###R Regular Earley completer
  135. else:
  136. # Empty has 0 length. If we complete an empty symbol in a particular
  137. # parse step, we need to be able to use that same empty symbol to complete
  138. # any predictions that result, that themselves require empty. Avoids
  139. # infinite recursion on empty symbols.
  140. # held_completions is 'H' in E.Scott's paper.
  141. is_empty_item = item.start == i
  142. if is_empty_item:
  143. held_completions[item.rule.origin] = item.node
  144. originators = [originator for originator in columns[item.start] if originator.expect is not None and originator.expect == item.s]
  145. for originator in originators:
  146. new_item = originator.advance()
  147. label = (new_item.s, originator.start, i)
  148. new_item.node = node_cache[label] if label in node_cache else node_cache.setdefault(label, SymbolNode(*label))
  149. new_item.node.add_family(new_item.s, new_item.rule, i, originator.node, item.node)
  150. if new_item.expect in self.TERMINALS:
  151. # Add (B :: aC.B, h, y) to Q
  152. to_scan.add(new_item)
  153. elif new_item not in column:
  154. # Add (B :: aC.B, h, y) to Ei and R
  155. column.add(new_item)
  156. items.append(new_item)
  157. ### The Earley predictor
  158. elif item.expect in self.NON_TERMINALS: ### (item.s == lr0)
  159. new_items = []
  160. for rule in self.predictions[item.expect]:
  161. new_item = Item(rule, 0, i)
  162. new_items.append(new_item)
  163. # Process any held completions (H).
  164. if item.expect in held_completions:
  165. new_item = item.advance()
  166. label = (new_item.s, item.start, i)
  167. new_item.node = node_cache[label] if label in node_cache else node_cache.setdefault(label, SymbolNode(*label))
  168. new_item.node.add_family(new_item.s, new_item.rule, new_item.start, item.node, held_completions[item.expect])
  169. new_items.append(new_item)
  170. for new_item in new_items:
  171. if new_item.expect in self.TERMINALS:
  172. to_scan.add(new_item)
  173. elif new_item not in column:
  174. column.add(new_item)
  175. items.append(new_item)
  176. def scan(i, token, to_scan):
  177. """The core Earley Scanner.
  178. This is a custom implementation of the scanner that uses the
  179. Lark lexer to match tokens. The scan list is built by the
  180. Earley predictor, based on the previously completed tokens.
  181. This ensures that at each phase of the parse we have a custom
  182. lexer context, allowing for more complex ambiguities."""
  183. next_to_scan = set()
  184. next_set = set()
  185. columns.append(next_set)
  186. next_transitives = dict()
  187. transitives.append(next_transitives)
  188. for item in set(to_scan):
  189. if match(item.expect, token):
  190. new_item = item.advance()
  191. label = (new_item.s, new_item.start, i)
  192. new_item.node = node_cache[label] if label in node_cache else node_cache.setdefault(label, SymbolNode(*label))
  193. new_item.node.add_family(new_item.s, item.rule, new_item.start, item.node, token)
  194. if new_item.expect in self.TERMINALS:
  195. # add (B ::= Aai+1.B, h, y) to Q'
  196. next_to_scan.add(new_item)
  197. else:
  198. # add (B ::= Aa+1.B, h, y) to Ei+1
  199. next_set.add(new_item)
  200. if not next_set and not next_to_scan:
  201. expect = {i.expect.name for i in to_scan}
  202. raise UnexpectedToken(token, expect, considered_rules = set(to_scan))
  203. return next_to_scan
  204. # Main loop starts
  205. columns.append(set())
  206. transitives.append(dict())
  207. ## The scan buffer. 'Q' in E.Scott's paper.
  208. to_scan = set()
  209. ## Predict for the start_symbol.
  210. # Add predicted items to the first Earley set (for the predictor) if they
  211. # result in a non-terminal, or the scanner if they result in a terminal.
  212. for rule in self.predictions[start_symbol]:
  213. item = Item(rule, 0, 0)
  214. if item.expect in self.TERMINALS:
  215. to_scan.add(item)
  216. else:
  217. columns[0].add(item)
  218. ## The main Earley loop.
  219. # Run the Prediction/Completion cycle for any Items in the current Earley set.
  220. # Completions will be added to the SPPF tree, and predictions will be recursively
  221. # processed down to terminals/empty nodes to be added to the scanner for the next
  222. # step.
  223. i = 0
  224. for token in stream:
  225. predict_and_complete(i, to_scan)
  226. # Clear the node_cache and token_cache, which are only relevant for each
  227. # step in the Earley pass.
  228. node_cache.clear()
  229. token_cache.clear()
  230. to_scan = scan(i, token, to_scan)
  231. i += 1
  232. predict_and_complete(i, to_scan)
  233. ## Column is now the final column in the parse. If the parse was successful, the start
  234. # symbol should have been completed in the last step of the Earley cycle, and will be in
  235. # this column. Find the item for the start_symbol, which is the root of the SPPF tree.
  236. solutions = [n.node for n in columns[i] if n.is_complete and n.node is not None and n.s == start_symbol and n.start == 0]
  237. if not solutions:
  238. raise ParseError('Incomplete parse: Could not find a solution to input')
  239. elif len(solutions) > 1:
  240. raise ParseError('Earley should not generate multiple start symbol items!')
  241. ## If we're not resolving ambiguity, we just return the root of the SPPF tree to the caller.
  242. # This means the caller can work directly with the SPPF tree.
  243. if not self.resolve_ambiguity:
  244. return solutions[0]
  245. # ... otherwise, disambiguate and convert the SPPF to an AST, removing any ambiguities
  246. # according to the rules.
  247. return self.forest_tree_visitor.go(solutions[0])
  248. class ApplyCallbacks(Transformer_InPlace):
  249. def __init__(self, postprocess):
  250. self.postprocess = postprocess
  251. @v_args(meta=True)
  252. def drv(self, children, meta):
  253. return self.postprocess[meta.rule](children)