mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	 7726ac9163
			
		
	
	
		7726ac9163
		
	
	
	
	
		
			
			including correctly generating code for Clinic blocks inside C preprocessor conditional blocks.
		
			
				
	
	
		
			191 lines
		
	
	
	
		
			5.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			191 lines
		
	
	
	
		
			5.8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| import re
 | |
| import sys
 | |
| 
 | |
| def negate(condition):
 | |
|     """
 | |
|     Returns a CPP conditional that is the opposite of the conditional passed in.
 | |
|     """
 | |
|     if condition.startswith('!'):
 | |
|         return condition[1:]
 | |
|     return "!" + condition
 | |
| 
 | |
| class Monitor:
 | |
|     """
 | |
|     A simple C preprocessor that scans C source and computes, line by line,
 | |
|     what the current C preprocessor #if state is.
 | |
| 
 | |
|     Doesn't handle everything--for example, if you have /* inside a C string,
 | |
|     without a matching */ (also inside a C string), or with a */ inside a C
 | |
|     string but on another line and with preprocessor macros in between...
 | |
|     the parser will get lost.
 | |
| 
 | |
|     Anyway this implementation seems to work well enough for the CPython sources.
 | |
|     """
 | |
| 
 | |
|     is_a_simple_defined = re.compile(r'^defined\s*\(\s*[A-Za-z0-9_]+\s*\)$').match
 | |
| 
 | |
|     def __init__(self, filename=None, *, verbose=False):
 | |
|         self.stack = []
 | |
|         self.in_comment = False
 | |
|         self.continuation = None
 | |
|         self.line_number = 0
 | |
|         self.filename = filename
 | |
|         self.verbose = verbose
 | |
| 
 | |
|     def __repr__(self):
 | |
|         return ''.join((
 | |
|             '<Monitor ',
 | |
|             str(id(self)),
 | |
|             " line=", str(self.line_number),
 | |
|             " condition=", repr(self.condition()),
 | |
|             ">"))
 | |
| 
 | |
|     def status(self):
 | |
|         return str(self.line_number).rjust(4) + ": " + self.condition()
 | |
| 
 | |
|     def condition(self):
 | |
|         """
 | |
|         Returns the current preprocessor state, as a single #if condition.
 | |
|         """
 | |
|         return " && ".join(condition for token, condition in self.stack)
 | |
| 
 | |
|     def fail(self, *a):
 | |
|         if self.filename:
 | |
|             filename = " " + self.filename
 | |
|         else:
 | |
|             filename = ''
 | |
|         print("Error at" + filename, "line", self.line_number, ":")
 | |
|         print("   ", ' '.join(str(x) for x in a))
 | |
|         sys.exit(-1)
 | |
| 
 | |
|     def close(self):
 | |
|         if self.stack:
 | |
|             self.fail("Ended file while still in a preprocessor conditional block!")
 | |
| 
 | |
|     def write(self, s):
 | |
|         for line in s.split("\n"):
 | |
|             self.writeline(line)
 | |
| 
 | |
|     def writeline(self, line):
 | |
|         self.line_number += 1
 | |
|         line = line.strip()
 | |
| 
 | |
|         def pop_stack():
 | |
|             if not self.stack:
 | |
|                 self.fail("#" + token + " without matching #if / #ifdef / #ifndef!")
 | |
|             return self.stack.pop()
 | |
| 
 | |
|         if self.continuation:
 | |
|             line = self.continuation + line
 | |
|             self.continuation = None
 | |
| 
 | |
|         if not line:
 | |
|             return
 | |
| 
 | |
|         if line.endswith('\\'):
 | |
|             self.continuation = line[:-1].rstrip() + " "
 | |
|             return
 | |
| 
 | |
|         # we have to ignore preprocessor commands inside comments
 | |
|         #
 | |
|         # we also have to handle this:
 | |
|         #     /* start
 | |
