17db96d56Sopenharmony_ciimport re 27db96d56Sopenharmony_ciimport sys 37db96d56Sopenharmony_ci 47db96d56Sopenharmony_cidef negate(condition): 57db96d56Sopenharmony_ci """ 67db96d56Sopenharmony_ci Returns a CPP conditional that is the opposite of the conditional passed in. 77db96d56Sopenharmony_ci """ 87db96d56Sopenharmony_ci if condition.startswith('!'): 97db96d56Sopenharmony_ci return condition[1:] 107db96d56Sopenharmony_ci return "!" + condition 117db96d56Sopenharmony_ci 127db96d56Sopenharmony_ciclass Monitor: 137db96d56Sopenharmony_ci """ 147db96d56Sopenharmony_ci A simple C preprocessor that scans C source and computes, line by line, 157db96d56Sopenharmony_ci what the current C preprocessor #if state is. 167db96d56Sopenharmony_ci 177db96d56Sopenharmony_ci Doesn't handle everything--for example, if you have /* inside a C string, 187db96d56Sopenharmony_ci without a matching */ (also inside a C string), or with a */ inside a C 197db96d56Sopenharmony_ci string but on another line and with preprocessor macros in between... 207db96d56Sopenharmony_ci the parser will get lost. 217db96d56Sopenharmony_ci 227db96d56Sopenharmony_ci Anyway this implementation seems to work well enough for the CPython sources. 237db96d56Sopenharmony_ci """ 247db96d56Sopenharmony_ci 257db96d56Sopenharmony_ci is_a_simple_defined = re.compile(r'^defined\s*\(\s*[A-Za-z0-9_]+\s*\)$').match 267db96d56Sopenharmony_ci 277db96d56Sopenharmony_ci def __init__(self, filename=None, *, verbose=False): 287db96d56Sopenharmony_ci self.stack = [] 297db96d56Sopenharmony_ci self.in_comment = False 307db96d56Sopenharmony_ci self.continuation = None 317db96d56Sopenharmony_ci self.line_number = 0 327db96d56Sopenharmony_ci self.filename = filename 337db96d56Sopenharmony_ci self.verbose = verbose 347db96d56Sopenharmony_ci 357db96d56Sopenharmony_ci def __repr__(self): 367db96d56Sopenharmony_ci return ''.join(( 377db96d56Sopenharmony_ci '<Monitor ', 387db96d56Sopenharmony_ci str(id(self)), 397db96d56Sopenharmony_ci " line=", str(self.line_number), 407db96d56Sopenharmony_ci " condition=", repr(self.condition()), 417db96d56Sopenharmony_ci ">")) 427db96d56Sopenharmony_ci 437db96d56Sopenharmony_ci def status(self): 447db96d56Sopenharmony_ci return str(self.line_number).rjust(4) + ": " + self.condition() 457db96d56Sopenharmony_ci 467db96d56Sopenharmony_ci def condition(self): 477db96d56Sopenharmony_ci """ 487db96d56Sopenharmony_ci Returns the current preprocessor state, as a single #if condition. 497db96d56Sopenharmony_ci """ 507db96d56Sopenharmony_ci return " && ".join(condition for token, condition in self.stack) 517db96d56Sopenharmony_ci 527db96d56Sopenharmony_ci def fail(self, *a): 537db96d56Sopenharmony_ci if self.filename: 547db96d56Sopenharmony_ci filename = " " + self.filename 557db96d56Sopenharmony_ci else: 567db96d56Sopenharmony_ci filename = '' 577db96d56Sopenharmony_ci print("Error at" + filename, "line", self.line_number, ":") 587db96d56Sopenharmony_ci print(" ", ' '.join(str(x) for x in a)) 597db96d56Sopenharmony_ci sys.exit(-1) 607db96d56Sopenharmony_ci 617db96d56Sopenharmony_ci def close(self): 627db96d56Sopenharmony_ci if self.stack: 637db96d56Sopenharmony_ci self.fail("Ended file while still in a preprocessor conditional block!") 647db96d56Sopenharmony_ci 657db96d56Sopenharmony_ci def write(self, s): 667db96d56Sopenharmony_ci for line in s.split("\n"): 677db96d56Sopenharmony_ci self.writeline(line) 687db96d56Sopenharmony_ci 697db96d56Sopenharmony_ci def writeline(self, line): 707db96d56Sopenharmony_ci self.line_number += 1 717db96d56Sopenharmony_ci line = line.strip() 727db96d56Sopenharmony_ci 737db96d56Sopenharmony_ci def pop_stack(): 747db96d56Sopenharmony_ci if not self.stack: 757db96d56Sopenharmony_ci self.fail("#" + token + " without matching #if / #ifdef / #ifndef!") 767db96d56Sopenharmony_ci return self.stack.pop() 777db96d56Sopenharmony_ci 787db96d56Sopenharmony_ci if self.continuation: 797db96d56Sopenharmony_ci line = self.continuation + line 807db96d56Sopenharmony_ci self.continuation = None 817db96d56Sopenharmony_ci 827db96d56Sopenharmony_ci if not line: 837db96d56Sopenharmony_ci return 847db96d56Sopenharmony_ci 857db96d56Sopenharmony_ci if line.endswith('\\'): 867db96d56Sopenharmony_ci self.continuation = line[:-1].rstrip() + " " 877db96d56Sopenharmony_ci return 887db96d56Sopenharmony_ci 897db96d56Sopenharmony_ci # we have to ignore preprocessor commands inside comments 907db96d56Sopenharmony_ci # 917db96d56Sopenharmony_ci # we also have to handle this: 927db96d56Sopenharmony_ci # /* start 937db96d56Sopenharmony_ci # ... 947db96d56Sopenharmony_ci # */ /* <-- tricky! 957db96d56Sopenharmony_ci # ... 967db96d56Sopenharmony_ci # */ 977db96d56Sopenharmony_ci # and this: 987db96d56Sopenharmony_ci # /* start 997db96d56Sopenharmony_ci # ... 1007db96d56Sopenharmony_ci # */ /* also tricky! */ 1017db96d56Sopenharmony_ci if self.in_comment: 1027db96d56Sopenharmony_ci if '*/' in line: 1037db96d56Sopenharmony_ci # snip out the comment and continue 1047db96d56Sopenharmony_ci # 1057db96d56Sopenharmony_ci # GCC allows 1067db96d56Sopenharmony_ci # /* comment 1077db96d56Sopenharmony_ci # */ #include <stdio.h> 1087db96d56Sopenharmony_ci # maybe other compilers too? 1097db96d56Sopenharmony_ci _, _, line = line.partition('*/') 1107db96d56Sopenharmony_ci self.in_comment = False 1117db96d56Sopenharmony_ci 1127db96d56Sopenharmony_ci while True: 1137db96d56Sopenharmony_ci if '/*' in line: 1147db96d56Sopenharmony_ci if self.in_comment: 1157db96d56Sopenharmony_ci self.fail("Nested block comment!") 1167db96d56Sopenharmony_ci 1177db96d56Sopenharmony_ci before, _, remainder = line.partition('/*') 1187db96d56Sopenharmony_ci comment, comment_ends, after = remainder.partition('*/') 1197db96d56Sopenharmony_ci if comment_ends: 1207db96d56Sopenharmony_ci # snip out the comment 1217db96d56Sopenharmony_ci line = before.rstrip() + ' ' + after.lstrip() 1227db96d56Sopenharmony_ci continue 1237db96d56Sopenharmony_ci # comment continues to eol 1247db96d56Sopenharmony_ci self.in_comment = True 1257db96d56Sopenharmony_ci line = before.rstrip() 1267db96d56Sopenharmony_ci break 1277db96d56Sopenharmony_ci 1287db96d56Sopenharmony_ci # we actually have some // comments 1297db96d56Sopenharmony_ci # (but block comments take precedence) 1307db96d56Sopenharmony_ci before, line_comment, comment = line.partition('//') 1317db96d56Sopenharmony_ci if line_comment: 1327db96d56Sopenharmony_ci line = before.rstrip() 1337db96d56Sopenharmony_ci 1347db96d56Sopenharmony_ci if not line.startswith('#'): 1357db96d56Sopenharmony_ci return 1367db96d56Sopenharmony_ci 1377db96d56Sopenharmony_ci line = line[1:].lstrip() 1387db96d56Sopenharmony_ci assert line 1397db96d56Sopenharmony_ci 1407db96d56Sopenharmony_ci fields = line.split() 1417db96d56Sopenharmony_ci token = fields[0].lower() 1427db96d56Sopenharmony_ci condition = ' '.join(fields[1:]).strip() 1437db96d56Sopenharmony_ci 1447db96d56Sopenharmony_ci if token in {'if', 'ifdef', 'ifndef', 'elif'}: 1457db96d56Sopenharmony_ci if not condition: 1467db96d56Sopenharmony_ci self.fail("Invalid format for #" + token + " line: no argument!") 1477db96d56Sopenharmony_ci if token in {'if', 'elif'}: 1487db96d56Sopenharmony_ci if not self.is_a_simple_defined(condition): 1497db96d56Sopenharmony_ci condition = "(" + condition + ")" 1507db96d56Sopenharmony_ci if token == 'elif': 1517db96d56Sopenharmony_ci previous_token, previous_condition = pop_stack() 1527db96d56Sopenharmony_ci self.stack.append((previous_token, negate(previous_condition))) 1537db96d56Sopenharmony_ci else: 1547db96d56Sopenharmony_ci fields = condition.split() 1557db96d56Sopenharmony_ci if len(fields) != 1: 1567db96d56Sopenharmony_ci self.fail("Invalid format for #" + token + " line: should be exactly one argument!") 1577db96d56Sopenharmony_ci symbol = fields[0] 1587db96d56Sopenharmony_ci condition = 'defined(' + symbol + ')' 1597db96d56Sopenharmony_ci if token == 'ifndef': 1607db96d56Sopenharmony_ci condition = '!' + condition 1617db96d56Sopenharmony_ci token = 'if' 1627db96d56Sopenharmony_ci 1637db96d56Sopenharmony_ci self.stack.append((token, condition)) 1647db96d56Sopenharmony_ci 1657db96d56Sopenharmony_ci elif token == 'else': 1667db96d56Sopenharmony_ci previous_token, previous_condition = pop_stack() 1677db96d56Sopenharmony_ci self.stack.append((previous_token, negate(previous_condition))) 1687db96d56Sopenharmony_ci 1697db96d56Sopenharmony_ci elif token == 'endif': 1707db96d56Sopenharmony_ci while pop_stack()[0] != 'if': 1717db96d56Sopenharmony_ci pass 1727db96d56Sopenharmony_ci 1737db96d56Sopenharmony_ci else: 1747db96d56Sopenharmony_ci return 1757db96d56Sopenharmony_ci 1767db96d56Sopenharmony_ci if self.verbose: 1777db96d56Sopenharmony_ci print(self.status()) 1787db96d56Sopenharmony_ci 1797db96d56Sopenharmony_ciif __name__ == '__main__': 1807db96d56Sopenharmony_ci for filename in sys.argv[1:]: 1817db96d56Sopenharmony_ci with open(filename, "rt") as f: 1827db96d56Sopenharmony_ci cpp = Monitor(filename, verbose=True) 1837db96d56Sopenharmony_ci print() 1847db96d56Sopenharmony_ci print(filename) 1857db96d56Sopenharmony_ci for line_number, line in enumerate(f.read().split('\n'), 1): 1867db96d56Sopenharmony_ci cpp.writeline(line) 187