summaryrefslogtreecommitdiff
path: root/scripts/preproc.py.in
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/preproc.py.in')
-rwxr-xr-xscripts/preproc.py.in452
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)