You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

191 lines
5.8 KiB

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