228 lines
5.4 KiB
Plaintext
228 lines
5.4 KiB
Plaintext
|
#!/usr/bin/env python
|
||
|
#
|
||
|
# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008
|
||
|
# Contributors to the Freedoom project. All rights reserved.
|
||
|
#
|
||
|
# Redistribution and use in source and binary forms, with or without
|
||
|
# modification, are permitted provided that the following conditions are
|
||
|
# met:
|
||
|
#
|
||
|
# * Redistributions of source code must retain the above copyright
|
||
|
# notice, this list of conditions and the following disclaimer.
|
||
|
# * Redistributions in binary form must reproduce the above copyright
|
||
|
# notice, this list of conditions and the following disclaimer in the
|
||
|
# documentation and/or other materials provided with the distribution.
|
||
|
# * Neither the name of the freedoom project nor the names of its
|
||
|
# contributors may be used to endorse or promote products derived from
|
||
|
# this software without specific prior written permission.
|
||
|
#
|
||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||
|
# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||
|
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||
|
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
|
||
|
# OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||
|
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||
|
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||
|
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||
|
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||
|
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||
|
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||
|
#
|
||
|
#
|
||
|
# simple cpp-style preprocessor
|
||
|
#
|
||
|
# Understands most features of the C preprocessor, including:
|
||
|
# #if .. #elif .. #else .. #endif
|
||
|
# - with expressions
|
||
|
# #ifdef
|
||
|
# #define
|
||
|
# #include
|
||
|
#
|
||
|
|
||
|
import collections
|
||
|
import sys
|
||
|
import re
|
||
|
|
||
|
debug = False
|
||
|
defines = collections.defaultdict(lambda: False)
|
||
|
|
||
|
command_re = re.compile(r"\#(\w+)(\s+(.*))?")
|
||
|
include_re = re.compile(r"\s*\"(.*)\"\s*")
|
||
|
define_re = re.compile(r"\s*(\S+)\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 not arg.startswith("-D"):
|
||
|
continue
|
||
|
|
||
|
name = arg[2:]
|
||
|
if '=' in name:
|
||
|
name, value = name.split('=', 1)
|
||
|
else:
|
||
|
value = True
|
||
|
|
||
|
defines[name] = value
|
||
|
|
||
|
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()
|
||
|
|
||
|
# Evaluate an expression using Python's eval() function.
|
||
|
|
||
|
def eval_expr(expr):
|
||
|
expr = expr.replace("||", " or ") \
|
||
|
.replace("&&", " and ") \
|
||
|
.replace("!", "not ")
|
||
|
|
||
|
code = compile(expr, "", "eval")
|
||
|
result = eval(code, {}, defines)
|
||
|
return result
|
||
|
|
||
|
# #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):
|
||
|
match = define_re.match(arg)
|
||
|
name = match.group(1)
|
||
|
value = match.group(2)
|
||
|
if value == '':
|
||
|
value = True
|
||
|
|
||
|
defines[name] = value
|
||
|
|
||
|
# #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
|
||
|
|
||
|
debug_msg("%s %s >\n" % (command, arg))
|
||
|
|
||
|
# Should we ignore the contents of this block?
|
||
|
|
||
|
sub_ignore = not eval_expr(arg)
|
||
|
|
||
|
if "n" in command:
|
||
|
sub_ignore = not sub_ignore
|
||
|
|
||
|
# Parse the block
|
||
|
|
||
|
result, newarg = 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, arg = read_block(stream, ignore or (not sub_ignore))
|
||
|
debug_msg("%s %s else < (%s)\n" % (command, arg, result))
|
||
|
|
||
|
if result == "elif":
|
||
|
debug_msg("%s %s elif %s>\n" % (command, arg, newarg))
|
||
|
cmd_ifdef(newarg, "if", stream, ignore or (not sub_ignore))
|
||
|
result = "endif"
|
||
|
|
||
|
# 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]
|
||
|
|
||
|
# Check if this line has a command
|
||
|
|
||
|
match = command_re.match(line)
|
||
|
|
||
|
if match:
|
||
|
command = match.group(1)
|
||
|
arg = match.group(3)
|
||
|
|
||
|
if command in ("else", "elif", "endif"):
|
||
|
return (command, arg)
|
||
|
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:
|
||
|
for key, value in defines.items():
|
||
|
if isinstance(value, str):
|
||
|
line = line.replace(key, value)
|
||
|
print(line)
|
||
|
|
||
|
parse_cmdline()
|
||
|
parse_stream(sys.stdin)
|
||
|
|