#!/usr/bin/python # type: ignore # # 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 inspect import os import re import sys import ply.lex as lex def lineno(): """Returns the current line number in our program.""" return inspect.currentframe().f_back.f_lineno version = __version__ = "2.7.4" 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', ] 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 = "'" # 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 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 {}\nto\n{}".format(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 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 = "{} {}".format(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 += "{} {}, ".format(inheritClass["access"], inheritClass["class"]) rtn += "\n" rtn += " {\n" for accessSpecifier in supportedAccessSpecifier: rtn += " %s\n" % (accessSpecifier) # Enums if len(self["enums"][accessSpecifier]): rtn += " \n" for enum in self["enums"][accessSpecifier]: rtn += " %s\n" % (repr(enum)) # Properties if len(self["properties"][accessSpecifier]): rtn += " \n" for property in self["properties"][accessSpecifier]: rtn += " %s\n" % (repr(property)) # Methods if len(self["methods"][accessSpecifier]): rtn += " \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 = "{} {}".format(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 += "{} {}, ".format(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 = "{} {}".format(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 # transforms ['someclass', '(', '0', '0', '0', ')'] into "someclass(0,0,0)'" r = [] hit = False for a in stack: if a == '(': hit = True elif a == ')': hit = False if hit or a == ')': r[-1] = r[-1] + a else: r.append(a) return r 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: {} ({})'.format(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')]) # Example: operator const bool(); joiner = " " if nameStack[nameStack.index('(') - 1].isalpha() else "" self["name"] = joiner.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(" ,", ",") 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 r" \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 # Create the variable now while len(paramsStack): # Find commas that are not nexted in <>'s like template types open_template_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 elm == ',' and open_template_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 :] 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 = {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 = filter(lambda x: x.isdigit(), arrayStack) # 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 = {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 : ..." syntax self["name"] = preBraceStack[1] self["type"] = preBraceStack[3] elif len(preBraceStack) >= 2 and ":" not in nameStack and "typedef" not in nameStack: # enum "enum ..." 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: 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 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 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: 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("\\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() 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'.split(): info[tag] = False header = stack[: stack.index('(')] header = ' '.join(header) header = header.replace(' : : ', '::') header = header.replace(' < ', '<') header = header.replace(' > ', '> ') 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) > 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 name = name[1:] elif not a or (name == self.curClass and len(self.curClass)): info['constructor'] = 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 '' 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 {} to type '{}'".format(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("" % 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", **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 = open(self.headerFileName) 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", "\\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(self.nameStack[1]) 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' and not is_enum_namestack(self.nameStack) ): 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): 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 == '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("{} ({}) ".format(self.curClass, self.curAccessSpecifier)) else: debug_print(" (%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 {} type {}".format(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(int): 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