diff options
Diffstat (limited to 'scripts/preproc.py.in')
-rwxr-xr-x | scripts/preproc.py.in | 452 |
1 files changed, 452 insertions, 0 deletions
diff --git a/scripts/preproc.py.in b/scripts/preproc.py.in new file mode 100755 index 0000000..f5cbd1f --- /dev/null +++ b/scripts/preproc.py.in @@ -0,0 +1,452 @@ +#!ENV_PATH python3 +#-------------------------------------------------------------------- +# +# preproc.py +# +# General purpose macro preprocessor +# +#-------------------------------------------------------------------- +# Usage: +# +# preproc.py input_file [output_file] [-D<variable> ...] +# +# Where <variable> may be a keyword or a key=value pair +# +# Syntax: Basically like cpp. However, this preprocessor handles +# only a limited set of keywords, so it does not otherwise mangle +# the file in the belief that it must be C code. Handling of boolean +# relations is important, so these are thoroughly defined (see below) +# +# #if defined(<variable>) [...] +# #ifdef <variable> +# #ifndef <variable> +# #elseif <variable> +# #else +# #endif +# +# #define <variable> [...] +# #undef <variable> +# +# #include <filename> +# +# <variable> may be +# <keyword> +# <keyword>=<value> +# +# <keyword> without '=' is effectively the same as <keyword>=1 +# Lack of a keyword is equivalent to <keyword>=0, in a conditional. +# +# Boolean operators (in order of precedence): +# ! NOT +# && AND +# || OR +# +# Comments: +# Most comments (C-like or Tcl-like) are output as-is. A +# line beginning with "###" is treated as a preprocessor +# comment and is not copied to the output. +# +# Examples; +# #if defined(X) || defined(Y) +# #else +# #if defined(Z) +# #endif +#-------------------------------------------------------------------- + +import re +import sys + +def solve_statement(condition): + + defrex = re.compile('defined[ \t]*\(([^\)]+)\)') + orrex = re.compile('(.+)\|\|(.+)') + andrex = re.compile('(.+)&&(.+)') + notrex = re.compile('!([^&\|]+)') + parenrex = re.compile('\(([^\)]+)\)') + leadspacerex = re.compile('^[ \t]+(.*)') + endspacerex = re.compile('(.*)[ \t]+$') + + matchfound = True + while matchfound: + matchfound = False + + # Search for defined(K) (K must be a single keyword) + # If the keyword was defined, then it should have been replaced by 1 + lmatch = defrex.search(condition) + if lmatch: + key = lmatch.group(1) + if key == 1 or key == '1' or key == True: + repl = 1 + else: + repl = 0 + + condition = defrex.sub(str(repl), condition) + matchfound = True + + # Search for (X) recursively + lmatch = parenrex.search(condition) + if lmatch: + repl = solve_statement(lmatch.group(1)) + condition = parenrex.sub(str(repl), condition) + matchfound = True + + # Search for !X recursively + lmatch = notrex.search(condition) + if lmatch: + only = solve_statement(lmatch.group(1)) + if only == '1': + repl = '0' + else: + repl = '1' + condition = notrex.sub(str(repl), condition) + matchfound = True + + # Search for A&&B recursively + lmatch = andrex.search(condition) + if lmatch: + first = solve_statement(lmatch.group(1)) + second = solve_statement(lmatch.group(2)) + if first == '1' and second == '1': + repl = '1' + else: + repl = '0' + condition = andrex.sub(str(repl), condition) + matchfound = True + + # Search for A||B recursively + lmatch = orrex.search(condition) + if lmatch: + first = solve_statement(lmatch.group(1)) + second = solve_statement(lmatch.group(2)) + if first == '1' or second == '1': + repl = '1' + else: + repl = '0' + condition = orrex.sub(str(repl), condition) + matchfound = True + + # Remove whitespace + lmatch = leadspacerex.match(condition) + if lmatch: + condition = lmatch.group(1) + lmatch = endspacerex.match(condition) + if lmatch: + condition = lmatch.group(1) + + return condition + +def solve_condition(condition, keys, defines, keyrex): + # Do definition replacement on the conditional + for keyword in keys: + condition = keyrex[keyword].sub(defines[keyword], condition) + + value = solve_statement(condition) + if value == '1': + return 1 + else: + return 0 + +def runpp(keys, keyrex, defines, ccomm, incdirs, inputfile, ofile): + + includerex = re.compile('^[ \t]*#include[ \t]+"*([^ \t\n\r"]+)') + definerex = re.compile('^[ \t]*#define[ \t]+([^ \t]+)[ \t]+(.+)') + defrex = re.compile('^[ \t]*#define[ \t]+([^ \t\n\r]+)') + undefrex = re.compile('^[ \t]*#undef[ \t]+([^ \t\n\r]+)') + ifdefrex = re.compile('^[ \t]*#ifdef[ \t]+(.+)') + ifndefrex = re.compile('^[ \t]*#ifndef[ \t]+(.+)') + ifrex = re.compile('^[ \t]*#if[ \t]+(.+)') + elseifrex = re.compile('^[ \t]*#elseif[ \t]+(.+)') + elserex = re.compile('^[ \t]*#else') + endifrex = re.compile('^[ \t]*#endif') + commentrex = re.compile('^###[^#]*$') + ccstartrex = re.compile('/\*') # C-style comment start + ccendrex = re.compile('\*/') # C-style comment end + + badifrex = re.compile('^[ \t]*#if[ \t]*.*') + badelserex = re.compile('^[ \t]*#else[ \t]*.*') + + # This code is not designed to operate on huge files. Neither is it designed to be + # efficient. + + # ifblock state: + # -1 : not in an if/else block + # 0 : no condition satisfied yet + # 1 : condition satisfied + # 2 : condition was handled, waiting for endif + + ifile = False + try: + ifile = open(inputfile, 'r') + except FileNotFoundError: + for dir in incdirs: + try: + ifile = open(dir + '/' + inputfile, 'r') + except FileNotFoundError: + pass + else: + break + + if not ifile: + print("Error: Cannot open file " + inputfile + " for reading.\n") + return + + ccblock = -1 + ifblock = -1 + ifstack = [] + lineno = 0 + + filetext = ifile.readlines() + for line in filetext: + lineno += 1 + + # C-style comments override everything else + if ccomm: + if ccblock == -1: + pmatch = ccstartrex.search(line) + if pmatch: + ematch = ccendrex.search(line[pmatch.end(0):]) + if ematch: + line = line[0:pmatch.start(0)] + line[ematch.end(0)+2:] + else: + line = line[0:pmatch.start(0)] + ccblock = 1 + elif ccblock == 1: + ematch = ccendrex.search(line) + if ematch: + line = line[ematch.end(0)+2:] + ccblock = -1 + else: + continue + + # Ignore lines beginning with "###" + pmatch = commentrex.match(line) + if pmatch: + continue + + # Handle include. Note that this code does not expect or + # handle 'if' blocks that cross file boundaries. + pmatch = includerex.match(line) + if pmatch: + inclfile = pmatch.group(1) + runpp(keys, keyrex, defines, ccomm, incdirs, inclfile, ofile) + continue + + # Handle define (with value) + pmatch = definerex.match(line) + if pmatch: + condition = pmatch.group(1) + value = pmatch.group(2) + defines[condition] = value + keyrex[condition] = re.compile(condition) + if condition not in keys: + keys.append(condition) + continue + + # Handle define (simple case, no value) + pmatch = defrex.match(line) + if pmatch: + condition = pmatch.group(1) + print("Defrex condition is " + condition) + defines[condition] = '1' + keyrex[condition] = re.compile(condition) + if condition not in keys: + keys.append(condition) + print("Defrex value is " + defines[condition]) + continue + + # Handle undef + pmatch = undefrex.match(line) + if pmatch: + condition = pmatch.group(1) + if condition in keys: + defines.pop(condition) + keyrex.pop(condition) + keys.remove(condition) + continue + + # Handle ifdef + pmatch = ifdefrex.match(line) + if pmatch: + if ifblock != -1: + ifstack.append(ifblock) + + if ifblock == 1 or ifblock == -1: + condition = pmatch.group(1) + ifblock = solve_condition(condition, keys, defines, keyrex) + else: + ifblock = 2 + continue + + # Handle ifndef + pmatch = ifndefrex.match(line) + if pmatch: + if ifblock != -1: + ifstack.append(ifblock) + + if ifblock == 1 or ifblock == -1: + condition = pmatch.group(1) + ifblock = solve_condition(condition, keys, defines, keyrex) + ifblock = 1 if ifblock == 0 else 0 + else: + ifblock = 2 + continue + + # Handle if + pmatch = ifrex.match(line) + if pmatch: + if ifblock != -1: + ifstack.append(ifblock) + + if ifblock == 1 or ifblock == -1: + condition = pmatch.group(1) + ifblock = solve_condition(condition, keys, defines, keyrex) + else: + ifblock = 2 + continue + + # Handle elseif + pmatch = elseifrex.match(line) + if pmatch: + if ifblock == -1: + print("Error: #elseif without preceding #if at line " + str(lineno) + ".") + ifblock = 0 + + if ifblock == 1: + ifblock = 2 + elif ifblock != 2: + condition = pmatch.group(1) + ifblock = solve_condition(condition, keys, defines, keyrex) + continue + + # Handle else + pmatch = elserex.match(line) + if pmatch: + if ifblock == -1: + print("Error: #else without preceding #if at line " + str(lineno) + ".") + ifblock = 0 + + if ifblock == 1: + ifblock = 2 + elif ifblock == 0: + ifblock = 1 + continue + + # Handle endif + pmatch = endifrex.match(line) + if pmatch: + if ifblock == -1: + print("Error: #endif outside of #if block at line " + str(lineno) + " (ignored)") + elif ifstack: + ifblock = ifstack.pop() + else: + ifblock = -1 + continue + + # Check for 'if' or 'else' that were not properly formed + pmatch = badifrex.match(line) + if pmatch: + print("Error: Badly formed #if statement at line " + str(lineno) + " (ignored)") + if ifblock != -1: + ifstack.append(ifblock) + + if ifblock == 1 or ifblock == -1: + ifblock = 0 + else: + ifblock = 2 + continue + + pmatch = badelserex.match(line) + if pmatch: + print("Error: Badly formed #else statement at line " + str(lineno) + " (ignored)") + ifblock = 2 + continue + + # Ignore all lines that are not satisfied by a conditional + if ifblock == 0 or ifblock == 2: + continue + + # Now do definition replacement on what's left (if anything) + for keyword in keys: + line = keyrex[keyword].sub(defines[keyword], line) + + # Output the line + print(line, file=ofile, end='') + + if ifblock != -1 or ifstack != []: + print("Error: input file ended with an unterminated #if block.") + + if ifile != sys.stdin: + ifile.close() + return + +def printusage(progname): + print('Usage: ' + progname + ' input_file [output_file] [-options]') + print(' Options are:') + print(' -help Print this help text.') + print(' -ccomm Remove C comments in /* ... */ delimiters.') + print(' -D<def> Define word <def> and set its value to 1.') + print(' -D<def>=<val> Define word <def> and set its value to <val>.') + print(' -I<dir> Add <dir> to search path for input files.') + return + +if __name__ == '__main__': + + # Parse command line for options and arguments + options = [] + arguments = [] + for item in sys.argv[1:]: + if item.find('-', 0) == 0: + options.append(item) + else: + arguments.append(item) + + if len(arguments) > 0: + inputfile = arguments[0] + if len(arguments) > 1: + outputfile = arguments[1] + else: + outputfile = [] + else: + printusage(sys.argv[0]) + sys.exit(0) + + defines = {} + keyrex = {} + keys = [] + incdirs = [] + ccomm = False + for item in options: + result = item.split('=') + if result[0] == '-help': + printusage(sys.argv[0]) + sys.exit(0) + elif result[0] == '-ccomm': + ccomm = True + elif result[0][0:2] == '-I': + incdirs.append(result[0][2:]) + elif result[0][0:2] == '-D': + keyword = result[0][2:] + try: + value = result[1] + except: + value = '1' + defines[keyword] = value + keyrex[keyword] = re.compile(keyword) + keys.append(keyword) + else: + print('Bad option ' + item + ', options are -help, -ccomm, -D<def> -I<dir>\n') + sys.exit(1) + + if outputfile: + ofile = open(outputfile, 'w') + else: + ofile = sys.stdout + + if not ofile: + print("Error: Cannot open file " + output_file + " for writing.") + sys.exit(1) + + runpp(keys, keyrex, defines, ccomm, incdirs, inputfile, ofile) + if ofile != sys.stdout: + ofile.close() + sys.exit(0) |