xearley.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. """This module implements an Earley parser with a dynamic lexer
  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. Instead of running a lexer beforehand, or using a costy char-by-char method, this parser
  9. uses regular expressions by necessity, achieving high-performance while maintaining all of
  10. Earley's power in parsing any CFG.
  11. """
  12. from typing import TYPE_CHECKING, Callable, Optional, List, Any
  13. from collections import defaultdict
  14. from ..tree import Tree
  15. from ..exceptions import UnexpectedCharacters
  16. from ..lexer import Token
  17. from ..grammar import Terminal
  18. from .earley import Parser as BaseParser
  19. from .earley_forest import TokenNode
  20. if TYPE_CHECKING:
  21. from ..common import LexerConf, ParserConf
  22. class Parser(BaseParser):
  23. def __init__(self, lexer_conf: 'LexerConf', parser_conf: 'ParserConf', term_matcher: Callable,
  24. resolve_ambiguity: bool=True, complete_lex: bool=False, debug: bool=False,
  25. tree_class: Optional[Callable[[str, List], Any]]=Tree, ordered_sets: bool=True):
  26. BaseParser.__init__(self, lexer_conf, parser_conf, term_matcher, resolve_ambiguity,
  27. debug, tree_class, ordered_sets)
  28. self.ignore = [Terminal(t) for t in lexer_conf.ignore]
  29. self.complete_lex = complete_lex
  30. def _parse(self, stream, columns, to_scan, start_symbol=None):
  31. def scan(i, to_scan):
  32. """The core Earley Scanner.
  33. This is a custom implementation of the scanner that uses the
  34. Lark lexer to match tokens. The scan list is built by the
  35. Earley predictor, based on the previously completed tokens.
  36. This ensures that at each phase of the parse we have a custom
  37. lexer context, allowing for more complex ambiguities."""
  38. node_cache = {}
  39. # 1) Loop the expectations and ask the lexer to match.
  40. # Since regexp is forward looking on the input stream, and we only
  41. # want to process tokens when we hit the point in the stream at which
  42. # they complete, we push all tokens into a buffer (delayed_matches), to
  43. # be held possibly for a later parse step when we reach the point in the
  44. # input stream at which they complete.
  45. for item in self.Set(to_scan):
  46. m = match(item.expect, stream, i)
  47. if m:
  48. t = Token(item.expect.name, m.group(0), i, text_line, text_column)
  49. delayed_matches[m.end()].append( (item, i, t) )
  50. if self.complete_lex:
  51. s = m.group(0)
  52. for j in range(1, len(s)):
  53. m = match(item.expect, s[:-j])
  54. if m:
  55. t = Token(item.expect.name, m.group(0), i, text_line, text_column)
  56. delayed_matches[i+m.end()].append( (item, i, t) )
  57. # XXX The following 3 lines were commented out for causing a bug. See issue #768
  58. # # Remove any items that successfully matched in this pass from the to_scan buffer.
  59. # # This ensures we don't carry over tokens that already matched, if we're ignoring below.
  60. # to_scan.remove(item)
  61. # 3) Process any ignores. This is typically used for e.g. whitespace.
  62. # We carry over any unmatched items from the to_scan buffer to be matched again after
  63. # the ignore. This should allow us to use ignored symbols in non-terminals to implement
  64. # e.g. mandatory spacing.
  65. for x in self.ignore:
  66. m = match(x, stream, i)
  67. if m:
  68. # Carry over any items still in the scan buffer, to past the end of the ignored items.
  69. delayed_matches[m.end()].extend([(item, i, None) for item in to_scan ])
  70. # If we're ignoring up to the end of the file, # carry over the start symbol if it already completed.
  71. delayed_matches[m.end()].extend([(item, i, None) for item in columns[i] if item.is_complete and item.s == start_symbol])
  72. next_to_scan = self.Set()
  73. next_set = self.Set()
  74. columns.append(next_set)
  75. transitives.append({})
  76. ## 4) Process Tokens from delayed_matches.
  77. # This is the core of the Earley scanner. Create an SPPF node for each Token,
  78. # and create the symbol node in the SPPF tree. Advance the item that completed,
  79. # and add the resulting new item to either the Earley set (for processing by the
  80. # completer/predictor) or the to_scan buffer for the next parse step.
  81. for item, start, token in delayed_matches[i+1]:
  82. if token is not None:
  83. token.end_line = text_line
  84. token.end_column = text_column + 1
  85. token.end_pos = i + 1
  86. new_item = item.advance()
  87. label = (new_item.s, new_item.start, i + 1)
  88. token_node = TokenNode(token, terminals[token.type])
  89. new_item.node = node_cache[label] if label in node_cache else node_cache.setdefault(label, self.SymbolNode(*label))
  90. new_item.node.add_family(new_item.s, item.rule, new_item.start, item.node, token_node)
  91. else:
  92. new_item = item
  93. if new_item.expect in self.TERMINALS:
  94. # add (B ::= Aai+1.B, h, y) to Q'
  95. next_to_scan.add(new_item)
  96. else:
  97. # add (B ::= Aa+1.B, h, y) to Ei+1
  98. next_set.add(new_item)
  99. del delayed_matches[i+1] # No longer needed, so unburden memory
  100. if not next_set and not delayed_matches and not next_to_scan:
  101. considered_rules = list(sorted(to_scan, key=lambda key: key.rule.origin.name))
  102. raise UnexpectedCharacters(stream, i, text_line, text_column, {item.expect.name for item in to_scan},
  103. set(to_scan), state=frozenset(i.s for i in to_scan),
  104. considered_rules=considered_rules
  105. )
  106. return next_to_scan, node_cache
  107. delayed_matches = defaultdict(list)
  108. match = self.term_matcher
  109. terminals = self.lexer_conf.terminals_by_name
  110. # Cache for nodes & tokens created in a particular parse step.
  111. transitives = [{}]
  112. text_line = 1
  113. text_column = 1
  114. ## The main Earley loop.
  115. # Run the Prediction/Completion cycle for any Items in the current Earley set.
  116. # Completions will be added to the SPPF tree, and predictions will be recursively
  117. # processed down to terminals/empty nodes to be added to the scanner for the next
  118. # step.
  119. i = 0
  120. node_cache = {}
  121. for token in stream:
  122. self.predict_and_complete(i, to_scan, columns, transitives, node_cache)
  123. to_scan, node_cache = scan(i, to_scan)
  124. if token == '\n':
  125. text_line += 1
  126. text_column = 1
  127. else:
  128. text_column += 1
  129. i += 1
  130. self.predict_and_complete(i, to_scan, columns, transitives, node_cache)
  131. ## Column is now the final column in the parse.
  132. assert i == len(columns)-1
  133. return to_scan