1import re 2import sys 3 4def 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 12class Monitor: 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_tokens = {'if', 'ifdef', 'ifndef'} 145 all_tokens = if_tokens | {'elif', 'else', 'endif'} 146 147 if token not in all_tokens: 148 return 149 150 # cheat a little here, to reuse the implementation of if 151 if token == 'elif': 152 pop_stack() 153 token = 'if' 154 155 if token in if_tokens: 156 if not condition: 157 self.fail("Invalid format for #" + token + " line: no argument!") 158 if token == 'if': 159 if not self.is_a_simple_defined(condition): 160 condition = "(" + condition + ")" 161 else: 162 fields = condition.split() 163 if len(fields) != 1: 164 self.fail("Invalid format for #" + token + " line: should be exactly one argument!") 165 symbol = fields[0] 166 condition = 'defined(' + symbol + ')' 167 if token == 'ifndef': 168 condition = '!' + condition 169 170 self.stack.append(("if", condition)) 171 if self.verbose: 172 print(self.status()) 173 return 174 175 previous_token, previous_condition = pop_stack() 176 177 if token == 'else': 178 self.stack.append(('else', negate(previous_condition))) 179 elif token == 'endif': 180 pass 181 if self.verbose: 182 print(self.status()) 183 184if __name__ == '__main__': 185 for filename in sys.argv[1:]: 186 with open(filename, "rt") as f: 187 cpp = Monitor(filename, verbose=True) 188 print() 189 print(filename) 190 for line_number, line in enumerate(f.read().split('\n'), 1): 191 cpp.writeline(line) 192