1  import re
       2  
       3  from ._regexes import (
       4      _ind,
       5      STRING_LITERAL,
       6      VAR_DECL as _VAR_DECL,
       7  )
       8  
       9  
      10  def log_match(group, m, depth_before=None, depth_after=None):
      11      from . import _logger
      12  
      13      if m is not None:
      14          text = m.group(0)
      15          if text.startswith(('(', ')')) or text.endswith(('(', ')')):
      16              _logger.debug(f'matched <{group}> ({text!r})')
      17          else:
      18              _logger.debug(f'matched <{group}> ({text})')
      19  
      20      elif depth_before is not None or depth_after is not None:
      21          if depth_before is None:
      22              depth_before = '???'
      23          elif depth_after is None:
      24              depth_after = '???'
      25          _logger.log(1, f'depth: %s -> %s', depth_before, depth_after)
      26  
      27      else:
      28          raise NotImplementedError('this should not have been hit')
      29  
      30  
      31  #############################
      32  # regex utils
      33  
      34  def set_capture_group(pattern, group, *, strict=True):
      35      old = f'(?:  # <{group}>'
      36      if strict and f'(?:  # <{group}>' not in pattern:
      37          raise ValueError(f'{old!r} not found in pattern')
      38      return pattern.replace(old, f'(  # <{group}>', 1)
      39  
      40  
      41  def set_capture_groups(pattern, groups, *, strict=True):
      42      for group in groups:
      43          pattern = set_capture_group(pattern, group, strict=strict)
      44      return pattern
      45  
      46  
      47  #############################
      48  # syntax-related utils
      49  
      50  _PAREN_RE = re.compile(rf'''
      51      (?:
      52          (?:
      53              [^'"()]*
      54              {_ind(STRING_LITERAL, 3)}
      55           )*
      56          [^'"()]*
      57          (?:
      58              ( [(] )
      59              |
      60              ( [)] )
      61           )
      62       )
      63      ''', re.VERBOSE)
      64  
      65  
      66  def match_paren(text, depth=0):
      67      pos = 0
      68      while (m := _PAREN_RE.match(text, pos)):
      69          pos = m.end()
      70          _open, _close = m.groups()
      71          if _open:
      72              depth += 1
      73          else:  # _close
      74              depth -= 1
      75              if depth == 0:
      76                  return pos
      77      else:
      78          raise ValueError(f'could not find matching parens for {text!r}')
      79  
      80  
      81  VAR_DECL = set_capture_groups(_VAR_DECL, (
      82      'STORAGE',
      83      'TYPE_QUAL',
      84      'TYPE_SPEC',
      85      'DECLARATOR',
      86      'IDENTIFIER',
      87      'WRAPPED_IDENTIFIER',
      88      'FUNC_IDENTIFIER',
      89  ))
      90  
      91  
      92  def parse_var_decl(decl):
      93      m = re.match(VAR_DECL, decl, re.VERBOSE)
      94      (storage, typequal, typespec, declarator,
      95       name,
      96       wrappedname,
      97       funcptrname,
      98       ) = m.groups()
      99      if name:
     100          kind = 'simple'
     101      elif wrappedname:
     102          kind = 'wrapped'
     103          name = wrappedname
     104      elif funcptrname:
     105          kind = 'funcptr'
     106          name = funcptrname
     107      else:
     108          raise NotImplementedError
     109      abstract = declarator.replace(name, '')
     110      vartype = {
     111          'storage': storage,
     112          'typequal': typequal,
     113          'typespec': typespec,
     114          'abstract': abstract,
     115      }
     116      return (kind, name, vartype)
     117  
     118  
     119  #############################
     120  # parser state utils
     121  
     122  # XXX Drop this or use it!
     123  def iter_results(results):
     124      if not results:
     125          return
     126      if callable(results):
     127          results = results()
     128  
     129      for result, text in results():
     130          if result:
     131              yield result, text