Source code for CppHeaderParser.CppHeaderParser
#!/usr/bin/python
#
# Author: Jashua R. Cloutier (contact via https://bitbucket.org/senex)
# Project: http://senexcanis.com/open-source/cppheaderparser/
#
# Copyright (C) 2011, Jashua R. Cloutier
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# * Neither the name of Jashua R. Cloutier nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission. Stories,
# blog entries etc making reference to this project may mention the
# name Jashua R. Cloutier in terms of project originator/creator etc.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
#
# The CppHeaderParser.py script is written in Python 2.4 and released to
# the open source community for continuous improvements under the BSD
# 2.0 new license, which can be found at:
#
# http://www.opensource.org/licenses/bsd-license.php
#
from __future__ import print_function
from collections import deque
import os
import sys
import re
import io
import inspect
from .lexer import Lexer
from .doxygen import extract_doxygen_method_params
try:
from .version import __version__
except ImportError:
__version__ = "devel"
version = __version__
# Controls error_print
print_errors = 1
# Controls warning_print
print_warnings = 1
# Controls debug_print
debug = 1 if os.environ.get("CPPHEADERPARSER_DEBUG") == "1" else 0
# Controls trace_print
debug_trace = 0
if sys.version_info >= (3, 3):
# `raise e from src_e` syntax only supported on python 3.3+
exec("def raise_exc(e, src_e): raise e from src_e", globals())
else:
def raise_exc(e, src_e):
raise e
def error_print(fmt, *args):
if print_errors:
fmt = "[%4d] " + fmt
args = (inspect.currentframe().f_back.f_lineno,) + args
print(fmt % args)
def warning_print(fmt, *args):
if print_warnings:
fmt = "[%4d] " + fmt
args = (inspect.currentframe().f_back.f_lineno,) + args
print(fmt % args)
if debug:
class _debug_caller_lineno:
def __str__(self):
return str(inspect.currentframe().f_back.f_back.f_back.f_lineno)
debug_caller_lineno = _debug_caller_lineno()
def debug_print(fmt, *args):
fmt = "[%4d] " + fmt
args = (inspect.currentframe().f_back.f_lineno,) + args
print(fmt % args)
else:
debug_caller_lineno = None
def debug_print(fmt, *args):
pass
if debug_trace:
def trace_print(*args):
sys.stdout.write("[%s]" % (inspect.currentframe().f_back.f_lineno))
for a in args:
sys.stdout.write(" %s" % a)
sys.stdout.write("\n")
else:
def trace_print(*args):
pass
#: Access specifiers
supportedAccessSpecifier = ["public", "protected", "private"]
#: Symbols to ignore, usually special macros
ignoreSymbols = ["Q_OBJECT"]
_BRACE_REASON_OTHER = 0
_BRACE_REASON_NS = 1
_BRACE_REASON_EXTERN = 2
# 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
_fundamentals = {
"size_t",
"struct",
"union",
"unsigned",
"signed",
"bool",
"char",
"short",
"int",
"float",
"double",
"long",
"void",
"*",
}
def is_fundamental(s):
for a in s.split():
if a not in _fundamentals:
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 stack[-1] == ":":
r = True
elif "{" 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 is_enum_namestack(nameStack):
"""Determines if a namestack is an enum namestack"""
if not nameStack:
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 set_location_info(thing, location):
filename, line_number = location
if filename:
thing["filename"] = filename
thing["line_number"] = line_number
_nhack = re.compile(r"[A-Za-z_][A-Za-z0-9_]*")
def _split_namespace(namestack):
"""
Given a list of name elements, find the namespace portion
and return that as a string
:rtype: Tuple[str, list]
"""
# TODO: this should be using tokens instead of nhack
typename = None
if namestack and namestack[0] == "typename":
typename = namestack[0]
namestack = namestack[1:]
last_colon = None
for i, n in enumerate(namestack):
if n == "::":
last_colon = i
if i and n != "::" and not _nhack.match(n):
break
if last_colon:
ns, namestack = (
"".join(namestack[: last_colon + 1]),
namestack[last_colon + 1 :],
)
else:
ns = ""
if typename:
namestack = [typename] + namestack
return ns, namestack
def _iter_ns_str_reversed(namespace):
"""
Take a namespace string, and yield successively smaller portions
of it (ex foo::bar::baz::, foo::bar::, foo::). The last item yielded
will always be an empty string
"""
if namespace:
parts = namespace.split("::")
for i in range(len(parts) - 1, 0, -1):
yield "::".join(parts[:i]) + "::"
yield ""
def _split_by_comma(namestack):
while namestack:
if "," not in namestack:
yield namestack
break
idx = namestack.index(",")
ns = namestack[:idx]
yield ns
namestack = namestack[idx + 1 :]
[docs]class TagStr(str):
"""Wrapper for a string that allows us to store the line number associated with it"""
def __new__(cls, *args, **kwargs):
location = kwargs.pop("location")
s = str.__new__(cls, *args, **kwargs)
s.location = location
return s
[docs]class CppParseError(Exception):
def __init__(self, msg, tok=None):
Exception.__init__(self, msg)
self.tok = tok
[docs]class CppTemplateParam(dict):
"""
Dictionary that contains the following:
- ``decltype`` - If True, this is a decltype
- ``param`` - Parameter value or typename
- ``params`` - Only present if a template specialization. Is a list of
:class:`.CppTemplateParam`
- ``...`` - If True, indicates a parameter pack
"""
def __init__(self):
self["param"] = ""
self["decltype"] = False
self["..."] = False
def __str__(self):
s = []
if self["decltype"]:
s.append("decltype")
s.append(self["param"])
params = self.get("params")
if params is not None:
s.append("<%s>" % ",".join(str(p) for p in params))
if self["..."]:
if s:
s[-1] = s[-1] + "..."
else:
s.append("...")
return "".join(s)
[docs]class CppBaseDecl(dict):
"""
Dictionary that contains the following
- ``access`` - Anything in supportedAccessSpecifier
- ``class`` - Name of the type, along with template specializations
- ``decl_name`` - Name of the type only
- ``decl_params`` - Only present if a template specialization (Foo<int>).
Is a list of :class:`.CppTemplateParam`.
- ``decltype`` - True/False indicates a decltype, the contents are in
``decl_name``
- ``virtual`` - True/False indicates virtual inheritance
- ``...`` - True/False indicates a parameter pack
"""
def __init__(self, default_access):
self["access"] = default_access
self["class"] = ""
self["decl_name"] = ""
self["decltype"] = False
self["virtual"] = False
self["..."] = False
def _fix_classname(self):
# set class to the full decl for legacy reasons
params = self.get("decl_params")
if self["decltype"]:
s = "decltype"
else:
s = ""
s += self["decl_name"]
if params:
s += "<%s>" % (",".join(str(p) for p in params))
if self["..."]:
s += "..."
return s
def _consume_parens(stack):
i = 0
sl = len(stack)
nested = 1
while i < sl:
t = stack[i]
i += 1
if t == ")":
nested -= 1
if nested == 0:
return i
elif t == "(":
nested += 1
raise CppParseError("Unmatched (")
def _parse_template_decl(stack):
debug_print("_parse_template_decl: %s", stack)
params = []
param = CppTemplateParam()
i = 0
sl = len(stack)
init = True
require_ending = False
while i < sl:
t = stack[i]
i += 1
if init:
init = False
if t == "decltype":
param["decltype"] = True
continue
if t == ",":
params.append(param)
init = True
require_ending = False
param = CppTemplateParam()
continue
elif t == ">":
params.append(param)
return params, i - 1
if require_ending:
raise CppParseError("expected comma, found %s" % t)
if t == "...":
param["..."] = True
require_ending = True
elif t == "(":
s = stack[i:]
n = _consume_parens(s)
i += n
param["param"] = param["param"] + "".join(s[:n])
else:
if t and t[0] == "<":
param["params"], n = _parse_template_decl([t[1:]] + stack[i:])
i += n
else:
param["param"] = param["param"] + t
raise CppParseError("Unmatched <")
def _parse_cppclass_name(c, stack):
# ignore first thing
i = 1
sl = len(stack)
name = ""
require_ending = False
while i < sl:
t = stack[i]
i += 1
if t == ":":
if i >= sl:
raise CppParseError("class decl ended with ':'")
break
elif t == "::":
name += "::"
continue
elif t == "final":
c["final"] = True
continue
if require_ending:
raise CppParseError("Unexpected '%s' in class" % ("".join(stack[i - 1 :])))
if t and t[0] == "<":
c["class_params"], n = _parse_template_decl([t[1:]] + stack[i:])
i += n
require_ending = True
else:
name += t
c["namespace"] = ""
# backwards compat
if name.startswith("anon-"):
if (
name.startswith("anon-class-")
or name.startswith("anon-struct-")
or name.startswith("anon-union-")
):
name = "<" + name + ">"
c["name"] = name
c["bare_name"] = name
debug_print("Found class '%s'", name)
# backwards compat
classParams = c.get("class_params")
if classParams is not None:
c["name"] = c["name"] + "<%s>" % ",".join(str(p) for p in classParams)
return i
def _parse_cpp_base(stack, default_access):
debug_print("Parsing base: %s (access %s)", stack, default_access)
inherits = []
i = 0
sl = len(stack)
init = True
base = CppBaseDecl(default_access)
require_ending = False
while i < sl:
t = stack[i]
i += 1
if init:
if t in supportedAccessSpecifier:
base["access"] = t
continue
elif t == "virtual":
base["virtual"] = True
continue
init = False
if t == "decltype":
base["decltype"] = True
continue
if t == ",":
inherits.append(base)
base = CppBaseDecl(default_access)
init = True
require_ending = False
continue
if require_ending:
if t == "::":
if "decl_params" in base:
base["decl_name"] = base._fix_classname()
del base["decl_params"]
base["..."]
require_ending = False
else:
raise CppParseError("expected comma, found '%s'" % t)
if t == "(":
s = stack[i:]
n = _consume_parens(s)
i += n
base["decl_name"] = base["decl_name"] + "".join(s[:n])
elif t == "...":
base["..."] = True
require_ending = True
else:
if t[0] == "<":
base["decl_params"], n = _parse_template_decl([t[1:]] + stack[i:])
i += n
require_ending = True
else:
base["decl_name"] = base["decl_name"] + t
# backwards compat
inherits.append(base)
for base in inherits:
base["class"] = base._fix_classname()
return inherits
[docs]class CppClass(dict):
"""
Dictionary that contains at least the following keys:
* ``name`` - Name of the class
* ``doxygen`` - Doxygen comments associated with the class if they exist
* ``inherits`` - List of Classes that this one inherits. Values are
:class:`.CppBaseDecl`
* ``methods`` - Dictionary where keys are from supportedAccessSpecifier
and values are a lists of :class:`.CppMethod`
* ``namespace`` - Namespace of class
* ``properties`` - Dictionary where keys are from supportedAccessSpecifier
and values are lists of :class:`.CppVariable`
* ``enums`` - Dictionary where keys are from supportedAccessSpecifier and
values are lists of :class:`.CppEnum`
* ``nested_classes`` - Classes and structs defined within this class
* ``final`` - True if final
* ``abstract`` - True if abstract
* ``using`` - Using directives in this class scope: key is name for lookup,
value is :class:`.CppVariable`
* ``parent`` - If not None, the class that this class is nested in
An example of how this could look is as follows::
{
'name': ""
'inherits':[]
'methods':
{
'public':[],
'protected':[],
'private':[]
},
'properties':
{
'public':[],
'protected':[],
'private':[]
},
'enums':
{
'public':[],
'protected':[],
'private':[]
}
}
"""
[docs] def get_all_methods(self):
r = []
for typ in supportedAccessSpecifier:
r += self["methods"][typ]
return r
[docs] def get_all_method_names(self):
r = []
for typ in supportedAccessSpecifier:
r += self.get_method_names(typ) # returns list
return r
[docs] def get_all_pure_virtual_methods(self):
r = {}
for typ in supportedAccessSpecifier:
r.update(self.get_pure_virtual_methods(typ)) # returns dict
return r
[docs] def get_method_names(self, type="public"):
return [meth["name"] for meth in self["methods"][type]]
[docs] 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 _lookup_type(self, name):
# TODO: should have indexes for these lookups... and they
# should be more unified
for access in supportedAccessSpecifier:
for e in self["enums"][access]:
if e.get("name") == name:
return {
"enum": self["name"] + "::" + e["name"],
"type": e["name"],
"namespace": e["namespace"],
}
for n in self["nested_classes"]:
if n["name"] == name:
return {"raw_type": self["name"] + "::" + n["name"], "type": n["name"]}
def __init__(self, nameStack, curTemplate, doxygen, location, defaultAccess):
self["nested_classes"] = []
self["parent"] = None
self["abstract"] = False
self["final"] = False
self._public_enums = {}
self._public_typedefs = {}
self._public_forward_declares = []
self["namespace"] = ""
self["using"] = {}
debug_print("Class: %s", nameStack)
debug_print("Template: %s", curTemplate)
if len(nameStack) < 2:
nameStack.insert(1, "") # anonymous struct
if doxygen:
self["doxygen"] = doxygen
# consume name of class, with any namespaces or templates
n = _parse_cppclass_name(self, nameStack)
# consume bases
baseStack = nameStack[n:]
if baseStack:
self["inherits"] = _parse_cpp_base(baseStack, defaultAccess)
else:
self["inherits"] = []
set_location_info(self, location)
if curTemplate:
self["template"] = curTemplate
trace_print("Setting template to", self["template"])
methodAccessSpecificList = {}
propertyAccessSpecificList = {}
enumAccessSpecificList = {}
typedefAccessSpecificList = {}
forwardAccessSpecificList = {}
for accessSpecifier in supportedAccessSpecifier:
methodAccessSpecificList[accessSpecifier] = []
propertyAccessSpecificList[accessSpecifier] = []
enumAccessSpecificList[accessSpecifier] = []
typedefAccessSpecificList[accessSpecifier] = []
forwardAccessSpecificList[accessSpecifier] = []
self["methods"] = methodAccessSpecificList
self["properties"] = propertyAccessSpecificList
self["enums"] = enumAccessSpecificList
self["typedefs"] = typedefAccessSpecificList
self["forward_declares"] = forwardAccessSpecificList
[docs] def show(self):
"""Convert class to a string"""
namespace_prefix = ""
if self["namespace"]:
namespace_prefix = self["namespace"] + "::"
rtn = "%s %s" % (self["declaration_method"], namespace_prefix + self["name"])
if self["final"]:
rtn += " final"
if self["abstract"]:
rtn += " (abstract)\n"
else:
rtn += "\n"
if "doxygen" in list(self.keys()):
rtn += self["doxygen"] + "\n"
if "parent" in list(self.keys()) and self["parent"]:
rtn += "parent class: " + self["parent"]["name"] + "\n"
if "inherits" in list(self.keys()):
rtn += " Inherits: "
for inheritClass in self["inherits"]:
if inheritClass["virtual"]:
rtn += "virtual "
rtn += "%s %s, " % (inheritClass["access"], inheritClass["class"])
rtn += "\n"
rtn += " {\n"
for accessSpecifier in supportedAccessSpecifier:
rtn += " %s\n" % (accessSpecifier)
# Enums
if len(self["enums"][accessSpecifier]):
rtn += " <Enums>\n"
for enum in self["enums"][accessSpecifier]:
rtn += " %s\n" % (repr(enum))
# Properties
if len(self["properties"][accessSpecifier]):
rtn += " <Properties>\n"
for property in self["properties"][accessSpecifier]:
rtn += " %s\n" % (repr(property))
# Methods
if len(self["methods"][accessSpecifier]):
rtn += " <Methods>\n"
for method in self["methods"][accessSpecifier]:
rtn += "\t\t" + method.show() + "\n"
rtn += " }\n"
print(rtn)
def __str__(self):
"""Convert class to a string"""
namespace_prefix = ""
if self["namespace"]:
namespace_prefix = self["namespace"] + "::"
rtn = "%s %s" % (self["declaration_method"], namespace_prefix + self["name"])
if self["final"]:
rtn += " final"
if self["abstract"]:
rtn += " (abstract)\n"
else:
rtn += "\n"
if "doxygen" in list(self.keys()):
rtn += self["doxygen"] + "\n"
if "parent" in list(self.keys()) and self["parent"]:
rtn += "parent class: " + self["parent"]["name"] + "\n"
if "inherits" in list(self.keys()) and len(self["inherits"]):
rtn += "Inherits: "
for inheritClass in self["inherits"]:
if inheritClass.get("virtual", False):
rtn += "virtual "
rtn += "%s %s, " % (inheritClass["access"], inheritClass["class"])
rtn += "\n"
rtn += "{\n"
for accessSpecifier in supportedAccessSpecifier:
rtn += "%s\n" % (accessSpecifier)
# Enums
if len(self["enums"][accessSpecifier]):
rtn += " // Enums\n"
for enum in self["enums"][accessSpecifier]:
rtn += " %s\n" % (repr(enum))
# Properties
if len(self["properties"][accessSpecifier]):
rtn += " // Properties\n"
for property in self["properties"][accessSpecifier]:
rtn += " %s\n" % (repr(property))
# Methods
if len(self["methods"][accessSpecifier]):
rtn += " // Methods\n"
for method in self["methods"][accessSpecifier]:
rtn += " %s\n" % (repr(method))
rtn += "}\n"
return rtn
[docs]class CppUnion(CppClass):
"""
Dictionary that contains at least the following keys:
* ``name`` - Name of the union
* ``doxygen`` - Doxygen comments associated with the union if they exist
* ``members`` - List of members of the union
"""
def __init__(self, nameStack, doxygen, location):
CppClass.__init__(self, nameStack, None, doxygen, location, "public")
self["members"] = self["properties"]["public"]
[docs] 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 __str__(self):
"""Convert class to a string"""
namespace_prefix = ""
if self["namespace"]:
namespace_prefix = self["namespace"] + "::"
rtn = "%s %s" % (self["declaration_method"], namespace_prefix + self["name"])
if self["abstract"]:
rtn += " (abstract)\n"
else:
rtn += "\n"
if "doxygen" in list(self.keys()):
rtn += self["doxygen"] + "\n"
if "parent" in list(self.keys()) and self["parent"]:
rtn += "parent class: " + self["parent"]["name"] + "\n"
rtn += "{\n"
for member in self["members"]:
rtn += " %s\n" % (repr(member))
rtn += "}\n"
return rtn
class _CppMethod(dict):
def _params_helper1(self, stack):
# deal with "throw" keyword
if "throw" in stack:
stack = stack[: stack.index("throw")]
## remove GCC keyword __attribute__(...) and preserve returns ##
cleaned = []
hit = False
hitOpen = 0
hitClose = 0
for a in stack:
if a == "__attribute__":
hit = True
if hit:
if a == "(":
hitOpen += 1
elif a == ")":
hitClose += 1
if a == ")" and hitOpen == hitClose:
hit = False
else:
cleaned.append(a)
stack = cleaned
# also deal with attribute((const)) function prefix #
# TODO this needs to be better #
if len(stack) > 5:
a = "".join(stack)
if a.startswith("((__const__))"):
stack = stack[5:]
elif a.startswith("__attribute__((__const__))"):
stack = stack[6:]
stack = stack[stack.index("(") + 1 :]
if not stack:
return []
if (
len(stack) >= 3 and stack[0] == ")" and stack[1] == ":"
): # is this always a constructor?
self["constructor"] = True
return []
stack.reverse()
_end_ = stack.index(")")
stack.reverse()
stack = stack[: len(stack) - (_end_ + 1)]
if "(" not in stack:
return stack # safe to return, no defaults that init a class
return stack
def _params_helper2(self, params):
for p in params:
p["method"] = self # save reference in variable to parent method
p["parent"] = self
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"]
[docs]class CppMethod(_CppMethod):
"""
Dictionary that contains at least the following keys:
* ``rtnType`` - Return type of the method (ex. "int")
* ``name`` - Name of the method
* ``doxygen`` - Doxygen comments associated with the method if they exist
* ``parameters`` - List of :class:`.CppVariable`
* ``parent`` - If not None, the class this method belongs to
"""
[docs] def show(self):
r = ["method name: %s (%s)" % (self["name"], self["debug"])]
if self["returns"]:
r.append("returns: %s" % self["returns"])
if self["parameters"]:
r.append("number arguments: %s" % len(self["parameters"]))
if self["pure_virtual"]:
r.append("pure virtual: %s" % self["pure_virtual"])
if self["constructor"]:
r.append("constructor")
if self["destructor"]:
r.append("destructor")
return "\n\t\t ".join(r)
def __init__(self, nameStack, curClass, methinfo, curTemplate, doxygen, location):
debug_print("Method: %s", nameStack)
debug_print("Template: %s", curTemplate)
if doxygen:
self["doxygen"] = doxygen
# Remove leading keywords
for i, word in enumerate(nameStack):
if word not in Resolver.C_KEYWORDS:
nameStack = nameStack[i:]
break
if "operator" in nameStack:
rtnType = " ".join(nameStack[: nameStack.index("operator")])
self["name"] = "".join(
nameStack[nameStack.index("operator") : nameStack.index("(")]
)
else:
rtnType = " ".join(nameStack[: nameStack.index("(") - 1])
self["name"] = " ".join(
nameStack[nameStack.index("(") - 1 : nameStack.index("(")]
)
if len(rtnType) == 0 or self["name"] == curClass:
rtnType = "void"
self["rtnType"] = (
rtnType.replace(" :: ", "::")
.replace(" < ", "<")
.replace(" > ", "> ")
.replace(">>", "> >")
.replace(" ,", ",")
)
# deal with "noexcept" specifier/operator
self["noexcept"] = None
if "noexcept" in nameStack:
noexcept_idx = nameStack.index("noexcept")
hit = True
cleaned = nameStack[:noexcept_idx]
parentCount = 0
noexcept = "noexcept"
for a in nameStack[noexcept_idx + 1 :]:
if a == "noexcept":
hit = True
if hit:
if a == "(":
parentCount += 1
elif a == ")":
parentCount -= 1
elif parentCount == 0 and a != "noexcept":
hit = False
cleaned.append(a)
continue # noexcept without parenthesis
if a == ")" and parentCount == 0:
hit = False
noexcept += a
else:
cleaned.append(a)
self["noexcept"] = noexcept
nameStack = cleaned
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)
set_location_info(self, location)
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
if "doxygen" in self:
doxyVarDesc = extract_doxygen_method_params(self["doxygen"])
else:
doxyVarDesc = {}
# non-vararg by default
self["vararg"] = False
# Create the variable now
while len(paramsStack):
# Find commas that are not nexted in <>'s like template types
open_template_count = 0
open_paren_count = 0
open_brace_count = 0
param_separator = 0
i = 0
for elm in paramsStack:
if elm in "<>(){},":
if elm == ",":
if (
open_template_count == 0
and open_paren_count == 0
and open_brace_count == 0
):
param_separator = i
break
elif "<" == elm:
open_template_count += 1
elif ">" == elm:
open_template_count -= 1
elif "(" == elm:
open_paren_count += 1
elif ")" == elm:
open_paren_count -= 1
elif "{" == elm:
open_brace_count += 1
elif "}" == elm:
open_brace_count -= 1
i += 1
if param_separator:
tpstack = paramsStack[0:param_separator]
param = CppVariable(
tpstack,
None,
getattr(tpstack[0], "location", location),
doxyVarDesc=doxyVarDesc,
)
if len(list(param.keys())):
params.append(param)
paramsStack = paramsStack[param_separator + 1 :]
elif len(paramsStack) and paramsStack[0] == "...":
self["vararg"] = True
paramsStack = paramsStack[1:]
else:
param = CppVariable(
paramsStack,
None,
getattr(paramsStack[0], "location", location),
doxyVarDesc=doxyVarDesc,
)
if len(list(param.keys())):
params.append(param)
break
# foo(void) should be zero parameters
if len(params) == 1 and params[0]["type"] == "void":
params = []
self["parameters"] = params
self._params_helper2(params) # mods params inplace
def __str__(self):
filter_keys = ("parent", "defined", "operator", "returns_reference")
cpy = dict((k, v) for (k, v) in list(self.items()) if k not in filter_keys)
return "%s" % cpy
_var_keywords = {
n: 0
for n in "constant constexpr reference pointer static typedefs class fundamental unresolved".split()
}
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
self.update(_var_keywords)
for b in self["type"].split():
if b == "__const__":
b = "const"
a.append(b)
self["type"] = " ".join(a)
[docs]class CppVariable(_CppVariable):
"""
Dictionary that contains at least the following keys:
* ``type`` - Type for the variable (ex. "const string &")
* ``name`` - Name of the variable (ex. "numItems")
* ``namespace`` - Namespace
* ``desc`` - If a method/function parameter, doxygen description for this parameter (optional)
* ``doxygen`` - If a normal property/variable, doxygen description for this
* ``default`` - Default value of the variable, this key will only
exist if there is a default value
* ``extern`` - True if its an extern, False if not
* ``parent`` - If not None, either the class this is a property of, or the
method this variable is a parameter in
* ``access`` - Anything in supportedAccessSpecifier
"""
Vars = []
def __init__(self, nameStack, doxygen, location, is_var=True, **kwargs):
debug_print("var trace %s", nameStack)
if len(nameStack) and nameStack[0] == "extern":
self["extern"] = True
del nameStack[0]
else:
self["extern"] = False
_stack_ = nameStack
if "[" in nameStack: # strip off array informatin
arrayStack = nameStack[nameStack.index("[") :]
if nameStack.count("[") > 1:
debug_print("Multi dimensional array")
debug_print("arrayStack=%s", arrayStack)
nums = [x for x in arrayStack if x.isdigit()]
# Calculate size by multiplying all dimensions
p = 1
for n in nums:
p *= int(n)
# Multi dimensional array
self["array_size"] = p
self["multi_dimensional_array"] = 1
self["multi_dimensional_array_size"] = "x".join(nums)
else:
debug_print("Array")
if len(arrayStack) == 3:
self["array_size"] = arrayStack[1]
nameStack = nameStack[: nameStack.index("[")]
self["array"] = 1
else:
self["array"] = 0
nameStack = self._name_stack_helper(nameStack)
if doxygen:
self["doxygen"] = doxygen
debug_print("Variable: %s", nameStack)
set_location_info(self, location)
self["function_pointer"] = 0
if len(nameStack) < 2: # +++
if len(nameStack) == 1:
self["type"] = nameStack[0]
self["name"] = ""
else:
error_print("%s", _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]
default = " ".join(nameStack[nameStack.index("=") + 1 :])
default = self._filter_name(default)
self["default"] = default
# backwards compat; deprecate camelCase in dicts
self["defaultValue"] = default
elif (
is_fundamental(nameStack[-1])
or nameStack[-1] in [">", "<", ":", "."]
or (len(nameStack) > 2 and nameStack[-2] == "::")
):
# Un named parameter
self["type"] = " ".join(nameStack)
self["name"] = ""
else: # common case
self["type"] = " ".join(nameStack[:-1])
self["name"] = nameStack[-1]
self["type"] = self._filter_name(self["type"])
# Optional doxygen description
try:
self["desc"] = kwargs["doxyVarDesc"][self["name"]]
except:
pass
self.init()
if is_var:
CppVariable.Vars.append(self) # save and resolve later
def _filter_name(self, name):
name = name.replace(" :", ":").replace(": ", ":")
name = name.replace(" < ", "<")
name = name.replace(" > ", "> ").replace(">>", "> >")
name = name.replace(") >", ")>")
name = name.replace(" {", "{").replace(" }", "}")
name = name.replace(" ,", ",")
return name
def __str__(self):
keys_white_list = [
"constant",
"name",
"reference",
"type",
"static",
"pointer",
"desc",
"line_number",
"extern",
"access",
]
cpy = dict((k, v) for (k, v) in list(self.items()) if k in keys_white_list)
if "array_size" in self:
cpy["array_size"] = self["array_size"]
return "%s" % cpy
class _CppEnum(dict):
def resolve_enum_values(self, values):
"""Evaluates the values list of dictionaries passed in and figures out what the enum value
for each enum is editing in place:
Example
From: [{'name': 'ORANGE'},
{'name': 'RED'},
{'name': 'GREEN', 'value': '8'}]
To: [{'name': 'ORANGE', 'value': 0},
{'name': 'RED', 'value': 1},
{'name': 'GREEN', 'value': 8}]
"""
t = int
i = 0
names = [v["name"] for v in values]
for v in values:
if "value" in v:
a = v["value"].strip()
# Remove single quotes from single quoted chars (unless part of some expression
if len(a) == 3 and a[0] == "'" and a[2] == "'":
a = v["value"] = a[1]
a = i = ord(a)
elif 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
self["type"] = t
[docs]class CppEnum(_CppEnum):
"""Contains the following keys:
* ``name`` - Name of the enum (ex. "ItemState")
* ``namespace`` - Namespace containing the enum
* ``isclass`` - True if created via 'enum class' or 'enum struct'
* ``values`` - List of values. The values are a dictionary with
the following key/values:
- ``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, name, doxygen, location):
if doxygen:
self["doxygen"] = doxygen
if name:
self["name"] = name
self["namespace"] = ""
self["typedef"] = False
self["isclass"] = False
self["values"] = []
set_location_info(self, location)
class _CppPreprocessorLiteral(dict):
"""Implementation for #pragmas, #defines and #includes, contains the
following keys:
* ``value`` the value literal of the preprocessor item
* ``line_number`` line number at which the item was found
"""
def __init__(self, macro, location):
self["value"] = re.split("[\t ]+", macro, 1)[1].strip()
set_location_info(self, location)
def __str__(self):
return self["value"]
class CppExternTemplate(dict):
pass
# Implementation is shared between CppPragma, CppDefine, CppInclude but they are
# distinct so that they may diverge if required without interface-breaking
# changes
class CppPragma(_CppPreprocessorLiteral):
pass
class CppDefine(_CppPreprocessorLiteral):
pass
class CppInclude(_CppPreprocessorLiteral):
pass
C99_NONSTANDARD = {
"int8": "signed char",
"int16": "short int",
"int32": "int",
"int64": "int64_t", # this can be: long int (64bit), or long long int (32bit)
"uint": "unsigned int",
"uint8": "unsigned char",
"uint16": "unsigned short int",
"uint32": "unsigned int",
"uint64": "uint64_t", # depends on host bits
}
def standardize_fundamental(s):
if s in C99_NONSTANDARD:
return C99_NONSTANDARD[s]
else:
return s
class Resolver(object):
C_FUNDAMENTAL = "size_t unsigned signed bool char wchar short int float double long void".split()
C_FUNDAMENTAL += "struct union enum".split()
C_FUNDAMENTAL = set(C_FUNDAMENTAL)
C_MODIFIERS = "* & const constexpr static mutable".split()
C_MODIFIERS = set(C_MODIFIERS)
C_KEYWORDS = "extern virtual static explicit inline friend constexpr".split()
C_KEYWORDS = set(C_KEYWORDS)
NAMESPACES = []
CLASSES = {}
def initextra(self):
self.typedefs = {}
self.typedefs_order = []
self.classes_order = []
self.namespaces = Resolver.NAMESPACES # save all namespaces
self.stack = (
[]
) # full name stack, good idea to keep both stacks? (simple stack and full stack)
self._classes_brace_level = {} # class name : level
self._forward_decls = {} # name: namespace
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 cur_linkage(self):
if len(self.linkage_stack):
return self.linkage_stack[-1]
return ""
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"):
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 _remove_modifiers(self, vtype):
return " ".join(x for x in vtype.split() if x not in self.C_MODIFIERS)
def _create_raw_type(self, vtype):
lt = vtype.find("<")
if lt != -1:
gt = vtype.rfind(">")
vtype = (
self._remove_modifiers(vtype[:lt])
+ vtype[lt : gt + 1]
+ self._remove_modifiers(vtype[gt + 1 :])
)
else:
vtype = self._remove_modifiers(vtype)
return vtype
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].split()
result["constant"] += s.count("const")
result["constexpr"] += s.count("constexpr")
result["static"] += s.count("static")
result["mutable"] = "mutable" in s
## these come after a template
s = string.split(">")[-1]
result["pointer"] += s.count("*")
result["reference"] += s.count("&")
x = string
alias = False
for a in self.C_MODIFIERS:
x = x.replace(a, "")
for y in x.split():
if y not in self.C_FUNDAMENTAL:
alias = y
break
# if alias == 'class':
# result['class'] = result['name'] # forward decl of class
# result['forward_decl'] = True
if alias == "__extension__":
result["fundamental_extension"] = True
elif alias:
if alias in result["aliases"]:
# already resolved
return
result["aliases"].append(alias)
if alias in C99_NONSTANDARD:
result["type"] = C99_NONSTANDARD[alias]
result["typedef"] = alias
result["typedefs"] += 1
elif alias in self.typedefs:
result["typedefs"] += 1
result["typedef"] = alias
self.resolve_type(self.typedefs[alias], result)
elif alias in self.classes:
klass = self.classes[alias]
result["fundamental"] = False
result["class"] = klass
result["unresolved"] = False
else:
used = None
# Search for in parents
if not used:
parent = result["parent"]
while parent:
p_using = parent.get("using")
if p_using:
used = p_using.get(alias)
if used:
break
lookup = getattr(parent, "_lookup_type", None)
if lookup:
used = lookup(alias)
if used:
break
parent = parent["parent"]
if not used and self.using:
# search for type in all enclosing namespaces
# TODO: would be nice if namespaces were an object?
for ns in _iter_ns_str_reversed(result.get("namespace", "")):
nsalias = ns + alias
used = self.using.get(nsalias)
if used:
break
if used:
for i in ("enum", "type", "namespace", "ctypes_type", "raw_type"):
if i in used:
result[i] = used[i]
result["unresolved"] = False
else:
result["fundamental"] = True
result["unresolved"] = False
def finalize_vars(self):
# 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
parent = var["parent"]
while parent:
nestedEnum = getattr(parent, "_public_enums", {}).get(tag)
if nestedEnum:
break
nestedTypedef = getattr(parent, "_public_typedefs", {}).get(tag)
if nestedTypedef:
break
parent = parent["parent"]
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
etype = enum.get("type")
if etype is int:
var["ctypes_type"] = "ctypes.c_int"
var["raw_type"] = "int"
elif etype is str:
var["ctypes_type"] = "ctypes.c_char_p"
var["raw_type"] = "char*"
if "method" in var:
var["enum"] = var["method"]["path"] + "::" + enum["name"]
else:
var["enum"] = 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"] and "method" in var:
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._forward_decls:
var["forward_declared"] = tag
var["namespace"] = self._forward_decls[tag]
var["ctypes_type"] = "ctypes.c_void_p"
elif tag in self.global_enums:
enum = self.global_enums[tag]
enum_type = enum.get("type")
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"] and var["unresolved"]:
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]
etype = enum.get("type")
if etype is int:
var["ctypes_type"] = "ctypes.c_int"
var["raw_type"] = "int"
elif etype 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("::")
etype = enum.get("type")
if etype is int:
var["ctypes_type"] = "ctypes.c_int"
var["raw_type"] = "int"
elif etype 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._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 #
for var in CppVariable.Vars:
if "raw_type" not in var:
var["raw_type"] = self._create_raw_type(var["type"])
# 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"]
)
else:
parent = var["class"]["parent"]
var["raw_type"] = (
parent["namespace"]
+ "::"
+ var["class"]["name"]
+ "::"
+ var["raw_type"]
)
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 NotImplementedError
## 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:", self._precomp_macro_buf)
for m, location in self._precomp_macro_buf:
macro = m.replace("<CppHeaderParser_newline_temp_replacement>\\n", "\n")
ml = macro.lower()
try:
if ml.startswith("#define"):
trace_print("Adding #define", macro)
define = CppDefine(macro, location)
self.defines.append(define["value"])
self.defines_detail.append(define)
elif ml.startswith("#pragma"):
trace_print("Adding #pragma", macro)
pragma = CppPragma(macro, location)
self.pragmas_detail.append(pragma)
self.pragmas.append(pragma["value"])
elif ml.startswith("#include"):
trace_print("Adding #include", macro)
include = CppInclude(macro, location)
self.includes.append(include["value"])
self.includes_detail.append(include)
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
# hack
rtnType = {
"aliases": [],
"parent": cls,
"unresolved": True,
"constant": 0,
"constexpr": 0,
"static": 0,
"pointer": 0,
"reference": 0,
"typedefs": 0,
}
self.resolve_type(meth["rtnType"], rtnType)
if not rtnType["unresolved"]:
if "enum" in rtnType:
meth["rtnType"] = rtnType["enum"]
elif "raw_type" in rtnType:
meth["rtnType"] = rtnType["raw_type"]
# TODO: all of this needs to die and be replaced by CppVariable
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 cls._public_enums:
enum = cls._public_enums[meth["returns"]]
meth["returns_enum"] = enum.get("type")
meth["returns_fundamental"] = True
if enum.get("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.get("type")
meth["returns_fundamental"] = True
if enum.get("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:
meth["returns_unknown"] = True
elif b in self.global_enums:
enum = self.global_enums[b]
meth["returns_enum"] = enum.get("type")
meth["returns_fundamental"] = True
if enum.get("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.get("type")
meth["returns_fundamental"] = True
if enum.get("type") == int:
meth["returns"] = "int"
else:
meth["returns"] = "char*"
elif b in klass._public_forward_declares:
meth["returns_class"] = True
elif b in klass._public_typedefs:
typedef = klass._public_typedefs[b]
meth["returns_fundamental"] = is_fundamental(typedef)
else:
trace_print(
meth
) # should be a nested class, TODO fix me.
meth["returns_unknown"] = True
elif "::" in meth["returns"]:
trace_print("TODO namespace or extra nested return:", meth)
meth["returns_unknown"] = True
else:
trace_print(
"WARN: UNKNOWN RETURN", meth["name"], meth["returns"]
)
meth["returns_unknown"] = True
if meth["returns"].startswith(":: "):
meth["returns"] = meth["returns"].replace(":: ", "::")
for cls in list(self.classes.values()):
methnames = cls.get_all_method_names()
pvm = cls.get_all_pure_virtual_methods()
for d in cls["inherits"]:
c = d["class"]
a = d["access"] # do not depend on this to be 'public'
trace_print("PARENT CLASS:", c)
if c not in self.classes:
trace_print("WARN: parent class not found")
if c in self.classes and self.classes[c]["abstract"]:
p = self.classes[c]
for meth in p.get_all_methods(): # p["methods"]["public"]:
trace_print(
"\t\tmeth",
meth["name"],
"pure virtual",
meth["pure_virtual"],
)
if meth["pure_virtual"] and meth["name"] not in methnames:
cls["abstract"] = True
break
_method_type_defaults = {
n: False
for n in "defined deleted pure_virtual operator constructor destructor extern template virtual static explicit inline friend returns returns_pointer returns_fundamental returns_class default".split()
}
def parse_method_type(self, stack):
trace_print("meth type info", stack)
info = {
"debug": " ".join(stack)
.replace(" :: ", "::")
.replace(" < ", "<")
.replace(" > ", "> ")
.replace(" >", ">")
.replace(">>", "> >")
.replace(">>", "> >"),
"class": None,
"namespace": self.cur_namespace(add_double_colon=True),
}
info.update(self._method_type_defaults)
header = stack[: stack.index("(")]
header = " ".join(header)
header = header.replace(" :: ", "::")
header = header.replace(" < ", "<")
header = header.replace(" > ", "> ")
header = header.replace("default ", "default")
header = header.strip()
if stack[-1] == "{":
info["defined"] = True
self._discard_contents("{", "}")
self.braceHandled = True
elif stack[-1] == ";":
info["defined"] = False
elif stack[-1] == ":":
info["defined"] = True
self._discard_ctor_initializer()
self.braceHandled = True
else:
assert 0
refqual = ""
if len(stack) > 3:
if stack[-1] == ";" and stack[-3] == "=":
if stack[-2] == "0":
info["pure_virtual"] = True
elif stack[-2] == "delete":
info["deleted"] = True
for e in reversed(stack):
if e == ")":
break
elif e == "&":
refqual += "&"
info["ref_qualifiers"] = refqual
r = header.split()
name = None
if "operator" in stack: # rare case op overload defined outside of class
op = stack[stack.index("operator") + 1 : stack.index("(")]
op = "".join(op)
if not op:
if " ".join(["operator", "(", ")", "("]) in " ".join(stack):
op = "()"
else:
trace_print("Error parsing operator")
return None
info["operator"] = op
name = "operator" + op
a = stack[: stack.index("operator")]
elif r:
name = r[-1]
a = r[:-1] # strip name
if name is None:
return None
# if name.startswith('~'): name = name[1:]
while a and a[0] == "}": # strip - can have multiple } }
a = a[1:]
if "::" in name:
# klass,name = name.split('::') # methods can be defined outside of class
klass = name[: name.rindex("::")]
name = name.split("::")[-1]
info["class"] = klass
if klass in self.classes and not self.curClass:
# Class function defined outside the class
return None
# info['name'] = name
# else: info['name'] = name
if name.startswith("~"):
info["destructor"] = True
if "default;" in stack:
info["defined"] = True
info["default"] = True
name = name[1:]
elif not a or (name == self.curClass and len(self.curClass)):
info["constructor"] = True
if "default;" in stack:
info["defined"] = True
info["default"] = True
info["name"] = name
for tag in self.C_KEYWORDS:
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"""
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,
self._get_stmt_doxygen(),
self._get_location(self.nameStack),
)
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,
self._get_stmt_doxygen(),
self._get_location(self.nameStack),
)
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._get_stmt_doxygen(),
self._get_location(self.nameStack),
)
newMethod["parent"] = None
newMethod["linkage"] = self.cur_linkage()
self.functions.append(newMethod)
# Reset template once it has been used
self.curTemplate = None
global parseHistory
parseHistory.append(
{
"braceDepth": self.braceDepth,
"item_type": "method",
"item": newMethod,
}
)
else:
trace_print("free function?", self.nameStack)
self.stack = []
self.stmtTokens = []
_function_point_typedef_format = re.compile(r".*?\(.*?\*(.*?)\).*?\(.*?\).*?")
def _function_point_typedef_parse(self, stack):
idx = stack.index("typedef")
expression = "".join(stack[idx + 1 :])
m = self._function_point_typedef_format.match(expression)
if m is None:
return {}
name = m.group(1)
s = " ".join([i for i in stack if i != name])
r = {"name": name, "raw": s, "type": s}
return r
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()
r = self._function_point_typedef_parse(stack)
if len(r) == 3:
return r
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, clearStack=True, addToVar=None):
"""Create a Property out of the name stack"""
global parseHistory
debug_print("trace")
if self.nameStack[0] == "typedef":
assert self.stack and self.stack[-1] == ";"
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"]
else:
assert 0
elif self.curClass:
if len(self.nameStack) == 1:
# See if we can de anonymize the type
filteredParseHistory = [
h for h in parseHistory if h["braceDepth"] == self.braceDepth
]
if (
len(filteredParseHistory)
and filteredParseHistory[-1]["item_type"] == "class"
):
self.nameStack.insert(0, filteredParseHistory[-1]["item"]["name"])
debug_print(
"DEANONYMOIZING %s to type '%s'",
self.nameStack[1],
self.nameStack[0],
)
if "," in self.nameStack: # Maybe we have a variable list
# Figure out what part is the variable separator but remember templates of function pointer
# First find left most comma outside of a > and )
leftMostComma = 0
for i in range(0, len(self.nameStack)):
name = self.nameStack[i]
if name in (">", ")"):
leftMostComma = 0
if leftMostComma == 0 and name == ",":
leftMostComma = i
# Is it really a list of variables?
if leftMostComma != 0:
trace_print(
"Multiple variables for namestack in",
self.nameStack,
". Separating processing",
)
orig_nameStack = self.nameStack[:]
type_nameStack = orig_nameStack[: leftMostComma - 1]
for name in orig_nameStack[leftMostComma - 1 :: 2]:
self.nameStack = type_nameStack + [name]
self._evaluate_property_stack(
clearStack=False, addToVar=addToVar
)
return
newVar = CppVariable(
self.nameStack,
self._get_stmt_doxygen(),
self._get_location(self.nameStack),
)
newVar["namespace"] = self.current_namespace()
newVar["linkage"] = self.cur_linkage()
if self.curClass:
klass = self.classes[self.curClass]
klass["properties"][self.curAccessSpecifier].append(newVar)
newVar["property_of_class"] = klass["name"]
newVar["parent"] = klass
parseHistory.append(
{"braceDepth": self.braceDepth, "item_type": "variable", "item": newVar}
)
if addToVar:
newVar.update(addToVar)
else:
debug_print("Found Global variable")
newVar = CppVariable(
self.nameStack,
self._get_stmt_doxygen(),
self._get_location(self.nameStack),
)
newVar["namespace"] = self.cur_namespace(False)
newVar["linkage"] = self.cur_linkage()
if addToVar:
newVar.update(addToVar)
self.variables.append(newVar)
if clearStack:
self.stack = [] # CLEAR STACK
self.stmtTokens = []
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 parent:
debug_print("found nested subclass")
self.accessSpecifierStack.append(self.curAccessSpecifier)
# 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:
if self.nameStack[0] == "struct":
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)
elif self.nameStack[0] == "union":
self.anon_union_counter += 1
# We cant handle more than 1 anonymous union, so name them uniquely
self.nameStack.append("anon-union-%d" % self.anon_union_counter)
elif self.nameStack[0] == "class":
self.anon_class_counter += 1
# We cant handle more than 1 anonymous class, so name them uniquely
self.nameStack.append("anon-class-%d" % self.anon_class_counter)
if self.nameStack[0] == "class":
self.curAccessSpecifier = "private"
else: # struct/union
self.curAccessSpecifier = "public"
debug_print(
"curAccessSpecifier changed/defaulted to %s", self.curAccessSpecifier
)
if self.nameStack[0] == "union":
newClass = CppUnion(
self.nameStack,
self._get_stmt_doxygen(),
self._get_location(self.nameStack),
)
else:
newClass = CppClass(
self.nameStack,
self.curTemplate,
self._get_stmt_doxygen(),
self._get_location(self.nameStack),
self.curAccessSpecifier,
)
self.curTemplate = None
newClass["declaration_method"] = self.nameStack[0]
newClass["linkage"] = self.cur_linkage()
self.classes_order.append(newClass) # good idea to save ordering
self.stack = [] # fixes if class declared with ';' in closing brace
self.stmtTokens = []
classKey = newClass["name"]
if parent:
newClass["namespace"] = self.classes[parent]["namespace"] + "::" + parent
newClass["parent"] = self.classes[parent]
newClass["access_in_parent"] = self.accessSpecifierStack[-1]
self.classes[parent]["nested_classes"].append(newClass)
## supports nested classes with the same name ##
self.curClass = key = parent + "::" + classKey
self._classes_brace_level[key] = self.braceDepth
elif newClass["parent"]: # nested class defined outside of parent. A::B {...}
pcls = newClass["parent"]
parent = pcls["name"]
newClass["namespace"] = pcls["namespace"] + "::" + parent
pcls["nested_classes"].append(newClass)
## supports nested classes with the same name ##
self.curClass = key = parent + "::" + classKey
self._classes_brace_level[key] = self.braceDepth
else:
newClass["namespace"] = self.cur_namespace()
self.curClass = key = classKey
self._classes_brace_level[classKey] = 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()
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[name] = self.current_namespace()
# fmt: off
_namestack_append_tokens = {
"{",
"}",
"[",
"]",
"=",
",",
"\\",
"DIVIDE",
"|",
"%",
"^",
"!",
"NUMBER",
"FLOAT_NUMBER",
"-",
"+",
"STRING_LITERAL",
"ELLIPSIS",
"DBL_COLON",
"SHIFT_LEFT",
}
_namestack_pass_tokens = {
"'",
"." # preserve behaviour and eat individual fullstops
}
_namestack_str_tokens = {
"NAME",
"&",
"*",
"<",
">",
"CHAR_LITERAL"
}
# fmt: on
[docs]class CppHeader(_CppHeader):
"""Parsed C++ class header"""
IGNORE_NAMES = "__extension__".split()
def __init__(
self,
headerFileName,
argType="file",
encoding=None,
preprocessed=False,
**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 ##
CppVariable.Vars = []
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 = {}
#: Dictionary of classes found in the header file. The key is the name
#: of the class, value is :class:`.CppClass`
self.classes = Resolver.CLASSES
#: List of free functions as :class:`.CppMethod`
self.functions = []
#: List of #pragma directives found as strings
self.pragmas = []
#: List of pragmas with location information
self.pragmas_detail = []
#: List of #define directives found
self.defines = []
#: List of #define directives found, with location information
self.defines_detail = []
#: List of #include directives found
self.includes = []
#: List of #include directives found with location information
self.includes_detail = []
#: Filenames encountered in #line directives while parsing
self.headerFileNames = []
self._precomp_macro_buf = (
[]
) # for internal purposes, will end up filling out pragmras and defines at the end
#: List of enums in this header as :class:`.CppEnum`
self.enums = []
self.extern_templates = []
#: List of variables in this header as :class:`.CppVariable`
self.variables = []
self.global_enums = {}
self.nameStack = []
#: Namespaces in this header
self.nameSpaces = []
self.curAccessSpecifier = "private" # private is default
self.curTemplate = None
self.accessSpecifierStack = []
self.linkage_stack = []
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 = 0
self.anon_class_counter = 0
#: Using directives in this header outside of class scope: key is
#: full name for lookup, value is :class:`.CppVariable`
self.using = {}
if len(self.headerFileName):
fd = io.open(self.headerFileName, "r", encoding=encoding)
headerFileStr = "".join(fd.readlines())
fd.close()
# Make sure supportedAccessSpecifier are sane
for i in range(0, len(supportedAccessSpecifier)):
if " " not in supportedAccessSpecifier[i]:
continue
supportedAccessSpecifier[i] = re.sub(
"[ ]+", " ", supportedAccessSpecifier[i]
).strip()
if not preprocessed:
# 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 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
self.braceReason = []
self.lastBraceReason = _BRACE_REASON_OTHER
lex = Lexer(self.headerFileName)
lex.input(headerFileStr)
self.lex = lex
self.headerFileNames = lex.filenames
#
# A note on parsing methodology
#
# The idea here is to consume as many tokens as needed to determine
# what the thing is that we're parsing. While some items can be identified
# early, typically the code below consumes until a '{', '}', or ; and
# then looks at the accumulated tokens to figure out what it is.
#
# Unfortunately, the code isn't always particularly consistent (but
# it's slowly getting there!), so take this with a grain of salt.
#
self._doxygen_cache = None
self.braceHandled = False
tok = None
self.stmtTokens = []
parenDepth = 0
try:
while True:
tok = lex.token(eof_ok=True)
if not tok:
break
tok.value = TagStr(tok.value, location=tok.location)
debug_print("TOK: %s", tok)
if tok.type == "NAME":
if tok.value in self.IGNORE_NAMES:
continue
elif tok.value == "template":
self._doxygen_cache = self.lex.get_doxygen()
self._parse_template()
continue
elif tok.value == "alignas":
self._doxygen_cache = self.lex.get_doxygen()
self._parse_attribute_specifier_seq(tok)
continue
elif tok.value == "__attribute__":
self._doxygen_cache = self.lex.get_doxygen()
self._parse_gcc_attribute()
continue
elif not self.stack and tok.value == "static_assert":
self._next_token_must_be("(")
self._discard_contents("(", ")")
continue
elif tok.type == "DBL_LBRACKET":
self._doxygen_cache = self.lex.get_doxygen()
self._parse_attribute_specifier_seq(tok)
continue
# TODO: get rid of stack, move to stmtTokens
self.stack.append(tok.value)
self.stmtTokens.append(tok)
nslen = len(self.nameStack)
if tok.type in ("PRECOMP_MACRO", "PRECOMP_MACRO_CONT"):
debug_print("PRECOMP: %s", tok)
self._precomp_macro_buf.append((tok.value, tok.location))
self.stack = []
self.stmtTokens = []
self.nameStack = []
continue
if parenDepth == 0 and tok.type == "{":
self.lastBraceReason = _BRACE_REASON_OTHER
if len(self.nameStack) >= 2 and is_namespace(
self.nameStack
): # namespace {} with no name used in boost, this sets default?
self.nameSpaces.append("".join(self.nameStack[1:]))
ns = self.cur_namespace()
self.stack = []
self.stmtTokens = []
if ns not in self.namespaces:
self.namespaces.append(ns)
self.lastBraceReason = _BRACE_REASON_NS
# Detect special condition of macro magic before class declaration so we
# can filter it out
elif "class" in self.nameStack and self.nameStack[0] != "class":
classLocationNS = self.nameStack.index("class")
classLocationS = self.stack.index("class")
if (
"(" not in self.nameStack[classLocationNS:]
and self.nameStack[classLocationNS - 1] != "enum"
):
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 set to True, indicates that the callee consumed
# all of the tokens between { and }
self.braceHandled = False
if self.nameStack:
self._evaluate_stack()
if self.stack and self.stack[0] == "class":
self.stack = []
self.stmtTokens = []
if not self.braceHandled:
self.braceDepth += 1
self.braceReason.append(self.lastBraceReason)
elif parenDepth == 0 and tok.type == "}":
if self.braceDepth == 0:
continue
reason = self.braceReason.pop()
if reason == _BRACE_REASON_NS:
self.nameSpaces.pop()
self.stack = [] # clear stack when namespace ends?
self.stmtTokens = []
elif reason == _BRACE_REASON_EXTERN:
self.linkage_stack.pop()
self.stack = [] # clear stack when linkage ends?
self.stmtTokens = []
else:
self._evaluate_stack()
self.braceDepth -= 1
# 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"]:
thisClass = self.classes[self.curClass]
self.curClass = self.curClass[
: -(len(thisClass["name"]) + 2)
]
# Detect anonymous union members
if (
self.curClass
and thisClass["declaration_method"] == "union"
and thisClass["name"].startswith("<")
and self.lex.token_if(";")
):
debug_print("Creating anonymous union")
# Force the processing of an anonymous union
self.nameStack = [""]
self.stack = self.nameStack + [";"]
debug_print("pre eval anon stack")
self._evaluate_stack(";")
debug_print("post eval anon stack")
self.stack = []
self.nameStack = []
self.stmtTokens = []
else:
self.curClass = ""
self.stack = []
self.stmtTokens = []
elif tok.type in _namestack_append_tokens:
self.nameStack.append(tok.value)
nameStackAppended = True
elif tok.type in _namestack_pass_tokens:
pass
elif tok.type in _namestack_str_tokens:
if tok.value in ignoreSymbols:
debug_print("Ignore symbol %s", tok.value)
elif tok.value == "class":
self.nameStack.append(tok.value)
else:
self.nameStack.append(tok.value)
elif tok.type == ":":
if self.nameStack and self.nameStack[0] in supportedAccessSpecifier:
specifier = " ".join(self.nameStack)
if specifier in supportedAccessSpecifier:
self.curAccessSpecifier = specifier
else:
self.curAccessSpecifier = self.nameStack[0]
debug_print(
"curAccessSpecifier updated to %s", self.curAccessSpecifier
)
self.nameStack = []
self.stack = []
self.stmtTokens = []
elif is_method_namestack(self.stack):
debug_print("trace")
self._evaluate_method_stack()
self.nameStack = []
self.stack = []
self.stmtTokens = []
else:
self.nameStack.append(tok.value)
elif tok.type == ";":
self._evaluate_stack(tok.type)
self.stack = []
self.nameStack = []
self.stmtTokens = []
elif tok.type == "(":
parenDepth += 1
self.nameStack.append(tok.value)
nameStackAppended = True
elif tok.type == ")":
self.nameStack.append(tok.value)
nameStackAppended = True
if parenDepth != 0:
parenDepth -= 1
newNsLen = len(self.nameStack)
if nslen != newNsLen and newNsLen == 1:
if not self._doxygen_cache:
self._doxygen_cache = self.lex.get_doxygen()
except Exception as e:
if debug:
raise
context = ""
if isinstance(e, CppParseError):
context = ": " + str(e)
if e.tok:
tok = e.tok
if tok:
filename, lineno = tok.location
msg = (
"Not able to parse %s on line %d evaluating '%s'%s\nError around: %s"
% (filename, lineno, tok.value, context, " ".join(self.nameStack))
)
else:
msg = "Error parsing %s%s\nError around: %s" % (
self.headerFileName,
context,
" ".join(self.nameStack),
)
raise_exc(
CppParseError(msg),
e,
)
self.finalize()
global parseHistory
parseHistory = []
# Delete some temporary variables
for key in [
"_precomp_macro_buf",
"_doxygen_cache",
"braceHandled",
"lex",
"nameStack",
"nameSpaces",
"curAccessSpecifier",
"accessSpecifierStack",
"nameStackHistory",
"anon_struct_counter",
"anon_union_counter",
"anon_class_counter",
"_classes_brace_level",
"_forward_decls",
"stack",
"mainClass",
"_template_typenames",
"braceDepth",
"stmtTokens",
"typedefs_order",
"curTemplate",
]:
del self.__dict__[key]
def _get_location(self, stack):
if stack:
location = getattr(stack[0], "location", None)
if location is not None:
return location
return self.lex.current_location()
def _get_stmt_doxygen(self):
# retrieves the doxygen comment associated with an accumulated
# statement (since doxygen comments have to be retrieved immediately)
doxygen, self._doxygen_cache = self._doxygen_cache, ""
if not doxygen:
doxygen = self.lex.get_doxygen()
return doxygen
def _parse_error(self, tokens, expected):
if not tokens:
# common case after a failed token_if
errtok = self.lex.token()
else:
errtok = tokens[-1]
if expected:
expected = ", expected '" + expected + "'"
msg = "unexpected '%s'%s" % (errtok.value, expected)
# TODO: better error message
return CppParseError(msg, errtok)
def _next_token_must_be(self, *tokenTypes):
tok = self.lex.token()
if tok.type not in tokenTypes:
raise self._parse_error((tok,), "' or '".join(tokenTypes))
return tok
_end_balanced_tokens = {">", "}", "]", ")", "DBL_RBRACKET"}
_balanced_token_map = {
"<": ">",
"{": "}",
"(": ")",
"[": "]",
"DBL_LBRACKET": "DBL_RBRACKET",
}
def _consume_balanced_tokens(self, *init_tokens):
_balanced_token_map = self._balanced_token_map
consumed = list(init_tokens)
match_stack = deque((_balanced_token_map[tok.type] for tok in consumed))
get_token = self.lex.token
while True:
tok = get_token()
consumed.append(tok)
if tok.type in self._end_balanced_tokens:
expected = match_stack.pop()
if tok.type != expected:
# hack: ambiguous right-shift issues here, really
# should be looking at the context
if tok.type == ">":
tok = self.lex.token_if(">")
if tok:
consumed.append(tok)
match_stack.append(expected)
continue
raise self._parse_error(consumed, expected)
if len(match_stack) == 0:
return consumed
continue
next_end = _balanced_token_map.get(tok.type)
if next_end:
match_stack.append(next_end)
def _discard_contents(self, start_type, end_type):
# use this instead of consume_balanced_tokens because
# we don't care at all about the internals
level = 1
get_token = self.lex.token
while True:
tok = get_token()
if tok.type == start_type:
level += 1
elif tok.type == end_type:
level -= 1
if level == 0:
break
def _discard_ctor_initializer(self):
"""
ctor_initializer: ":" mem_initializer_list
mem_initializer_list: mem_initializer ["..."]
| mem_initializer "," mem_initializer_list ["..."]
mem_initializer: mem_initializer_id "(" [expression_list] ")"
| mem_initializer_id braced_init_list
mem_initializer_id: class_or_decltype
| IDENTIFIER
"""
debug_print("discarding ctor intializer")
# all of this is discarded.. the challenge is to determine
# when the initializer ends and the function starts
while True:
tok = self.lex.token()
if tok.type == "DBL_COLON":
tok = self.lex.token()
if tok.type == "decltype":
tok = self._next_token_must_be("(")
self._consume_balanced_tokens(tok)
tok = self.lex.token()
# each initializer is either foo() or foo{}, so look for that
while True:
if tok.type not in ("{", "("):
tok = self.lex.token()
continue
if tok.type == "{":
self._discard_contents("{", "}")
elif tok.type == "(":
self._discard_contents("(", ")")
tok = self.lex.token()
break
# at the end
if tok.type == "ELLIPSIS":
tok = self.lex.token()
if tok.type == ",":
continue
elif tok.type == "{":
# reached the function
self._discard_contents("{", "}")
return
else:
raise self._parse_error((tok,), ",' or '{")
def _evaluate_stack(self, token=None):
"""Evaluates the current name stack"""
nameStackCopy = self.nameStack[:]
debug_print(
"Evaluating stack %s\n BraceDepth: %s (called from %s)",
self.nameStack,
self.braceDepth,
debug_caller_lineno,
)
# Handle special case of overloading operator ()
if "operator()(" in "".join(self.nameStack):
operator_index = self.nameStack.index("operator")
self.nameStack.pop(operator_index + 2)
self.nameStack.pop(operator_index + 1)
self.nameStack[operator_index] = "operator()"
if len(self.curClass):
debug_print("%s (%s) ", self.curClass, self.curAccessSpecifier)
else:
debug_print("<anonymous> (%s) ", self.curAccessSpecifier)
# Filter special case of array with casting in it
try:
bracePos = self.nameStack.index("[")
parenPos = self.nameStack.index("(")
if bracePos == parenPos - 1:
endParen = self.nameStack.index(")")
self.nameStack = (
self.nameStack[: bracePos + 1] + self.nameStack[endParen + 1 :]
)
debug_print("Filtered namestack to=%s", self.nameStack)
except:
pass
# if 'typedef' in self.nameStack: self._evaluate_typedef() # allows nested typedefs, probably a bad idea
if (
not self.curClass
and "typedef" in self.nameStack
and (
(
"struct" not in self.nameStack
and "union" not in self.nameStack
and "enum" not in self.nameStack
)
or self.stack[-1] == ";"
)
):
debug_print("trace")
trace_print("typedef", self.stack)
self._evaluate_typedef()
return
elif len(self.nameStack) == 0:
debug_print("trace (Empty Stack)")
return
elif self.nameStack[0] == "namespace":
# Taken care of outside of here
pass
elif len(self.nameStack) == 2 and self.nameStack[0] == "extern":
debug_print("trace extern")
self.linkage_stack.append(self.nameStack[1].strip('"'))
self.stack = []
self.stmtTokens = []
self.lastBraceReason = _BRACE_REASON_EXTERN
elif (
len(self.nameStack) == 2 and self.nameStack[0] == "friend"
): # friend class declaration
pass
elif len(self.nameStack) >= 2 and self.nameStack[0] == "using":
if self.nameStack[1] == "namespace":
pass
else:
if len(self.nameStack) > 3 and self.nameStack[2] == "=":
# using foo = ns::bar
# -> type alias: same behavior in all scopes
alias = self.nameStack[1]
ns, stack = _split_namespace(self.nameStack[3:])
atype = CppVariable(
stack,
self._get_stmt_doxygen(),
self._get_location(stack),
is_var=False,
)
# namespace refers to the embedded type
atype["namespace"] = ns
atype["using_type"] = "typealias"
atype["typealias"] = alias
else:
# using foo::bar
# -> in global scope this is bringing in something
# from a different namespace
# -> in class scope this is bringing in a member
# from a base class
ns, stack = _split_namespace(self.nameStack[1:])
atype = CppVariable(
stack,
self._get_stmt_doxygen(),
self._get_location(stack),
is_var=False,
)
alias = atype["type"]
atype["using_type"] = "declaration"
if self.curClass:
atype["baseclass"] = ns
else:
atype["namespace"] = ns
atype["template"] = self.curTemplate
self.curTemplate = None
if atype["type"].startswith("typename "):
atype["raw_type"] = "typename " + ns + atype["type"][9:]
else:
atype["raw_type"] = ns + atype["type"]
atype["access"] = self.curAccessSpecifier
if self.curClass:
klass = self.classes[self.curClass]
klass["using"][alias] = atype
else:
# lookup is done
alias = self.current_namespace() + alias
self.using[alias] = atype
elif is_method_namestack(self.stack) and "(" in self.nameStack:
debug_print("trace")
self._evaluate_method_stack()
elif is_enum_namestack(self.nameStack):
debug_print("trace")
self._parse_enum()
self.stack = []
self.stmtTokens = []
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")
elif self.braceDepth < 1:
debug_print("trace")
# Ignore global stuff for now
debug_print("Global stuff: %s" % self.nameStack)
elif self.braceDepth > len(self.nameSpaces) + 1:
debug_print("trace")
else:
debug_print("Discarded statement %s", self.nameStack)
try:
self.nameStackHistory[self.braceDepth] = (nameStackCopy, self.curClass)
except:
self.nameStackHistory.append((nameStackCopy, self.curClass))
# its a little confusing to have some if/else above return and others not, and then clearning the nameStack down here
self.nameStack = []
self._doxygen_cache = None
self.curTemplate = None
def _parse_template(self):
# check for 'extern template'
extern_template = False
if len(self.stmtTokens) == 1 and self.stmtTokens[0].value == "extern":
extern_template = True
tok = self._next_token_must_be("NAME")
if not tok.value == "class":
raise self._parse_error((tok,), "class")
tok = self._next_token_must_be("NAME")
if tok.value == "__attribute__":
self._parse_gcc_attribute()
tok = self._next_token_must_be("NAME")
extern_template_name = tok.value
tok = self._next_token_must_be("<")
consumed = self._consume_balanced_tokens(tok)
tmpl = " ".join(tok.value for tok in consumed)
tmpl = (
tmpl.replace(" :: ", "::")
.replace(" <", "<")
.replace("< ", "<")
.replace(" >", ">")
.replace("> ", ">")
.replace(" , ", ", ")
.replace(" = ", "=")
)
if extern_template:
self.extern_templates.append(
CppExternTemplate(
name=extern_template_name,
params=tmpl,
namespace=self.cur_namespace(),
)
)
self.stack = []
self.nameStack = []
self.stmtTokens = []
else:
self.curTemplate = "template" + tmpl
def _parse_gcc_attribute(self):
tok1 = self._next_token_must_be("(")
tok2 = self._next_token_must_be("(")
self._consume_balanced_tokens(tok1, tok2)
_attribute_specifier_seq_start_types = ("DBL_LBRACKET", "NAME")
_attribute_specifier_seq_start_values = ("[[", "alignas")
def _parse_attribute_specifier_seq(self, tok):
# TODO: retain the attributes and do something with them
# attrs = []
while True:
if tok.type == "DBL_LBRACKET":
tokens = self._consume_balanced_tokens(tok)
# attrs.append(Attribute(tokens))
elif tok.type == "NAME" and tok.value == "alignas":
next_tok = self._next_token_must_be("(")
tokens = self._consume_balanced_tokens(next_tok)
# attrs.append(AlignasAttribute(tokens))
else:
self.lex.return_token(tok)
break
# multiple attributes can be specified
tok = self.lex.token_if(*self._attribute_specifier_seq_start_types)
if tok is None:
break
# return attrs
def _parse_enum(self):
"""
opaque_enum_declaration: enum_key [attribute_specifier_seq] IDENTIFIER [enum_base] ";"
enum_specifier: enum_head "{" [enumerator_list] "}"
| enum_head "{" enumerator_list "," "}"
enum_head: enum_key [attribute_specifier_seq] [IDENTIFIER] [enum_base]
| enum_key [attribute_specifier_seq] nested_name_specifier IDENTIFIER [enum_base]
enum_key: "enum"
| "enum" "class"
| "enum" "struct"
enum_base: ":" type_specifier_seq
"""
debug_print("parsing enum")
is_typedef = False
self.lex.return_tokens(self.stmtTokens)
doxygen = self._get_stmt_doxygen()
tok = self.lex.token()
if tok.value == "typedef":
is_typedef = True
tok = self.lex.token()
if tok.value != "enum":
raise self._parse_error((tok,), "enum")
location = tok.location
is_class = False
nametok = self.lex.token()
if nametok.value in ("class", "struct"):
is_class = True
nametok = self.lex.token()
if nametok.value == "__attribute__":
self._parse_gcc_attribute()
nametok = self.lex.token()
if nametok.value in self._attribute_specifier_seq_start_values:
self._parse_attribute_specifier_seq(nametok)
nametok = self.lex.token()
# TODO: nested_name_specifier
name = ""
if nametok.type == "NAME":
name = nametok.value
debug_print("enum name is '%s'", name)
tok = self.lex.token()
else:
debug_print("anonymous enum")
tok = nametok
base = []
if tok.type == ":":
while True:
tok = self.lex.token()
if tok.type in ("{", ";"):
break
base.append(tok.value)
newEnum = CppEnum(name, doxygen, location)
if is_typedef:
newEnum["typedef"] = True
if is_class:
newEnum["isclass"] = True
if base:
newEnum["type"] = "".join(base)
instancesData = []
if tok.type == "{":
self.braceHandled = True
self._parse_enumerator_list(newEnum["values"])
newEnum.resolve_enum_values(newEnum["values"])
tok = self.lex.token()
if tok.value == "__attribute__":
self._parse_gcc_attribute()
tok = self.lex.token()
if tok.type == "NAME":
if newEnum["typedef"]:
newEnum["name"] = tok.value
self._next_token_must_be(";")
else:
# this is an instance of the enum
instancesData.append(tok.value)
while True:
tok = self.lex.token()
if tok.type == ";":
break
instancesData.append(tok.value)
elif tok.type != ";":
raise self._parse_error((tok,), ";")
self._install_enum(newEnum, instancesData)
def _install_enum(self, newEnum, instancesData):
if len(self.curClass):
newEnum["namespace"] = self.cur_namespace(False)
newEnum["linkage"] = self.cur_linkage()
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)
newEnum["linkage"] = self.cur_linkage()
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 instancesData:
instances = list(_split_by_comma(instancesData))
instanceType = "enum"
if "name" in newEnum:
instanceType = newEnum["name"]
addToVar = {"enum_type": newEnum}
for instance in instances:
self.nameStack = [instanceType] + instance
self._evaluate_property_stack(clearStack=False, addToVar=addToVar)
def _parse_enumerator_list(self, values):
"""
enumerator_list: enumerator_definition
| enumerator_list "," enumerator_definition
enumerator_definition: enumerator
| enumerator "=" constant_expression
enumerator: IDENTIFIER
"""
while True:
name_tok = self._next_token_must_be("}", "NAME")
if name_tok.value == "}":
return
value = {"name": name_tok.value}
doxygen = self.lex.get_doxygen()
if doxygen:
value["doxygen"] = doxygen
values.append(value)
debug_print("enumerator value '%s'", value["name"])
tok = self._next_token_must_be("}", ",", "=", "DBL_LBRACKET")
if tok.type == "DBL_LBRACKET":
self._parse_attribute_specifier_seq(tok)
tok = self._next_token_must_be("}", ",", "=")
if tok.type == "}":
return
elif tok.type == ",":
continue
elif tok.type == "=":
v = []
while True:
tok = self.lex.token()
if tok.type == "}":
value["value"] = " ".join(v)
return
elif tok.type == ",":
value["value"] = " ".join(v)
break
elif tok.type in self._balanced_token_map:
v.extend(t.value for t in self._consume_balanced_tokens(tok))
else:
v.append(tok.value)
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", obj, "type", type(obj))
try:
if "parent" in obj.keys():
del obj["parent"]
trace_print("Stripped parent from", obj.keys())
except:
pass
try:
if "method" in obj.keys():
del obj["method"]
trace_print("Stripped method from", 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", k)
trace_print("-type", 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", i)
obj_queue.append(i)
elif type(obj[k]) == dict:
if len(obj):
trace_print("push d", obj[k])
obj_queue.append(obj[k])
elif type(obj[k]) == type(type(0)):
if type(obj[k]) == int:
obj[k] = "int"
elif type(obj[k]) == str:
obj[k] = "string"
else:
obj[k] = "???"
trace_print("next key\n")
except:
trace_print("Exception")
[docs] def toJSON(self, indent=4, separators=None):
"""Converts a parsed structure to JSON"""
import json
self._strip_parent_keys()
def clean_dict(markers, keys=[]):
if id(markers) in keys:
return None
elif isinstance(markers, dict):
keys_ = keys + [id(markers)]
return {
key: clean_dict(markers[key], keys_)
for key, value in markers.items()
}
elif type(markers) in [list, set, tuple]:
return type(markers)(clean_dict(m, keys) for m in markers)
return markers
try:
del self.__dict__["classes_order"]
except:
pass
d = self.__dict__
d["classes"] = clean_dict(d["classes"])
return json.dumps(d, indent=indent, separators=separators, default="")
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