File indexing completed on 2024-12-08 07:21:24

0001 # -*- coding: utf-8 -*-
0002 #     Copyright 2007-8 Jim Bublitz <jbublitz@nwinternet.com>
0003 #
0004 # This program is free software; you can redistribute it and/or modify
0005 # it under the terms of the GNU General Public License as published by
0006 # the Free Software Foundation; either version 2 of the License, or
0007 # (at your option) any later version.
0008 #
0009 # This program is distributed in the hope that it will be useful,
0010 # but WITHOUT ANY WARRANTY; without even the implied warranty of
0011 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0012 # GNU General Public License for more details.
0013 #
0014 # You should have received a copy of the GNU General Public License
0015 # along with this program; if not, write to the
0016 # Free Software Foundation, Inc.,
0017 # 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
0018 
0019 import re
0020 import ply.lex as lex
0021 # handles the evaluation of conditionals
0022 from .exprparser import ExpressionParser
0023 
0024 newtext   = []
0025 macros    = []
0026 bitBucket = False
0027 sentinel  = False
0028 
0029 preprocessor_tokens =  ['cond', 'else', 'endif', 'include', 'define', 'undef', 'line', 'error', 'pragma', 'warning']
0030 tokens = preprocessor_tokens + ['anyline']
0031 
0032 values = {}
0033 evaluate = ExpressionParser ().parse
0034 
0035 # Completely ignored characters
0036 t_ANY_ignore           = ' \t\x0c'
0037 
0038 def stripComment (s):
0039     pos1 = s.find ('\/\*')
0040     pos2 = s.find ('\/\/')
0041     if pos1 > 0 and pos2 > 0:
0042         pos = min (pos1, pos2)
0043     elif pos1 < 0 and pos2 < 0:
0044         pos = -1
0045     else:
0046         pos = max (pos1, pos2)
0047     
0048     if pos > 0:
0049         return s [:pos].strip (), s[pos:].strip ()
0050     else:
0051         return s, ''
0052 
0053 def t_cond (t):
0054     r'\#\s*(?P<ifType>ifdef\s|ifndef\s|if\s|elif\s)\s*(?P<cond>.*?)\n'
0055 
0056     # All conditionals that perform a test are handled here
0057     global newtext
0058     ifType = t.lexer.lexmatch.group ('ifType').strip ()
0059     condition, comment  = stripComment (t.lexer.lexmatch.group ('cond'))
0060     
0061     # #if/#elif look for True/False, others for defintion only
0062     # #if defined - 'defined' is handled as an operator by the
0063     # expression parser which evaluates the conditional
0064     if ifType in ['if', 'elif']:
0065         mode = 'calc'
0066     else:
0067         mode = 'def'
0068 
0069     ifCondition = evaluate (condition, mode, values)
0070         
0071     global bitBucket, sentinel
0072     bitBucket = ((not ifCondition) and (ifType != 'ifndef')) or (ifCondition and (ifType == 'ifndef'))
0073     
0074     # remove #define <sentinel>?
0075     sentinel = not bitBucket and ('_h' in condition or '_H' in condition)
0076 
0077     # A multiline comment could begin on a preprocessor line
0078     # that's being eliminated here
0079     if bitBucket and comment:
0080         newtext.append (comment + '\n')
0081     else:
0082         newtext.append ('\n')
0083 
0084     t.lexer.lineno += 1
0085     
0086 def t_else (t):
0087     r'\#\s*else(.*?)\n'  # comments?
0088     global bitBucket, newtext
0089     bitBucket = not bitBucket
0090     t.lexer.lineno += 1
0091     newtext.append ('\n')
0092     
0093 def t_endif (t):
0094     r'\#\s*endif(.*?)\n'
0095     global bitBucket, newtext
0096     bitBucket = False
0097     t.lexer.lineno += 1
0098     newtext.append ('\n')
0099 
0100 def t_include (t):
0101     r'\#\s*include.*?\n'
0102     global newtext
0103     t.lexer.lineno += 1
0104     newtext.append ('\n')
0105     
0106 def t_line (t):
0107     r'\#\s*line.*?\n'
0108     global newtext
0109     t.lexer.lineno += 1
0110     newtext.append ('\n')
0111 
0112 def t_error (t):    
0113     r'\#\s*error.*?\n'
0114     global newtext
0115     t.lexer.lineno += 1
0116     newtext.append ('\n')
0117     
0118 def t_pragma (t):    
0119     r'\#\s*pragma.*?\n'
0120     global newtext
0121     t.lexer.lineno += 1
0122     newtext.append ('\n')
0123     
0124 def t_warning (t):    
0125     r'\#\s*warning.*?\n'
0126     global newtext
0127     t.lexer.lineno += 1
0128     newtext.append ('\n')
0129 
0130 def t_undef (t):
0131     r'\#\s*undef\s*(?P<item>.*?)\n'
0132     global macros, values, newtext
0133     item = t.lexer.lexmatch.group ('item').strip ()
0134     if item in values:
0135         macros = [macro for macro in macros if len(macro)==2 or macro[2] != item]
0136         del values [item]
0137     t.lexer.lineno += 1
0138     newtext.append ('\n')
0139     
0140 def t_define (t):
0141     r'\#\s*define\s*(?P<first>[\S]+)\s*?(?P<second>[^\n]*?)\n'    
0142     global sentinel, values, macros, newtext
0143     a = t.lexer.lexmatch.group ('first')
0144     b = t.lexer.lexmatch.group ('second')
0145     
0146     # Append any continuation lines
0147     newlines = 1
0148     start = t.lexer.lexpos
0149     if b and b.endswith ('\\'):              
0150         data = t.lexer.lexdata
0151         for i in range (start, len (data)):
0152             if data [i] == '\n':
0153                 t.lexer.lineno += 1
0154                 newlines += 1
0155             if data [i] == '\n' and data [i - 1] != '\\':                
0156                 break
0157         t.lexer.lexpos = i + 1
0158         b += data [start:t.lexer.lexpos].replace ('\\\n', ' ')
0159 
0160     if '(' in a and not ')' in a:
0161         pos = b.find (')')
0162         if pos < 0:
0163             return
0164         a += b [:pos + 1]
0165         b = b [pos + 1:]
0166     
0167     # remove #define <sentinel>
0168     sentinel = sentinel and not b and ('_h' in a or '_H' in a)
0169     if not sentinel:
0170         if not b or '(' in a:
0171             values [a] = ''
0172             macros.insert (0, (re.compile (a), '', a))
0173         else:
0174             values [a] = b
0175             macros.insert (0, (re.compile (a), b.strip (), a))
0176     
0177     sentinel = False
0178             
0179     newtext.append (newlines *'\n')
0180     t.lexer.lineno += 1
0181     
0182 def t_anyline (t):
0183     r'[^\n]*?\n(([^#\n][^\n]*\n)|\n)*'
0184     """
0185     Process anything that's not a preprocesor directive.
0186 
0187     Apply all #define macros to each line. Code that has
0188     been #if'd out (bitBucket == True) is replaced by
0189     a single newline for each line removed.
0190     """
0191     global sentinel, newtext
0192     sentinel = False
0193     if not bitBucket:
0194         line = t.value
0195         for m in macros:
0196             line = m[0].sub(m[1], line)
0197         newtext.append (line)
0198         t.lexer.lineno += line.count('\n')
0199     else:
0200         c = t.value.count('\n')
0201         for x in range(c):
0202             newtext.append('\n')
0203         t.lexer.lineno += c
0204 
0205 # this needs to be HERE - not above token definitions
0206 ppLexer = lex.lex (debug=0)
0207 
0208     
0209 def preprocess (text, global_values={}, global_macros=[]):
0210     """
0211     Preprocess a C/C++ header file text
0212     
0213     Preprocesses h files - does #define substitutions and
0214     evaluates conditionals to include/exclude code. No
0215     substitutions are performed on preprocessor lines (any
0216     line beginning with '#'). Global #defines are applied
0217     LAST, so they override any local #defines.
0218 
0219     All C preprocessor code is stripped, and along with any
0220     lines eliminated conditionally, is replaced with newlines
0221     so that error messages still refer to the correct line in
0222     the original file.
0223     
0224     Arguments:
0225     text -- The text to process.
0226     global_values -- Dict mapping string variable names to values.
0227     global_macros -- List of tuples. The first value in a tuple is a
0228                      regular expression object. The second is that
0229                      replacement string which may contain re module
0230                      back references.
0231     
0232     Returns the processed string.
0233     """
0234     global newtext, bitBucket, macros, values
0235     newtext   = []
0236     bitBucket = False
0237     macros    = [] + global_macros
0238     values    = {}
0239         
0240     values.update (global_values)
0241     if text[-1]!='\n':
0242         text = text + '\n'
0243     ppLexer.input (text)
0244     token = ppLexer.token()
0245     #print(newtext)
0246     #return "".join (fixDoc (newtext))
0247     return "".join(newtext)
0248 
0249 def fixDoc (textList):
0250     doReplace = False
0251     doBackReplace = False
0252     nLines    = len (textList)
0253     for i in range (nLines):
0254         if i >= nLines - 1:
0255             break
0256             
0257         if textList [i].startswith ('/////'):
0258             textList [i] = '\n'
0259             continue
0260             
0261         haveBackCmt = textList [i].find ('///<') >= 0
0262         haveCmt = textList [i].find ('///') >= 0 and not haveBackCmt
0263         if haveBackCmt:
0264             if not doBackReplace:
0265                 doBackReplace = textList [i + 1].strip ().startswith ('///<')
0266                 if doBackReplace:
0267                     textList [i] = textList [i].replace ('///<', '/**<')
0268             else:
0269                 textList [i] = textList [i].replace ('///<', '*')            
0270         elif doBackReplace:
0271             textList.insert (i, '*/\n')
0272             doBackReplace = False
0273             
0274         if not haveBackCmt and haveCmt:
0275             if not doReplace:
0276                 doReplace = textList [i + 1].strip ().startswith ('///')
0277                 if doReplace:
0278                     textList [i] = textList [i].replace ('///', '/**')
0279             else:
0280                 textList [i] = textList [i].replace ('///', '*')
0281         elif doReplace:
0282             textList.insert (i, '*/\n')
0283             doReplace = False
0284             
0285     return textList
0286     
0287 if __name__ == '__main__':
0288     text = """#define foo bar"""