exceptions.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. from textwrap import dedent
  2. from parsimonious.utils import StrAndRepr
  3. class ParseError(StrAndRepr, Exception):
  4. """A call to ``Expression.parse()`` or ``match()`` didn't match."""
  5. def __init__(self, text, pos=-1, expr=None):
  6. # It would be nice to use self.args, but I don't want to pay a penalty
  7. # to call descriptors or have the confusion of numerical indices in
  8. # Expression.match_core().
  9. self.text = text
  10. self.pos = pos
  11. self.expr = expr
  12. def __str__(self):
  13. rule_name = (("'%s'" % self.expr.name) if self.expr.name else
  14. str(self.expr))
  15. return "Rule %s didn't match at '%s' (line %s, column %s)." % (
  16. rule_name,
  17. self.text[self.pos:self.pos + 20],
  18. self.line(),
  19. self.column())
  20. # TODO: Add line, col, and separated-out error message so callers can build
  21. # their own presentation.
  22. def line(self):
  23. """Return the 1-based line number where the expression ceased to
  24. match."""
  25. # This is a method rather than a property in case we ever wanted to
  26. # pass in which line endings we want to use.
  27. if isinstance(self.text, list): # TokenGrammar
  28. return None
  29. else:
  30. return self.text.count('\n', 0, self.pos) + 1
  31. def column(self):
  32. """Return the 1-based column where the expression ceased to match."""
  33. # We choose 1-based because that's what Python does with SyntaxErrors.
  34. try:
  35. return self.pos - self.text.rindex('\n', 0, self.pos)
  36. except (ValueError, AttributeError):
  37. return self.pos + 1
  38. class LeftRecursionError(ParseError):
  39. def __str__(self):
  40. rule_name = self.expr.name if self.expr.name else str(self.expr)
  41. window = self.text[self.pos:self.pos + 20]
  42. return dedent(f"""
  43. Left recursion in rule {rule_name!r} at {window!r} (line {self.line()}, column {self.column()}).
  44. Parsimonious is a packrat parser, so it can't handle left recursion.
  45. See https://en.wikipedia.org/wiki/Parsing_expression_grammar#Indirect_left_recursion
  46. for how to rewrite your grammar into a rule that does not use left-recursion.
  47. """
  48. ).strip()
  49. class IncompleteParseError(ParseError):
  50. """A call to ``parse()`` matched a whole Expression but did not consume the
  51. entire text."""
  52. def __str__(self):
  53. return "Rule '%s' matched in its entirety, but it didn't consume all the text. The non-matching portion of the text begins with '%s' (line %s, column %s)." % (
  54. self.expr.name,
  55. self.text[self.pos:self.pos + 20],
  56. self.line(),
  57. self.column())
  58. class VisitationError(Exception):
  59. """Something went wrong while traversing a parse tree.
  60. This exception exists to augment an underlying exception with information
  61. about where in the parse tree the error occurred. Otherwise, it could be
  62. tiresome to figure out what went wrong; you'd have to play back the whole
  63. tree traversal in your head.
  64. """
  65. # TODO: Make sure this is pickleable. Probably use @property pattern. Make
  66. # the original exc and node available on it if they don't cause a whole
  67. # raft of stack frames to be retained.
  68. def __init__(self, exc, exc_class, node):
  69. """Construct.
  70. :arg exc: What went wrong. We wrap this and add more info.
  71. :arg node: The node at which the error occurred
  72. """
  73. self.original_class = exc_class
  74. super().__init__(
  75. '%s: %s\n\n'
  76. 'Parse tree:\n'
  77. '%s' %
  78. (exc_class.__name__,
  79. exc,
  80. node.prettily(error=node)))
  81. class BadGrammar(StrAndRepr, Exception):
  82. """Something was wrong with the definition of a grammar.
  83. Note that a ParseError might be raised instead if the error is in the
  84. grammar definition syntax.
  85. """
  86. class UndefinedLabel(BadGrammar):
  87. """A rule referenced in a grammar was never defined.
  88. Circular references and forward references are okay, but you have to define
  89. stuff at some point.
  90. """
  91. def __init__(self, label):
  92. self.label = label
  93. def __str__(self):
  94. return 'The label "%s" was never defined.' % self.label