home *** CD-ROM | disk | FTP | other *** search
/ Hackers Magazine 57 / CdHackersMagazineNr57.iso / Software / Multimedia / k3d-setup-0.7.11.0.exe / lib / site-packages / cgkit / simplecpp.py < prev    next >
Encoding:
Python Source  |  2008-02-17  |  17.9 KB  |  529 lines

  1. # ***** BEGIN LICENSE BLOCK *****
  2. # Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3. #
  4. # The contents of this file are subject to the Mozilla Public License Version
  5. # 1.1 (the "License"); you may not use this file except in compliance with
  6. # the License. You may obtain a copy of the License at
  7. # http://www.mozilla.org/MPL/
  8. #
  9. # Software distributed under the License is distributed on an "AS IS" basis,
  10. # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11. # for the specific language governing rights and limitations under the
  12. # License.
  13. #
  14. # The Original Code is the Python Computer Graphics Kit.
  15. #
  16. # The Initial Developer of the Original Code is Matthias Baas.
  17. # Portions created by the Initial Developer are Copyright (C) 2004
  18. # the Initial Developer. All Rights Reserved.
  19. #
  20. # Contributor(s):
  21. #
  22. # Alternatively, the contents of this file may be used under the terms of
  23. # either the GNU General Public License Version 2 or later (the "GPL"), or
  24. # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  25. # in which case the provisions of the GPL or the LGPL are applicable instead
  26. # of those above. If you wish to allow use of your version of this file only
  27. # under the terms of either the GPL or the LGPL, and not to allow others to
  28. # use your version of this file under the terms of the MPL, indicate your
  29. # decision by deleting the provisions above and replace them with the notice
  30. # and other provisions required by the GPL or the LGPL. If you do not delete
  31. # the provisions above, a recipient may use your version of this file under
  32. # the terms of any one of the MPL, the GPL or the LGPL.
  33. #
  34. # ***** END LICENSE BLOCK *****
  35. # $Id: simplecpp.py,v 1.2 2006/01/27 22:11:09 mbaas Exp $
  36.  
  37. import sys, types, os.path
  38.  
  39. class Context:
  40.     """This class carries information about a particular source file.
  41.     
  42.     Each #include command produces a new context that gets pushed
  43.     onto the stack.
  44.     """
  45.     def __init__(self, filehandle):
  46.         # The file handle (file-like object)
  47.         self.filehandle = filehandle
  48.         # The current line number (i.e. the line number of the next line read)
  49.         self.linenr = 1
  50.         # The line number where the current (continued) line started.
  51.         # This is only different from self.linenr when lines were continued
  52.         # with a backslash at the end of a line. In this case start_linenr
  53.         # is the line number where the long line began and linenr is the
  54.         # last line number of the long line.
  55.         self.start_linenr = 1
  56.         # The file name
  57.         self.filename = getattr(filehandle, "name", "<?>")
  58.         
  59.         # This is basically a stack that contains the evaluated conditions
  60.         # of the #if, #ifdef, etc. commands
  61.         self.condition_list = []
  62.         # Flag that specifies whether a line should be ignored or not
  63.         # (because it is inside an #ifdef ... #endif block, for example).
  64.         # If this flag is False there is still be output generated, but
  65.         # it's just an empty string)
  66.         self.output_line = True
  67.         
  68.  
  69. # PreProcessor
  70. class PreProcessor:
  71.     """A simple 'preprocessor'.
  72.  
  73.     This class implements a subset of the functionality of the C preprocessor.
  74.     """
  75.  
  76.     def __init__(self, defines=None, includedirs=None):
  77.         """Constructor.
  78.         
  79.         defines is a list of tuples (name, value) that specify the initial set
  80.         of defined macros.
  81.         includedirs is a list of strings that is used to find include files.
  82.         """
  83.  
  84.         # The error stream
  85.         self.errstream = sys.stderr
  86.         
  87.         # Flag that specifies if the starting position of the next line is
  88.         # already inside a comment (i.e. if there was a '/*' without a '*/'
  89.         self.inside_comment = False
  90.         
  91.         # A 'stack' of Context objects
  92.         self.context_stack = []
  93.         # Current context (this is the topmost element from context_stack)
  94.         self.context = None
  95.         
  96.         # Contains the defined macros.
  97.         self.defined = {}
  98.         # This is a sorted list containing the macros (longest names first)
  99.         # The items are tuples (name, value).
  100.         self.substitution_list = []
  101.         
  102.         self.output_buffer = []
  103.  
  104.         # Set the predefined macros
  105.         if defines!=None:
  106.             for name,val in defines:
  107.                 if val==None:
  108.                     val = ""
  109.                 self.onDefine("define", "%s %s"%(name, str(val)))
  110.  
  111.         # Set the include directories
  112.         self.includedirs = []
  113.         if includedirs!=None:
  114.             self.includedirs.extend(includedirs)
  115.            
  116.         # This flag can be used to abort the preprocessor
  117.         self.abort_flag = False
  118.         
  119.     def __call__(self, source, errstream=None):
  120.         """Preprocess a file or a file-like object.
  121.  
  122.         source may either be the name of a file or a file-like object.
  123.         """
  124.         
  125.         self.errstream = errstream
  126.         
  127.         if isinstance(source, types.StringTypes):
  128.             fhandle = file(source, "rt")
  129.         else:
  130.             fhandle = source
  131.  
  132.         self.output_buffer = []
  133.         self.preprocess(fhandle)
  134.         return "\n".join(self.output_buffer)
  135.  
  136.     def preprocess(self, fhandle):
  137.         """Preprocess a source file.
  138.         """
  139.         
  140.         self.context_stack = []
  141.         self.context = None
  142.         ctx = Context(fhandle)
  143.         self.readFile(ctx)
  144.         
  145.     def readFile(self, ctx):
  146.         """Read another file.
  147.         
  148.         ctx is a initialized Context object.
  149.         """
  150.         
  151.         self.pushContext(ctx)
  152.         linebuffer = ""
  153.         defined = self.defined
  154.         
  155.         for line in ctx.filehandle:
  156.             if line[-1:]=='\n':
  157.                 line = line[:-1]
  158.             # Append the line to the line buffer...
  159.             if line[-1:]=='\\':
  160.                 linebuffer += line[:-1]
  161.                 # Read another line before processing the line
  162.                 self.context.linenr += 1
  163.                 continue
  164.             else:
  165.                 linebuffer += line
  166.                 
  167.             intervals = list(self.iterStringIntervals(linebuffer))
  168.             out = "".join(intervals)
  169.                  
  170.             stripped = out.strip()
  171.             # Preprocessor directive?
  172.             if stripped[:1]=="#":
  173.                 # Invoke the appropriate directive handler method...
  174.                 # (i.e. onInclude(), onIfdef(), ...) 
  175.                 s = stripped[1:].strip()
  176.                 a = s.split()
  177.                 directive = a[0].lower()
  178.                 handlername = "on%s"%directive.capitalize()
  179.                 arg = s[len(directive)+1:].strip()
  180.                 handler = getattr(self, handlername, None)
  181.                 if handler!=None:
  182.                     handler(directive, arg)
  183.             # no preprocessor directive but regular code...
  184.             else:
  185.                 out = ""
  186.                 for s in intervals:
  187.                     # Do macro substitutions if it's not a string
  188.                     if s[0:]!='"':
  189.                         for name,value in self.substitution_list:
  190.                             s = s.replace(name, value)
  191.                     out += s
  192.  
  193.                 if ctx.output_line:
  194.                     self.output(out)
  195.                 else:
  196.                     self.output("")
  197.                     
  198.             # Should preprocessing be aborted?
  199.             if self.abort_flag:
  200.                 break
  201.             
  202.             self.context.linenr += 1
  203.             self.context.start_linenr = self.context.linenr
  204.             linebuffer = ""
  205.             
  206.         self.popContext()
  207.  
  208.     def output(self, s):
  209.         """This method is called for every (extended) output line.
  210.         
  211.         Lines that were split using '\' at the end of the line are
  212.         reported as one single line.
  213.         """
  214.         self.output_buffer.append(s)
  215.             
  216.     def abort(self):
  217.         """Tell the preprocessor to abort operation.
  218.         
  219.         This method can be called by a derived class in the output()
  220.         handler method.
  221.         """
  222.         self.abort_flag = True
  223.         
  224.     # Directive handler methods:
  225.     # Each method takes the lowercase directive name as input and
  226.     # the argument string (which is everything following the directive)
  227.     # Example: #include <spam.h> -> directive: "include"  arg: "<spam.h>"
  228.             
  229.     def onInclude(self, directive, arg):
  230.         """Handle #include directives.
  231.         """
  232.         filename = arg[1:-1]
  233.         fullfilename = self.findFile(filename)
  234.         if fullfilename==None:
  235.             ctx = self.context
  236.             print >>self.errstream, "%s:%d: %s: No such file or directory"%(ctx.filename, ctx.linenr, filename)
  237.             return
  238.  
  239.         f = file(fullfilename, "rt")
  240.         ctx = Context(f)
  241.         self.readFile(ctx)
  242.         
  243.     def onDefine(self, directive, arg):
  244.         """Handle #define directives.
  245.         """
  246.         a = arg.split()
  247.         if len(a)==0:
  248.             ctx = self.context
  249.             print >>self.errstream, "%s:%d: Invalid macro definition"%(ctx.filename, ctx.linenr)
  250.             return
  251.         name = a[0]
  252.         value = " ".join(a[1:])
  253.         self.defined[name] = value
  254.         self.substitution_list.append((name, value))
  255.         self.substitution_list.sort(lambda a,b: cmp(len(b[0]), len(a[0])))
  256.  
  257.     def onUndef(self, directive, arg):
  258.         """Handle #undef directives.
  259.         """
  260.         if arg=="":
  261.             ctx = self.context
  262.             print >>self.errstream, "%s:%d: Invalid macro name"%(ctx.filename, ctx.linenr)
  263.             return
  264.         if arg not in self.defined:
  265.             return
  266.         
  267.         del self.defined[arg]
  268.         # Remove the macro from the substitution list
  269.         lst = []
  270.         for name,value in self.substitution_list:
  271.             if name!=arg:
  272.                 lst.append((name,value))
  273.         self.substitution_list = lst
  274.         
  275.     def onIf(self, directive, arg):
  276.         """Handle #if directives.
  277.         """
  278.         try:
  279.             cond = bool(int(arg))
  280.         except:
  281.             cond = False
  282.         self.context.condition_list.append(cond)
  283.         self.context.output_line = cond
  284.         
  285.     def onIfdef(self, directive, arg):
  286.         """Handle #ifdef directives.
  287.         """
  288.         a = arg.split()
  289.         if len(a)==0:
  290.             ctx = self.context
  291.             print >>self.errstream, "%s:%d: '#ifdef' with no argument"%(ctx.filename, ctx.linenr)
  292.             return
  293.  
  294.         name = a[0]
  295.         cond = self.defined.has_key(name)
  296.         self.context.condition_list.append(cond)
  297.         self.context.output_line = cond
  298.  
  299.     def onIfndef(self, directive, arg):
  300.         """Handle #ifndef directives.
  301.         """
  302.         a = arg.split()
  303.         if len(a)==0:
  304.             ctx = self.context
  305.             print >>self.errstream, "%s:%d: '#ifndef' with no argument"%(ctx.filename, ctx.linenr)
  306.             return
  307.         
  308.         name = a[0]
  309.         cond = not self.defined.has_key(name)
  310.         self.context.condition_list.append(cond)
  311.         self.context.output_line = cond
  312.  
  313.     def onElif(self, directive, arg):
  314.         """Handle #elif directives.
  315.         """
  316.         try:
  317.             cond = bool(int(arg))
  318.         except:
  319.             cond = False
  320.         self.context.output_line = cond
  321.         
  322.     def onElse(self, directive, arg):
  323.         """Handle #else directives.
  324.         """
  325.         self.context.output_line = not self.context.output_line
  326.                 
  327.     def onEndif(self, directive, arg):
  328.         """Handle #endif directives.
  329.         """
  330.         if len(self.context.condition_list)==0:
  331.             print >>self.errstream, "%s:%d: unbalanced '#endif'"%(self.context.filename, self.context.linenr)
  332.             return
  333.         
  334.         self.context.condition_list.pop()
  335.         self.context.output_line = True
  336.         
  337.     def onPragma(self, directive, arg):
  338.         """Handle #pragma directives.
  339.         """
  340.         pass
  341.  
  342.     def pushContext(self, ctx):
  343.         """Push a context object onto the context stack.
  344.         """
  345.         self.context_stack.append(ctx)
  346.         self.context = ctx
  347.         self.output('# %d "%s"'%(ctx.linenr, ctx.filename))
  348.         
  349.     def popContext(self):
  350.         """Pop the topmost context object from the context stack.
  351.         """
  352.         ctx = self.context_stack.pop()
  353.         if len(self.context_stack)==0:
  354.             self.context = None
  355.         else:
  356.             self.context = self.context_stack[-1]
  357.             self.output('# %d "%s"'%(self.context.linenr, self.context.filename))
  358.             self.output("")
  359.         return ctx
  360.  
  361.     def findFile(self, filename):
  362.         """Search for an include file and return its name.
  363.  
  364.         The returned file name is guaranteed to refer to an existing file.
  365.         If the file is not found None is returned.
  366.         """
  367.         
  368.         if os.path.exists(filename):
  369.             return filename
  370.  
  371.         for incdir in self.includedirs:
  372.             name = os.path.join(incdir, filename)
  373.             if os.path.exists(name):
  374.                 return name
  375.  
  376.         return None
  377.         
  378.     def iterStringIntervals(self, s):
  379.         """Iterate over the separate 'intervals' in the given string.
  380.         
  381.         This iterator splits the string into several intervals and yields
  382.         each interval. The intervals can be one of the following:
  383.         
  384.         - Content: Anything outside a comment and outside a string
  385.         - String: Always begins with '"'
  386.         - Comment: Is replaced by blanks (or not yielded at all)
  387.         
  388.         Example string: 
  389.         
  390.           'result = myfunc(5, "text" /* comment */, 6) // Comment'
  391.           
  392.         Intervals:
  393.         
  394.         - 'result = myfunc(5, '
  395.         - '"text"'
  396.         - ' '
  397.         - '             '
  398.         - ', 6) '
  399.         """
  400.  
  401.         slength = len(s)
  402.  
  403.         start = 0
  404.         while start<slength:
  405.         
  406.             if self.inside_comment:
  407.                 # Search for the end of the comment
  408.                 n = s.find('*/', start)
  409.                 if n==-1:
  410.                     start = slength
  411.                 else:
  412.                     self.inside_comment = False
  413.                     start = n+2
  414.             else:
  415.                 n1 = s.find('//', start)
  416.                 n2 = s.find('/*', start)
  417.                 n3 = s.find('"', start)
  418.  
  419.                 # Determine which of the above strings is encountered first...
  420.                 if n1==-1:
  421.                     if n2==-1:
  422.                         if n3==-1:
  423.                             # n1==-1 and n2==-1 and n3==-1
  424.                             first = 0
  425.                         else:
  426.                             # n1==-1 and n2==-1 and n3!=-1
  427.                             first = 3
  428.                     else:
  429.                         if n3==-1:
  430.                             # n1==-1 and n2!=-1 and n3==-1
  431.                             first = 2
  432.                         else:
  433.                             # n1==-1 and n2!=-1 and n3!=-1
  434.                             if n2<n3:
  435.                                 first = 2
  436.                             else:
  437.                                 first = 3
  438.                 else:
  439.                     if n2==-1:
  440.                         if n3==-1:
  441.                             # n1!=-1 and n2==-1 and n3==-1
  442.                             first = 1
  443.                         else:
  444.                             # n1!=-1 and n2==-1 and n3!=-1
  445.                             if n1<n3:
  446.                                 first = 1
  447.                             else:
  448.                                 first = 3
  449.                     else:
  450.                         if n3==-1:
  451.                             # n1!=-1 and n2!=-1 and n3==-1
  452.                             if n1<n2:
  453.                                 first = 1
  454.                             else:
  455.                                 first = 2
  456.                         else:
  457.                             # n1!=-1 and n2!=-1 and n3!=-1
  458.                             if n1<n2 and n1<n3:
  459.                                 first = 1
  460.                             else:
  461.                                 if n2<n3:
  462.                                     first = 2
  463.                                 else:
  464.                                     first = 3
  465.  
  466.                 # Neither a comment, nor quotes
  467.                 if first==0:
  468.                     # The entire remaining string is content
  469.                     yield s[start:]
  470.                     start = slength
  471.                 # '//' comment
  472.                 elif first==1:
  473.                     if n1>start:
  474.                         yield s[start:n1]
  475.                     start = slength
  476.                 # '/*' comment
  477.                 elif first==2:
  478.                     if n2>start:
  479.                         yield s[start:n2]
  480.                     # Search for the closing comment
  481.                     n = s.find('*/', n2+2)
  482.                     if n==-1:
  483.                         self.inside_comment = True
  484.                         start = slength
  485.                     else:
  486.                         yield (n-n2+2)*" "
  487.                         start = n+2
  488.                 # '"'
  489.                 else:
  490.                     if n3>start:
  491.                         yield s[start:n3]
  492.                     # Search for the closing apostrophes and yield the string
  493.                     n = s.find('"', n3+1)
  494.                     if n==-1:
  495.                         # No closing apostrophes in the same line? This is
  496.                         # actually an error
  497.                         yield s[n3:]
  498.                         start = slength
  499.                     else:
  500.                         n += 1
  501.                         yield s[n3:n]
  502.                         start = n
  503.                 
  504.                         
  505.                     
  506.                     
  507.     
  508.  
  509. ######################################################################
  510.  
  511. if __name__=="__main__":
  512.     import optparse
  513.     
  514.     op = optparse.OptionParser()
  515.     op.add_option("-D", "--define", action="append", default=[],
  516.                  help="Define a symbol")    
  517.     op.add_option("-I", "--includedir", action="append", default=[],
  518.                  help="Specify include paths")    
  519.     
  520.     opts, args = op.parse_args()
  521.     p = PreProcessor(includedirs=opts.includedir, defines=map(lambda x: (x,None), opts.define))
  522.     if len(args)==0:
  523.         print p(sys.stdin)
  524.     else:
  525.         try:
  526.             print p(file(args[0]))
  527.         except IOError, e:
  528.             print e
  529.