python (3.11.7)
       1  # exceptions.py
       2  
       3  import re
       4  import sys
       5  import typing
       6  
       7  from .util import col, line, lineno, _collapse_string_to_ranges
       8  from .unicode import pyparsing_unicode as ppu
       9  
      10  
      11  class ESC[4;38;5;81mExceptionWordUnicode(ESC[4;38;5;149mppuESC[4;38;5;149m.ESC[4;38;5;149mLatin1, ESC[4;38;5;149mppuESC[4;38;5;149m.ESC[4;38;5;149mLatinA, ESC[4;38;5;149mppuESC[4;38;5;149m.ESC[4;38;5;149mLatinB, ESC[4;38;5;149mppuESC[4;38;5;149m.ESC[4;38;5;149mGreek, ESC[4;38;5;149mppuESC[4;38;5;149m.ESC[4;38;5;149mCyrillic):
      12      pass
      13  
      14  
      15  _extract_alphanums = _collapse_string_to_ranges(ExceptionWordUnicode.alphanums)
      16  _exception_word_extractor = re.compile("([" + _extract_alphanums + "]{1,16})|.")
      17  
      18  
      19  class ESC[4;38;5;81mParseBaseException(ESC[4;38;5;149mException):
      20      """base exception class for all parsing runtime exceptions"""
      21  
      22      # Performance tuning: we construct a *lot* of these, so keep this
      23      # constructor as small and fast as possible
      24      def __init__(
      25          self,
      26          pstr: str,
      27          loc: int = 0,
      28          msg: typing.Optional[str] = None,
      29          elem=None,
      30      ):
      31          self.loc = loc
      32          if msg is None:
      33              self.msg = pstr
      34              self.pstr = ""
      35          else:
      36              self.msg = msg
      37              self.pstr = pstr
      38          self.parser_element = self.parserElement = elem
      39          self.args = (pstr, loc, msg)
      40  
      41      @staticmethod
      42      def explain_exception(exc, depth=16):
      43          """
      44          Method to take an exception and translate the Python internal traceback into a list
      45          of the pyparsing expressions that caused the exception to be raised.
      46  
      47          Parameters:
      48  
      49          - exc - exception raised during parsing (need not be a ParseException, in support
      50            of Python exceptions that might be raised in a parse action)
      51          - depth (default=16) - number of levels back in the stack trace to list expression
      52            and function names; if None, the full stack trace names will be listed; if 0, only
      53            the failing input line, marker, and exception string will be shown
      54  
      55          Returns a multi-line string listing the ParserElements and/or function names in the
      56          exception's stack trace.
      57          """
      58          import inspect
      59          from .core import ParserElement
      60  
      61          if depth is None:
      62              depth = sys.getrecursionlimit()
      63          ret = []
      64          if isinstance(exc, ParseBaseException):
      65              ret.append(exc.line)
      66              ret.append(" " * (exc.column - 1) + "^")
      67          ret.append("{}: {}".format(type(exc).__name__, exc))
      68  
      69          if depth > 0:
      70              callers = inspect.getinnerframes(exc.__traceback__, context=depth)
      71              seen = set()
      72              for i, ff in enumerate(callers[-depth:]):
      73                  frm = ff[0]
      74  
      75                  f_self = frm.f_locals.get("self", None)
      76                  if isinstance(f_self, ParserElement):
      77                      if frm.f_code.co_name not in ("parseImpl", "_parseNoCache"):
      78                          continue
      79                      if id(f_self) in seen:
      80                          continue
      81                      seen.add(id(f_self))
      82  
      83                      self_type = type(f_self)
      84                      ret.append(
      85                          "{}.{} - {}".format(
      86                              self_type.__module__, self_type.__name__, f_self
      87                          )
      88                      )
      89  
      90                  elif f_self is not None:
      91                      self_type = type(f_self)
      92                      ret.append("{}.{}".format(self_type.__module__, self_type.__name__))
      93  
      94                  else:
      95                      code = frm.f_code
      96                      if code.co_name in ("wrapper", "<module>"):
      97                          continue
      98  
      99                      ret.append("{}".format(code.co_name))
     100  
     101                  depth -= 1
     102                  if not depth:
     103                      break
     104  
     105          return "\n".join(ret)
     106  
     107      @classmethod
     108      def _from_exception(cls, pe):
     109          """
     110          internal factory method to simplify creating one type of ParseException
     111          from another - avoids having __init__ signature conflicts among subclasses
     112          """
     113          return cls(pe.pstr, pe.loc, pe.msg, pe.parserElement)
     114  
     115      @property
     116      def line(self) -> str:
     117          """
     118          Return the line of text where the exception occurred.
     119          """
     120          return line(self.loc, self.pstr)
     121  
     122      @property
     123      def lineno(self) -> int:
     124          """
     125          Return the 1-based line number of text where the exception occurred.
     126          """
     127          return lineno(self.loc, self.pstr)
     128  
     129      @property
     130      def col(self) -> int:
     131          """
     132          Return the 1-based column on the line of text where the exception occurred.
     133          """
     134          return col(self.loc, self.pstr)
     135  
     136      @property
     137      def column(self) -> int:
     138          """
     139          Return the 1-based column on the line of text where the exception occurred.
     140          """
     141          return col(self.loc, self.pstr)
     142  
     143      def __str__(self) -> str:
     144          if self.pstr:
     145              if self.loc >= len(self.pstr):
     146                  foundstr = ", found end of text"
     147              else:
     148                  # pull out next word at error location
     149                  found_match = _exception_word_extractor.match(self.pstr, self.loc)
     150                  if found_match is not None:
     151                      found = found_match.group(0)
     152                  else:
     153                      found = self.pstr[self.loc : self.loc + 1]
     154                  foundstr = (", found %r" % found).replace(r"\\", "\\")
     155          else:
     156              foundstr = ""
     157          return "{}{}  (at char {}), (line:{}, col:{})".format(
     158              self.msg, foundstr, self.loc, self.lineno, self.column
     159          )
     160  
     161      def __repr__(self):
     162          return str(self)
     163  
     164      def mark_input_line(self, marker_string: str = None, *, markerString=">!<") -> str:
     165          """
     166          Extracts the exception line from the input string, and marks
     167          the location of the exception with a special symbol.
     168          """
     169          markerString = marker_string if marker_string is not None else markerString
     170          line_str = self.line
     171          line_column = self.column - 1
     172          if markerString:
     173              line_str = "".join(
     174                  (line_str[:line_column], markerString, line_str[line_column:])
     175              )
     176          return line_str.strip()
     177  
     178      def explain(self, depth=16) -> str:
     179          """
     180          Method to translate the Python internal traceback into a list
     181          of the pyparsing expressions that caused the exception to be raised.
     182  
     183          Parameters:
     184  
     185          - depth (default=16) - number of levels back in the stack trace to list expression
     186            and function names; if None, the full stack trace names will be listed; if 0, only
     187            the failing input line, marker, and exception string will be shown
     188  
     189          Returns a multi-line string listing the ParserElements and/or function names in the
     190          exception's stack trace.
     191  
     192          Example::
     193  
     194              expr = pp.Word(pp.nums) * 3
     195              try:
     196                  expr.parse_string("123 456 A789")
     197              except pp.ParseException as pe:
     198                  print(pe.explain(depth=0))
     199  
     200          prints::
     201  
     202              123 456 A789
     203                      ^
     204              ParseException: Expected W:(0-9), found 'A'  (at char 8), (line:1, col:9)
     205  
     206          Note: the diagnostic output will include string representations of the expressions
     207          that failed to parse. These representations will be more helpful if you use `set_name` to
     208          give identifiable names to your expressions. Otherwise they will use the default string
     209          forms, which may be cryptic to read.
     210  
     211          Note: pyparsing's default truncation of exception tracebacks may also truncate the
     212          stack of expressions that are displayed in the ``explain`` output. To get the full listing
     213          of parser expressions, you may have to set ``ParserElement.verbose_stacktrace = True``
     214          """
     215          return self.explain_exception(self, depth)
     216  
     217      markInputline = mark_input_line
     218  
     219  
     220  class ESC[4;38;5;81mParseException(ESC[4;38;5;149mParseBaseException):
     221      """
     222      Exception thrown when a parse expression doesn't match the input string
     223  
     224      Example::
     225  
     226          try:
     227              Word(nums).set_name("integer").parse_string("ABC")
     228          except ParseException as pe:
     229              print(pe)
     230              print("column: {}".format(pe.column))
     231  
     232      prints::
     233  
     234         Expected integer (at char 0), (line:1, col:1)
     235          column: 1
     236  
     237      """
     238  
     239  
     240  class ESC[4;38;5;81mParseFatalException(ESC[4;38;5;149mParseBaseException):
     241      """
     242      User-throwable exception thrown when inconsistent parse content
     243      is found; stops all parsing immediately
     244      """
     245  
     246  
     247  class ESC[4;38;5;81mParseSyntaxException(ESC[4;38;5;149mParseFatalException):
     248      """
     249      Just like :class:`ParseFatalException`, but thrown internally
     250      when an :class:`ErrorStop<And._ErrorStop>` ('-' operator) indicates
     251      that parsing is to stop immediately because an unbacktrackable
     252      syntax error has been found.
     253      """
     254  
     255  
     256  class ESC[4;38;5;81mRecursiveGrammarException(ESC[4;38;5;149mException):
     257      """
     258      Exception thrown by :class:`ParserElement.validate` if the
     259      grammar could be left-recursive; parser may need to enable
     260      left recursion using :class:`ParserElement.enable_left_recursion<ParserElement.enable_left_recursion>`
     261      """
     262  
     263      def __init__(self, parseElementList):
     264          self.parseElementTrace = parseElementList
     265  
     266      def __str__(self) -> str:
     267          return "RecursiveGrammarException: {}".format(self.parseElementTrace)