(root)/
Python-3.11.7/
Tools/
clinic/
cpp.py
       1  import re
       2  import sys
       3  
       4  def negate(condition):
       5      """
       6      Returns a CPP conditional that is the opposite of the conditional passed in.
       7      """
       8      if condition.startswith('!'):
       9          return condition[1:]
      10      return "!" + condition
      11  
      12  class ESC[4;38;5;81mMonitor:
      13      """
      14      A simple C preprocessor that scans C source and computes, line by line,
      15      what the current C preprocessor #if state is.
      16  
      17      Doesn't handle everything--for example, if you have /* inside a C string,
      18      without a matching */ (also inside a C string), or with a */ inside a C
      19      string but on another line and with preprocessor macros in between...
      20      the parser will get lost.
      21  
      22      Anyway this implementation seems to work well enough for the CPython sources.
      23      """
      24  
      25      is_a_simple_defined = re.compile(r'^defined\s*\(\s*[A-Za-z0-9_]+\s*\)$').match
      26  
      27      def __init__(self, filename=None, *, verbose=False):
      28          self.stack = []
      29          self.in_comment = False
      30          self.continuation = None
      31          self.line_number = 0
      32          self.filename = filename
      33          self.verbose = verbose
      34  
      35      def __repr__(self):
      36          return ''.join((
      37              '<Monitor ',
      38              str(id(self)),
      39              " line=", str(self.line_number),
      40              " condition=", repr(self.condition()),
      41              ">"))
      42  
      43      def status(self):
      44          return str(self.line_number).rjust(4) + ": " + self.condition()
      45  
      46      def condition(self):
      47          """
      48          Returns the current preprocessor state, as a single #if condition.
      49          """
      50          return " && ".join(condition for token, condition in self.stack)
      51  
      52      def fail(self, *a):
      53          if self.filename:
      54              filename = " " + self.filename
      55          else:
      56              filename = ''
      57          print("Error at" + filename, "line", self.line_number, ":")
      58          print("   ", ' '.join(str(x) for x in a))
      59          sys.exit(-1)
      60  
      61      def close(self):
      62          if self.stack:
      63              self.fail("Ended file while still in a preprocessor conditional block!")
      64  
      65      def write(self, s):
      66          for line in s.split("\n"):
      67              self.writeline(line)
      68  
      69      def writeline(self, line):
      70          self.line_number += 1
      71          line = line.strip()
      72  
      73          def pop_stack():
      74              if not self.stack:
      75                  self.fail("#" + token + " without matching #if / #ifdef / #ifndef!")
      76              return self.stack.pop()
      77  
      78          if self.continuation:
      79              line = self.continuation + line
      80              self.continuation = None
      81  
      82          if not line:
      83              return
      84  
      85          if line.endswith('\\'):
      86              self.continuation = line[:-1].rstrip() + " "
      87              return
      88  
      89          # we have to ignore preprocessor commands inside comments
      90          #
      91          # we also have to handle this:
      92          #     /* start
      93          #     ...
      94          #     */   /*    <-- tricky!
      95          #     ...
      96          #     */
      97          # and this:
      98          #     /* start
      99          #     ...
     100          #     */   /* also tricky! */
     101          if self.in_comment:
     102              if '*/' in line:
     103                  # snip out the comment and continue
     104                  #
     105                  # GCC allows
     106                  #    /* comment
     107                  #    */ #include <stdio.h>
     108                  # maybe other compilers too?
     109                  _, _, line = line.partition('*/')
     110                  self.in_comment = False
     111  
     112          while True:
     113              if '/*' in line:
     114                  if self.in_comment:
     115                      self.fail("Nested block comment!")
     116  
     117                  before, _, remainder = line.partition('/*')
     118                  comment, comment_ends, after = remainder.partition('*/')
     119                  if comment_ends:
     120                      # snip out the comment
     121                      line = before.rstrip() + ' ' + after.lstrip()
     122                      continue
     123                  # comment continues to eol
     124                  self.in_comment = True
     125                  line = before.rstrip()
     126              break
     127  
     128          # we actually have some // comments
     129          # (but block comments take precedence)
     130          before, line_comment, comment = line.partition('//')
     131          if line_comment:
     132              line = before.rstrip()
     133  
     134          if not line.startswith('#'):
     135              return
     136  
     137          line = line[1:].lstrip()
     138          assert line
     139  
     140          fields = line.split()
     141          token = fields[0].lower()
     142          condition = ' '.join(fields[1:]).strip()
     143  
     144          if token in {'if', 'ifdef', 'ifndef', 'elif'}:
     145              if not condition:
     146                  self.fail("Invalid format for #" + token + " line: no argument!")
     147              if token in {'if', 'elif'}:
     148                  if not self.is_a_simple_defined(condition):
     149                      condition = "(" + condition + ")"
     150                  if token == 'elif':
     151                      previous_token, previous_condition = pop_stack()
     152                      self.stack.append((previous_token, negate(previous_condition)))
     153              else:
     154                  fields = condition.split()
     155                  if len(fields) != 1:
     156                      self.fail("Invalid format for #" + token + " line: should be exactly one argument!")
     157                  symbol = fields[0]
     158                  condition = 'defined(' + symbol + ')'
     159                  if token == 'ifndef':
     160                      condition = '!' + condition
     161                  token = 'if'
     162  
     163              self.stack.append((token, condition))
     164  
     165          elif token == 'else':
     166              previous_token, previous_condition = pop_stack()
     167              self.stack.append((previous_token, negate(previous_condition)))
     168  
     169          elif token == 'endif':
     170              while pop_stack()[0] != 'if':
     171                  pass
     172  
     173          else:
     174              return
     175  
     176          if self.verbose:
     177              print(self.status())
     178  
     179  if __name__ == '__main__':
     180      for filename in sys.argv[1:]:
     181          with open(filename, "rt") as f:
     182              cpp = Monitor(filename, verbose=True)
     183              print()
     184              print(filename)
     185              for line_number, line in enumerate(f.read().split('\n'), 1):
     186                  cpp.writeline(line)