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.

314 lines
10 KiB

  1. "This module implements an Earley Parser"
  2. # The parser uses a parse-forest to keep track of derivations and ambiguations.
  3. # When the parse ends successfully, a disambiguation stage resolves all ambiguity
  4. # (right now ambiguity resolution is not developed beyond the needs of lark)
  5. # Afterwards the parse tree is reduced (transformed) according to user callbacks.
  6. # I use the no-recursion version of Transformer and Visitor, because the tree might be
  7. # deeper than Python's recursion limit (a bit absurd, but that's life)
  8. #
  9. # The algorithm keeps track of each state set, using a corresponding Column instance.
  10. # Column keeps track of new items using NewsList instances.
  11. #
  12. # Author: Erez Shinan (2017)
  13. # Email : erezshin@gmail.com
  14. from functools import cmp_to_key
  15. from ..utils import compare
  16. from ..common import ParseError, UnexpectedToken, Terminal
  17. from ..tree import Tree, Visitor_NoRecurse, Transformer_NoRecurse
  18. from .grammar_analysis import GrammarAnalyzer
  19. class EndToken:
  20. type = '$end'
  21. class Derivation(Tree):
  22. def __init__(self, rule, items=None):
  23. Tree.__init__(self, 'drv', items or [])
  24. self.rule = rule
  25. def _pretty_label(self): # Nicer pretty for debugging the parser
  26. return self.rule.origin if self.rule else self.data
  27. END_TOKEN = EndToken()
  28. class Item(object):
  29. "An Earley Item, the atom of the algorithm."
  30. def __init__(self, rule, ptr, start, tree):
  31. self.rule = rule
  32. self.ptr = ptr
  33. self.start = start
  34. self.tree = tree if tree is not None else Derivation(self.rule)
  35. @property
  36. def expect(self):
  37. return self.rule.expansion[self.ptr]
  38. @property
  39. def is_complete(self):
  40. return self.ptr == len(self.rule.expansion)
  41. def advance(self, tree):
  42. assert self.tree.data == 'drv'
  43. new_tree = Derivation(self.rule, self.tree.children + [tree])
  44. return Item(self.rule, self.ptr+1, self.start, new_tree)
  45. def similar(self, other):
  46. return self.start is other.start and self.ptr == other.ptr and self.rule == other.rule
  47. def __eq__(self, other):
  48. return self.similar(other) and (self.tree is other.tree or self.tree == other.tree)
  49. def __hash__(self):
  50. return hash((self.rule, self.ptr, id(self.start)))
  51. def __repr__(self):
  52. before = list(map(str, self.rule.expansion[:self.ptr]))
  53. after = list(map(str, self.rule.expansion[self.ptr:]))
  54. return '<(%d) %s : %s * %s>' % (id(self.start), self.rule.origin, ' '.join(before), ' '.join(after))
  55. class NewsList(list):
  56. "Keeps track of newly added items (append-only)"
  57. def __init__(self, initial=None):
  58. list.__init__(self, initial or [])
  59. self.last_iter = 0
  60. def get_news(self):
  61. i = self.last_iter
  62. self.last_iter = len(self)
  63. return self[i:]
  64. class Column:
  65. "An entry in the table, aka Earley Chart. Contains lists of items."
  66. def __init__(self, i):
  67. self.i = i
  68. self.to_reduce = NewsList()
  69. self.to_predict = NewsList()
  70. self.to_scan = NewsList()
  71. self.item_count = 0
  72. self.added = set()
  73. self.completed = {}
  74. def add(self, items):
  75. """Sort items into scan/predict/reduce newslists
  76. Makes sure only unique items are added.
  77. """
  78. for item in items:
  79. if item.is_complete:
  80. # XXX Potential bug: What happens if there's ambiguity in an empty rule?
  81. if item.rule.expansion and item in self.completed:
  82. old_tree = self.completed[item].tree
  83. if old_tree.data != '_ambig':
  84. new_tree = old_tree.copy()
  85. new_tree.rule = old_tree.rule
  86. old_tree.set('_ambig', [new_tree])
  87. old_tree.rule = None # No longer a 'drv' node
  88. if item.tree.children[0] is old_tree: # XXX a little hacky!
  89. raise ParseError("Infinite recursion in grammar! (Rule %s)" % item.rule)
  90. old_tree.children.append(item.tree)
  91. else:
  92. self.completed[item] = item
  93. self.to_reduce.append(item)
  94. else:
  95. if item not in self.added:
  96. self.added.add(item)
  97. if isinstance(item.expect, Terminal):
  98. self.to_scan.append(item)
  99. else:
  100. self.to_predict.append(item)
  101. self.item_count += 1 # Only count if actually added
  102. def __nonzero__(self):
  103. return bool(self.item_count)
  104. class Parser:
  105. def __init__(self, rules, start_symbol, callback, resolve_ambiguity=True):
  106. self.analysis = GrammarAnalyzer(rules, start_symbol)
  107. self.start_symbol = start_symbol
  108. self.resolve_ambiguity = resolve_ambiguity
  109. self.postprocess = {}
  110. self.predictions = {}
  111. for rule in self.analysis.rules:
  112. if rule.origin != '$root': # XXX kinda ugly
  113. a = rule.alias
  114. self.postprocess[rule] = a if callable(a) else (a and getattr(callback, a))
  115. self.predictions[rule.origin] = [x.rule for x in self.analysis.expand_rule(rule.origin)]
  116. def parse(self, stream, start_symbol=None):
  117. # Define parser functions
  118. start_symbol = start_symbol or self.start_symbol
  119. def predict(nonterm, column):
  120. assert not isinstance(nonterm, Terminal), nonterm
  121. return [Item(rule, 0, column, None) for rule in self.predictions[nonterm]]
  122. def complete(item):
  123. name = item.rule.origin
  124. return [i.advance(item.tree) for i in item.start.to_predict if i.expect == name]
  125. def predict_and_complete(column):
  126. while True:
  127. to_predict = {x.expect for x in column.to_predict.get_news()
  128. if x.ptr} # if not part of an already predicted batch
  129. to_reduce = column.to_reduce.get_news()
  130. if not (to_predict or to_reduce):
  131. break
  132. for nonterm in to_predict:
  133. column.add( predict(nonterm, column) )
  134. for item in to_reduce:
  135. new_items = list(complete(item))
  136. for new_item in new_items:
  137. if new_item.similar(item):
  138. raise ParseError('Infinite recursion detected! (rule %s)' % new_item.rule)
  139. column.add(new_items)
  140. def scan(i, token, column):
  141. to_scan = column.to_scan.get_news()
  142. next_set = Column(i)
  143. next_set.add(item.advance(token) for item in to_scan if item.expect.match(token))
  144. if not next_set:
  145. expect = {i.expect for i in column.to_scan}
  146. raise UnexpectedToken(token, expect, stream, i)
  147. return next_set
  148. # Main loop starts
  149. column0 = Column(0)
  150. column0.add(predict(start_symbol, column0))
  151. column = column0
  152. for i, token in enumerate(stream):
  153. predict_and_complete(column)
  154. column = scan(i, token, column)
  155. predict_and_complete(column)
  156. # Parse ended. Now build a parse tree
  157. solutions = [n.tree for n in column.to_reduce
  158. if n.rule.origin==start_symbol and n.start is column0]
  159. if not solutions:
  160. raise ParseError('Incomplete parse: Could not find a solution to input')
  161. elif len(solutions) == 1:
  162. tree = solutions[0]
  163. else:
  164. tree = Tree('_ambig', solutions)
  165. if self.resolve_ambiguity:
  166. ResolveAmbig().visit(tree)
  167. return ApplyCallbacks(self.postprocess).transform(tree)
  168. class ApplyCallbacks(Transformer_NoRecurse):
  169. def __init__(self, postprocess):
  170. self.postprocess = postprocess
  171. def drv(self, tree):
  172. children = tree.children
  173. callback = self.postprocess[tree.rule]
  174. if callback:
  175. return callback(children)
  176. else:
  177. return Tree(rule.origin, children)
  178. def _compare_rules(rule1, rule2):
  179. if rule1.origin != rule2.origin:
  180. if rule1.options and rule2.options:
  181. if rule1.options.priority is not None and rule2.options.priority is not None:
  182. assert rule1.options.priority != rule2.options.priority, "Priority is the same between both rules: %s == %s" % (rule1, rule2)
  183. return -compare(rule1.options.priority, rule2.options.priority)
  184. return 0
  185. c = compare( len(rule1.expansion), len(rule2.expansion))
  186. if rule1.origin.startswith('__'): # XXX hack! We need to set priority in parser, not here
  187. c = -c
  188. return c
  189. def _compare_drv(tree1, tree2):
  190. if not (isinstance(tree1, Tree) and isinstance(tree2, Tree)):
  191. return -compare(tree1, tree2)
  192. try:
  193. rule1, rule2 = tree1.rule, tree2.rule
  194. except AttributeError:
  195. # Probably trees that don't take part in this parse (better way to distinguish?)
  196. return -compare(tree1, tree2)
  197. # XXX These artifacts can appear due to imperfections in the ordering of Visitor_NoRecurse,
  198. # when confronted with duplicate (same-id) nodes. Fixing this ordering is possible, but would be
  199. # computationally inefficient. So we handle it here.
  200. if tree1.data == '_ambig':
  201. _resolve_ambig(tree1)
  202. if tree2.data == '_ambig':
  203. _resolve_ambig(tree2)
  204. c = _compare_rules(tree1.rule, tree2.rule)
  205. if c:
  206. return c
  207. # rules are "equal", so compare trees
  208. for t1, t2 in zip(tree1.children, tree2.children):
  209. c = _compare_drv(t1, t2)
  210. if c:
  211. return c
  212. return compare(len(tree1.children), len(tree2.children))
  213. def _resolve_ambig(tree):
  214. assert tree.data == '_ambig'
  215. best = min(tree.children, key=cmp_to_key(_compare_drv))
  216. assert best.data == 'drv'
  217. tree.set('drv', best.children)
  218. tree.rule = best.rule # needed for applying callbacks
  219. assert tree.data != '_ambig'
  220. class ResolveAmbig(Visitor_NoRecurse):
  221. def _ambig(self, tree):
  222. _resolve_ambig(tree)
  223. # RULES = [
  224. # ('a', ['d']),
  225. # ('d', ['b']),
  226. # ('b', ['C']),
  227. # ('b', ['b', 'C']),
  228. # ('b', ['C', 'b']),
  229. # ]
  230. # p = Parser(RULES, 'a')
  231. # for x in p.parse('CC'):
  232. # print x.pretty()
  233. #---------------
  234. # RULES = [
  235. # ('s', ['a', 'a']),
  236. # ('a', ['b', 'b']),
  237. # ('b', ['C'], lambda (x,): x),
  238. # ('b', ['b', 'C']),
  239. # ]
  240. # p = Parser(RULES, 's', {})
  241. # print p.parse('CCCCC').pretty()