#!/usr/bin/env python3 # SPDX-License-Identifier: BSD-3-Clause # # simple cpp-style preprocessor # # Understands: # # #define NAME # # Set an option # You can use -D on the command line too # # #undef NAME # # Unset an option if it is set # # #if .. #endif / #ifdef .. #endif # # Specify a list of options set, eg #ifdef PHASE1 || PHASE2 # The block is only displayed if one of the options is set # # #ifn .. #endif / #ifndef .. #endif # # Similarly specify a list of options # The block is displayed if none of the options are set # # #include "filename" # # include the contents of a file import sys import re debug = False defines = {} command_re = re.compile("\#(\w+)(\s+(.*))?") include_re = re.compile('\s*"(.*)"\s*') def debug_msg(message): if debug: sys.stderr.write(message) # Parse command line options def parse_cmdline(): for arg in sys.argv[1:]: if arg.startswith("-D"): name = arg[2:] defines[name] = True def parse_stream(stream): result = read_block(stream, False) if result is not None: raise Exception("Mismatched #if in '%s'" % stream.name) def parse_file(filename): f = open(filename) try: parse_stream(f) finally: f.close() # #include def cmd_include(arg): # Extract the filename match = include_re.match(arg) if not match: raise Exception("Invalid 'include' command") filename = match.group(1) # Open the file and process it parse_file(filename) # #define def cmd_define(arg): defines[arg] = True # #undef def cmd_undef(arg): if arg in defines: del defines[arg] # #ifdef/#ifndef def cmd_ifdef(arg, command, stream, ignore): # Get the define name name = arg.strip() debug_msg("%s %s >\n" % (command, arg)) # Should we ignore the contents of this block? sub_ignore = name not in defines if "n" in command: sub_ignore = not sub_ignore # Parse the block result = read_block(stream, ignore or sub_ignore) debug_msg("%s %s < (%s)\n" % (command, arg, result)) # There may be a second "else" block to parse: if result == "else": debug_msg("%s %s else >\n" % (command, arg)) result = read_block(stream, ignore or (not sub_ignore)) debug_msg("%s %s else < (%s)\n" % (command, arg, result)) # Should end in an endif: if result != "endif": raise Exception("'if' block did not end in an 'endif'") commands = { "include": cmd_include, "define": cmd_define, "undef": cmd_undef, "if": cmd_ifdef, "ifdef": cmd_ifdef, "ifn": cmd_ifdef, "ifndef": cmd_ifdef, } # Recursive block reading function # if 'ignore' argument is 1, contents are ignored def read_block(stream, ignore): for line in stream: # Remove newline line = line[0:-1] # Ignore, but keep empty lines if line == " " * len(line): print(line) continue # Check if this line has a command match = command_re.match(line) if match: command = match.group(1) arg = match.group(3) if command == "else" or command == "endif": return command elif command not in commands: raise Exception("Unknown command: '%s'" % command) # Get the callback function. func = commands[command] # Invoke the callback function. #ifdef commands # are a special case and need extra arguments. # Other commands are only executed if we are not # ignoring this block. if func == cmd_ifdef: cmd_ifdef(arg, command=command, stream=stream, ignore=ignore) elif not ignore: func(arg) else: if not ignore: print(line) parse_cmdline() parse_stream(sys.stdin)