mirror of
https://github.com/RTimothyEdwards/magic.git
synced 2026-08-30 18:02:32 +02:00
preproc.py script from another project. Had to further develop it to get around the amazingly complicated preprocessor usage in the scmos subdirectory. Needed to add handling of parameterized definitions; could not figure out how to align the syntax used in scmos/extract_template with any consistent syntax. Gave up and rewrote some of the contents of extract_template to avoid the more ambiguous usage. All of this is to support a completely deprecated scmos.tech. However, it does avoid both the M4 and cpp preprocessors altogether. Also did auto-detection of python3 in the configure script for use of the preproc.py preprocessor, and applied the same preprocessor to the macro definitions.
577 lines
18 KiB
Python
Executable File
577 lines
18 KiB
Python
Executable File
#!/usr/bin/env 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> [...]
|
|
# #define <variable>(<parameters>) [...]
|
|
# #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 sortkeys(keys):
|
|
newkeys = []
|
|
for i in range(0, len(keys)):
|
|
keyword = keys[i]
|
|
found = False
|
|
for j in range(0, len(newkeys)):
|
|
inword = newkeys[j]
|
|
if inword in keyword:
|
|
# Insert keyword before inword
|
|
newkeys.insert(j, keyword)
|
|
found = True
|
|
break
|
|
if not found:
|
|
newkeys.append(keyword)
|
|
return newkeys
|
|
|
|
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]+(.+)')
|
|
paramrex = re.compile('^([^\(]+)\(([^\)]+)\)')
|
|
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
|
|
contrex = re.compile('.*\\\\$') # Backslash continuation line
|
|
|
|
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", file=sys.stderr)
|
|
return
|
|
|
|
ccblock = -1
|
|
ifblock = -1
|
|
ifstack = []
|
|
lineno = 0
|
|
|
|
filetext = ifile.readlines()
|
|
lastline = []
|
|
|
|
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[pmatch.end(0) + ematch.end(0):]
|
|
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
|
|
|
|
# Handle continuation detected in previous line
|
|
if lastline:
|
|
# Note: Apparently there is a character retained after the backslash,
|
|
# so strip the last two characters from the line.
|
|
line = lastline[0:-2] + line
|
|
lastline = []
|
|
|
|
# Continuation lines have the next highest priority. However, this
|
|
# script will attempt to keep continuation lines in the body of the
|
|
# text and only collapse lines where continuation lines occur in
|
|
# a preprocessor statement.
|
|
|
|
cmatch = contrex.match(line)
|
|
|
|
# Ignore lines beginning with "###"
|
|
pmatch = commentrex.match(line)
|
|
if pmatch:
|
|
continue
|
|
|
|
# Handle ifdef
|
|
pmatch = ifdefrex.match(line)
|
|
if pmatch:
|
|
if cmatch:
|
|
lastline = line
|
|
continue
|
|
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 cmatch:
|
|
lastline = line
|
|
continue
|
|
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 cmatch:
|
|
lastline = line
|
|
continue
|
|
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 cmatch:
|
|
lastline = line
|
|
continue
|
|
if ifblock == -1:
|
|
print("Error: #elseif without preceding #if at line " + str(lineno) + ".", file=sys.stderr)
|
|
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 cmatch:
|
|
lastline = line
|
|
continue
|
|
if ifblock == -1:
|
|
print("Error: #else without preceding #if at line " + str(lineno) + ".", file=sys.stderr)
|
|
ifblock = 0
|
|
|
|
if ifblock == 1:
|
|
ifblock = 2
|
|
elif ifblock == 0:
|
|
ifblock = 1
|
|
continue
|
|
|
|
# Handle endif
|
|
pmatch = endifrex.match(line)
|
|
if pmatch:
|
|
if cmatch:
|
|
lastline = line
|
|
continue
|
|
if ifblock == -1:
|
|
print("Error: #endif outside of #if block at line " + str(lineno) + " (ignored)", file=sys.stderr)
|
|
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)", file=sys.stderr)
|
|
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)", file=sys.stderr)
|
|
ifblock = 2
|
|
continue
|
|
|
|
# Ignore all lines that are not satisfied by a conditional
|
|
if ifblock == 0 or ifblock == 2:
|
|
continue
|
|
|
|
# Handle include. Note that this code does not expect or
|
|
# handle 'if' blocks that cross file boundaries.
|
|
pmatch = includerex.match(line)
|
|
if pmatch:
|
|
if cmatch:
|
|
lastline = line
|
|
continue
|
|
inclfile = pmatch.group(1)
|
|
runpp(keys, keyrex, defines, ccomm, incdirs, inclfile, ofile)
|
|
continue
|
|
|
|
# Handle define (with value)
|
|
pmatch = definerex.match(line)
|
|
if pmatch:
|
|
if cmatch:
|
|
lastline = line
|
|
continue
|
|
condition = pmatch.group(1)
|
|
|
|
# Additional handling of definition w/parameters: #define X(a,b,c) ..."
|
|
rmatch = paramrex.match(condition)
|
|
if rmatch:
|
|
# 'condition' as a key into keyrex only needs to be unique.
|
|
# Use the definition word without everything in parentheses
|
|
condition = rmatch.group(1)
|
|
|
|
# 'pcondition' is the actual search regexp and must capture all
|
|
# the parameters individually for substitution
|
|
|
|
parameters = rmatch.group(2).split(',')
|
|
|
|
# Generate the regexp string to match comma-separate values
|
|
# Note that this is based on the cpp preprocessor, which
|
|
# apparently allows commas in arguments if surrounded by
|
|
# parentheses; e.g., "def(a, b, (c1,c2))". This is NOT
|
|
# handled.
|
|
|
|
pcondition = condition + '\('
|
|
for param in parameters[0:-1]:
|
|
pcondition += '(.*),'
|
|
pcondition += '(.*)\)'
|
|
|
|
# Generate the substitution string with group substitutions
|
|
pvalue = pmatch.group(2)
|
|
idx = 1
|
|
for param in parameters:
|
|
pvalue = pvalue.replace(param, '\g<' + str(idx) + '>')
|
|
idx = idx + 1
|
|
|
|
defines[condition] = pvalue
|
|
keyrex[condition] = re.compile(pcondition)
|
|
else:
|
|
parameters = []
|
|
value = pmatch.group(2)
|
|
# Note: Need to check for infinite recursion here, but it's tricky.
|
|
defines[condition] = value
|
|
keyrex[condition] = re.compile(condition)
|
|
|
|
if condition not in keys:
|
|
# Parameterized keys go to the front of the list
|
|
if parameters:
|
|
keys.insert(0, condition)
|
|
else:
|
|
keys.append(condition)
|
|
keys = sortkeys(keys)
|
|
continue
|
|
|
|
# Handle define (simple case, no value)
|
|
pmatch = defrex.match(line)
|
|
if pmatch:
|
|
if cmatch:
|
|
lastline = line
|
|
continue
|
|
condition = pmatch.group(1)
|
|
defines[condition] = '1'
|
|
keyrex[condition] = re.compile(condition)
|
|
if condition not in keys:
|
|
keys.append(condition)
|
|
keys = sortkeys(keys)
|
|
continue
|
|
|
|
# Handle undef
|
|
pmatch = undefrex.match(line)
|
|
if pmatch:
|
|
if cmatch:
|
|
lastline = line
|
|
continue
|
|
condition = pmatch.group(1)
|
|
if condition in keys:
|
|
defines.pop(condition)
|
|
keyrex.pop(condition)
|
|
keys.remove(condition)
|
|
continue
|
|
|
|
# Now do definition replacement on what's left (if anything)
|
|
# This must be done repeatedly from the top until there are no
|
|
# more substitutions to make.
|
|
|
|
while True:
|
|
origline = line
|
|
for keyword in keys:
|
|
newline = keyrex[keyword].sub(defines[keyword], line)
|
|
if newline != line:
|
|
line = newline
|
|
break
|
|
|
|
if line == origline:
|
|
break
|
|
|
|
# Output the line
|
|
print(line, file=ofile, end='')
|
|
|
|
if ifblock != -1 or ifstack != []:
|
|
print("Error: input file ended with an unterminated #if block.", file=sys.stderr)
|
|
|
|
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)
|
|
keys = sortkeys(keys)
|
|
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)
|
|
|
|
# Sort keys so that if any definition contains another definition, the
|
|
# subset word is handled last; otherwise the subset word will get
|
|
# substituted, screwing up the definition names in which it occurs.
|
|
|
|
keys = sortkeys(keys)
|
|
|
|
runpp(keys, keyrex, defines, ccomm, incdirs, inputfile, ofile)
|
|
if ofile != sys.stdout:
|
|
ofile.close()
|
|
sys.exit(0)
|