|         #     ...
 | |
|         #     */   /*    <-- tricky!
 | |
|         #     ...
 | |
|         #     */
 | |
|         # and this:
 | |
|         #     /* start
 | |
|         #     ...
 | |
|         #     */   /* also tricky! */
 | |
|         if self.in_comment:
 | |
|             if '*/' in line:
 | |
|                 # snip out the comment and continue
 | |
|                 #
 | |
|                 # GCC allows
 | |
|                 #    /* comment
 | |
|                 #    */ #include <stdio.h>
 | |
|                 # maybe other compilers too?
 | |
|                 _, _, line = line.partition('*/')
 | |
|                 self.in_comment = False
 | |
| 
 | |
|         while True:
 | |
|             if '/*' in line:
 | |
|                 if self.in_comment:
 | |
|                     self.fail("Nested block comment!")
 | |
| 
 | |
|                 before, _, remainder = line.partition('/*')
 | |
|                 comment, comment_ends, after = remainder.partition('*/')
 | |
|                 if comment_ends:
 | |
|                     # snip out the comment
 | |
|                     line = before.rstrip() + ' ' + after.lstrip()
 | |
|                     continue
 | |
|                 # comment continues to eol
 | |
|                 self.in_comment = True
 | |
|                 line = before.rstrip()
 | |
|             break
 | |
| 
 | |
|         # we actually have some // comments
 | |
|         # (but block comments take precedence)
 | |
|         before, line_comment, comment = line.partition('//')
 | |
|         if line_comment:
 | |
|             line = before.rstrip()
 | |
| 
 | |
|         if not line.startswith('#'):
 | |
|             return
 | |
| 
 | |
|         line = line[1:].lstrip()
 | |
|         assert line
 | |
| 
 | |
|         fields = line.split()
 | |
|         token = fields[0].lower()
 | |
|         condition = ' '.join(fields[1:]).strip()
 | |
| 
 | |
|         if_tokens = {'if', 'ifdef', 'ifndef'}
 | |
|         all_tokens = if_tokens | {'elif', 'else', 'endif'}
 | |
| 
 | |
|         if token not in all_tokens:
 | |
|             return
 | |
| 
 | |
|         # cheat a little here, to reuse the implementation of if
 | |
|         if token == 'elif':
 | |
|             pop_stack()
 | |
|             token = 'if'
 | |
| 
 | |
|         if token in if_tokens:
 | |
|             if not condition:
 | |
|                 self.fail("Invalid format for #" + token + " line: no argument!")
 | |
|             if token == 'if':
 | |
|                 if not self.is_a_simple_defined(condition):
 | |
|                     condition = "(" + condition + ")"
 | |
|             else:
 | |
|                 fields = condition.split()
 | |
|                 if len(fields) != 1:
 | |
|                     self.fail("Invalid format for #" + token + " line: should be exactly one argument!")
 | |
|                 symbol = fields[0]
 | |
|                 condition = 'defined(' + symbol + ')'
 | |
|                 if token == 'ifndef':
 | |
|                     condition = '!' + condition
 | |
| 
 | |
|             self.stack.append(("if", condition))
 | |
|             if self.verbose:
 | |
|                 print(self.status())
 | |
|             return
 | |
| 
 | |
|         previous_token, previous_condition = pop_stack()
 | |
| 
 | |
|         if token == 'else':
 | |
|             self.stack.append(('else', negate(previous_condition)))
 | |
|         elif token == 'endif':
 | |
|             pass
 | |
|         if self.verbose:
 | |
|             print(self.status())
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     for filename in sys.argv[1:]:
 | |
|         with open(filename, "rt") as f:
 | |
|             cpp = Monitor(filename, verbose=True)
 | |
|             print()
 | |
|             print(filename)
 | |
|             for line_number, line in enumerate(f.read().split('\n'), 1):
 | |
|                 cpp.writeline(line)
 |