#!/usr/bin/python # # Author: Jashua R. Cloutier (contact via https://bitbucket.org/senex) # Project: http://senexcanis.com/open-source/cppheaderparser/ # # Copyright (C) 2011, Jashua R. Cloutier # 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 Jashua R. Cloutier nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. Stories, # blog entries etc making reference to this project may mention the # name Jashua R. Cloutier in terms of project originator/creator etc. # # 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. # # # The CppHeaderParser.py script is written in Python 2.4 and released to # the open source community for continuous improvements under the BSD # 2.0 new license, which can be found at: # # http://www.opensource.org/licenses/bsd-license.php # """Parse C++ header files and generate a data structure representing the class """ import ply.lex as lex import os import sys import re import io import inspect def lineno(): """Returns the current line number in our program.""" return inspect.currentframe().f_back.f_lineno version = __version__ = "2.7.5" tokens = [ 'NUMBER', 'FLOAT_NUMBER', 'TEMPLATE_NAME', 'NAME', 'OPEN_PAREN', 'CLOSE_PAREN', 'OPEN_BRACE', 'CLOSE_BRACE', 'OPEN_SQUARE_BRACKET', 'CLOSE_SQUARE_BRACKET', 'COLON', 'SEMI_COLON', 'COMMA', 'TAB', 'BACKSLASH', 'PIPE', 'PERCENT', 'EXCLAMATION', 'CARET', 'COMMENT_SINGLELINE', 'COMMENT_MULTILINE', 'PRECOMP_MACRO', 'PRECOMP_MACRO_CONT', 'ASTERISK', 'AMPERSTAND', 'EQUALS', 'MINUS', 'PLUS', 'DIVIDE', 'CHAR_LITERAL', 'STRING_LITERAL', 'NEW_LINE', 'SQUOTE', 'ELLIPSIS', 'DOT', ] t_ignore = " \r?@\f" t_NUMBER = r'[0-9][0-9XxA-Fa-f]*' t_FLOAT_NUMBER = r'[-+]?[0-9]*\.[0-9]+([eE][-+]?[0-9]+)?' t_TEMPLATE_NAME = r'CppHeaderParser_template_[0-9]+' t_NAME = r'[<>A-Za-z_~][A-Za-z0-9_]*' t_OPEN_PAREN = r'\(' t_CLOSE_PAREN = r'\)' t_OPEN_BRACE = r'{' t_CLOSE_BRACE = r'}' t_OPEN_SQUARE_BRACKET = r'\[' t_CLOSE_SQUARE_BRACKET = r'\]' t_SEMI_COLON = r';' t_COLON = r':' t_COMMA = r',' t_TAB = r'\t' t_BACKSLASH = r'\\' t_PIPE = r'\|' t_PERCENT = r'%' t_CARET = r'\^' t_EXCLAMATION = r'!' t_PRECOMP_MACRO = r'\#.*' t_PRECOMP_MACRO_CONT = r'.*\\\n' def t_COMMENT_SINGLELINE(t): r'\/\/.*\n?' global doxygenCommentCache if t.value.startswith("///") or t.value.startswith("//!"): if doxygenCommentCache: doxygenCommentCache += "\n" if t.value.endswith("\n"): doxygenCommentCache += t.value[:-1] else: doxygenCommentCache += t.value t.lexer.lineno += len([a for a in t.value if a=="\n"]) t_ASTERISK = r'\*' t_MINUS = r'\-' t_PLUS = r'\+' t_DIVIDE = r'/(?!/)' t_AMPERSTAND = r'&' t_EQUALS = r'=' t_CHAR_LITERAL = "'.'" t_SQUOTE = "'" t_ELLIPSIS = r'\.\.\.' t_DOT = r'\.' #found at http://wordaligned.org/articles/string-literals-and-regular-expressions #TODO: This does not work with the string "bla \" bla" t_STRING_LITERAL = r'"([^"\\]|\\.)*"' #Found at http://ostermiller.org/findcomment.html def t_COMMENT_MULTILINE(t): r'/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/' global doxygenCommentCache if t.value.startswith("/**") or t.value.startswith("/*!"): #not sure why, but get double new lines v = t.value.replace("\n\n", "\n") #strip prefixing whitespace v = re.sub("\n[\s]+\*", "\n*", v) doxygenCommentCache += v t.lexer.lineno += len([a for a in t.value if a=="\n"]) def t_NEWLINE(t): r'\n+' t.lexer.lineno += len(t.value) def t_error(v): print(( "Lex error: ", v )) lex.lex() # Controls error_print print_errors = 1 # Controls warning_print print_warnings = 1 # Controls debug_print debug = 0 # Controls trace_print debug_trace = 0 def error_print(arg): if print_errors: print(("[%4d] %s"%(inspect.currentframe().f_back.f_lineno, arg))) def warning_print(arg): if print_warnings: print(("[%4d] %s"%(inspect.currentframe().f_back.f_lineno, arg))) def debug_print(arg): global debug if debug: print(("[%4d] %s"%(inspect.currentframe().f_back.f_lineno, arg))) def trace_print(*arg): global debug_trace if debug_trace: sys.stdout.write("[%s] "%(inspect.currentframe().f_back.f_lineno)) for a in arg: sys.stdout.write("%s "%a) sys.stdout.write("\n") supportedAccessSpecifier = [ 'public', 'protected', 'private' ] #Symbols to ignore, usually special macros ignoreSymbols = [ 'Q_OBJECT', ] doxygenCommentCache = "" #Track what was added in what order and at what depth parseHistory = [] def is_namespace(nameStack): """Determines if a namespace is being specified""" if len(nameStack) == 0: return False if nameStack[0] == "namespace": return True return False def extract_namespace_namestack(nameStack): if not is_namespace(nameStack): return "" ns = "" nstack = nameStack[1:] namespaceSeparator = 2 for i in range(len(nstack)): if namespaceSeparator is 2: ns += nstack[i] if ns is not nstack[i] and i is not len(nstack) - 1: ns += "::" namespaceSeparator = 0 if nstack[i] == ":": namespaceSeparator += 1 return ns def is_enum_namestack(nameStack): """Determines if a namestack is an enum namestack""" if len(nameStack) == 0: return False if nameStack[0] == "enum": return True if len(nameStack) > 1 and nameStack[0] == "typedef" and nameStack[1] == "enum": return True return False def is_fundamental(s): for a in s.split(): if a not in ["size_t", "struct", "union", "unsigned", "signed", "bool", "char", "short", "int", "float", "double", "long", "void", "*"]: return False return True def is_function_pointer_stack(stack): """Count how many non-nested paranthesis are in the stack. Useful for determining if a stack is a function pointer""" paren_depth = 0 paren_count = 0 star_after_first_paren = False last_e = None for e in stack: if e == "(": paren_depth += 1 elif e == ")" and paren_depth > 0: paren_depth -= 1 if paren_depth == 0: paren_count += 1 elif e == "*" and last_e == "(" and paren_count == 0 and paren_depth == 1: star_after_first_paren = True last_e = e if star_after_first_paren and paren_count == 2: return True else: return False def is_method_namestack(stack): r = False if '(' not in stack: r = False elif stack[0] == 'typedef': r = False # TODO deal with typedef function prototypes #elif '=' in stack and stack.index('=') < stack.index('(') and stack[stack.index('=')-1] != 'operator': r = False #disabled July6th - allow all operators elif 'operator' in stack: r = True # allow all operators elif '{' in stack and stack.index('{') < stack.index('('): r = False # struct that looks like a method/class elif '(' in stack and ')' in stack: if '{' in stack and '}' in stack: r = True elif stack[-1] == ';': if is_function_pointer_stack(stack): r = False else: r = True elif '{' in stack: r = True # ideally we catch both braces... TODO else: r = False #Test for case of property set to something with parens such as "static const int CONST_A = (1 << 7) - 1;" if r and "(" in stack and "=" in stack and 'operator' not in stack: if stack.index("=") < stack.index("("): r = False return r def is_property_namestack(nameStack): r = False if '(' not in nameStack and ')' not in nameStack: r = True elif "(" in nameStack and "=" in nameStack and nameStack.index("=") < nameStack.index("("): r = True #See if we are a function pointer if not r and is_function_pointer_stack(nameStack): r = True return r def detect_lineno(s): """Detect the line number for a given token string""" try: rtn = s.lineno() if rtn != -1: return rtn except: pass global curLine return curLine def filter_out_attribute_keyword(stack): """Strips __attribute__ and its parenthetical expression from the stack""" if "__attribute__" not in stack: return stack try: debug_print("Stripping __attribute__ from %s"% stack) attr_index = stack.index("__attribute__") attr_end = attr_index + 1 #Assuming not followed by parenthetical expression which wont happen #Find final paren if stack[attr_index + 1] == '(': paren_count = 1 for i in range(attr_index + 2, len(stack)): elm = stack[i] if elm == '(': paren_count += 1 elif elm == ')': paren_count -= 1 if paren_count == 0: attr_end = i + 1 break new_stack = stack[0:attr_index] + stack[attr_end:] debug_print("stripped stack is %s"% new_stack) return new_stack except: return stack class TagStr(str): """Wrapper for a string that allows us to store the line number associated with it""" lineno_reg = {} def __new__(cls,*args,**kw): new_obj = str.__new__(cls,*args) if "lineno" in kw: TagStr.lineno_reg[id(new_obj)] = kw["lineno"] return new_obj def __del__(self): try: del TagStr.lineno_reg[id(self)] except: pass def lineno(self): return TagStr.lineno_reg.get(id(self), -1) class CppParseError(Exception): pass class CppClass(dict): """Takes a name stack and turns it into a class Contains the following Keys: self['name'] - Name of the class self['doxygen'] - Doxygen comments associated with the class if they exist self['inherits'] - List of Classes that this one inherits where the values are of the form {"access": Anything in supportedAccessSpecifier "class": Name of the class self['methods'] - Dictionary where keys are from supportedAccessSpecifier and values are a lists of CppMethod's self['properties'] - Dictionary where keys are from supportedAccessSpecifier and values are lists of CppVariable's self['enums'] - Dictionary where keys are from supportedAccessSpecifier and values are lists of CppEnum's self['structs'] - Dictionary where keys are from supportedAccessSpecifier and values are lists of nested Struct's An example of how this could look is as follows: #self = { 'name': "" 'inherits':[] 'methods': { 'public':[], 'protected':[], 'private':[] }, 'properties': { 'public':[], 'protected':[], 'private':[] }, 'enums': { 'public':[], 'protected':[], 'private':[] } } """ def get_all_methods(self): r = [] for typ in supportedAccessSpecifier: r += self['methods'][typ] return r def get_all_method_names( self ): r = [] for typ in supportedAccessSpecifier: r += self.get_method_names(typ) # returns list return r def get_all_pure_virtual_methods( self ): r = {} for typ in supportedAccessSpecifier: r.update(self.get_pure_virtual_methods(typ)) # returns dict return r def get_method_names( self, type='public' ): return [ meth['name'] for meth in self['methods'][ type ] ] def get_pure_virtual_methods( self, type='public' ): r = {} for meth in self['methods'][ type ]: if meth['pure_virtual']: r[ meth['name'] ] = meth return r def __init__(self, nameStack, curTemplate): self['nested_classes'] = [] self['parent'] = None self['abstract'] = False self._public_enums = {} self._public_structs = {} self._public_typedefs = {} self._public_forward_declares = [] self['namespace'] = "" debug_print( "Class: %s"%nameStack ) debug_print( "Template: %s"%curTemplate) if (len(nameStack) < 2): nameStack.insert(1, "")#anonymous struct global doxygenCommentCache if len(doxygenCommentCache): self["doxygen"] = doxygenCommentCache doxygenCommentCache = "" if "::" in "".join(nameStack): #Re-Join class paths (ex ['class', 'Bar', ':', ':', 'Foo'] -> ['class', 'Bar::Foo'] try: new_nameStack = [] for name in nameStack: if len(new_nameStack) == 0: new_nameStack.append(name) elif name == ":" and new_nameStack[-1].endswith(":"): new_nameStack[-1] += name elif new_nameStack[-1].endswith("::"): new_nameStack[-2] += new_nameStack[-1] + name del new_nameStack[-1] else: new_nameStack.append(name) trace_print("Convert from namestack\n %s\nto\n%s"%(nameStack, new_nameStack)) nameStack = new_nameStack except: pass # Handle final specifier self["final"] = False try: final_index = nameStack.index("final") # Dont trip up the rest of the logic del nameStack[final_index] self["final"] = True trace_print("final") except: pass self["name"] = nameStack[1] self["line_number"] = detect_lineno(nameStack[0]) #Handle template classes if len(nameStack) > 3 and nameStack[2].startswith("<"): open_template_count = 0 param_separator = 0 found_first = False i = 0 for elm in nameStack: if '<' in elm : open_template_count += 1 found_first = True elif '>' in elm: open_template_count -= 1 if found_first and open_template_count == 0: self["name"] = "".join(nameStack[1:i + 1]) break; i += 1 elif ":" in nameStack: self['name'] = nameStack[ nameStack.index(':') - 1 ] inheritList = [] if nameStack.count(':') == 1: nameStack = nameStack[nameStack.index(":") + 1:] while len(nameStack): tmpStack = [] tmpInheritClass = {"access":"private", "virtual": False} if "," in nameStack: tmpStack = nameStack[:nameStack.index(",")] nameStack = nameStack[nameStack.index(",") + 1:] else: tmpStack = nameStack nameStack = [] # Convert template classes to one name in the last index for i in range(0, len(tmpStack)): if '<' in tmpStack[i]: tmpStack2 = tmpStack[:i-1] tmpStack2.append("".join(tmpStack[i-1:])) tmpStack = tmpStack2 break if len(tmpStack) == 0: break; elif len(tmpStack) == 1: tmpInheritClass["class"] = tmpStack[0] elif len(tmpStack) == 2: tmpInheritClass["access"] = tmpStack[0] tmpInheritClass["class"] = tmpStack[1] elif len(tmpStack) == 3 and "virtual" in tmpStack: tmpInheritClass["access"] = tmpStack[1] if tmpStack[1] != "virtual" else tmpStack[0] tmpInheritClass["class"] = tmpStack[2] tmpInheritClass["virtual"] = True else: warning_print( "Warning: can not parse inheriting class %s"%(" ".join(tmpStack))) if '>' in tmpStack: pass # allow skip templates for now else: raise NotImplemented if 'class' in tmpInheritClass: inheritList.append(tmpInheritClass) elif nameStack.count(':') == 2: self['parent'] = self['name']; self['name'] = nameStack[-1] elif nameStack.count(':') > 2 and nameStack[0] in ("class", "struct"): tmpStack = nameStack[nameStack.index(":") + 1:] superTmpStack = [[]] for tok in tmpStack: if tok == ',': superTmpStack.append([]) else: superTmpStack[-1].append(tok) for tmpStack in superTmpStack: tmpInheritClass = {"access":"private"} if len(tmpStack) and tmpStack[0] in supportedAccessSpecifier: tmpInheritClass["access"] = tmpStack[0] tmpStack = tmpStack[1:] inheritNSStack = [] while len(tmpStack) > 3: if tmpStack[0] == ':': break; if tmpStack[1] != ':': break; if tmpStack[2] != ':': break; inheritNSStack.append(tmpStack[0]) tmpStack = tmpStack[3:] if len(tmpStack) == 1 and tmpStack[0] != ':': inheritNSStack.append(tmpStack[0]) tmpInheritClass["class"] = "::".join(inheritNSStack) inheritList.append(tmpInheritClass) self['inherits'] = inheritList if curTemplate: self["template"] = curTemplate trace_print("Setting template to '%s'"%self["template"]) methodAccessSpecificList = {} propertyAccessSpecificList = {} enumAccessSpecificList = {} structAccessSpecificList = {} typedefAccessSpecificList = {} forwardAccessSpecificList = {} for accessSpecifier in supportedAccessSpecifier: methodAccessSpecificList[accessSpecifier] = [] propertyAccessSpecificList[accessSpecifier] = [] enumAccessSpecificList[accessSpecifier] = [] structAccessSpecificList[accessSpecifier] = [] typedefAccessSpecificList[accessSpecifier] = [] forwardAccessSpecificList[accessSpecifier] = [] self['methods'] = methodAccessSpecificList self['properties'] = propertyAccessSpecificList self['enums'] = enumAccessSpecificList self['structs'] = structAccessSpecificList self['typedefs'] = typedefAccessSpecificList self['forward_declares'] = forwardAccessSpecificList def show(self): """Convert class to a string""" namespace_prefix = "" if self["namespace"]: namespace_prefix = self["namespace"] + "::" rtn = "%s %s"%(self["declaration_method"], namespace_prefix + self["name"]) if self["final"]: rtn += " final" if self['abstract']: rtn += ' (abstract)\n' else: rtn += '\n' if 'doxygen' in list(self.keys()): rtn += self["doxygen"] + '\n' if 'parent' in list(self.keys()) and self['parent']: rtn += 'parent class: ' + self['parent'] + '\n' if "inherits" in list(self.keys()): rtn += " Inherits: " for inheritClass in self["inherits"]: if inheritClass["virtual"]: rtn += "virtual " rtn += "%s %s, "%(inheritClass["access"], inheritClass["class"]) rtn += "\n" rtn += " {\n" for accessSpecifier in supportedAccessSpecifier: rtn += " %s\n"%(accessSpecifier) #Enums if (len(self["enums"][accessSpecifier])): rtn += " <Enums>\n" for enum in self["enums"][accessSpecifier]: rtn += " %s\n"%(repr(enum)) #Properties if (len(self["properties"][accessSpecifier])): rtn += " <Properties>\n" for property in self["properties"][accessSpecifier]: rtn += " %s\n"%(repr(property)) #Methods if (len(self["methods"][accessSpecifier])): rtn += " <Methods>\n" for method in self["methods"][accessSpecifier]: rtn += "\t\t" + method.show() + '\n' rtn += " }\n" print(rtn) def __str__(self): """Convert class to a string""" namespace_prefix = "" if self["namespace"]: namespace_prefix = self["namespace"] + "::" rtn = "%s %s"%(self["declaration_method"], namespace_prefix + self["name"]) if self["final"]: rtn += " final" if self['abstract']: rtn += ' (abstract)\n' else: rtn += '\n' if 'doxygen' in list(self.keys()): rtn += self["doxygen"] + '\n' if 'parent' in list(self.keys()) and self['parent']: rtn += 'parent class: ' + self['parent'] + '\n' if "inherits" in list(self.keys()) and len(self["inherits"]): rtn += "Inherits: " for inheritClass in self["inherits"]: if inheritClass.get("virtual", False): rtn += "virtual " rtn += "%s %s, "%(inheritClass["access"], inheritClass["class"]) rtn += "\n" rtn += "{\n" for accessSpecifier in supportedAccessSpecifier: rtn += "%s\n"%(accessSpecifier) #Enums if (len(self["enums"][accessSpecifier])): rtn += " // Enums\n" for enum in self["enums"][accessSpecifier]: rtn += " %s\n"%(repr(enum)) #Properties if (len(self["properties"][accessSpecifier])): rtn += " // Properties\n" for property in self["properties"][accessSpecifier]: rtn += " %s\n"%(repr(property)) #Methods if (len(self["methods"][accessSpecifier])): rtn += " // Methods\n" for method in self["methods"][accessSpecifier]: rtn += " %s\n"%(repr(method)) rtn += "}\n" return rtn class CppUnion( CppClass ): """Takes a name stack and turns it into a union Contains the following Keys: self['name'] - Name of the union self['doxygen'] - Doxygen comments associated with the union if they exist self['members'] - List of members the union has An example of how this could look is as follows: #self = { 'name': "" 'members': [] } """ def __init__(self, nameStack): CppClass.__init__(self, nameStack, None) self["name"] = "union " + self["name"] self["members"] = self["properties"]["public"] def transform_to_union_keys(self): print("union keys: %s"%list(self.keys())) for key in ['inherits', 'parent', 'abstract', 'namespace', 'typedefs', 'methods']: del self[key] def show(self): """Convert class to a string""" print(self) def __str__(self): """Convert class to a string""" namespace_prefix = "" if self["namespace"]: namespace_prefix = self["namespace"] + "::" rtn = "%s %s"%(self["declaration_method"], namespace_prefix + self["name"]) if self['abstract']: rtn += ' (abstract)\n' else: rtn += '\n' if 'doxygen' in list(self.keys()): rtn += self["doxygen"] + '\n' if 'parent' in list(self.keys()) and self['parent']: rtn += 'parent class: ' + self['parent'] + '\n' rtn += "{\n" for member in self["members"]: rtn += " %s\n"%(repr(member)) rtn += "}\n" return rtn class _CppMethod( dict ): def _params_helper1( self, stack ): # deal with "throw" keyword if 'throw' in stack: stack = stack[ : stack.index('throw') ] ## remove GCC keyword __attribute__(...) and preserve returns ## cleaned = [] hit = False; hitOpen = 0; hitClose = 0 for a in stack: if a == '__attribute__': hit = True if hit: if a == '(': hitOpen += 1 elif a == ')': hitClose += 1 if a==')' and hitOpen == hitClose: hit = False else: cleaned.append( a ) stack = cleaned # also deal with attribute((const)) function prefix # # TODO this needs to be better # if len(stack) > 5: a = ''.join(stack) if a.startswith('((__const__))'): stack = stack[ 5 : ] elif a.startswith('__attribute__((__const__))'): stack = stack[ 6 : ] stack = stack[stack.index('(') + 1: ] if not stack: return [] if len(stack)>=3 and stack[0]==')' and stack[1]==':': # is this always a constructor? self['constructor'] = True return [] stack.reverse(); _end_ = stack.index(')'); stack.reverse() stack = stack[ : len(stack)-(_end_+1) ] if '(' not in stack: return stack # safe to return, no defaults that init a class return stack def _params_helper2( self, params ): for p in params: p['method'] = self # save reference in variable to parent method if '::' in p['type']: ns = p['type'].split('::')[0] if ns not in Resolver.NAMESPACES and ns in Resolver.CLASSES: p['type'] = self['namespace'] + p['type'] else: p['namespace'] = self[ 'namespace' ] class CppMethod( _CppMethod ): """Takes a name stack and turns it into a method Contains the following Keys: self['rtnType'] - Return type of the method (ex. "int") self['name'] - Name of the method (ex. "getSize") self['doxygen'] - Doxygen comments associated with the method if they exist self['parameters'] - List of CppVariables """ def show(self): r = ['method name: %s (%s)' %(self['name'],self['debug']) ] if self['returns']: r.append( 'returns: %s'%self['returns'] ) if self['parameters']: r.append( 'number arguments: %s' %len(self['parameters'])) if self['pure_virtual']: r.append( 'pure virtual: %s'%self['pure_virtual'] ) if self['constructor']: r.append( 'constructor' ) if self['destructor']: r.append( 'destructor' ) return '\n\t\t '.join( r ) def __init__(self, nameStack, curClass, methinfo, curTemplate): debug_print( "Method: %s"%nameStack ) debug_print( "Template: %s"%curTemplate ) global doxygenCommentCache if len(doxygenCommentCache): self["doxygen"] = doxygenCommentCache doxygenCommentCache = "" if "operator" in nameStack: self["rtnType"] = " ".join(nameStack[:nameStack.index('operator')]) self["name"] = "".join(nameStack[nameStack.index('operator'):nameStack.index('(')]) else: self["rtnType"] = " ".join(nameStack[:nameStack.index('(') - 1]) self["name"] = " ".join(nameStack[nameStack.index('(') - 1:nameStack.index('(')]) if self["rtnType"].startswith("virtual"): self["rtnType"] = self["rtnType"][len("virtual"):].strip() if len(self["rtnType"]) == 0 or self["name"] == curClass: self["rtnType"] = "void" self["rtnType"] = self["rtnType"].replace(' : : ', '::' ) self["rtnType"] = self["rtnType"].replace(" <","<") self["rtnType"] = self["rtnType"].replace(" >",">").replace(">>", "> >").replace(">>", "> >") self["rtnType"] = self["rtnType"].replace(" ,",",") # deal with "noexcept" specifier/operator cleaned = [] hit = False; parentCount = 0 self['noexcept'] = '' for a in nameStack: if a == 'noexcept': hit = True if hit: if a == '(': parentCount += 1 elif a == ')': parentCount -= 1 elif parentCount == 0 and a != 'noexcept': hit = False; cleaned.append( a ); continue # noexcept without parenthesis if a==')' and parentCount == 0: hit = False self['noexcept'] += a else: cleaned.append( a ) nameStack = cleaned self['noexcept'] = self['noexcept'] if self['noexcept'] else None for spec in ["const", "final", "override"]: self[spec] = False for i in reversed(nameStack): if i == spec: self[spec] = True break elif i == ")": break self.update( methinfo ) self["line_number"] = detect_lineno(nameStack[0]) #Filter out initializer lists used in constructors try: paren_depth_counter = 0 for i in range(0, len(nameStack)): elm = nameStack[i] if elm == "(": paren_depth_counter += 1 if elm == ")": paren_depth_counter -=1 if paren_depth_counter == 0 and nameStack[i+1] == ':': debug_print("Stripping out initializer list") nameStack = nameStack[:i+1] break except: pass paramsStack = self._params_helper1( nameStack ) debug_print( "curTemplate: %s"%curTemplate) if curTemplate: self["template"] = curTemplate debug_print( "SET self['template'] to `%s`"%self["template"]) params = [] #See if there is a doxygen comment for the variable doxyVarDesc = {} if "doxygen" in self: doxyLines = self["doxygen"].split("\n") lastParamDesc = "" for doxyLine in doxyLines: if " @param " in doxyLine or " \param " in doxyLine: try: #Strip out the param doxyLine = doxyLine[doxyLine.find("param ") + 6:] (var, desc) = doxyLine.split(" ", 1) doxyVarDesc[var] = desc.strip() lastParamDesc = var except: pass elif " @return " in doxyLine or " \return " in doxyLine: lastParamDesc = "" # not handled for now elif lastParamDesc: try: doxyLine = doxyLine.strip() if " " not in doxyLine: lastParamDesc = "" continue doxyLine = doxyLine[doxyLine.find(" ") + 1:] doxyVarDesc[lastParamDesc] += " " + doxyLine except: pass # non-vararg by default self["vararg"] = False #Create the variable now while (len(paramsStack)): # Find commas that are not nexted in <>'s like template types open_template_count = 0 open_paren_count = 0 param_separator = 0 i = 0 for elm in paramsStack: if '<' in elm : open_template_count += 1 elif '>' in elm: open_template_count -= 1 elif '(' in elm : open_paren_count += 1 elif ')' in elm: open_paren_count -= 1 elif elm == ',' and open_template_count == 0 and open_paren_count==0: param_separator = i break i += 1 if param_separator: param = CppVariable(paramsStack[0:param_separator], doxyVarDesc=doxyVarDesc) if len(list(param.keys())): params.append(param) paramsStack = paramsStack[param_separator + 1:] elif len(paramsStack) and paramsStack[0] == "...": self["vararg"] = True paramsStack = paramsStack[1:] else: param = CppVariable(paramsStack, doxyVarDesc=doxyVarDesc) if len(list(param.keys())): params.append(param) break self["parameters"] = params self._params_helper2( params ) # mods params inplace def __str__(self): filter_keys = ("parent", "defined", "operator", "returns_reference") cpy = dict((k,v) for (k,v) in list(self.items()) if k not in filter_keys) return "%s"%cpy class _CppVariable(dict): def _name_stack_helper( self, stack ): stack = list(stack) if '=' not in stack: # TODO refactor me # check for array[n] and deal with funny array syntax: "int myvar:99" array = [] while stack and stack[-1].isdigit(): array.append( stack.pop() ) if array: array.reverse(); self['array'] = int(''.join(array)) if stack and stack[-1].endswith(':'): stack[-1] = stack[-1][:-1] while stack and not stack[-1]: stack.pop() # can be empty return stack def init(self): #assert self['name'] # allow unnamed variables, methods like this: "void func(void);" a = [] self['aliases'] = []; self['parent'] = None; self['typedef'] = None for key in 'constant reference pointer static typedefs class fundamental unresolved'.split(): self[ key ] = 0 for b in self['type'].split(): if b == '__const__': b = 'const' a.append( b ) self['type'] = ' '.join( a ) class CppVariable( _CppVariable ): """Takes a name stack and turns it into a method Contains the following Keys: self['type'] - Type for the variable (ex. "const string &") self['name'] - Name of the variable (ex. "numItems") self['namespace'] - Namespace containing the enum self['desc'] - Description of the variable if part of a method (optional) self['doxygen'] - Doxygen comments associated with the method if they exist self['defaultValue'] - Default value of the variable, this key will only exist if there is a default value self['extern'] - True if its an extern, false if not """ Vars = [] def __init__(self, nameStack, **kwargs): debug_print("trace %s"%nameStack) if len(nameStack) and nameStack[0] == "extern": self['extern'] = True del nameStack[0] else: self['extern'] = False _stack_ = nameStack if "[" in nameStack: #strip off array informatin arrayStack = nameStack[nameStack.index("["):] if nameStack.count("[") > 1: debug_print("Multi dimensional array") debug_print("arrayStack=%s"%arrayStack) nums = [x for x in arrayStack if x.isdigit()] # Calculate size by multiplying all dimensions p = 1 for n in nums: p *= int(n) #Multi dimensional array self["array_size"] = p self["multi_dimensional_array"] = 1 self["multi_dimensional_array_size"] = "x".join(nums) else: debug_print("Array") if len(arrayStack) == 3: self["array_size"] = arrayStack[1] nameStack = nameStack[:nameStack.index("[")] self["array"] = 1 else: self["array"] = 0 nameStack = self._name_stack_helper( nameStack ) global doxygenCommentCache if len(doxygenCommentCache): self["doxygen"] = doxygenCommentCache doxygenCommentCache = "" debug_print( "Variable: %s"%nameStack ) self["line_number"] = detect_lineno(nameStack[0]) self["function_pointer"] = 0 if (len(nameStack) < 2): # +++ if len(nameStack) == 1: self['type'] = nameStack[0]; self['name'] = '' else: error_print(_stack_); assert 0 elif is_function_pointer_stack(nameStack): #function pointer self["type"] = " ".join(nameStack[:nameStack.index("(") + 2] + nameStack[nameStack.index(")") :]) self["name"] = " ".join(nameStack[nameStack.index("(") + 2 : nameStack.index(")")]) self["function_pointer"] = 1 elif ("=" in nameStack): self["type"] = " ".join(nameStack[:nameStack.index("=") - 1]) self["name"] = nameStack[nameStack.index("=") - 1] self["defaultValue"] = " ".join(nameStack[nameStack.index("=") + 1:]) # deprecate camelCase in dicts self['default'] = " ".join(nameStack[nameStack.index("=") + 1:]) elif is_fundamental(nameStack[-1]) or nameStack[-1] in ['>', '<' , ':', '.']: #Un named parameter self["type"] = " ".join(nameStack) self["name"] = "" else: # common case self["type"] = " ".join(nameStack[:-1]) self["name"] = nameStack[-1] self["type"] = self["type"].replace(" :",":") self["type"] = self["type"].replace(": ",":") self["type"] = self["type"].replace(" <","<") self["type"] = self["type"].replace(" >",">").replace(">>", "> >").replace(">>", "> >") self["type"] = self["type"].replace(" ,",",") #Optional doxygen description try: self["desc"] = kwargs["doxyVarDesc"][self["name"]] except: pass self.init() CppVariable.Vars.append( self ) # save and resolve later def __str__(self): keys_white_list = ['constant','name','reference','type','static','pointer','desc', 'line_number', 'extern'] cpy = dict((k,v) for (k,v) in list(self.items()) if k in keys_white_list) if "array_size" in self: cpy["array_size"] = self["array_size"] return "%s"%cpy class _CppEnum(dict): def resolve_enum_values( self, values ): """Evaluates the values list of dictionaries passed in and figures out what the enum value for each enum is editing in place: Example: From: [{'name': 'ORANGE'}, {'name': 'RED'}, {'name': 'GREEN', 'value': '8'}] To: [{'name': 'ORANGE', 'value': 0}, {'name': 'RED', 'value': 1}, {'name': 'GREEN', 'value': 8}] """ t = int; i = 0 names = [ v['name'] for v in values ] for v in values: if 'value' in v: a = v['value'].strip() # Remove single quotes from single quoted chars (unless part of some expression if len(a) == 3 and a[0] == "'" and a[2] == "'": a = v['value'] = a[1] if a.lower().startswith("0x"): try: i = a = int(a , 16) except:pass elif a.isdigit(): i = a = int( a ) elif a in names: for other in values: if other['name'] == a: v['value'] = other['value'] break elif '"' in a or "'" in a: t = str # only if there are quotes it this a string enum else: try: a = i = ord(a) except: pass #Allow access of what is in the file pre-convert if converted if v['value'] != str(a): v['raw_value'] = v['value'] v['value'] = a else: v['value'] = i try: v['value'] = v['value'].replace(" < < ", " << ").replace(" >> ", " >> ") except: pass i += 1 return t class CppEnum(_CppEnum): """Takes a name stack and turns it into an Enum Contains the following Keys: self['name'] - Name of the enum (ex. "ItemState") self['namespace'] - Namespace containing the enum self['values'] - List of values where the values are a dictionary of the form {"name": name of the key (ex. "PARSING_HEADER"), "value": Specified value of the enum, this key will only exist if a value for a given enum value was defined } """ def __init__(self, nameStack): global doxygenCommentCache if len(doxygenCommentCache): self["doxygen"] = doxygenCommentCache doxygenCommentCache = "" if len(nameStack) == 3 and nameStack[0] == "enum": debug_print("Created enum as just name/value") self["name"] = nameStack[1] self["instances"]=[nameStack[2]] if len(nameStack) < 4 or "{" not in nameStack or "}" not in nameStack: #Not enough stuff for an enum debug_print("Bad enum") return valueList = [] self["line_number"] = detect_lineno(nameStack[0]) #Figure out what values it has valueStack = nameStack[nameStack.index('{') + 1: nameStack.index('}')] while len(valueStack): tmpStack = [] if "," in valueStack: tmpStack = valueStack[:valueStack.index(",")] valueStack = valueStack[valueStack.index(",") + 1:] else: tmpStack = valueStack valueStack = [] d = {} if len(tmpStack) == 1: d["name"] = tmpStack[0] elif len(tmpStack) >= 3 and tmpStack[1] == "=": d["name"] = tmpStack[0]; d["value"] = " ".join(tmpStack[2:]) elif len(tmpStack) == 2 and tmpStack[1] == "=": debug_print( "WARN-enum: parser missed value for %s"%tmpStack[0] ) d["name"] = tmpStack[0] if d: valueList.append( d ) if len(valueList): self['type'] = self.resolve_enum_values( valueList ) # returns int for standard enum self["values"] = valueList else: warning_print( 'WARN-enum: empty enum %s'%nameStack ) return #Figure out if it has a name preBraceStack = nameStack[:nameStack.index("{")] postBraceStack = nameStack[nameStack.index("}") + 1:] self["typedef"] = False if (len(preBraceStack) == 4 and ":" in nameStack and "typedef" not in nameStack): # C++11 specify enum type with "enum <enum_name> : <type> ..." syntax self["name"] = preBraceStack[1] self["type"] = preBraceStack[3] elif (len(preBraceStack) == 2 and "typedef" not in nameStack): # enum "enum <enum_name> ..." syntax self["name"] = preBraceStack[1] elif len(postBraceStack) and "typedef" in nameStack: self["name"] = " ".join(postBraceStack) self["typedef"] = True else: warning_print( 'WARN-enum: nameless enum %s'%nameStack ) #See if there are instances of this if "typedef" not in nameStack and len(postBraceStack): self["instances"] = [] for var in postBraceStack: if "," in var: continue self["instances"].append(var) self["namespace"] = "" class CppStruct(dict): Structs = [] def __init__(self, nameStack): if len(nameStack) >= 2: self['type'] = nameStack[1] else: self['type'] = None self['fields'] = [] self.Structs.append( self ) global curLine self["line_number"] = curLine C99_NONSTANDARD = { 'int8' : 'signed char', 'int16' : 'short int', 'int32' : 'int', 'int64' : 'int64_t', # this can be: long int (64bit), or long long int (32bit) 'uint' : 'unsigned int', 'uint8' : 'unsigned char', 'uint16' : 'unsigned short int', 'uint32' : 'unsigned int', 'uint64' : 'uint64_t', # depends on host bits } def standardize_fundamental( s ): if s in C99_NONSTANDARD: return C99_NONSTANDARD[ s ] else: return s class Resolver(object): C_FUNDAMENTAL = 'size_t unsigned signed bool char wchar short int float double long void'.split() C_FUNDAMENTAL += 'struct union enum'.split() SubTypedefs = {} # TODO deprecate? NAMESPACES = [] CLASSES = {} STRUCTS = {} def initextra(self): self.typedefs = {} self.typedefs_order = [] self.classes_order = [] self.structs = Resolver.STRUCTS self.structs_order = [] self.namespaces = Resolver.NAMESPACES # save all namespaces self.curStruct = None self.stack = [] # full name stack, good idea to keep both stacks? (simple stack and full stack) self._classes_brace_level = {} # class name : level self._structs_brace_level = {} # struct type : level self._method_body = None self._forward_decls = [] self._template_typenames = [] # template<typename XXX> def current_namespace(self): return self.cur_namespace(True) def cur_namespace(self, add_double_colon=False): rtn = "" i = 0 while i < len(self.nameSpaces): rtn += self.nameSpaces[i] if add_double_colon or i < len(self.nameSpaces) - 1: rtn += "::" i+=1 return rtn def guess_ctypes_type( self, string ): pointers = string.count('*') string = string.replace('*','') a = string.split() if 'unsigned' in a: u = 'u' else: u = '' if 'long' in a and 'double' in a: b = 'longdouble' # there is no ctypes.c_ulongdouble (this is a 64bit float?) elif a.count('long') == 2 and 'int' in a: b = '%sint64' %u elif a.count('long') == 2: b = '%slonglong' %u elif 'long' in a: b = '%slong' %u elif 'double' in a: b = 'double' # no udouble in ctypes elif 'short' in a: b = '%sshort' %u elif 'char' in a: b = '%schar' %u elif 'wchar' in a: b = 'wchar' elif 'bool' in a: b = 'bool' elif 'float' in a: b = 'float' elif 'int' in a: b = '%sint' %u elif 'int8' in a: b = 'int8' elif 'int16' in a: b = 'int16' elif 'int32' in a: b = 'int32' elif 'int64' in a: b = 'int64' elif 'uint' in a: b = 'uint' elif 'uint8' in a: b = 'uint8' elif 'uint16' in a: b = 'uint16' elif 'uint32' in a: b = 'uint32' elif 'uint64' in a: b = 'uint64' elif 'size_t' in a: b = 'size_t' elif 'void' in a: b = 'void_p' elif string in 'struct union'.split(): b = 'void_p' # what should be done here? don't trust struct, it could be a class, no need to expose via ctypes else: b = 'void_p' if not pointers: return 'ctypes.c_%s' %b else: x = '' for i in range(pointers): x += 'ctypes.POINTER(' x += 'ctypes.c_%s' %b x += ')' * pointers return x def resolve_type( self, string, result ): # recursive ''' keeps track of useful things like: how many pointers, number of typedefs, is fundamental or a class, etc... ''' ## be careful with templates, what is inside <something*> can be a pointer but the overall type is not a pointer ## these come before a template s = string.split('<')[0] result[ 'constant' ] += s.split().count('const') result[ 'static' ] += s.split().count('static') result[ 'mutable' ] = 'mutable' in s.split() ## these come after a template s = string.split('>')[-1] result[ 'pointer' ] += s.count('*') result[ 'reference' ] += s.count('&') x = string; alias = False for a in '* & const static mutable'.split(): x = x.replace(a,'') for y in x.split(): if y not in self.C_FUNDAMENTAL: alias = y; break #if alias == 'class': # result['class'] = result['name'] # forward decl of class # result['forward_decl'] = True if alias == '__extension__': result['fundamental_extension'] = True elif alias: if alias in result['aliases']: # already resolved return result['aliases'].append( alias ) if alias in C99_NONSTANDARD: result['type'] = C99_NONSTANDARD[ alias ] result['typedef'] = alias result['typedefs'] += 1 elif alias in self.typedefs: result['typedefs'] += 1 result['typedef'] = alias self.resolve_type( self.typedefs[alias], result ) elif alias in self.classes: klass = self.classes[alias]; result['fundamental'] = False result['class'] = klass result['unresolved'] = False else: result['unresolved'] = True else: result['fundamental'] = True result['unresolved'] = False def finalize_vars(self): for s in CppStruct.Structs: # vars within structs can be ignored if they do not resolve for var in s['fields']: var['parent'] = s['type'] #for c in self.classes.values(): # for var in c.get_all_properties(): var['parent'] = c['name'] ## RESOLVE ## for var in CppVariable.Vars: self.resolve_type( var['type'], var ) #if 'method' in var and var['method']['name'] == '_notifyCurrentCamera': print(var); assert 0 # then find concrete type and best guess ctypes type # for var in CppVariable.Vars: if not var['aliases']: #var['fundamental']: var['ctypes_type'] = self.guess_ctypes_type( var['type'] ) else: var['unresolved'] = False # below may test to True if var['class']: var['ctypes_type'] = 'ctypes.c_void_p' else: assert var['aliases'] tag = var['aliases'][0] klass = None nestedEnum = None nestedStruct = None nestedTypedef = None if 'method' in var and 'parent' in list(var['method'].keys()): klass = var['method']['parent'] if tag in var['method']['parent']._public_enums: nestedEnum = var['method']['parent']._public_enums[ tag ] elif tag in var['method']['parent']._public_structs: nestedStruct = var['method']['parent']._public_structs[ tag ] elif tag in var['method']['parent']._public_typedefs: nestedTypedef = var['method']['parent']._public_typedefs[ tag ] if '<' in tag: # should also contain '>' var['template'] = tag # do not resolve templates var['ctypes_type'] = 'ctypes.c_void_p' var['unresolved'] = True elif nestedEnum: enum = nestedEnum if enum['type'] is int: var['ctypes_type'] = 'ctypes.c_int' var['raw_type'] = 'int' elif enum['type'] is str: var['ctypes_type'] = 'ctypes.c_char_p' var['raw_type'] = 'char*' var['enum'] = var['method']['path'] + '::' + enum['name'] var['fundamental'] = True elif nestedStruct: var['ctypes_type'] = 'ctypes.c_void_p' var['raw_type'] = var['method']['path'] + '::' + nestedStruct['type'] var['fundamental'] = False elif nestedTypedef: var['fundamental'] = is_fundamental( nestedTypedef ) if not var['fundamental']: var['raw_type'] = var['method']['path'] + '::' + tag else: _tag = tag if '::' in tag and tag.split('::')[0] in self.namespaces: tag = tag.split('::')[-1] con = self.concrete_typedef( _tag ) if con: var['concrete_type'] = con var['ctypes_type'] = self.guess_ctypes_type( var['concrete_type'] ) elif tag in self.structs: trace_print( 'STRUCT', var ) var['struct'] = tag var['ctypes_type'] = 'ctypes.c_void_p' var['raw_type'] = self.structs[tag]['namespace'] + '::' + tag elif tag in self._forward_decls: var['forward_declared'] = tag var['ctypes_type'] = 'ctypes.c_void_p' elif tag in self.global_enums: enum = self.global_enums[ tag ] if enum['type'] is int: var['ctypes_type'] = 'ctypes.c_int' var['raw_type'] = 'int' elif enum['type'] is str: var['ctypes_type'] = 'ctypes.c_char_p' var['raw_type'] = 'char*' var['enum'] = enum['namespace'] + enum['name'] var['fundamental'] = True elif var['parent']: warning_print( 'WARN unresolved %s'%_tag) var['ctypes_type'] = 'ctypes.c_void_p' var['unresolved'] = True elif tag.count('::')==1: trace_print( 'trying to find nested something in', tag ) a = tag.split('::')[0] b = tag.split('::')[-1] if a in self.classes: # a::b is most likely something nested in a class klass = self.classes[ a ] if b in klass._public_enums: trace_print( '...found nested enum', b ) enum = klass._public_enums[ b ] if enum['type'] is int: var['ctypes_type'] = 'ctypes.c_int' var['raw_type'] = 'int' elif enum['type'] is str: var['ctypes_type'] = 'ctypes.c_char_p' var['raw_type'] = 'char*' try: if 'method' in var: var['enum'] = var['method']['path'] + '::' + enum['name'] else: # class property var['unresolved'] = True except: var['unresolved'] = True var['fundamental'] = True else: var['unresolved'] = True # TODO klass._public_xxx elif a in self.namespaces: # a::b can also be a nested namespace if b in self.global_enums: enum = self.global_enums[ b ] trace_print(enum) trace_print(var) assert 0 elif b in self.global_enums: # falling back, this is a big ugly enum = self.global_enums[ b ] assert a in enum['namespace'].split('::') if enum['type'] is int: var['ctypes_type'] = 'ctypes.c_int' var['raw_type'] = 'int' elif enum['type'] is str: var['ctypes_type'] = 'ctypes.c_char_p' var['raw_type'] = 'char*' var['fundamental'] = True else: # boost::gets::crazy trace_print('NAMESPACES', self.namespaces) trace_print( a, b ) trace_print( '---- boost gets crazy ----' ) var['ctypes_type'] = 'ctypes.c_void_p' var['unresolved'] = True elif 'namespace' in var and self.concrete_typedef(var['namespace']+tag): #print( 'TRYING WITH NS', var['namespace'] ) con = self.concrete_typedef( var['namespace']+tag ) if con: var['typedef'] = var['namespace']+tag var['type'] = con if 'struct' in con.split(): var['raw_type'] = var['typedef'] var['ctypes_type'] = 'ctypes.c_void_p' else: self.resolve_type( var['type'], var ) var['ctypes_type'] = self.guess_ctypes_type( var['type'] ) elif '::' in var: var['ctypes_type'] = 'ctypes.c_void_p' var['unresolved'] = True elif tag in self.SubTypedefs: # TODO remove SubTypedefs if 'property_of_class' in var or 'property_of_struct' in var: trace_print( 'class:', self.SubTypedefs[ tag ], 'tag:', tag ) var['typedef'] = self.SubTypedefs[ tag ] # class name var['ctypes_type'] = 'ctypes.c_void_p' else: trace_print( "WARN-this should almost never happen!" ) trace_print( var ); trace_print('-'*80) var['unresolved'] = True elif tag in self._template_typenames: var['typename'] = tag var['ctypes_type'] = 'ctypes.c_void_p' var['unresolved'] = True # TODO, how to deal with templates? elif tag.startswith('_'): # assume starting with underscore is not important for wrapping warning_print( 'WARN unresolved %s'%_tag) var['ctypes_type'] = 'ctypes.c_void_p' var['unresolved'] = True else: trace_print( 'WARN: unknown type', var ) assert 'property_of_class' in var or 'property_of_struct' # only allow this case var['unresolved'] = True ## if not resolved and is a method param, not going to wrap these methods ## if var['unresolved'] and 'method' in var: var['method']['unresolved_parameters'] = True # create stripped raw_type # p = '* & const static mutable'.split() # +++ new July7: "mutable" for var in CppVariable.Vars: if 'raw_type' not in var: raw = [] for x in var['type'].split(): if x not in p: raw.append( x ) var['raw_type'] = ' '.join( raw ) #if 'AutoConstantEntry' in var['raw_type']: print(var); assert 0 if var['class']: if '::' not in var['raw_type']: if not var['class']['parent']: var['raw_type'] = var['class']['namespace'] + '::' + var['raw_type'] elif var['class']['parent'] in self.classes: parent = self.classes[ var['class']['parent'] ] var['raw_type'] = parent['namespace'] + '::' + var['class']['name'] + '::' + var['raw_type'] else: var['unresolved'] = True elif '::' in var['raw_type'] and var['raw_type'].split('::')[0] not in self.namespaces: var['raw_type'] = var['class']['namespace'] + '::' + var['raw_type'] else: var['unresolved'] = True elif 'forward_declared' in var and 'namespace' in var: if '::' not in var['raw_type']: var['raw_type'] = var['namespace'] + var['raw_type'] elif '::' in var['raw_type'] and var['raw_type'].split('::')[0] in self.namespaces: pass else: trace_print('-'*80); trace_print(var); raise NotImplemented ## need full name space for classes in raw type ## if var['raw_type'].startswith( '::' ): #print(var) #print('NAMESPACE', var['class']['namespace']) #print( 'PARENT NS', var['class']['parent']['namespace'] ) #assert 0 var['unresolved'] = True if 'method' in var: var['method']['unresolved_parameters'] = True #var['raw_type'] = var['raw_type'][2:] # Take care of #defines and #pragmas etc trace_print("Processing precomp_macro_buf: %s"%self._precomp_macro_buf) for m in self._precomp_macro_buf: macro = m.replace("<CppHeaderParser_newline_temp_replacement>\\n", "\n") try: if macro.lower().startswith("#define"): trace_print("Adding #define %s"%macro) self.defines.append(re.split("[\t ]+", macro, 1)[1].strip()) elif macro.lower().startswith("#pragma"): trace_print("Adding #pragma %s"%macro) self.pragmas.append(re.split("[\t ]+", macro, 1)[1].strip()) elif macro.lower().startswith("#include"): trace_print("Adding #include %s"%macro) self.includes.append(re.split("[\t ]+", macro, 1)[1].strip()) else: debug_print("Cant detect what to do with precomp macro '%s'"%macro) except: pass self._precomp_macro_buf = None def concrete_typedef( self, key ): if key not in self.typedefs: #print( 'FAILED typedef', key ) return None while key in self.typedefs: prev = key key = self.typedefs[ key ] if '<' in key or '>' in key: return prev # stop at template if key.startswith('std::'): return key # stop at std lib return key class _CppHeader( Resolver ): def finalize(self): self.finalize_vars() # finalize classes and method returns types for cls in list(self.classes.values()): for meth in cls.get_all_methods(): if meth['pure_virtual']: cls['abstract'] = True if not meth['returns_fundamental'] and meth['returns'] in C99_NONSTANDARD: meth['returns'] = C99_NONSTANDARD[meth['returns']] meth['returns_fundamental'] = True elif not meth['returns_fundamental']: # describe the return type con = None if cls['namespace'] and '::' not in meth['returns']: con = self.concrete_typedef( cls['namespace'] + '::' + meth['returns'] ) else: con = self.concrete_typedef( meth['returns'] ) if con: meth['returns_concrete'] = con meth['returns_fundamental'] = is_fundamental( con ) elif meth['returns'] in self.classes: trace_print( 'meth returns class:', meth['returns'] ) meth['returns_class'] = True elif meth['returns'] in self.SubTypedefs: meth['returns_class'] = True meth['returns_nested'] = self.SubTypedefs[ meth['returns'] ] elif meth['returns'] in cls._public_enums: enum = cls._public_enums[ meth['returns'] ] meth['returns_enum'] = enum['type'] meth['returns_fundamental'] = True if enum['type'] == int: meth['returns'] = 'int' else: meth['returns'] = 'char*' elif meth['returns'] in self.global_enums: enum = self.global_enums[ meth['returns'] ] meth['returns_enum'] = enum['type'] meth['returns_fundamental'] = True if enum['type'] == int: meth['returns'] = 'int' else: meth['returns'] = 'char*' elif meth['returns'].count('::')==1: trace_print( meth ) a,b = meth['returns'].split('::') if a in self.namespaces: if b in self.classes: klass = self.classes[ b ] meth['returns_class'] = a + '::' + b elif '<' in b and '>' in b: warning_print( 'WARN-can not return template: %s'%b ) meth['returns_unknown'] = True elif b in self.global_enums: enum = self.global_enums[ b ] meth['returns_enum'] = enum['type'] meth['returns_fundamental'] = True if enum['type'] == int: meth['returns'] = 'int' else: meth['returns'] = 'char*' else: trace_print( a, b); trace_print( meth); meth['returns_unknown'] = True # +++ elif a in self.classes: klass = self.classes[ a ] if b in klass._public_enums: trace_print( '...found nested enum', b ) enum = klass._public_enums[ b ] meth['returns_enum'] = enum['type'] meth['returns_fundamental'] = True if enum['type'] == int: meth['returns'] = 'int' else: meth['returns'] = 'char*' elif b in klass._public_forward_declares: meth['returns_class'] = True elif b in klass._public_typedefs: typedef = klass._public_typedefs[ b ] meth['returns_fundamental'] = is_fundamental( typedef ) else: trace_print( meth ) # should be a nested class, TODO fix me. meth['returns_unknown'] = True elif '::' in meth['returns']: trace_print('TODO namespace or extra nested return:', meth) meth['returns_unknown'] = True else: trace_print( 'WARN: UNKNOWN RETURN', meth['name'], meth['returns']) meth['returns_unknown'] = True if meth["returns"].startswith(": : "): meth["returns"] = meth["returns"].replace(": : ", "::") for cls in list(self.classes.values()): methnames = cls.get_all_method_names() pvm = cls.get_all_pure_virtual_methods() for d in cls['inherits']: c = d['class'] a = d['access'] # do not depend on this to be 'public' trace_print( 'PARENT CLASS:', c ) if c not in self.classes: trace_print('WARN: parent class not found') if c in self.classes and self.classes[c]['abstract']: p = self.classes[ c ] for meth in p.get_all_methods(): #p["methods"]["public"]: trace_print( '\t\tmeth', meth['name'], 'pure virtual', meth['pure_virtual'] ) if meth['pure_virtual'] and meth['name'] not in methnames: cls['abstract'] = True; break def evaluate_struct_stack(self): """Create a Struct out of the name stack (but not its parts)""" #print( 'eval struct stack', self.nameStack ) #if self.braceDepth != len(self.nameSpaces): return struct = CppStruct(self.nameStack) struct["namespace"] = self.cur_namespace() self.structs[ struct['type'] ] = struct self.structs_order.append( struct ) if self.curClass: struct['parent'] = self.curClass klass = self.classes[ self.curClass ] klass['structs'][self.curAccessSpecifier].append( struct ) if self.curAccessSpecifier == 'public': klass._public_structs[ struct['type'] ] = struct self.curStruct = struct self._structs_brace_level[ struct['type'] ] = self.braceDepth def parse_method_type( self, stack ): trace_print( 'meth type info', stack ) if stack[0] in ':;' and stack[1] != ':': stack = stack[1:] info = { 'debug': ' '.join(stack).replace(' : : ', '::' ).replace(' < ', '<' ).replace(' > ', '> ' ).replace(" >",">").replace(">>", "> >").replace(">>", "> >"), 'class':None, 'namespace':self.cur_namespace(add_double_colon=True), } for tag in 'defined pure_virtual operator constructor destructor extern template virtual static explicit inline friend returns returns_pointer returns_fundamental returns_class default'.split(): info[tag]=False header = stack[ : stack.index('(') ] header = ' '.join( header ) header = header.replace(' : : ', '::' ) header = header.replace(' < ', '<' ) header = header.replace(' > ', '> ' ) header = header.replace('default ', 'default' ) header = header.replace('delete ', 'delete' ) header = header.strip() if '{' in stack: info['defined'] = True self._method_body = self.braceDepth + 1 trace_print( 'NEW METHOD WITH BODY', self.braceDepth ) elif stack[-1] == ';': info['defined'] = False self._method_body = None # not a great idea to be clearing here else: assert 0 if len(stack) > 2 and (stack[-1] == ';' and stack[-2] == 'delete'): info['defined'] = True if len(stack) > 3 and stack[-1] == ';' and stack[-2] == '0' and stack[-3] == '=': info['pure_virtual'] = True r = header.split() name = None if 'operator' in stack: # rare case op overload defined outside of class op = stack[ stack.index('operator')+1 : stack.index('(') ] op = ''.join(op) if not op: if " ".join(['operator', '(', ')', '(']) in " ".join(stack): op = "()" else: trace_print( 'Error parsing operator') return None info['operator'] = op name = 'operator' + op a = stack[ : stack.index('operator') ] elif r: name = r[-1] a = r[ : -1 ] # strip name if name is None: return None #if name.startswith('~'): name = name[1:] while a and a[0] == '}': # strip - can have multiple } } a = a[1:] if '::' in name: #klass,name = name.split('::') # methods can be defined outside of class klass = name[ : name.rindex('::') ] name = name.split('::')[-1] info['class'] = klass if klass in self.classes and not self.curClass: #Class function defined outside the class return None # info['name'] = name #else: info['name'] = name if name.startswith('~'): info['destructor'] = True if 'default' in stack or 'delete' in stack: info['defined'] = True info['default'] = True name = name[1:] elif not a or (name == self.curClass and len(self.curClass)): info['constructor'] = True if 'default' in stack or 'delete' in stack: info['defined'] = True info['default'] = True info['name'] = name for tag in 'extern virtual static explicit inline friend'.split(): if tag in a: info[ tag ] = True; a.remove( tag ) # inplace if 'template' in a: a.remove('template') b = ' '.join( a ) if '>' in b: info['template'] = b[ : b.index('>')+1 ] info['returns'] = b[ b.index('>')+1 : ] # find return type, could be incorrect... TODO if '<typename' in info['template'].split(): typname = info['template'].split()[-1] typname = typname[ : -1 ] # strip '>' if typname not in self._template_typenames: self._template_typenames.append( typname ) else: info['returns'] = ' '.join( a ) else: info['returns'] = ' '.join( a ) info['returns'] = info['returns'].replace(' <', '<').strip() ## be careful with templates, do not count pointers inside template info['returns_pointer'] = info['returns'].split('>')[-1].count('*') if info['returns_pointer']: info['returns'] = info['returns'].replace('*','').strip() info['returns_reference'] = '&' in info['returns'] if info['returns']: info['returns'] = info['returns'].replace('&','').strip() a = [] for b in info['returns'].split(): if b == '__const__': info['returns_const'] = True elif b == 'const': info['returns_const'] = True else: a.append( b ) info['returns'] = ' '.join( a ) info['returns_fundamental'] = is_fundamental( info['returns'] ) return info def evaluate_method_stack(self): """Create a method out of the name stack""" if self.curStruct: trace_print( 'WARN - struct contains methods - skipping' ) trace_print( self.stack ) assert 0 info = self.parse_method_type( self.stack ) if info: if info[ 'class' ] and info['class'] in self.classes: # case where methods are defined outside of class newMethod = CppMethod(self.nameStack, info['name'], info, self.curTemplate) klass = self.classes[ info['class'] ] klass[ 'methods' ][ 'public' ].append( newMethod ) newMethod['parent'] = klass if klass['namespace']: newMethod['path'] = klass['namespace'] + '::' + klass['name'] else: newMethod['path'] = klass['name'] elif self.curClass: # normal case newMethod = CppMethod(self.nameStack, self.curClass, info, self.curTemplate) klass = self.classes[self.curClass] klass['methods'][self.curAccessSpecifier].append(newMethod) newMethod['parent'] = klass if klass['namespace']: newMethod['path'] = klass['namespace'] + '::' + klass['name'] else: newMethod['path'] = klass['name'] else: #non class functions debug_print("FREE FUNCTION") newMethod = CppMethod(self.nameStack, None, info, self.curTemplate) self.functions.append(newMethod) global parseHistory parseHistory.append({"braceDepth": self.braceDepth, "item_type": "method", "item": newMethod}) else: trace_print( 'free function?', self.nameStack ) self.stack = [] def _parse_typedef( self, stack, namespace='' ): if not stack or 'typedef' not in stack: return stack = list( stack ) # copy just to be safe if stack[-1] == ';': stack.pop() while stack and stack[-1].isdigit(): stack.pop() # throw away array size for now idx = stack.index('typedef') if stack[-1] == "]": try: name = namespace + "".join(stack[-4:]) # Strip off the array part so the rest of the parsing is better stack = stack[:-3] except: name = namespace + stack[-1] else: name = namespace + stack[-1] s = '' for a in stack[idx+1:-1]: if a == '{': break if not s or s[-1] in ':<>' or a in ':<>': s += a # keep compact else: s += ' ' + a # spacing r = {'name':name, 'raw':s, 'type':s} if not is_fundamental(s): if 'struct' in s.split(): pass # TODO is this right? "struct ns::something" elif '::' not in s: s = namespace + s # only add the current name space if no namespace given r['type'] = s if s: return r def evaluate_typedef(self): ns = self.cur_namespace(add_double_colon=True) res = self._parse_typedef( self.stack, ns ) if res: name = res['name'] self.typedefs[ name ] = res['type'] if name not in self.typedefs_order: self.typedefs_order.append( name ) def evaluate_property_stack(self): """Create a Property out of the name stack""" global parseHistory assert self.stack[-1] == ';' debug_print( "trace" ) if self.nameStack[0] == 'typedef': if self.curClass: typedef = self._parse_typedef( self.stack ) name = typedef['name'] klass = self.classes[ self.curClass ] klass[ 'typedefs' ][ self.curAccessSpecifier ].append( name ) if self.curAccessSpecifier == 'public': klass._public_typedefs[ name ] = typedef['type'] Resolver.SubTypedefs[ name ] = self.curClass else: assert 0 elif self.curStruct or self.curClass: if len(self.nameStack) == 1: #See if we can de anonymize the type filteredParseHistory = [h for h in parseHistory if h["braceDepth"] == self.braceDepth] if len(filteredParseHistory) and filteredParseHistory[-1]["item_type"] == "class": self.nameStack.insert(0, filteredParseHistory[-1]["item"]["name"]) debug_print("DEANONYMOIZING %s to type '%s'"%(self.nameStack[1], self.nameStack[0])) if "," in self.nameStack: #Maybe we have a variable list #Figure out what part is the variable separator but remember templates of function pointer #First find left most comma outside of a > and ) leftMostComma = 0; for i in range(0, len(self.nameStack)): name = self.nameStack[i] if name in (">", ")"): leftMostComma = 0 if leftMostComma == 0 and name == ",": leftMostComma = i # Is it really a list of variables? if leftMostComma != 0: trace_print("Multiple variables for namestack in %s. Separating processing"%self.nameStack) orig_nameStack = self.nameStack[:] orig_stack = self.stack[:] type_nameStack = orig_nameStack[:leftMostComma-1] for name in orig_nameStack[leftMostComma - 1::2]: self.nameStack = type_nameStack + [name] self.stack = orig_stack[:] # Not maintained for mucking, but this path it doesnt matter self.evaluate_property_stack() return newVar = CppVariable(self.nameStack) newVar['namespace'] = self.current_namespace() if self.curStruct: self.curStruct[ 'fields' ].append( newVar ) newVar['property_of_struct'] = self.curStruct elif self.curClass: klass = self.classes[self.curClass] klass["properties"][self.curAccessSpecifier].append(newVar) newVar['property_of_class'] = klass['name'] parseHistory.append({"braceDepth": self.braceDepth, "item_type": "variable", "item": newVar}) else: debug_print( "Found Global variable" ) newVar = CppVariable(self.nameStack) self.variables.append(newVar) self.stack = [] # CLEAR STACK def evaluate_class_stack(self): """Create a Class out of the name stack (but not its parts)""" #dont support sub classes today #print( 'eval class stack', self.nameStack ) parent = self.curClass if self.braceDepth > len( self.nameSpaces) and parent: trace_print( 'HIT NESTED SUBCLASS' ) self.accessSpecifierStack.append(self.curAccessSpecifier) elif self.braceDepth != len(self.nameSpaces): error_print( 'ERROR: WRONG BRACE DEPTH' ) return # When dealing with typedefed structs, get rid of typedef keyword to handle later on if self.nameStack[0] == "typedef": del self.nameStack[0] if len(self.nameStack) == 1: self.anon_struct_counter += 1 # We cant handle more than 1 anonymous struct, so name them uniquely self.nameStack.append("<anon-struct-%d>"%self.anon_struct_counter) if self.nameStack[0] == "class": self.curAccessSpecifier = 'private' else:#struct self.curAccessSpecifier = 'public' debug_print("curAccessSpecifier changed/defaulted to %s"%self.curAccessSpecifier) if self.nameStack[0] == "union": newClass = CppUnion(self.nameStack) if newClass['name'] == 'union ': self.anon_union_counter = [self.braceDepth, 2] else: self.anon_union_counter = [self.braceDepth, 1] trace_print( 'NEW UNION', newClass['name'] ) else: newClass = CppClass(self.nameStack, self.curTemplate) trace_print( 'NEW CLASS', newClass['name'] ) newClass["declaration_method"] = self.nameStack[0] self.classes_order.append( newClass ) # good idea to save ordering self.stack = [] # fixes if class declared with ';' in closing brace if parent: newClass["namespace"] = self.classes[ parent ]['namespace'] + '::' + parent newClass['parent'] = parent self.classes[ parent ]['nested_classes'].append( newClass ) ## supports nested classes with the same name ## self.curClass = key = parent+'::'+newClass['name'] self._classes_brace_level[ key ] = self.braceDepth elif newClass['parent']: # nested class defined outside of parent. A::B {...} parent = newClass['parent'] newClass["namespace"] = self.classes[ parent ]['namespace'] + '::' + parent self.classes[ parent ]['nested_classes'].append( newClass ) ## supports nested classes with the same name ## self.curClass = key = parent+'::'+newClass['name'] self._classes_brace_level[ key ] = self.braceDepth else: newClass["namespace"] = self.cur_namespace() key = newClass['name'] self.curClass = newClass["name"] self._classes_brace_level[ newClass['name'] ] = self.braceDepth if not key.endswith("::") and not key.endswith(" ") and len(key) != 0: if key in self.classes: trace_print( 'ERROR name collision:', key ) self.classes[key].show() trace_print('-'*80) newClass.show() assert key not in self.classes # namespace collision self.classes[ key ] = newClass global parseHistory parseHistory.append({"braceDepth": self.braceDepth, "item_type": "class", "item": newClass}) def evalute_forward_decl(self): trace_print( 'FORWARD DECL', self.nameStack ) assert self.nameStack[0] in ('class', 'struct') name = self.nameStack[-1] if self.curClass: klass = self.classes[ self.curClass ] klass['forward_declares'][self.curAccessSpecifier].append( name ) if self.curAccessSpecifier == 'public': klass._public_forward_declares.append( name ) else: self._forward_decls.append( name ) class CppHeader( _CppHeader ): """Parsed C++ class header Variables produced: self.classes - Dictionary of classes found in a given header file where the key is the name of the class """ IGNORE_NAMES = '__extension__'.split() def show(self): for className in list(self.classes.keys()):self.classes[className].show() def __init__(self, headerFileName, argType="file", encoding=None, **kwargs): """Create the parsed C++ header file parse tree headerFileName - Name of the file to parse OR actual file contents (depends on argType) argType - Indicates how to interpret headerFileName as a file string or file name kwargs - Supports the following keywords """ ## reset global state ## global doxygenCommentCache doxygenCommentCache = "" CppVariable.Vars = [] CppStruct.Structs = [] if (argType == "file"): self.headerFileName = os.path.expandvars(headerFileName) self.mainClass = os.path.split(self.headerFileName)[1][:-2] headerFileStr = "" elif argType == "string": self.headerFileName = "" self.mainClass = "???" headerFileStr = headerFileName else: raise Exception("Arg type must be either file or string") self.curClass = "" # nested classes have parent::nested, but no extra namespace, # this keeps the API compatible, TODO proper namespace for everything. Resolver.CLASSES = {} self.classes = Resolver.CLASSES #Functions that are not part of a class self.functions = [] self.pragmas = [] self.defines = [] self.includes = [] self._precomp_macro_buf = [] #for internal purposes, will end up filling out pragmras and defines at the end self.enums = [] self.variables = [] self.global_enums = {} self.nameStack = [] self.nameSpaces = [] self.curAccessSpecifier = 'private' # private is default self.curTemplate = None self.accessSpecifierStack = [] self.accessSpecifierScratch = [] debug_print("curAccessSpecifier changed/defaulted to %s"%self.curAccessSpecifier) self.initextra() # Old namestacks for a given level self.nameStackHistory = [] self.anon_struct_counter = 0 self.anon_union_counter = [-1, 0] self.templateRegistry = [] if (len(self.headerFileName)): fd = io.open(self.headerFileName, 'r', encoding=encoding) headerFileStr = "".join(fd.readlines()) fd.close() # Make sure supportedAccessSpecifier are sane for i in range(0, len(supportedAccessSpecifier)): if " " not in supportedAccessSpecifier[i]: continue supportedAccessSpecifier[i] = re.sub("[ ]+", " ", supportedAccessSpecifier[i]).strip() # Strip out template declarations templateSectionsToSliceOut = [] try: for m in re.finditer("template[\t ]*<[^>]*>", headerFileStr): start = m.start() # Search for the final '>' which may or may not be caught in the case of nexted <>'s for i in range(start, len(headerFileStr)): if headerFileStr[i] == '<': firstBracket = i break ltgtStackCount = 1 #Now look for fianl '>' for i in range(firstBracket + 1, len(headerFileStr)): if headerFileStr[i] == '<': ltgtStackCount += 1 elif headerFileStr[i] == '>': ltgtStackCount -= 1 if ltgtStackCount == 0: end = i break templateSectionsToSliceOut.append((start, end)) # Now strip out all instances of the template templateSectionsToSliceOut.reverse() for tslice in templateSectionsToSliceOut: # Replace the template symbol with a single symbol template_symbol="CppHeaderParser_template_%d"%len(self.templateRegistry) self.templateRegistry.append(headerFileStr[tslice[0]: tslice[1]+1]) newlines = headerFileStr[tslice[0]: tslice[1]].count("\n") * "\n" #Keep line numbers the same headerFileStr = headerFileStr[:tslice[0]] + newlines + " " + template_symbol + " " + headerFileStr[tslice[1] + 1:] except: pass # Change multi line #defines and expressions to single lines maintaining line nubmers # Based from http://stackoverflow.com/questions/2424458/regular-expression-to-match-cs-multiline-preprocessor-statements matches = re.findall(r'(?m)^(?:.*\\\r?\n)+.*$', headerFileStr) is_define = re.compile(r'[ \t\v]*#[Dd][Ee][Ff][Ii][Nn][Ee]') for m in matches: #Keep the newlines so that linecount doesnt break num_newlines = len([a for a in m if a=="\n"]) if is_define.match(m): new_m = m.replace("\n", "<CppHeaderParser_newline_temp_replacement>\\n") else: # Just expression taking up multiple lines, make it take 1 line for easier parsing new_m = m.replace("\\\n", " ") if (num_newlines > 0): new_m += "\n"*(num_newlines) headerFileStr = headerFileStr.replace(m, new_m) #Filter out Extern "C" statements. These are order dependent matches = re.findall(re.compile(r'extern[\t ]+"[Cc]"[\t \n\r]*{', re.DOTALL), headerFileStr) for m in matches: #Keep the newlines so that linecount doesnt break num_newlines = len([a for a in m if a=="\n"]) headerFileStr = headerFileStr.replace(m, "\n" * num_newlines) headerFileStr = re.sub(r'extern[ ]+"[Cc]"[ ]*', "", headerFileStr) #Filter out any ignore symbols that end with "()" to account for #define magic functions for ignore in ignoreSymbols: if not ignore.endswith("()"): continue while True: locStart = headerFileStr.find(ignore[:-1]) if locStart == -1: break; locEnd = None #Now walk till we find the last paren and account for sub parens parenCount = 1 inQuotes = False for i in range(locStart + len(ignore) - 1, len(headerFileStr)): c = headerFileStr[i] if not inQuotes: if c == "(": parenCount += 1 elif c == ")": parenCount -= 1 elif c == '"': inQuotes = True if parenCount == 0: locEnd = i + 1 break; else: if c == '"' and headerFileStr[i-1] != '\\': inQuotes = False if locEnd: #Strip it out but keep the linecount the same so line numbers are right match_str = headerFileStr[locStart:locEnd] debug_print("Striping out '%s'"%match_str) num_newlines = len([a for a in match_str if a=="\n"]) headerFileStr = headerFileStr.replace(headerFileStr[locStart:locEnd], "\n"*num_newlines) self.braceDepth = 0 lex.lex() lex.input(headerFileStr) global curLine global curChar curLine = 0 curChar = 0 try: while True: tok = lex.token() if not tok: break if self.anon_union_counter[0] == self.braceDepth and self.anon_union_counter[1]: self.anon_union_counter[1] -= 1 tok.value = TagStr(tok.value, lineno=tok.lineno) #debug_print("TOK: %s"%tok) if tok.type == 'NAME' and tok.value in self.IGNORE_NAMES: continue if tok.type != 'TEMPLATE_NAME': self.stack.append( tok.value ) curLine = tok.lineno curChar = tok.lexpos if (tok.type in ('PRECOMP_MACRO', 'PRECOMP_MACRO_CONT')): debug_print("PRECOMP: %s"%tok) self._precomp_macro_buf.append(tok.value) self.stack = [] self.nameStack = [] continue if tok.type == 'TEMPLATE_NAME': try: templateId = int(tok.value.replace("CppHeaderParser_template_","")) self.curTemplate = self.templateRegistry[templateId] except: pass if (tok.type == 'OPEN_BRACE'): if len(self.nameStack) >= 2 and is_namespace(self.nameStack): # namespace {} with no name used in boost, this sets default? if self.nameStack[1] == "__IGNORED_NAMESPACE__CppHeaderParser__":#Used in filtering extern "C" self.nameStack[1] = "" self.nameSpaces.append(extract_namespace_namestack(self.nameStack)) ns = self.cur_namespace(); self.stack = [] if ns not in self.namespaces: self.namespaces.append( ns ) # Detect special condition of macro magic before class declaration so we # can filter it out if 'class' in self.nameStack and self.nameStack[0] != 'class': classLocationNS = self.nameStack.index("class") classLocationS = self.stack.index("class") if "(" not in self.nameStack[classLocationNS:]: debug_print("keyword 'class' found in unexpected location in nameStack, must be following #define magic. Process that before moving on") origNameStack = self.nameStack origStack = self.stack #Process first part of stack which is probably #define macro magic and may cause issues self.nameStack = self.nameStack[:classLocationNS] self.stack = self.stack[:classLocationS] try: self.evaluate_stack() except: debug_print("Error processing #define magic... Oh well") #Process rest of stack self.nameStack = origNameStack[classLocationNS:] self.stack = origStack[classLocationS:] if len(self.nameStack) and not is_enum_namestack(self.nameStack): self.evaluate_stack() else: self.nameStack.append(tok.value) if self.stack and self.stack[0] == 'class': self.stack = [] self.braceDepth += 1 elif (tok.type == 'CLOSE_BRACE'): if self.braceDepth == 0: continue if (self.braceDepth == len(self.nameSpaces)): tmp = self.nameSpaces.pop() self.stack = [] # clear stack when namespace ends? if len(self.nameStack) and is_enum_namestack(self.nameStack): self.nameStack.append(tok.value) elif self.braceDepth < 10: self.evaluate_stack() else: self.nameStack = [] self.braceDepth -= 1 #self.stack = []; print 'BRACE DEPTH', self.braceDepth, 'NS', len(self.nameSpaces) if self.curClass: debug_print( 'CURBD %s'%self._classes_brace_level[ self.curClass ] ) if (self.braceDepth == 0) or (self.curClass and self._classes_brace_level[self.curClass]==self.braceDepth): trace_print( 'END OF CLASS DEF' ) if self.accessSpecifierStack: self.curAccessSpecifier = self.accessSpecifierStack[-1] self.accessSpecifierStack = self.accessSpecifierStack[:-1] if self.curClass and self.classes[ self.curClass ]['parent']: self.curClass = self.classes[ self.curClass ]['parent'] else: self.curClass = ""; #self.curStruct = None self.stack = [] #if self.curStruct: self.curStruct = None if self.braceDepth == 0 or (self.curStruct and self._structs_brace_level[self.curStruct['type']]==self.braceDepth): trace_print( 'END OF STRUCT DEF' ) self.curStruct = None if self._method_body and (self.braceDepth + 1) <= self._method_body: self._method_body = None; self.stack = []; self.nameStack = []; trace_print( 'FORCE CLEAR METHBODY' ) if (tok.type == 'OPEN_PAREN'): self.nameStack.append(tok.value) elif (tok.type == 'CLOSE_PAREN'): self.nameStack.append(tok.value) elif (tok.type == 'OPEN_SQUARE_BRACKET'): self.nameStack.append(tok.value) elif (tok.type == 'CLOSE_SQUARE_BRACKET'): self.nameStack.append(tok.value) elif (tok.type == 'TAB'): pass elif (tok.type == 'EQUALS'): self.nameStack.append(tok.value) elif (tok.type == 'COMMA'): self.nameStack.append(tok.value) elif (tok.type == 'BACKSLASH'): self.nameStack.append(tok.value) elif (tok.type == 'DIVIDE'): self.nameStack.append(tok.value) elif (tok.type == 'PIPE'): self.nameStack.append(tok.value) elif (tok.type == 'PERCENT'): self.nameStack.append(tok.value) elif (tok.type == 'CARET'): self.nameStack.append(tok.value) elif (tok.type == 'EXCLAMATION'): self.nameStack.append(tok.value) elif (tok.type == 'SQUOTE'): pass elif (tok.type == 'NUMBER' or tok.type == 'FLOAT_NUMBER'): self.nameStack.append(tok.value) elif (tok.type == 'MINUS'): self.nameStack.append(tok.value) elif (tok.type == 'PLUS'): self.nameStack.append(tok.value) elif (tok.type == 'STRING_LITERAL'): self.nameStack.append(tok.value) elif (tok.type == 'ELLIPSIS'): self.nameStack.append(tok.value) elif (tok.type == 'DOT'): pass # preserve behaviour and eat individual fullstops elif (tok.type == 'NAME' or tok.type == 'AMPERSTAND' or tok.type == 'ASTERISK' or tok.type == 'CHAR_LITERAL'): if tok.value in ignoreSymbols: debug_print("Ignore symbol %s"%tok.value) elif (tok.value == 'class'): self.nameStack.append(tok.value) elif tok.value in supportedAccessSpecifier: if len(self.nameStack) and self.nameStack[0] in ("class", "struct", "union"): self.nameStack.append(tok.value) elif self.braceDepth == len(self.nameSpaces) + 1 or self.braceDepth == (len(self.nameSpaces) + len(self.curClass.split("::"))): self.curAccessSpecifier = tok.value; self.accessSpecifierScratch.append(tok.value) debug_print("curAccessSpecifier updated to %s"%self.curAccessSpecifier) self.stack = [] else: self.nameStack.append(tok.value) if self.anon_union_counter[0] == self.braceDepth: self.anon_union_counter = [-1, 0] elif (tok.type == 'COLON'): #Dont want colon to be first in stack if len(self.nameStack) == 0: self.accessSpecifierScratch = [] continue # Handle situation where access specifiers can be multi words such as "public slots" jns = " ".join(self.accessSpecifierScratch + self.nameStack) if jns in supportedAccessSpecifier: self.curAccessSpecifier = jns; debug_print("curAccessSpecifier updated to %s"%self.curAccessSpecifier) self.stack = [] self.nameStack = [] else: self.nameStack.append(tok.value) self.accessSpecifierScratch = [] elif (tok.type == 'SEMI_COLON'): if self.anon_union_counter[0] == self.braceDepth and self.anon_union_counter[1]: debug_print("Creating anonymous union") #Force the processing of an anonymous union saved_namestack = self.nameStack[:] saved_stack = self.stack[:] self.nameStack = [""] self.stack = self.nameStack + [";"] self.nameStack = self.nameStack[0:1] debug_print("pre eval anon stack") self.evaluate_stack( tok.type ) debug_print("post eval anon stack") self.nameStack = saved_namestack self.stack = saved_stack self.anon_union_counter = [-1, 0]; if (self.braceDepth < 10): self.evaluate_stack( tok.type ) self.stack = [] self.nameStack = [] except: if (debug): raise raise CppParseError("Not able to parse %s on line %d evaluating \"%s\"\nError around: %s" % (self.headerFileName, tok.lineno, tok.value, " ".join(self.nameStack))) self.finalize() global parseHistory parseHistory = [] # Delete some temporary variables for key in ["_precomp_macro_buf", "nameStack", "nameSpaces", "curAccessSpecifier", "accessSpecifierStack", "accessSpecifierScratch", "nameStackHistory", "anon_struct_counter", "anon_union_counter", "_classes_brace_level", "_forward_decls", "stack", "mainClass", "curStruct", "_template_typenames", "_method_body", "braceDepth", "_structs_brace_level", "typedefs_order", "curTemplate", "templateRegistry"]: del self.__dict__[key] def evaluate_stack(self, token=None): """Evaluates the current name stack""" global doxygenCommentCache self.nameStack = filter_out_attribute_keyword(self.nameStack) self.stack = filter_out_attribute_keyword(self.stack) nameStackCopy = self.nameStack[:] debug_print( "Evaluating stack %s\n BraceDepth: %s (called from %d)" %(self.nameStack,self.braceDepth, inspect.currentframe().f_back.f_lineno)) #Handle special case of overloading operator () if "operator()(" in "".join(self.nameStack): operator_index = self.nameStack.index("operator") self.nameStack.pop(operator_index + 2) self.nameStack.pop(operator_index + 1) self.nameStack[operator_index] = "operator()" if (len(self.curClass)): debug_print( "%s (%s) "%(self.curClass, self.curAccessSpecifier)) else: debug_print( "<anonymous> (%s) "%self.curAccessSpecifier) #Filter special case of array with casting in it try: bracePos = self.nameStack.index("[") parenPos = self.nameStack.index("(") if bracePos == parenPos - 1: endParen = self.nameStack.index(")") self.nameStack = self.nameStack[:bracePos + 1] + self.nameStack[endParen + 1:] debug_print("Filtered namestack to=%s"%self.nameStack) except: pass #if 'typedef' in self.nameStack: self.evaluate_typedef() # allows nested typedefs, probably a bad idea if (not self.curClass and 'typedef' in self.nameStack and (('struct' not in self.nameStack and 'union' not in self.nameStack) or self.stack[-1] == ";") and not is_enum_namestack(self.nameStack)): trace_print('STACK', self.stack) self.evaluate_typedef() return elif (len(self.nameStack) == 0): debug_print( "trace" ) debug_print( "(Empty Stack)" ) return elif (self.nameStack[0] == "namespace"): #Taken care of outside of here pass elif len(self.nameStack) == 2 and self.nameStack[0] == "friend":#friend class declaration pass elif len(self.nameStack) >= 2 and self.nameStack[0] == 'using' and self.nameStack[1] == 'namespace': pass # TODO elif is_enum_namestack(self.nameStack): debug_print( "trace" ) self.evaluate_enum_stack() elif self._method_body and (self.braceDepth + 1) > self._method_body: trace_print( 'INSIDE METHOD DEF' ) elif is_method_namestack(self.stack) and not self.curStruct and '(' in self.nameStack: debug_print( "trace" ) if self.braceDepth > 0: if "{" in self.stack and self.stack[0] != '{' and self.stack[-1] == ';' and self.braceDepth == 1: #Special case of a method defined outside a class that has a body pass else: self.evaluate_method_stack() else: #Free function self.evaluate_method_stack() elif (len(self.nameStack) == 1 and len(self.nameStackHistory) > self.braceDepth and (self.nameStackHistory[self.braceDepth][0][0:2] == ["typedef", "struct"] or self.nameStackHistory[self.braceDepth][0][0:2] == ["typedef", "union"])): # Look for the name of a typedef struct: struct typedef {...] StructName; or unions to get renamed debug_print("found the naming of a union") type_name_to_rename = self.nameStackHistory[self.braceDepth][1] new_name = self.nameStack[0] type_to_rename = self.classes[type_name_to_rename] type_to_rename["name"] = self.nameStack[0] #Now re install it in its new location self.classes[new_name] = type_to_rename if new_name != type_name_to_rename: del self.classes[type_name_to_rename] elif is_property_namestack(self.nameStack) and self.stack[-1] == ';': debug_print( "trace" ) if self.nameStack[0] in ('class', 'struct') and len(self.stack) == 3: self.evalute_forward_decl() elif len(self.nameStack) >= 2 and (self.nameStack[0]=='friend' and self.nameStack[1]=='class'): pass else: self.evaluate_property_stack() # catches class props and structs in a namespace elif self.nameStack[0] in ("class", "struct", "union") or self.nameStack[0] == 'typedef' and self.nameStack[1] in ('struct', 'union'): #Parsing a union can reuse much of the class parsing debug_print( "trace" ) self.evaluate_class_stack() elif not self.curClass: debug_print( "trace" ) if is_enum_namestack(self.nameStack): self.evaluate_enum_stack() elif self.curStruct and self.stack[-1] == ';': self.evaluate_property_stack() # this catches fields of global structs self.nameStack = [] doxygenCommentCache = "" elif (self.braceDepth < 1): debug_print( "trace" ) #Ignore global stuff for now debug_print( "Global stuff: %s"%self.nameStack ) self.nameStack = [] doxygenCommentCache = "" elif (self.braceDepth > len(self.nameSpaces) + 1): debug_print( "trace" ) self.nameStack = [] doxygenCommentCache = "" try: self.nameStackHistory[self.braceDepth] = (nameStackCopy, self.curClass) except: self.nameStackHistory.append((nameStackCopy, self.curClass)) self.nameStack = [] # its a little confusing to have some if/else above return and others not, and then clearning the nameStack down here doxygenCommentCache = "" self.curTemplate = None def evaluate_enum_stack(self): """Create an Enum out of the name stack""" debug_print( "evaluating enum" ) newEnum = CppEnum(self.nameStack) if len(list(newEnum.keys())): if len(self.curClass): newEnum["namespace"] = self.cur_namespace(False) klass = self.classes[self.curClass] klass["enums"][self.curAccessSpecifier].append(newEnum) if self.curAccessSpecifier == 'public' and 'name' in newEnum: klass._public_enums[ newEnum['name'] ] = newEnum else: newEnum["namespace"] = self.cur_namespace(True) self.enums.append(newEnum) if 'name' in newEnum and newEnum['name']: self.global_enums[ newEnum['name'] ] = newEnum #This enum has instances, turn them into properties if "instances" in newEnum: instanceType = "enum" if "name" in newEnum: instanceType = newEnum["name"] for instance in newEnum["instances"]: self.nameStack = [instanceType, instance] self.evaluate_property_stack() del newEnum["instances"] def strip_parent_keys(self): """Strip all parent (and method) keys to prevent loops""" obj_queue = [self] while len(obj_queue): obj = obj_queue.pop() trace_print("pop %s type %s"%(obj, type(obj))) try: if "parent" in obj.keys(): del obj["parent"] trace_print("Stripped parent from %s"%obj.keys()) except: pass try: if "method" in obj.keys(): del obj["method"] trace_print("Stripped method from %s"%obj.keys()) except: pass # Figure out what sub types are one of ours try: if not hasattr(obj, 'keys'): obj = obj.__dict__ for k in obj.keys(): trace_print("-Try key %s"%(k)) trace_print("-type %s"%(type(obj[k]))) if k in ["nameStackHistory", "parent", "_public_typedefs"]: continue if type(obj[k]) == list: for i in obj[k]: trace_print("push l %s"%i) obj_queue.append(i) elif type(obj[k]) == dict: if len(obj): trace_print("push d %s"%obj[k]) obj_queue.append(obj[k]) elif type(obj[k]) == type(type(0)): if type(obj[k]) == int: obj[k] = "int" elif type(obj[k]) == str: obj[k] = "string" else: obj[k] = "???" trace_print("next key\n") except: trace_print("Exception") def toJSON(self, indent=4): """Converts a parsed structure to JSON""" import json self.strip_parent_keys() try: del self.__dict__["classes_order"] except: pass return json.dumps(self.__dict__, indent=indent) def __repr__(self): rtn = { "classes": self.classes, "functions": self.functions, "enums": self.enums, "variables": self.variables, } return repr(rtn) def __str__(self): rtn = "" for className in list(self.classes.keys()): rtn += "%s\n"%self.classes[className] if self.functions: rtn += "// functions\n" for f in self.functions: rtn += "%s\n"%f if self.variables: rtn += "// variables\n" for f in self.variables: rtn += "%s\n"%f if self.enums: rtn += "// enums\n" for f in self.enums: rtn += "%s\n"%f return rtn