1  import os.path
       2  import re
       3  
       4  from . import common as _common
       5  
       6  
       7  TOOL = 'gcc'
       8  
       9  # https://gcc.gnu.org/onlinedocs/cpp/Preprocessor-Output.html
      10  LINE_MARKER_RE = re.compile(r'^# (\d+) "([^"]+)"(?: [1234])*$')
      11  PREPROC_DIRECTIVE_RE = re.compile(r'^\s*#\s*(\w+)\b.*')
      12  COMPILER_DIRECTIVE_RE = re.compile(r'''
      13      ^
      14      (.*?)  # <before>
      15      (__\w+__)  # <directive>
      16      \s*
      17      [(] [(]
      18      (
      19          [^()]*
      20          (?:
      21              [(]
      22              [^()]*
      23              [)]
      24              [^()]*
      25           )*
      26       )  # <args>
      27      ( [)] [)] )?  # <closed>
      28  ''', re.VERBOSE)
      29  
      30  POST_ARGS = (
      31      '-pthread',
      32      '-std=c99',
      33      #'-g',
      34      #'-Og',
      35      #'-Wno-unused-result',
      36      #'-Wsign-compare',
      37      #'-Wall',
      38      #'-Wextra',
      39      '-E',
      40  )
      41  
      42  
      43  def preprocess(filename, incldirs=None, macros=None, samefiles=None):
      44      text = _common.preprocess(
      45          TOOL,
      46          filename,
      47          incldirs=incldirs,
      48          macros=macros,
      49          #preargs=PRE_ARGS,
      50          postargs=POST_ARGS,
      51          executable=['gcc'],
      52          compiler='unix',
      53      )
      54      return _iter_lines(text, filename, samefiles)
      55  
      56  
      57  def _iter_lines(text, filename, samefiles, *, raw=False):
      58      lines = iter(text.splitlines())
      59  
      60      # Build the lines and filter out directives.
      61      partial = 0  # depth
      62      origfile = None
      63      for line in lines:
      64          m = LINE_MARKER_RE.match(line)
      65          if m:
      66              lno, origfile = m.groups()
      67              lno = int(lno)
      68          elif _filter_orig_file(origfile, filename, samefiles):
      69              if (m := PREPROC_DIRECTIVE_RE.match(line)):
      70                  name, = m.groups()
      71                  if name != 'pragma':
      72                      raise Exception(line)
      73              else:
      74                  if not raw:
      75                      line, partial = _strip_directives(line, partial=partial)
      76                  yield _common.SourceLine(
      77                      _common.FileInfo(filename, lno),
      78                      'source',
      79                      line or '',
      80                      None,
      81                  )
      82              lno += 1
      83  
      84  
      85  def _strip_directives(line, partial=0):
      86      # We assume there are no string literals with parens in directive bodies.
      87      while partial > 0:
      88          if not (m := re.match(r'[^{}]*([()])', line)):
      89              return None, partial
      90          delim, = m.groups()
      91          partial += 1 if delim == '(' else -1  # opened/closed
      92          line = line[m.end():]
      93  
      94      line = re.sub(r'__extension__', '', line)
      95  
      96      while (m := COMPILER_DIRECTIVE_RE.match(line)):
      97          before, _, _, closed = m.groups()
      98          if closed:
      99              line = f'{before} {line[m.end():]}'
     100          else:
     101              after, partial = _strip_directives(line[m.end():], 2)
     102              line = f'{before} {after or ""}'
     103              if partial:
     104                  break
     105  
     106      return line, partial
     107  
     108  
     109  def _filter_orig_file(origfile, current, samefiles):
     110      if origfile == current:
     111          return True
     112      if origfile == '<stdin>':
     113          return True
     114      if os.path.isabs(origfile):
     115          return False
     116  
     117      for filename in samefiles or ():
     118          if filename.endswith(os.path.sep):
     119              filename += os.path.basename(current)
     120          if origfile == filename:
     121              return True
     122  
     123      return False