Files
magic/scripts/preproc.py
T
Tim Edwards 088fc759c4 Set of changes updating version 8.2 to the level of 8.1, since 8.2
development had been halted since it was first created back in April.
Version 8.2 is now the official development version, with the first
development push to create a Cairo graphics interface.
2017-08-01 22:14:42 -04:00

453 lines
14 KiB
Python
Executable File

#!/ef/efabless/opengalaxy/venv/bin/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)