Index: scons-2.0.1/engine/SCons/cpp.py |
=================================================================== |
--- scons-2.0.1/engine/SCons/cpp.py (revision 0) |
+++ scons-2.0.1/engine/SCons/cpp.py (revision 0) |
@@ -0,0 +1,589 @@ |
+# |
+# Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 The SCons Foundation |
+# |
+# Permission is hereby granted, free of charge, to any person obtaining |
+# a copy of this software and associated documentation files (the |
+# "Software"), to deal in the Software without restriction, including |
+# without limitation the rights to use, copy, modify, merge, publish, |
+# distribute, sublicense, and/or sell copies of the Software, and to |
+# permit persons to whom the Software is furnished to do so, subject to |
+# the following conditions: |
+# |
+# The above copyright notice and this permission notice shall be included |
+# in all copies or substantial portions of the Software. |
+# |
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY |
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE |
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE |
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION |
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
+# |
+ |
+__revision__ = "src/engine/SCons/cpp.py 5134 2010/08/16 23:02:40 bdeegan" |
+ |
+__doc__ = """ |
+SCons C Pre-Processor module |
+""" |
+#TODO 2.3 and before has no sorted() |
+import SCons.compat |
+ |
+import os |
+import re |
+ |
+# |
+# First "subsystem" of regular expressions that we set up: |
+# |
+# Stuff to turn the C preprocessor directives in a file's contents into |
+# a list of tuples that we can process easily. |
+# |
+ |
+# A table of regular expressions that fetch the arguments from the rest of |
+# a C preprocessor line. Different directives have different arguments |
+# that we want to fetch, using the regular expressions to which the lists |
+# of preprocessor directives map. |
+cpp_lines_dict = { |
+ # Fetch the rest of a #if/#elif/#ifdef/#ifndef as one argument, |
+ # separated from the keyword by white space. |
+ ('if', 'elif', 'ifdef', 'ifndef',) |
+ : '\s+(.+)', |
+ |
+ # Fetch the rest of a #import/#include/#include_next line as one |
+ # argument, with white space optional. |
+ ('import', 'include', 'include_next',) |
+ : '\s*(.+)', |
+ |
+ # We don't care what comes after a #else or #endif line. |
+ ('else', 'endif',) : '', |
+ |
+ # Fetch three arguments from a #define line: |
+ # 1) The #defined keyword. |
+ # 2) The optional parentheses and arguments (if it's a function-like |
+ # macro, '' if it's not). |
+ # 3) The expansion value. |
+ ('define',) : '\s+([_A-Za-z][_A-Za-z0-9_]*)(\([^)]*\))?\s*(.*)', |
+ |
+ # Fetch the #undefed keyword from a #undef line. |
+ ('undef',) : '\s+([_A-Za-z][A-Za-z0-9_]*)', |
+} |
+ |
+# Create a table that maps each individual C preprocessor directive to |
+# the corresponding compiled regular expression that fetches the arguments |
+# we care about. |
+Table = {} |
+for op_list, expr in cpp_lines_dict.items(): |
+ e = re.compile(expr) |
+ for op in op_list: |
+ Table[op] = e |
+del e |
+del op |
+del op_list |
+ |
+# Create a list of the expressions we'll use to match all of the |
+# preprocessor directives. These are the same as the directives |
+# themselves *except* that we must use a negative lookahead assertion |
+# when matching "if" so it doesn't match the "if" in "ifdef." |
+override = { |
+ 'if' : 'if(?!def)', |
+} |
+l = [override.get(x, x) for x in Table.keys()] |
+ |
+ |
+# Turn the list of expressions into one big honkin' regular expression |
+# that will match all the preprocessor lines at once. This will return |
+# a list of tuples, one for each preprocessor line. The preprocessor |
+# directive will be the first element in each tuple, and the rest of |
+# the line will be the second element. |
+e = '^\s*#\s*(' + '|'.join(l) + ')(.*)$' |
+ |
+# And last but not least, compile the expression. |
+CPP_Expression = re.compile(e, re.M) |
+ |
+ |
+ |
+ |
+# |
+# Second "subsystem" of regular expressions that we set up: |
+# |
+# Stuff to translate a C preprocessor expression (as found on a #if or |
+# #elif line) into an equivalent Python expression that we can eval(). |
+# |
+ |
+# A dictionary that maps the C representation of Boolean operators |
+# to their Python equivalents. |
+CPP_to_Python_Ops_Dict = { |
+ '!' : ' not ', |
+ '!=' : ' != ', |
+ '&&' : ' and ', |
+ '||' : ' or ', |
+ '?' : ' and ', |
+ ':' : ' or ', |
+ '\r' : '', |
+} |
+ |
+CPP_to_Python_Ops_Sub = lambda m: CPP_to_Python_Ops_Dict[m.group(0)] |
+ |
+# We have to sort the keys by length so that longer expressions |
+# come *before* shorter expressions--in particular, "!=" must |
+# come before "!" in the alternation. Without this, the Python |
+# re module, as late as version 2.2.2, empirically matches the |
+# "!" in "!=" first, instead of finding the longest match. |
+# What's up with that? |
+l = sorted(CPP_to_Python_Ops_Dict.keys(), key=lambda a: len(a), reverse=True) |
+ |
+# Turn the list of keys into one regular expression that will allow us |
+# to substitute all of the operators at once. |
+expr = '|'.join(map(re.escape, l)) |
+ |
+# ...and compile the expression. |
+CPP_to_Python_Ops_Expression = re.compile(expr) |
+ |
+# A separate list of expressions to be evaluated and substituted |
+# sequentially, not all at once. |
+CPP_to_Python_Eval_List = [ |
+ ['defined\s+(\w+)', '"\\1" in __dict__'], |
+ ['defined\s*\((\w+)\)', '"\\1" in __dict__'], |
+ ['/\*.*\*/', ''], |
+ ['/\*.*', ''], |
+ ['//.*', ''], |
+ ['(0x[0-9A-Fa-f]*)[UL]+', '\\1'], |
+] |
+ |
+# Replace the string representations of the regular expressions in the |
+# list with compiled versions. |
+for l in CPP_to_Python_Eval_List: |
+ l[0] = re.compile(l[0]) |
+ |
+# Wrap up all of the above into a handy function. |
+def CPP_to_Python(s): |
+ """ |
+ Converts a C pre-processor expression into an equivalent |
+ Python expression that can be evaluated. |
+ """ |
+ s = CPP_to_Python_Ops_Expression.sub(CPP_to_Python_Ops_Sub, s) |
+ for expr, repl in CPP_to_Python_Eval_List: |
+ s = expr.sub(repl, s) |
+ return s |
+ |
+ |
+ |
+del expr |
+del l |
+del override |
+ |
+ |
+ |
+class FunctionEvaluator(object): |
+ """ |
+ Handles delayed evaluation of a #define function call. |
+ """ |
+ def __init__(self, name, args, expansion): |
+ """ |
+ Squirrels away the arguments and expansion value of a #define |
+ macro function for later evaluation when we must actually expand |
+ a value that uses it. |
+ """ |
+ self.name = name |
+ self.args = function_arg_separator.split(args) |
+ try: |
+ expansion = expansion.split('##') |
+ except AttributeError: |
+ pass |
+ self.expansion = expansion |
+ def __call__(self, *values): |
+ """ |
+ Evaluates the expansion of a #define macro function called |
+ with the specified values. |
+ """ |
+ if len(self.args) != len(values): |
+ raise ValueError("Incorrect number of arguments to `%s'" % self.name) |
+ # Create a dictionary that maps the macro arguments to the |
+ # corresponding values in this "call." We'll use this when we |
+ # eval() the expansion so that arguments will get expanded to |
+ # the right values. |
+ locals = {} |
+ for k, v in zip(self.args, values): |
+ locals[k] = v |
+ |
+ parts = [] |
+ for s in self.expansion: |
+ if not s in self.args: |
+ s = repr(s) |
+ parts.append(s) |
+ statement = ' + '.join(parts) |
+ |
+ return eval(statement, globals(), locals) |
+ |
+ |
+ |
+# Find line continuations. |
+line_continuations = re.compile('\\\\\r?\n') |
+ |
+# Search for a "function call" macro on an expansion. Returns the |
+# two-tuple of the "function" name itself, and a string containing the |
+# arguments within the call parentheses. |
+function_name = re.compile('(\S+)\(([^)]*)\)') |
+ |
+# Split a string containing comma-separated function call arguments into |
+# the separate arguments. |
+function_arg_separator = re.compile(',\s*') |
+ |
+ |
+ |
+class PreProcessor(object): |
+ """ |
+ The main workhorse class for handling C pre-processing. |
+ """ |
+ def __init__(self, current=os.curdir, cpppath=(), dict={}, all=0): |
+ global Table |
+ |
+ cpppath = tuple(cpppath) |
+ |
+ self.searchpath = { |
+ '"' : (current,) + cpppath, |
+ '<' : cpppath + (current,), |
+ } |
+ |
+ # Initialize our C preprocessor namespace for tracking the |
+ # values of #defined keywords. We use this namespace to look |
+ # for keywords on #ifdef/#ifndef lines, and to eval() the |
+ # expressions on #if/#elif lines (after massaging them from C to |
+ # Python). |
+ self.cpp_namespace = dict.copy() |
+ self.cpp_namespace['__dict__'] = self.cpp_namespace |
+ |
+ if all: |
+ self.do_include = self.all_include |
+ |
+ # For efficiency, a dispatch table maps each C preprocessor |
+ # directive (#if, #define, etc.) to the method that should be |
+ # called when we see it. We accomodate state changes (#if, |
+ # #ifdef, #ifndef) by pushing the current dispatch table on a |
+ # stack and changing what method gets called for each relevant |
+ # directive we might see next at this level (#else, #elif). |
+ # #endif will simply pop the stack. |
+ d = { |
+ 'scons_current_file' : self.scons_current_file |
+ } |
+ for op in Table.keys(): |
+ d[op] = getattr(self, 'do_' + op) |
+ self.default_table = d |
+ |
+ # Controlling methods. |
+ |
+ def tupleize(self, contents): |
+ """ |
+ Turns the contents of a file into a list of easily-processed |
+ tuples describing the CPP lines in the file. |
+ |
+ The first element of each tuple is the line's preprocessor |
+ directive (#if, #include, #define, etc., minus the initial '#'). |
+ The remaining elements are specific to the type of directive, as |
+ pulled apart by the regular expression. |
+ """ |
+ global CPP_Expression, Table |
+ contents = line_continuations.sub('', contents) |
+ cpp_tuples = CPP_Expression.findall(contents) |
+ return [(m[0],) + Table[m[0]].match(m[1]).groups() for m in cpp_tuples] |
+ |
+ def __call__(self, file): |
+ """ |
+ Pre-processes a file. |
+ |
+ This is the main public entry point. |
+ """ |
+ self.current_file = file |
+ return self.process_contents(self.read_file(file), file) |
+ |
+ def process_contents(self, contents, fname=None): |
+ """ |
+ Pre-processes a file contents. |
+ |
+ This is the main internal entry point. |
+ """ |
+ self.stack = [] |
+ self.dispatch_table = self.default_table.copy() |
+ self.current_file = fname |
+ self.tuples = self.tupleize(contents) |
+ |
+ self.initialize_result(fname) |
+ while self.tuples: |
+ t = self.tuples.pop(0) |
+ # Uncomment to see the list of tuples being processed (e.g., |
+ # to validate the CPP lines are being translated correctly). |
+ #print t |
+ self.dispatch_table[t[0]](t) |
+ return self.finalize_result(fname) |
+ |
+ # Dispatch table stack manipulation methods. |
+ |
+ def save(self): |
+ """ |
+ Pushes the current dispatch table on the stack and re-initializes |
+ the current dispatch table to the default. |
+ """ |
+ self.stack.append(self.dispatch_table) |
+ self.dispatch_table = self.default_table.copy() |
+ |
+ def restore(self): |
+ """ |
+ Pops the previous dispatch table off the stack and makes it the |
+ current one. |
+ """ |
+ try: self.dispatch_table = self.stack.pop() |
+ except IndexError: pass |
+ |
+ # Utility methods. |
+ |
+ def do_nothing(self, t): |
+ """ |
+ Null method for when we explicitly want the action for a |
+ specific preprocessor directive to do nothing. |
+ """ |
+ pass |
+ |
+ def scons_current_file(self, t): |
+ self.current_file = t[1] |
+ |
+ def eval_expression(self, t): |
+ """ |
+ Evaluates a C preprocessor expression. |
+ |
+ This is done by converting it to a Python equivalent and |
+ eval()ing it in the C preprocessor namespace we use to |
+ track #define values. |
+ """ |
+ t = CPP_to_Python(' '.join(t[1:])) |
+ try: return eval(t, self.cpp_namespace) |
+ except (NameError, TypeError): return 0 |
+ |
+ def initialize_result(self, fname): |
+ self.result = [fname] |
+ |
+ def finalize_result(self, fname): |
+ return self.result[1:] |
+ |
+ def find_include_file(self, t): |
+ """ |
+ Finds the #include file for a given preprocessor tuple. |
+ """ |
+ fname = t[2] |
+ for d in self.searchpath[t[1]]: |
+ if d == os.curdir: |
+ f = fname |
+ else: |
+ f = os.path.join(d, fname) |
+ if os.path.isfile(f): |
+ return f |
+ return None |
+ |
+ def read_file(self, file): |
+ return open(file).read() |
+ |
+ # Start and stop processing include lines. |
+ |
+ def start_handling_includes(self, t=None): |
+ """ |
+ Causes the PreProcessor object to start processing #import, |
+ #include and #include_next lines. |
+ |
+ This method will be called when a #if, #ifdef, #ifndef or #elif |
+ evaluates True, or when we reach the #else in a #if, #ifdef, |
+ #ifndef or #elif block where a condition already evaluated |
+ False. |
+ |
+ """ |
+ d = self.dispatch_table |
+ d['import'] = self.do_import |
+ d['include'] = self.do_include |
+ d['include_next'] = self.do_include |
+ |
+ def stop_handling_includes(self, t=None): |
+ """ |
+ Causes the PreProcessor object to stop processing #import, |
+ #include and #include_next lines. |
+ |
+ This method will be called when a #if, #ifdef, #ifndef or #elif |
+ evaluates False, or when we reach the #else in a #if, #ifdef, |
+ #ifndef or #elif block where a condition already evaluated True. |
+ """ |
+ d = self.dispatch_table |
+ d['import'] = self.do_nothing |
+ d['include'] = self.do_nothing |
+ d['include_next'] = self.do_nothing |
+ |
+ # Default methods for handling all of the preprocessor directives. |
+ # (Note that what actually gets called for a given directive at any |
+ # point in time is really controlled by the dispatch_table.) |
+ |
+ def _do_if_else_condition(self, condition): |
+ """ |
+ Common logic for evaluating the conditions on #if, #ifdef and |
+ #ifndef lines. |
+ """ |
+ self.save() |
+ d = self.dispatch_table |
+ if condition: |
+ self.start_handling_includes() |
+ d['elif'] = self.stop_handling_includes |
+ d['else'] = self.stop_handling_includes |
+ else: |
+ self.stop_handling_includes() |
+ d['elif'] = self.do_elif |
+ d['else'] = self.start_handling_includes |
+ |
+ def do_ifdef(self, t): |
+ """ |
+ Default handling of a #ifdef line. |
+ """ |
+ self._do_if_else_condition(t[1] in self.cpp_namespace) |
+ |
+ def do_ifndef(self, t): |
+ """ |
+ Default handling of a #ifndef line. |
+ """ |
+ self._do_if_else_condition(t[1] not in self.cpp_namespace) |
+ |
+ def do_if(self, t): |
+ """ |
+ Default handling of a #if line. |
+ """ |
+ self._do_if_else_condition(self.eval_expression(t)) |
+ |
+ def do_elif(self, t): |
+ """ |
+ Default handling of a #elif line. |
+ """ |
+ d = self.dispatch_table |
+ if self.eval_expression(t): |
+ self.start_handling_includes() |
+ d['elif'] = self.stop_handling_includes |
+ d['else'] = self.stop_handling_includes |
+ |
+ def do_else(self, t): |
+ """ |
+ Default handling of a #else line. |
+ """ |
+ pass |
+ |
+ def do_endif(self, t): |
+ """ |
+ Default handling of a #endif line. |
+ """ |
+ self.restore() |
+ |
+ def do_define(self, t): |
+ """ |
+ Default handling of a #define line. |
+ """ |
+ _, name, args, expansion = t |
+ try: |
+ expansion = int(expansion) |
+ except (TypeError, ValueError): |
+ pass |
+ if args: |
+ evaluator = FunctionEvaluator(name, args[1:-1], expansion) |
+ self.cpp_namespace[name] = evaluator |
+ else: |
+ self.cpp_namespace[name] = expansion |
+ |
+ def do_undef(self, t): |
+ """ |
+ Default handling of a #undef line. |
+ """ |
+ try: del self.cpp_namespace[t[1]] |
+ except KeyError: pass |
+ |
+ def do_import(self, t): |
+ """ |
+ Default handling of a #import line. |
+ """ |
+ # XXX finish this -- maybe borrow/share logic from do_include()...? |
+ pass |
+ |
+ def do_include(self, t): |
+ """ |
+ Default handling of a #include line. |
+ """ |
+ t = self.resolve_include(t) |
+ include_file = self.find_include_file(t) |
+ if include_file: |
+ #print "include_file =", include_file |
+ self.result.append(include_file) |
+ contents = self.read_file(include_file) |
+ new_tuples = [('scons_current_file', include_file)] + \ |
+ self.tupleize(contents) + \ |
+ [('scons_current_file', self.current_file)] |
+ self.tuples[:] = new_tuples + self.tuples |
+ |
+ # Date: Tue, 22 Nov 2005 20:26:09 -0500 |
+ # From: Stefan Seefeld <seefeld@sympatico.ca> |
+ # |
+ # By the way, #include_next is not the same as #include. The difference |
+ # being that #include_next starts its search in the path following the |
+ # path that let to the including file. In other words, if your system |
+ # include paths are ['/foo', '/bar'], and you are looking at a header |
+ # '/foo/baz.h', it might issue an '#include_next <baz.h>' which would |
+ # correctly resolve to '/bar/baz.h' (if that exists), but *not* see |
+ # '/foo/baz.h' again. See http://www.delorie.com/gnu/docs/gcc/cpp_11.html |
+ # for more reasoning. |
+ # |
+ # I have no idea in what context 'import' might be used. |
+ |
+ # XXX is #include_next really the same as #include ? |
+ do_include_next = do_include |
+ |
+ # Utility methods for handling resolution of include files. |
+ |
+ def resolve_include(self, t): |
+ """Resolve a tuple-ized #include line. |
+ |
+ This handles recursive expansion of values without "" or <> |
+ surrounding the name until an initial " or < is found, to handle |
+ #include FILE |
+ where FILE is a #define somewhere else. |
+ """ |
+ s = t[1] |
+ while not s[0] in '<"': |
+ #print "s =", s |
+ try: |
+ s = self.cpp_namespace[s] |
+ except KeyError: |
+ m = function_name.search(s) |
+ s = self.cpp_namespace[m.group(1)] |
+ if callable(s): |
+ args = function_arg_separator.split(m.group(2)) |
+ s = s(*args) |
+ if not s: |
+ return None |
+ return (t[0], s[0], s[1:-1]) |
+ |
+ def all_include(self, t): |
+ """ |
+ """ |
+ self.result.append(self.resolve_include(t)) |
+ |
+class DumbPreProcessor(PreProcessor): |
+ """A preprocessor that ignores all #if/#elif/#else/#endif directives |
+ and just reports back *all* of the #include files (like the classic |
+ SCons scanner did). |
+ |
+ This is functionally equivalent to using a regular expression to |
+ find all of the #include lines, only slower. It exists mainly as |
+ an example of how the main PreProcessor class can be sub-classed |
+ to tailor its behavior. |
+ """ |
+ def __init__(self, *args, **kw): |
+ PreProcessor.__init__(self, *args, **kw) |
+ d = self.default_table |
+ for func in ['if', 'elif', 'else', 'endif', 'ifdef', 'ifndef']: |
+ d[func] = d[func] = self.do_nothing |
+ |
+del __revision__ |
+ |
+# Local Variables: |
+# tab-width:4 |
+# indent-tabs-mode:nil |
+# End: |
+# vim: set expandtab tabstop=4 shiftwidth=4: |
Property changes on: scons-2.0.1/engine/SCons/cpp.py |
___________________________________________________________________ |
Added: svn:eol-style |
+ LF |