our_knp/examples/tools/python-bindings-gen/py-gen/CppHeaderParser_l/CppHeaderParser.py
int 8f43927575 Initial import: KNP experiments (examples, viz, cats-dogs, reports)
examples/ is based on KasperskyLab/knp at 55470ed6 with local changes
to the mnist-learn altai/blifat models, cuda-backend CMake and model_loader.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:17:15 +03:00

2978 lines
116 KiB
Python

#!/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 += " <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 = "{} {}".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 <enum_name> : <type> ..." syntax
self["name"] = preBraceStack[1]
self["type"] = preBraceStack[3]
elif len(preBraceStack) >= 2 and ":" not in nameStack 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:
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:
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()
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 '<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 {} 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("<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", **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", "<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(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("<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 {} 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