home *** CD-ROM | disk | FTP | other *** search
- # ***** BEGIN LICENSE BLOCK *****
- # Version: MPL 1.1/GPL 2.0/LGPL 2.1
- #
- # The contents of this file are subject to the Mozilla Public License Version
- # 1.1 (the "License"); you may not use this file except in compliance with
- # the License. You may obtain a copy of the License at
- # http://www.mozilla.org/MPL/
- #
- # Software distributed under the License is distributed on an "AS IS" basis,
- # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- # for the specific language governing rights and limitations under the
- # License.
- #
- # The Original Code is the Python Computer Graphics Kit.
- #
- # The Initial Developer of the Original Code is Matthias Baas.
- # Portions created by the Initial Developer are Copyright (C) 2004
- # the Initial Developer. All Rights Reserved.
- #
- # Contributor(s):
- #
- # Alternatively, the contents of this file may be used under the terms of
- # either the GNU General Public License Version 2 or later (the "GPL"), or
- # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- # in which case the provisions of the GPL or the LGPL are applicable instead
- # of those above. If you wish to allow use of your version of this file only
- # under the terms of either the GPL or the LGPL, and not to allow others to
- # use your version of this file under the terms of the MPL, indicate your
- # decision by deleting the provisions above and replace them with the notice
- # and other provisions required by the GPL or the LGPL. If you do not delete
- # the provisions above, a recipient may use your version of this file under
- # the terms of any one of the MPL, the GPL or the LGPL.
- #
- # ***** END LICENSE BLOCK *****
- # $Id: simplecpp.py,v 1.2 2006/01/27 22:11:09 mbaas Exp $
-
- import sys, types, os.path
-
- class Context:
- """This class carries information about a particular source file.
-
- Each #include command produces a new context that gets pushed
- onto the stack.
- """
- def __init__(self, filehandle):
- # The file handle (file-like object)
- self.filehandle = filehandle
- # The current line number (i.e. the line number of the next line read)
- self.linenr = 1
- # The line number where the current (continued) line started.
- # This is only different from self.linenr when lines were continued
- # with a backslash at the end of a line. In this case start_linenr
- # is the line number where the long line began and linenr is the
- # last line number of the long line.
- self.start_linenr = 1
- # The file name
- self.filename = getattr(filehandle, "name", "<?>")
-
- # This is basically a stack that contains the evaluated conditions
- # of the #if, #ifdef, etc. commands
- self.condition_list = []
- # Flag that specifies whether a line should be ignored or not
- # (because it is inside an #ifdef ... #endif block, for example).
- # If this flag is False there is still be output generated, but
- # it's just an empty string)
- self.output_line = True
-
-
- # PreProcessor
- class PreProcessor:
- """A simple 'preprocessor'.
-
- This class implements a subset of the functionality of the C preprocessor.
- """
-
- def __init__(self, defines=None, includedirs=None):
- """Constructor.
-
- defines is a list of tuples (name, value) that specify the initial set
- of defined macros.
- includedirs is a list of strings that is used to find include files.
- """
-
- # The error stream
- self.errstream = sys.stderr
-
- # Flag that specifies if the starting position of the next line is
- # already inside a comment (i.e. if there was a '/*' without a '*/'
- self.inside_comment = False
-
- # A 'stack' of Context objects
- self.context_stack = []
- # Current context (this is the topmost element from context_stack)
- self.context = None
-
- # Contains the defined macros.
- self.defined = {}
- # This is a sorted list containing the macros (longest names first)
- # The items are tuples (name, value).
- self.substitution_list = []
-
- self.output_buffer = []
-
- # Set the predefined macros
- if defines!=None:
- for name,val in defines:
- if val==None:
- val = ""
- self.onDefine("define", "%s %s"%(name, str(val)))
-
- # Set the include directories
- self.includedirs = []
- if includedirs!=None:
- self.includedirs.extend(includedirs)
-
- # This flag can be used to abort the preprocessor
- self.abort_flag = False
-
- def __call__(self, source, errstream=None):
- """Preprocess a file or a file-like object.
-
- source may either be the name of a file or a file-like object.
- """
-
- self.errstream = errstream
-
- if isinstance(source, types.StringTypes):
- fhandle = file(source, "rt")
- else:
- fhandle = source
-
- self.output_buffer = []
- self.preprocess(fhandle)
- return "\n".join(self.output_buffer)
-
- def preprocess(self, fhandle):
- """Preprocess a source file.
- """
-
- self.context_stack = []
- self.context = None
- ctx = Context(fhandle)
- self.readFile(ctx)
-
- def readFile(self, ctx):
- """Read another file.
-
- ctx is a initialized Context object.
- """
-
- self.pushContext(ctx)
- linebuffer = ""
- defined = self.defined
-
- for line in ctx.filehandle:
- if line[-1:]=='\n':
- line = line[:-1]
- # Append the line to the line buffer...
- if line[-1:]=='\\':
- linebuffer += line[:-1]
- # Read another line before processing the line
- self.context.linenr += 1
- continue
- else:
- linebuffer += line
-
- intervals = list(self.iterStringIntervals(linebuffer))
- out = "".join(intervals)
-
- stripped = out.strip()
- # Preprocessor directive?
- if stripped[:1]=="#":
- # Invoke the appropriate directive handler method...
- # (i.e. onInclude(), onIfdef(), ...)
- s = stripped[1:].strip()
- a = s.split()
- directive = a[0].lower()
- handlername = "on%s"%directive.capitalize()
- arg = s[len(directive)+1:].strip()
- handler = getattr(self, handlername, None)
- if handler!=None:
- handler(directive, arg)
- # no preprocessor directive but regular code...
- else:
- out = ""
- for s in intervals:
- # Do macro substitutions if it's not a string
- if s[0:]!='"':
- for name,value in self.substitution_list:
- s = s.replace(name, value)
- out += s
-
- if ctx.output_line:
- self.output(out)
- else:
- self.output("")
-
- # Should preprocessing be aborted?
- if self.abort_flag:
- break
-
- self.context.linenr += 1
- self.context.start_linenr = self.context.linenr
- linebuffer = ""
-
- self.popContext()
-
- def output(self, s):
- """This method is called for every (extended) output line.
-
- Lines that were split using '\' at the end of the line are
- reported as one single line.
- """
- self.output_buffer.append(s)
-
- def abort(self):
- """Tell the preprocessor to abort operation.
-
- This method can be called by a derived class in the output()
- handler method.
- """
- self.abort_flag = True
-
- # Directive handler methods:
- # Each method takes the lowercase directive name as input and
- # the argument string (which is everything following the directive)
- # Example: #include <spam.h> -> directive: "include" arg: "<spam.h>"
-
- def onInclude(self, directive, arg):
- """Handle #include directives.
- """
- filename = arg[1:-1]
- fullfilename = self.findFile(filename)
- if fullfilename==None:
- ctx = self.context
- print >>self.errstream, "%s:%d: %s: No such file or directory"%(ctx.filename, ctx.linenr, filename)
- return
-
- f = file(fullfilename, "rt")
- ctx = Context(f)
- self.readFile(ctx)
-
- def onDefine(self, directive, arg):
- """Handle #define directives.
- """
- a = arg.split()
- if len(a)==0:
- ctx = self.context
- print >>self.errstream, "%s:%d: Invalid macro definition"%(ctx.filename, ctx.linenr)
- return
- name = a[0]
- value = " ".join(a[1:])
- self.defined[name] = value
- self.substitution_list.append((name, value))
- self.substitution_list.sort(lambda a,b: cmp(len(b[0]), len(a[0])))
-
- def onUndef(self, directive, arg):
- """Handle #undef directives.
- """
- if arg=="":
- ctx = self.context
- print >>self.errstream, "%s:%d: Invalid macro name"%(ctx.filename, ctx.linenr)
- return
- if arg not in self.defined:
- return
-
- del self.defined[arg]
- # Remove the macro from the substitution list
- lst = []
- for name,value in self.substitution_list:
- if name!=arg:
- lst.append((name,value))
- self.substitution_list = lst
-
- def onIf(self, directive, arg):
- """Handle #if directives.
- """
- try:
- cond = bool(int(arg))
- except:
- cond = False
- self.context.condition_list.append(cond)
- self.context.output_line = cond
-
- def onIfdef(self, directive, arg):
- """Handle #ifdef directives.
- """
- a = arg.split()
- if len(a)==0:
- ctx = self.context
- print >>self.errstream, "%s:%d: '#ifdef' with no argument"%(ctx.filename, ctx.linenr)
- return
-
- name = a[0]
- cond = self.defined.has_key(name)
- self.context.condition_list.append(cond)
- self.context.output_line = cond
-
- def onIfndef(self, directive, arg):
- """Handle #ifndef directives.
- """
- a = arg.split()
- if len(a)==0:
- ctx = self.context
- print >>self.errstream, "%s:%d: '#ifndef' with no argument"%(ctx.filename, ctx.linenr)
- return
-
- name = a[0]
- cond = not self.defined.has_key(name)
- self.context.condition_list.append(cond)
- self.context.output_line = cond
-
- def onElif(self, directive, arg):
- """Handle #elif directives.
- """
- try:
- cond = bool(int(arg))
- except:
- cond = False
- self.context.output_line = cond
-
- def onElse(self, directive, arg):
- """Handle #else directives.
- """
- self.context.output_line = not self.context.output_line
-
- def onEndif(self, directive, arg):
- """Handle #endif directives.
- """
- if len(self.context.condition_list)==0:
- print >>self.errstream, "%s:%d: unbalanced '#endif'"%(self.context.filename, self.context.linenr)
- return
-
- self.context.condition_list.pop()
- self.context.output_line = True
-
- def onPragma(self, directive, arg):
- """Handle #pragma directives.
- """
- pass
-
- def pushContext(self, ctx):
- """Push a context object onto the context stack.
- """
- self.context_stack.append(ctx)
- self.context = ctx
- self.output('# %d "%s"'%(ctx.linenr, ctx.filename))
-
- def popContext(self):
- """Pop the topmost context object from the context stack.
- """
- ctx = self.context_stack.pop()
- if len(self.context_stack)==0:
- self.context = None
- else:
- self.context = self.context_stack[-1]
- self.output('# %d "%s"'%(self.context.linenr, self.context.filename))
- self.output("")
- return ctx
-
- def findFile(self, filename):
- """Search for an include file and return its name.
-
- The returned file name is guaranteed to refer to an existing file.
- If the file is not found None is returned.
- """
-
- if os.path.exists(filename):
- return filename
-
- for incdir in self.includedirs:
- name = os.path.join(incdir, filename)
- if os.path.exists(name):
- return name
-
- return None
-
- def iterStringIntervals(self, s):
- """Iterate over the separate 'intervals' in the given string.
-
- This iterator splits the string into several intervals and yields
- each interval. The intervals can be one of the following:
-
- - Content: Anything outside a comment and outside a string
- - String: Always begins with '"'
- - Comment: Is replaced by blanks (or not yielded at all)
-
- Example string:
-
- 'result = myfunc(5, "text" /* comment */, 6) // Comment'
-
- Intervals:
-
- - 'result = myfunc(5, '
- - '"text"'
- - ' '
- - ' '
- - ', 6) '
- """
-
- slength = len(s)
-
- start = 0
- while start<slength:
-
- if self.inside_comment:
- # Search for the end of the comment
- n = s.find('*/', start)
- if n==-1:
- start = slength
- else:
- self.inside_comment = False
- start = n+2
- else:
- n1 = s.find('//', start)
- n2 = s.find('/*', start)
- n3 = s.find('"', start)
-
- # Determine which of the above strings is encountered first...
- if n1==-1:
- if n2==-1:
- if n3==-1:
- # n1==-1 and n2==-1 and n3==-1
- first = 0
- else:
- # n1==-1 and n2==-1 and n3!=-1
- first = 3
- else:
- if n3==-1:
- # n1==-1 and n2!=-1 and n3==-1
- first = 2
- else:
- # n1==-1 and n2!=-1 and n3!=-1
- if n2<n3:
- first = 2
- else:
- first = 3
- else:
- if n2==-1:
- if n3==-1:
- # n1!=-1 and n2==-1 and n3==-1
- first = 1
- else:
- # n1!=-1 and n2==-1 and n3!=-1
- if n1<n3:
- first = 1
- else:
- first = 3
- else:
- if n3==-1:
- # n1!=-1 and n2!=-1 and n3==-1
- if n1<n2:
- first = 1
- else:
- first = 2
- else:
- # n1!=-1 and n2!=-1 and n3!=-1
- if n1<n2 and n1<n3:
- first = 1
- else:
- if n2<n3:
- first = 2
- else:
- first = 3
-
- # Neither a comment, nor quotes
- if first==0:
- # The entire remaining string is content
- yield s[start:]
- start = slength
- # '//' comment
- elif first==1:
- if n1>start:
- yield s[start:n1]
- start = slength
- # '/*' comment
- elif first==2:
- if n2>start:
- yield s[start:n2]
- # Search for the closing comment
- n = s.find('*/', n2+2)
- if n==-1:
- self.inside_comment = True
- start = slength
- else:
- yield (n-n2+2)*" "
- start = n+2
- # '"'
- else:
- if n3>start:
- yield s[start:n3]
- # Search for the closing apostrophes and yield the string
- n = s.find('"', n3+1)
- if n==-1:
- # No closing apostrophes in the same line? This is
- # actually an error
- yield s[n3:]
- start = slength
- else:
- n += 1
- yield s[n3:n]
- start = n
-
-
-
-
-
-
- ######################################################################
-
- if __name__=="__main__":
- import optparse
-
- op = optparse.OptionParser()
- op.add_option("-D", "--define", action="append", default=[],
- help="Define a symbol")
- op.add_option("-I", "--includedir", action="append", default=[],
- help="Specify include paths")
-
- opts, args = op.parse_args()
- p = PreProcessor(includedirs=opts.includedir, defines=map(lambda x: (x,None), opts.define))
- if len(args)==0:
- print p(sys.stdin)
- else:
- try:
- print p(file(args[0]))
- except IOError, e:
- print e
-