Files
pybindgen/pybindgen/castxmlparser.py
T
2018-03-17 14:28:34 +00:00

2468 lines
114 KiB
Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals, print_function, absolute_import
DEBUG = False
import sys
PY3 = (sys.version_info[0] >= 3)
if PY3:
string_types = str,
else:
string_types = basestring,
import os.path
import warnings
import re
import logging
import time
import pygccxml
from pygccxml import parser
from pygccxml import declarations
from .module import Module
from .typehandlers.codesink import FileCodeSink, CodeSink, NullCodeSink
from .typehandlers import base
from . import typehandlers
from .typehandlers.base import ctypeparser
from .typehandlers.base import ReturnValue, Parameter, TypeLookupError, TypeConfigurationError, NotSupportedError
from pygccxml.declarations.enumeration import enumeration_t
from .cppclass import CppClass, ReferenceCountingMethodsPolicy, FreeFunctionPolicy, ReferenceCountingFunctionsPolicy
from .cppexception import CppException
from pygccxml.declarations import type_traits
from pygccxml.declarations import type_traits_classes
from pygccxml.declarations import traits_impl_details
from pygccxml.declarations import cpptypes, typedef
from pygccxml.declarations import calldef
from pygccxml.declarations import calldef_members
from pygccxml.declarations import calldef_types
from pygccxml.declarations import templates
from pygccxml.declarations import container_traits, class_declaration
from pygccxml.declarations.declaration import declaration_t
from pygccxml.declarations.class_declaration import class_declaration_t, class_t
from . import settings
from . import utils
from pygccxml.utils import find_xml_generator
#from pygccxml.declarations.calldef import \
# destructor_t, constructor_t, member_function_t
from pygccxml.declarations.variable import variable_t
import collections
import subprocess
from cxxfilt import demangle
logger = logging.getLogger(__name__)
def check_template (demangled_name, function_name):
index = demangled_name.find(function_name)
end_index = index+len(function_name)
while end_index < len(demangled_name):
if demangled_name[end_index] == ' ':
end_index+=1
continue
if demangled_name[end_index] == '<':
return True
else:
return False
def get_template_arg (demangled_name, function_name):
index = demangled_name.find(function_name)
end_index = index+len(function_name)
arg = ""
started = False
opened = 0
while end_index < len(demangled_name):
if demangled_name[end_index] == ' ' and not started:
end_index+=1
continue
elif demangled_name[end_index] == '<' and opened == 0:
opened+=1
started = True
end_index+=1
continue
elif demangled_name[end_index] == '<' and not opened == 0:
opened +=1
elif demangled_name[end_index] == '>' and not opened == 1:
opened-=1
elif demangled_name[end_index] == '>' and opened == 1:
return arg.split(',')
arg+=demangled_name[end_index]
end_index+=1
def get_demangled_arg_type (demangled_decl_string):
arg_list = []
arg = ""
index = demangled_decl_string.rfind(')')-1
level = 0
loop = True
while loop:
if level == 0 and demangled_decl_string[index] == '(':
arg_list = [(arg.strip()).encode("utf-8")] + arg_list
loop = False
continue
elif level == 0 and demangled_decl_string[index] == ',':
arg_list = [(arg.strip()).encode("utf-8")] + arg_list
arg = ""
index-=1
continue
if demangled_decl_string[index] in ['>', ')', ']', '}']:
level+=1
if demangled_decl_string[index] in ['<', '(', '[', '{']:
level-=1
arg = demangled_decl_string[index] + arg
index-=1
return arg_list
###
### some patched pygccxml functions, from the type_traits module
###
def remove_pointer(type):
"""removes pointer from the type definition
If type is not pointer type, it will be returned as is.
"""
#nake_type = remove_alias( type )
nake_type = type
if not type_traits.is_pointer( nake_type ):
return type
elif isinstance( nake_type, cpptypes.volatile_t ) and isinstance( nake_type.base, cpptypes.pointer_t ):
return cpptypes.volatile_t( nake_type.base.base )
elif isinstance( nake_type, cpptypes.const_t ) and isinstance( nake_type.base, cpptypes.pointer_t ):
return cpptypes.const_t( nake_type.base.base )
elif isinstance(nake_type, cpptypes.compound_t) and isinstance( nake_type.base, cpptypes.calldef_type_t ):
return type
else:
if isinstance(nake_type, cpptypes.compound_t):
return nake_type.base
else:
return nake_type
def remove_reference(type):
"""removes reference from the type definition
If type is not reference type, it will be returned as is.
"""
#nake_type = remove_alias( type )
nake_type = type
if not type_traits.is_reference( nake_type ):
return type
else:
if isinstance(nake_type, cpptypes.compound_t):
return nake_type.base
else:
return nake_type
def remove_const(type):
"""removes const from the type definition
If type is not const type, it will be returned as is
"""
#nake_type = remove_alias( type )
nake_type = type
if not type_traits.is_const( nake_type ):
return type
else:
if isinstance(nake_type, cpptypes.compound_t):
return nake_type.base
else:
return nake_type
###
### end of patched pygccxml functions
###
## --- utility ---
import pygccxml.declarations.type_traits
def find_declaration_from_name(global_ns, declaration_name):
decl = pygccxml.declarations.traits_impl_details.impl_details.find_value_type(global_ns, declaration_name)
return decl
class ModuleParserWarning(Warning):
"""
Base class for all warnings reported here.
"""
class NotSupportedWarning(ModuleParserWarning):
"""
Warning for pybindgen GccxmlParser, to report something pybindgen does not support.
"""
class WrapperWarning(ModuleParserWarning):
"""
Warning for pybindgen GccxmlParser, to be used when a C++
definition cannot be converted to a pybindgen wrapper.
"""
class AnnotationsWarning(ModuleParserWarning):
"""
Warning for pybindgen GccxmlParser to report a problem in annotations.
"""
## ------------------------
class ErrorHandler(settings.ErrorHandler):
def handle_error(self, wrapper, exception, traceback_):
if hasattr(wrapper, "castxml_definition"):
definition = wrapper.castxml_definition
elif hasattr(wrapper, "main_wrapper"):
try:
definition = wrapper.main_wrapper.castxml_definition
except AttributeError:
definition = None
else:
definition = None
if definition is None:
print("exception %r in wrapper %s" % (exception, wrapper), file=sys.stderr)
else:
warnings.warn_explicit("exception %r in wrapper for %s"
% (exception, definition),
WrapperWarning, definition.location.file_name,
definition.location.line)
return True
settings.error_handler = ErrorHandler()
def normalize_name(decl_string):
return ctypeparser.normalize_type_string(decl_string)
def normalize_class_name(class_name, module_namespace):
class_name = utils.ascii(class_name)
if not class_name.startswith(module_namespace):
class_name = module_namespace + class_name
class_name = normalize_name(class_name)
return class_name
def _pygen_kwargs(kwargs):
l = []
for key, val in kwargs.items():
if isinstance(val, (CppClass, CppException)):
l.append("%s=root_module[%r]" % (key, utils.ascii(val.full_name)))
else:
if key == 'throw':
l.append("throw=[%s]" % (', '.join(["root_module[%r]" % utils.ascii(t.full_name) for t in val])))
elif key == 'parent' and isinstance(val, list):
l.append("parent=[%s]" % (', '.join(["root_module[%r]" % utils.ascii(cls.full_name) for cls in val])))
else:
l.append("%s=%r" % (key, val))
return l
def _pygen_args_kwargs(args, kwargs):
return ", ".join([repr(arg) for arg in args] + _pygen_kwargs(kwargs))
def _pygen_args_kwargs_dict(args, kwargs):
l = [repr(arg) for arg in args]
if kwargs:
l.append("dict(%s)" % ', '.join(_pygen_kwargs(kwargs)))
return ", ".join(l)
def _pygen_retval(args, kwargs):
if len(args) == 1 and len(kwargs) == 0:
return repr(args[0])
return "retval(%s)" % _pygen_args_kwargs(args, kwargs)
def _pygen_param(args, kwargs):
return "param(%s)" % _pygen_args_kwargs(args, kwargs)
class GccXmlTypeRegistry(object):
def __init__(self, root_module):
"""
:param root_module: the root L{Module} object
"""
assert isinstance(root_module, Module)
assert root_module.parent is None
self.root_module = root_module
self.ordered_classes = [] # registered classes list, by registration order
self._root_ns_rx = re.compile(r"(^|\s)(::)")
def class_registered(self, cpp_class):
assert isinstance(cpp_class, (CppClass, CppException))
self.ordered_classes.append(cpp_class)
# def get_type_traits(self, type_info):
# #assert isinstance(type_info, cpptypes.type_t)
# debug = False #('int64_t' in type_info.decl_string)
# if debug:
# print >> sys.stderr, "***** type traits for %r" % (type_info.decl_string, )
# is_const = False
# is_reference = False
# is_pointer = 0
# pointer_or_ref_count = 0
# inner_const = False
# while 1:
# prev_type_info = type_info
# if type_traits.is_pointer(type_info):
# is_pointer += 1
# type_info = remove_pointer(type_info)
# pointer_or_ref_count += 1
# elif type_traits.is_const(type_info):
# type_info = remove_const(type_info)
# if pointer_or_ref_count == 0:
# is_const = True
# elif pointer_or_ref_count == 1:
# inner_const = True
# else:
# warnings.warn("multiple consts not handled")
# elif type_traits.is_reference(type_info):
# warnings.warn("multiple &'s not handled")
# is_reference = True
# type_info = remove_reference(type_info)
# pointer_or_ref_count += 1
# else:
# break
# if type_info is prev_type_info:
# break
# type_name = normalize_name(type_info.partial_decl_string)
# try:
# cpp_type = self.root_module[type_name]
# except KeyError:
# cpp_type = type_name
# if not isinstance(cpp_type, CppClass):
# cpp_type = type_name
# if debug:
# print >> sys.stderr, "*** > return ", repr((cpp_type, is_const, is_pointer, is_reference))
# return (cpp_type, is_const, inner_const, is_pointer, is_reference)
def _fixed_std_type_name(self, type_name):
type_name = utils.ascii(type_name)
decl = self._root_ns_rx.sub('', type_name)
return decl
def lookup_return(self, type_info, annotations={}):
kwargs = {}
for name, value in annotations.items():
if name == 'caller_owns_return':
kwargs['caller_owns_return'] = annotations_scanner.parse_boolean(value)
elif name == 'reference_existing_object':
kwargs['reference_existing_object'] = annotations_scanner.parse_boolean(value)
elif name == 'return_internal_reference':
kwargs['return_internal_reference'] = annotations_scanner.parse_boolean(value)
elif name == 'custodian':
kwargs['custodian'] = int(value)
else:
warnings.warn("invalid annotation name %r" % name, AnnotationsWarning)
cpp_type = normalize_name(type_info.partial_decl_string)
return (cpp_type,), kwargs
def lookup_parameter(self, type_info, param_name, annotations={}, default_value=None):
kwargs = {}
for name, value in annotations.items():
if name == 'transfer_ownership':
kwargs['transfer_ownership'] = annotations_scanner.parse_boolean(value)
elif name == 'direction':
if value.lower() == 'in':
kwargs['direction'] = Parameter.DIRECTION_IN
elif value.lower() == 'out':
kwargs['direction'] = Parameter.DIRECTION_OUT
elif value.lower() == 'inout':
kwargs['direction'] = Parameter.DIRECTION_INOUT
else:
warnings.warn("invalid direction direction %r" % value, AnnotationsWarning)
elif name == 'custodian':
kwargs['custodian'] = int(value)
elif name == 'array_length':
kwargs['array_length'] = int(value)
elif name == 'default_value':
kwargs['default_value'] = value
elif name == 'null_ok':
kwargs['null_ok'] = annotations_scanner.parse_boolean(value)
else:
warnings.warn("invalid annotation name %r" % name, AnnotationsWarning)
if default_value:
kwargs['default_value'] = utils.ascii(default_value)
cpp_type = normalize_name(type_info.partial_decl_string)
return (cpp_type, param_name), kwargs
class AnnotationsScanner(object):
def __init__(self):
self.files = {} # file name -> list(lines)
self.used_annotations = {} # file name -> list(line_numbers)
self._comment_rx = re.compile(
r"^\s*(?://\s+-#-(?P<annotation1>.*)-#-\s*)|(?:/\*\s+-#-(?P<annotation2>.*)-#-\s*\*/)")
self._global_annotation_rx = re.compile(r"(\w+)(?:=([^\s;]+))?")
self._param_annotation_rx = re.compile(r"@(\w+)\(([^;]+)\)")
def _declare_used_annotation(self, file_name, line_number):
try:
l = self.used_annotations[file_name]
except KeyError:
l = []
self.used_annotations[file_name] = l
l.append(line_number)
def get_annotations(self, decl):
"""
:param decl: pygccxml declaration_t object
"""
assert isinstance(decl, declaration_t)
if isinstance(decl, calldef.calldef_t) \
and decl.is_artificial:
#print >> sys.stderr, "********** ARTIFICIAL:", decl
return {}, {}
file_name = decl.location.file_name
line_number = decl.location.line
if not file_name:
return {}, {}
try:
lines = self.files[file_name]
except KeyError:
with open(file_name, "rt") as f:
lines = f.readlines()
self.files[file_name] = lines
line_number -= 2
global_annotations = {}
parameter_annotations = {}
while 1:
line = lines[line_number]
line_number -= 1
m = self._comment_rx.match(line)
if m is None:
break
s = m.group('annotation1')
if s is None:
s = m.group('annotation2')
line = s.strip()
self._declare_used_annotation(file_name, line_number + 2)
for annotation_str in line.split(';'):
annotation_str = annotation_str.strip()
m = self._global_annotation_rx.match(annotation_str)
if m is not None:
global_annotations[m.group(1)] = m.group(2)
continue
m = self._param_annotation_rx.match(annotation_str)
if m is not None:
param_annotation = {}
parameter_annotations[m.group(1)] = param_annotation
for param in m.group(2).split(','):
m = self._global_annotation_rx.match(param.strip())
if m is not None:
param_annotation[m.group(1)] = m.group(2)
else:
warnings.warn_explicit("could not parse %r as parameter annotation element" %
(param.strip()),
AnnotationsWarning, file_name, line_number)
continue
warnings.warn_explicit("could not parse %r" % (annotation_str),
AnnotationsWarning, file_name, line_number)
return global_annotations, parameter_annotations
def parse_boolean(self, value):
if isinstance(value, int):
return bool(value)
if value.lower() in ['false', 'off']:
return False
elif value.lower() in ['true', 'on']:
return True
else:
raise ValueError("bad boolean value %r" % value)
def warn_unused_annotations(self):
for file_name, lines in self.files.items():
try:
used_annotations = self.used_annotations[file_name]
except KeyError:
used_annotations = []
for line_number, line in enumerate(lines):
m = self._comment_rx.match(line)
if m is None:
continue
#print >> sys.stderr, (line_number+1), used_annotations
if (line_number + 1) not in used_annotations:
warnings.warn_explicit("unused annotation",
AnnotationsWarning, file_name, line_number+1)
annotations_scanner = AnnotationsScanner()
## ------------------------
class PygenSection(object):
"Class to hold information about a python generation section"
def __init__(self, name, code_sink, local_customizations_module=None):
"""
:param name: section name; this name should be a valid python
module name; the special name '__main__' is used to denote the
main section, which comprises the main script itself
:type name: str
:param code_sink: code sink that will receive the generated
code for the section. Normally the code sink should write to
a file with the name of the section and a .py extension, to
allow importing it as module.
:type code_sink: L{CodeSink}
:param local_customizations_module: name of the python module
that may contain local customizations for the section, or
None. If not None, PyBindGen will generate code that tries to
import that module in the respective section and call
functions on it, or ignore it if the module does not exist.
:type local_customizations_module: str
"""
assert isinstance(name, string_types)
self.name = name
assert isinstance(code_sink, CodeSink)
self.code_sink = code_sink
assert local_customizations_module is None or isinstance(local_customizations_module, string_types)
self.local_customizations_module = local_customizations_module
class PygenClassifier(object):
def classify(self, pygccxml_definition):
"""
This is a pure virtual method that must be implemented by
subclasses. It will be called by PyBindGen for every API
definition, and should return a section name.
:param pygccxml_definition: castxml definition object
:returns: section name
"""
raise NotImplementedError
def get_section_precedence(self, section_name):
"""
This is a pure virtual method that may (or not) be implemented by
subclasses. It will be called by PyBindGen for every API
definition, and should return the precedence of a section.
This is used when sections reflect 'modules' whose types must be
registered in a certain order.
:param section_name: the name of the section
:returns: order of precedence of the section. The lower the
number, the sooner the section is to be registered.
"""
raise NotImplementedError
class ModuleParser(object):
"""
:attr enable_anonymous_containers: if True, pybindgen will attempt
to scan for all std containers, even the ones that have no
typedef'ed name. Enabled by default.
"""
def __init__(self, module_name, module_namespace_name='::'):
"""
Creates an object that will be able parse header files and
create a pybindgen module definition.
:param module_name: name of the Python module
:param module_namespace_name: optional C++ namespace name; if
given, only definitions of this
namespace will be included in the
python module
"""
self.module_name = module_name
self.module_namespace_name = module_namespace_name
self.location_filter = None
self.header_files = None
self.castxml_config = None
self.whitelist_paths = []
self.module_namespace = None # pygccxml module C++ namespace
self.module = None # the toplevel pybindgen.module.Module instance (module being generated)
self.declarations = None # (as returned by pygccxml.parser.parse)
self.global_ns = None
self._types_scanned = False
self._pre_scan_hooks = []
self._post_scan_hooks = []
self.type_registry = None
self._stage = None
self._pygen_sink = None
self._pygen_factory = None
self._anonymous_structs = [] # list of (pygccxml_anonymous_class, outer_pybindgen_class)
self._containers_to_register = []
self._containers_registered = {}
self.enable_anonymous_containers = True
def add_pre_scan_hook(self, hook):
"""
Add a function to be called right before converting a castxml
definition to a PyBindGen wrapper object. This hook function
will be called for every scanned type, function, or method,
and given the a chance to modify the annotations for that
definition. It will be called like this:::
pre_scan_hook(module_parser, pygccxml_definition, global_annotations,
parameter_annotations)
where:
- module_parser -- the ModuleParser (this class) instance
- pygccxml_definition -- the definition reported by pygccxml
- global_annotations -- a dicionary containing the "global annotations"
for the definition, i.e. a set of key=value
pairs not associated with any particular
parameter
- parameter_annotations -- a dicionary containing the "parameter
annotations" for the definition. It is a
dict whose keys are parameter names and
whose values are dicts containing the
annotations for that parameter. Annotations
pertaining the return value of functions or
methods are denoted by a annotation for a
parameter named 'return'.
"""
if not isinstance(hook, collections.Callable):
raise TypeError("hook must be callable")
self._pre_scan_hooks.append(hook)
def add_post_scan_hook(self, hook):
"""
Add a function to be called right after converting a castxml definition
to a PyBindGen wrapper object. This hook function will be called for
every scanned type, function, or method. It will be called like this::
post_scan_hook(module_parser, pygccxml_definition, pybindgen_wrapper)
where:
- module_parser -- the ModuleParser (this class) instance
- pygccxml_definition -- the definition reported by pygccxml
- pybindgen_wrapper -- a pybindgen object that generates a wrapper,
such as CppClass, Function, or CppMethod.
"""
if not isinstance(hook, collections.Callable):
raise TypeError("hook must be callable")
self._post_scan_hooks.append(hook)
def __location_match(self, decl):
if decl.location.file_name in self.header_files:
return True
for incdir in self.whitelist_paths:
if os.path.abspath(decl.location.file_name).startswith(incdir):
return True
return False
def parse(self, header_files, include_paths=None, whitelist_paths=None, includes=(),
pygen_sink=None, pygen_classifier=None, castxml_options=None,
gccxml_options=None):
"""
parses a set of header files and returns a pybindgen Module instance.
It is equivalent to calling the following methods:
1. parse_init(header_files, include_paths, whitelist_paths)
2. scan_types()
3. scan_methods()
4. scan_functions()
5. parse_finalize()
The documentation for L{ModuleParser.parse_init} explains the parameters.
"""
options = castxml_options or gccxml_options
self.parse_init(header_files, include_paths, whitelist_paths, includes, pygen_sink,
pygen_classifier, options)
self.scan_types()
self.scan_methods()
self.scan_functions()
self.parse_finalize()
return self.module
def parse_init(self, header_files, include_paths=None,
whitelist_paths=None, includes=(), pygen_sink=None, pygen_classifier=None,
castxml_options=None, gccxml_options=None):
"""
Prepares to parse a set of header files. The following
methods should then be called in order to finish the rest of
scanning process:
#. scan_types()
#. scan_methods()
#. scan_functions()
#. parse_finalize()
:param header_files: header files to parse
:type header_files: list of string
:param include_paths: (deprecated, use the parameter castxml_options) list of include paths
:type include_paths: list of string
:param whitelist_paths: additional directories for definitions to be included
Normally the module parser filters out API definitions that
have been defined outside one of the header files indicated
for parsing. The parameter whitelist_paths instructs the
module parser to accept definitions defined in another
header file if such header file is inside one of the
directories listed by whitelist_paths.
:type whitelist_paths: list of string
:param pygen_sink: code sink for python script generation.
This parameter activates a mode wherein ModuleParser, in
addition to building in memory API definitions, creates a
python script that will generate the module, when executed.
The generated Python script can be human editable and does
not require pygccxml or castxml to run, only PyBindGen to be
installed.
The pygen parameter can be either:
#. A single code sink: this will become the main and only script file to be generated
#. A list of L{PygenSection} objects. This option
requires the pygen_classifier to be given.
:type pygen_sink: L{CodeSink} or list of L{PygenSection} objects
:param pygen_classifier: the classifier to use when pygen is given and is a dict
:param castxml_options: extra options to pass into the
:class:`pygccxml.parser.config.gccxml_configuration_t` object as keyword
arguments for more information).
:type castxml_options: dict
"""
assert isinstance(header_files, list)
assert isinstance(includes, (list, tuple))
options = castxml_options or gccxml_options
self._pygen = pygen_sink
self._pygen_classifier = pygen_classifier
if isinstance(pygen_sink, list):
assert isinstance(pygen_classifier, PygenClassifier)
has_main = False
for sect in self._pygen:
if not isinstance(sect, PygenSection):
raise TypeError
if sect.name == '__main__':
has_main = True
if not has_main:
raise ValueError("missing __main__ section")
elif pygen_sink is None:
pass
else:
assert isinstance(pygen_sink, CodeSink)
self.header_files = [os.path.abspath(f) for f in header_files]
self.location_filter = declarations.custom_matcher_t(self.__location_match)
if whitelist_paths is not None:
assert isinstance(whitelist_paths, list)
self.whitelist_paths = [os.path.abspath(p) for p in whitelist_paths]
if options is None:
options = {}
else:
options = dict(options)
generator_path, generator_name = find_xml_generator()
options['xml_generator_path'] = generator_path
options['xml_generator'] = generator_name
if include_paths is not None:
assert isinstance(include_paths, list)
warnings.warn("Parameter include_paths is deprecated, use castxml_options instead", DeprecationWarning,
stacklevel=2)
options['include_paths'] = include_paths
logger.debug("castxml options: %r", options)
self.castxml_config = parser.xml_generator_configuration_t(**options)
self.declarations = parser.parse(header_files, self.castxml_config)
self.global_ns = declarations.get_global_namespace(self.declarations)
if self.module_namespace_name == '::':
self.module_namespace = self.global_ns
else:
self.module_namespace = self.global_ns.namespace(self.module_namespace_name)
self.module = Module(self.module_name,
cpp_namespace=self.module_namespace.decl_string)
for inc in includes:
self.module.add_include(inc)
for pygen_sink in self._get_all_pygen_sinks():
pygen_sink.writeln("from pybindgen import Module, FileCodeSink, param, retval, cppclass, typehandlers")
pygen_sink.writeln()
pygen_sink = self._get_main_pygen_sink()
if pygen_sink:
pygen_sink.writeln("""
import pybindgen.settings
import warnings
class ErrorHandler(pybindgen.settings.ErrorHandler):
def handle_error(self, wrapper, exception, traceback_):
warnings.warn("exception %r in wrapper %s" % (exception, wrapper))
return True
pybindgen.settings.error_handler = ErrorHandler()
""")
pygen_sink.writeln("import sys")
if isinstance(self._pygen, list):
for sect in self._pygen:
if sect.name == '__main__':
continue
pygen_sink.writeln("import %s" % sect.name)
pygen_sink.writeln()
pygen_sink.writeln("def module_init():")
pygen_sink.indent()
pygen_sink.writeln("root_module = Module(%r, cpp_namespace=%r)"
% (self.module_name, utils.ascii(self.module_namespace.decl_string)))
for inc in includes:
pygen_sink.writeln("root_module.add_include(%r)" % inc)
pygen_sink.writeln("return root_module")
pygen_sink.unindent()
pygen_sink.writeln()
self.type_registry = GccXmlTypeRegistry(self.module)
self._stage = 'init'
def _get_main_pygen_sink(self):
if isinstance (self._pygen, CodeSink):
return self._pygen
elif isinstance(self._pygen, list):
for sect in self._pygen:
if sect.name == '__main__':
return sect.code_sink
else:
return None
def _get_all_pygen_sinks(self):
if isinstance (self._pygen, CodeSink):
return [self._pygen]
elif isinstance(self._pygen, list):
return [sect.code_sink for sect in self._pygen]
else:
return []
def _get_pygen_sink_for_definition(self, pygccxml_definition, with_section_precedence=False):
if self._pygen_classifier is None:
if with_section_precedence:
return (0, self._pygen)
else:
return self._pygen
else:
if isinstance(pygccxml_definition, declaration_t):
section = self._pygen_classifier.classify(pygccxml_definition)
for sect in self._pygen:
if sect is section or sect.name == section:
sink = sect.code_sink
break
else:
raise ValueError("CodeSink for section %r not available" % section)
else:
sink = self._get_main_pygen_sink()
section = '__main__'
if with_section_precedence:
try:
prec = self._pygen_classifier.get_section_precedence(section)
except NotImplementedError:
prec = 0
return (prec, sink)
else:
return sink
def scan_types(self):
self._stage = 'scan types'
self._registered_classes = {} # class_t -> CppClass
self._scan_namespace_types(self.module, self.module_namespace, pygen_register_function_name="register_types")
self._types_scanned = True
def scan_methods(self):
self._stage = 'scan methods'
assert self._types_scanned
for pygen_sink in self._get_all_pygen_sinks():
pygen_sink.writeln("def register_methods(root_module):")
pygen_sink.indent()
for class_wrapper in self.type_registry.ordered_classes:
if isinstance(class_wrapper.castxml_definition, class_declaration_t):
continue # skip classes not fully defined
if isinstance(class_wrapper, CppException):
continue # exceptions cannot have methods (yet)
#if class_wrapper.import_from_module:
# continue # foreign class
pygen_sink = self._get_pygen_sink_for_definition(class_wrapper.castxml_definition)
if pygen_sink:
register_methods_func = "register_%s_methods" % (class_wrapper.mangled_full_name,)
pygen_sink.writeln("%s(root_module, root_module[%r])" % (register_methods_func, class_wrapper.full_name))
for pygen_sink in self._get_all_pygen_sinks():
if pygen_sink is self._get_main_pygen_sink() and isinstance(self._pygen, list):
for sect in self._pygen:
if sect.name == '__main__':
continue
pygen_sink.writeln("root_module.begin_section(%r)" % sect.name)
pygen_sink.writeln("%s.register_methods(root_module)" % sect.name)
if sect.local_customizations_module:
pygen_sink.writeln("\ntry:\n"
" import %s\n"
"except ImportError:\n"
" pass\n"
"else:\n"
" %s.register_methods(root_module)\n"
% (sect.local_customizations_module, sect.local_customizations_module))
pygen_sink.writeln("root_module.end_section(%r)" % sect.name)
pygen_sink.writeln("return")
pygen_sink.unindent()
pygen_sink.writeln()
for class_wrapper in self.type_registry.ordered_classes:
if isinstance(class_wrapper.castxml_definition, class_declaration_t):
continue # skip classes not fully defined
if isinstance(class_wrapper, CppException):
continue # exceptions cannot have methods (yet)
#if class_wrapper.import_from_module:
# continue # this is a foreign class from another module, we don't scan it
register_methods_func = "register_%s_methods" % (class_wrapper.mangled_full_name,)
pygen_sink = self._get_pygen_sink_for_definition(class_wrapper.castxml_definition)
if pygen_sink:
pygen_sink.writeln("def %s(root_module, cls):" % (register_methods_func,))
pygen_sink.indent()
## Add attributes from inner anonymous to each outer class (LP#237054)
for anon_cls, wrapper in self._anonymous_structs:
if wrapper is class_wrapper:
self._scan_class_methods(anon_cls, wrapper, pygen_sink)
self._scan_class_methods(class_wrapper.castxml_definition, class_wrapper, pygen_sink)
if pygen_sink:
pygen_sink.writeln("return")
pygen_sink.unindent()
pygen_sink.writeln()
def parse_finalize(self):
annotations_scanner.warn_unused_annotations()
pygen_sink = self._get_main_pygen_sink()
if pygen_sink:
pygen_sink.writeln("def main():")
pygen_sink.indent()
pygen_sink.writeln("out = FileCodeSink(sys.stdout)")
pygen_sink.writeln("root_module = module_init()")
pygen_sink.writeln("register_types(root_module)")
pygen_sink.writeln("register_methods(root_module)")
pygen_sink.writeln("register_functions(root_module)")
pygen_sink.writeln("root_module.generate(out)")
pygen_sink.unindent()
pygen_sink.writeln()
pygen_sink.writeln("if __name__ == '__main__':\n main()")
pygen_sink.writeln()
return self.module
def _apply_class_annotations(self, cls, annotations, kwargs):
is_exception = False
logger.debug("cls %s annotations: %r", cls.name, annotations)
for name, value in annotations.items():
if name == 'allow_subclassing':
kwargs.setdefault('allow_subclassing', annotations_scanner.parse_boolean(value))
elif name == 'is_singleton':
kwargs.setdefault('is_singleton', annotations_scanner.parse_boolean(value))
elif name == 'incref_method':
kwargs.setdefault('memory_policy', ReferenceCountingMethodsPolicy(
incref_method=value, decref_method=annotations.get('decref_method', None),
peekref_method=annotations.get('peekref_method', None)))
elif name == 'decref_method':
pass
elif name == 'peekref_method':
pass
elif name == 'automatic_type_narrowing':
kwargs.setdefault('automatic_type_narrowing', annotations_scanner.parse_boolean(value))
elif name == 'free_function':
kwargs.setdefault('memory_policy', FreeFunctionPolicy(value))
elif name == 'incref_function':
kwargs.setdefault('memory_policy', ReferenceCountingFunctionsPolicy(
incref_function=value, decref_function=annotations.get('decref_function', None)))
elif name == 'decref_function':
pass
elif name == 'python_name':
kwargs.setdefault('custom_name', value)
warnings.warn_explicit("Class annotation 'python_name' is deprecated in favour of 'custom_name'",
AnnotationsWarning, cls.location.file_name, cls.location.line)
elif name == 'custom_name':
kwargs.setdefault('custom_name', value)
elif name == 'pygen_comment':
pass
elif name == 'exception':
is_exception = True
elif name == 'import_from_module':
kwargs.setdefault('import_from_module', value)
else:
warnings.warn_explicit("Class annotation %r ignored" % name,
AnnotationsWarning, cls.location.file_name, cls.location.line)
if isinstance(cls, class_t):
if self._class_has_virtual_methods(cls) and not cls.bases:
kwargs.setdefault('allow_subclassing', True)
#if not self._has_public_destructor(cls):
# kwargs.setdefault('is_singleton', True)
# #print >> sys.stderr, "##### class %s has no public destructor" % cls.decl_string
des = self._get_destructor_visibility(cls)
#print >> sys.stderr, "##### class %s destructor is %s" % (cls.decl_string, des)
if des != 'public':
kwargs.setdefault('destructor_visibility', des)
return is_exception
def _get_destructor_visibility(self, cls):
for member in cls.get_members():
if isinstance(member, calldef_members.destructor_t):
return member.access_type
def _has_public_destructor(self, cls):
for member in cls.get_members():
if isinstance(member, calldef_members.destructor_t):
if member.access_type != 'public':
return False
return True
def _scan_namespace_types(self, module, module_namespace, outer_class=None, pygen_register_function_name=None):
logger.debug("_scan_namespace_types(%s, %s)", module, module_namespace)
root_module = module.get_root()
if pygen_register_function_name:
for pygen_sink in self._get_all_pygen_sinks():
pygen_sink.writeln("def %s(module):" % pygen_register_function_name)
pygen_sink.indent()
pygen_sink.writeln("root_module = module.get_root()")
pygen_sink.writeln()
if pygen_sink is self._get_main_pygen_sink() and isinstance(self._pygen, list):
for section in self._pygen:
if section.name == '__main__':
continue
if pygen_register_function_name == "register_types":
pygen_sink.writeln("root_module.begin_section(%r)" % section.name)
pygen_sink.writeln("%s.%s(module)" % (section.name, pygen_register_function_name))
if section.local_customizations_module:
pygen_sink.writeln("\ntry:\n"
" import %s\n"
"except ImportError:\n"
" pass\n"
"else:\n"
" %s.register_types(module)\n"
% (section.local_customizations_module, section.local_customizations_module))
pygen_sink.writeln("root_module.end_section(%r)" % section.name)
## detect use of unregistered container types: need to look at
## all parameters and return values of all functions in this namespace...
for fun in module_namespace.free_functions(function=self.location_filter,
allow_empty=True, recursive=False):
# logger.debug("Saw free function: %s", fun)
if fun.name.startswith('__'):
continue
for dependency in fun.i_depend_on_them(recursive=True):
type_info = dependency.depend_on_it
if type_traits.is_pointer(type_info):
type_info = type_traits.remove_pointer(type_info)
elif type_traits.is_reference(type_info):
type_info = type_traits.remove_reference(type_info)
if type_traits.is_const(type_info):
type_info = type_traits.remove_const(type_info)
traits = container_traits.find_container_traits(type_info)
if traits is None:
continue
name = normalize_name(type_info.partial_decl_string)
#print >> sys.stderr, "** type: %s; ---> partial_decl_string: %r; name: %r" %\
# (type_info, type_info.partial_decl_string, name)
self._containers_to_register.append((traits, type_info, None, name))
## scan enumerations
if outer_class is None:
enums = module_namespace.enumerations(function=self.location_filter,
recursive=False, allow_empty=True)
else:
enums = []
for enum in outer_class.castxml_definition.enumerations(function=self.location_filter,
recursive=False, allow_empty=True):
if outer_class.castxml_definition.find_out_member_access_type(enum) != 'public':
continue
if enum.name.startswith('__'):
continue
#if not enum.name:
# warnings.warn_explicit("Enum %s ignored because it has no name"
# % (enum, ),
# NotSupportedWarning, enum.location.file_name, enum.location.line)
# continue
enums.append(enum)
for enum in enums:
# logger.debug("Saw enum: %s", enum)
global_annotations, param_annotations = annotations_scanner.get_annotations(enum)
for hook in self._pre_scan_hooks:
hook(self, enum, global_annotations, param_annotations)
if 'ignore' in global_annotations:
continue
enum_values_repr = '[' + ', '.join([repr(utils.ascii(name)) for name, dummy_val in enum.values]) + ']'
l = [repr(utils.ascii(enum.name)), enum_values_repr]
if outer_class is not None:
l.append('outer_class=root_module[%r]' % outer_class.full_name)
pygen_sink = self._get_pygen_sink_for_definition(enum)
if 'import_from_module' in global_annotations:
l.append("import_from_module=%r" % (global_annotations["import_from_module"],))
if pygen_sink:
if 'pygen_comment' in global_annotations:
pygen_sink.writeln('## ' + global_annotations['pygen_comment'])
pygen_sink.writeln('module.add_enum(%s)' % ', '.join(l))
module.add_enum(utils.ascii(enum.name), [utils.ascii(name) for name, dummy_val in enum.values],
outer_class=outer_class)
## scan classes
if outer_class is None:
unregistered_classes = [cls for cls in
module_namespace.classes(function=self.location_filter,
recursive=False, allow_empty=True)
if not cls.name.startswith('__')]
typedefs = [typedef for typedef in
module_namespace.typedefs(function=self.location_filter,
recursive=False, allow_empty=True)
if not typedef.name.startswith('__')]
else:
unregistered_classes = []
typedefs = []
for cls in outer_class.castxml_definition.classes(function=self.location_filter,
recursive=False, allow_empty=True):
if outer_class.castxml_definition.find_out_member_access_type(cls) != 'public':
continue
if cls.name.startswith('__'):
continue
unregistered_classes.append(cls)
for typedef in outer_class.castxml_definition.typedefs(function=self.location_filter,
recursive=False, allow_empty=True):
if outer_class.castxml_definition.find_out_member_access_type(typedef) != 'public':
continue
if typedef.name.startswith('__'):
continue
typedefs.append(typedef)
unregistered_classes.sort(key=lambda c: c.decl_string)
# logger.debug("unregistered_classes: %r", unregistered_classes)
def postpone_class(cls, reason):
## detect the case of a class being postponed many times; that
## is almost certainly an error and a sign of an infinite
## loop.
count = getattr(cls, "_pybindgen_postpone_count", 0)
count += 1
cls._pybindgen_postpone_count = count
if count >= 10:
raise AssertionError("The class %s registration is being postponed for "
"the %ith time (last reason: %r, current reason: %r);"
" something is wrong, please file a bug report"
" (https://bugs.launchpad.net/pybindgen/+filebug) with a test case."
% (cls, count, cls._pybindgen_postpone_reason, reason))
cls._pybindgen_postpone_reason = reason
if DEBUG:
print(">>> class %s is being postponed (%s)" % (str(cls), reason), file=sys.stderr)
unregistered_classes.append(cls)
timings = []
while unregistered_classes:
timings.append(time.time())
if len(timings) > 1 and timings[-1] - timings[0] > 0.1:
tl = ["{:.1f}".format((t2 - t1) * 1000)
for t2, t1 in zip(timings[1:], timings[:-1])]
logger.debug("timings (total: %.1f ms): %s",
(timings[-1] - timings[0]) * 1000,
" + ".join(tl))
del timings[:]
timings.append(time.time())
cls = unregistered_classes.pop(0)
logger.info("looking at class (%i left): %s",
len(unregistered_classes), cls)
typedef = None
kwargs = {}
global_annotations, param_annotations = annotations_scanner.get_annotations(cls)
timings.append(time.time())
for hook in self._pre_scan_hooks:
hook(self, cls, global_annotations, param_annotations)
if 'ignore' in global_annotations:
logger.debug("Class %s annotated with 'ignore', skipping",
cls)
continue
timings.append(time.time())
if not cls.name:
if outer_class is None:
warnings.warn_explicit(("Class %s ignored: anonymous structure not inside a named structure/union."
% cls.partial_decl_string),
NotSupportedWarning, cls.location.file_name, cls.location.line)
continue
self._anonymous_structs.append((cls, outer_class))
continue
timings.append(time.time())
if '<' in cls.name:
for typedef in module_namespace.typedefs(function=self.location_filter,
recursive=False, allow_empty=True):
typedef_type = type_traits.remove_declarated(typedef.decl_type)
if typedef_type == cls:
break
else:
typedef = None
timings.append(time.time())
base_class_wrappers = []
bases_ok = True
for cls_bases_item in cls.bases:
base_cls = cls_bases_item.related_class
try:
base_class_wrapper = self._registered_classes[base_cls]
except KeyError:
## base class not yet registered => postpone this class registration
if base_cls not in unregistered_classes:
warnings.warn_explicit("Class %s ignored because it uses a base class (%s) "
"which is not declared."
% (cls.partial_decl_string, base_cls.partial_decl_string),
ModuleParserWarning, cls.location.file_name, cls.location.line)
bases_ok = False
break
postpone_class(cls, "waiting for base class %s to be registered first" % base_cls)
bases_ok = False
break
else:
base_class_wrappers.append(base_class_wrapper)
del base_class_wrapper
del base_cls
if not bases_ok:
continue
timings.append(time.time())
## If this class implicitly converts to another class, but
## that other class is not yet registered, postpone.
for operator in cls.casting_operators(allow_empty=True):
target_type = type_traits.remove_declarated(operator.return_type)
if not isinstance(target_type, class_t):
continue
target_class_name = normalize_class_name(operator.return_type.partial_decl_string, '::')
try:
dummy = root_module[target_class_name]
except KeyError:
if target_class_name not in [normalize_class_name(t.partial_decl_string, '::') for t in unregistered_classes]:
ok = True # (lp:455689)
else:
ok = False
break
else:
ok = True
if not ok:
postpone_class(cls, ("waiting for implicit conversion target class %s to be registered first"
% (operator.return_type.partial_decl_string,)))
continue
is_exception = self._apply_class_annotations(cls, global_annotations, kwargs)
timings.append(time.time())
custom_template_class_name = None
template_parameters = ()
if typedef is None:
alias = None
isinst = templates.is_instantiation(cls.decl_string)
timings.append(time.time())
if isinst:
cls_name, template_parameters = templates.split(cls.name)
assert template_parameters
if '::' in cls_name:
cls_name = cls_name.split('::')[-1]
template_instance_names = global_annotations.get('template_instance_names', '')
if template_instance_names:
for mapping in template_instance_names.split('|'):
type_names, name = mapping.split('=>')
instance_types = type_names.split(',')
if instance_types == template_parameters:
custom_template_class_name = name
break
else:
cls_name = cls.name
else:
timings.append(time.time())
cls_name = typedef.name
alias = '::'.join([module.cpp_namespace_prefix, cls.name])
template_parameters_decls = [find_declaration_from_name(self.global_ns, templ_param)
for templ_param in template_parameters]
ignore_class = False
for template_param in template_parameters_decls:
if not isinstance(template_param, class_t):
continue
if not isinstance(template_param.parent, class_t):
continue
access = template_param.parent.find_out_member_access_type(template_param)
if access != 'public':
# this templated class depends on a private type => we can't wrap it
ignore_class = True
break
if ignore_class:
continue
timings.append(time.time())
if 0: # this is disabled due to ns3
## if any template argument is a class that is not yet
## registered, postpone scanning/registering the template
## instantiation class until the template argument gets
## registered.
postponed = False
for templ in template_parameters_decls:
if isinstance(templ, class_t):
try:
self._registered_classes[templ]
except KeyError:
if templ in unregistered_classes:
postpone_class(cls, "waiting for template argument class %s to be registered first" % templ)
postponed = True
if postponed:
continue
if base_class_wrappers:
if len(base_class_wrappers) > 1:
kwargs["parent"] = base_class_wrappers
else:
kwargs["parent"] = base_class_wrappers[0]
if outer_class is not None:
kwargs["outer_class"] = outer_class
if template_parameters:
kwargs["template_parameters"] = template_parameters
if custom_template_class_name:
kwargs["custom_name"] = custom_template_class_name
# given the pygen sinks for the class itself and the sinks
# for the template parameters, get the one with lowest
# precedence (the higher the number, the lowest the
# precedence).
pygen_sinks = [self._get_pygen_sink_for_definition(cls, with_section_precedence=True)]
for templ in template_parameters_decls:
if templ is not None:
pygen_sinks.append(self._get_pygen_sink_for_definition(templ, with_section_precedence=True))
pygen_sinks.sort()
pygen_sink = pygen_sinks[-1][1]
del pygen_sinks
if pygen_sink:
if 'pygen_comment' in global_annotations:
pygen_sink.writeln('## ' + global_annotations['pygen_comment'])
if is_exception:
pygen_sink.writeln("module.add_exception(%s)" %
", ".join([repr(cls_name)] + _pygen_kwargs(kwargs)))
else:
pygen_sink.writeln("module.add_class(%s)" %
", ".join([repr(cls_name)] + _pygen_kwargs(kwargs)))
timings.append(time.time())
## detect use of unregistered container types: need to look at
## all parameters and return values of all functions in this namespace...
for member in cls.get_members(access='public'):
if member.name.startswith('__'):
continue
for dependency in member.i_depend_on_them(recursive=True):
type_info = dependency.depend_on_it
if type_traits.is_pointer(type_info):
type_info = type_traits.remove_pointer(type_info)
elif type_traits.is_reference(type_info):
type_info = type_traits.remove_reference(type_info)
if type_traits.is_const(type_info):
type_info = type_traits.remove_const(type_info)
traits = container_traits.find_container_traits(type_info)
if traits is None:
continue
name = normalize_name(type_info.partial_decl_string)
# now postpone container registration until after
# all classes are registered, because we may
# depend on one of those classes for the element
# type.
self._containers_to_register.append((traits, type_info, None, name))
timings.append(time.time())
if is_exception:
class_wrapper = module.add_exception(cls_name, **kwargs)
else:
class_wrapper = module.add_class(cls_name, **kwargs)
#print >> sys.stderr, "<<<<<ADD CLASS>>>>> ", cls_name
class_wrapper.castxml_definition = cls
self._registered_classes[cls] = class_wrapper
if alias:
class_wrapper.register_alias(normalize_name(alias))
self.type_registry.class_registered(class_wrapper)
timings.append(time.time())
for hook in self._post_scan_hooks:
hook(self, cls, class_wrapper)
timings.append(time.time())
del cls_name
## scan for nested classes/enums
self._scan_namespace_types(module, module_namespace, outer_class=class_wrapper)
timings.append(time.time())
# scan for implicit conversion casting operators
for operator in cls.casting_operators(allow_empty=True):
target_type = type_traits.remove_declarated(operator.return_type)
if not isinstance(target_type, class_t):
continue
other_class_name = normalize_class_name(operator.return_type.partial_decl_string, '::')
try:
other_class = root_module[other_class_name]
except KeyError:
warnings.warn_explicit("Implicit conversion target type %s not registered"
% (other_class_name,),
WrapperWarning, operator.location.file_name,
operator.location.line)
else:
class_wrapper.implicitly_converts_to(other_class)
if pygen_sink:
if 'pygen_comment' in global_annotations:
pygen_sink.writeln('## ' + global_annotations['pygen_comment'])
pygen_sink.writeln("root_module[%r].implicitly_converts_to(root_module[%r])"
% (class_wrapper.full_name, other_class.full_name))
timings.append(time.time())
# -- register containers
if outer_class is None:
for (traits, type_info, _outer_class, name) in self._containers_to_register:
self._register_container(module, traits, type_info, _outer_class, name)
self._containers_to_register = []
if pygen_register_function_name:
pygen_function_closed = False
else:
pygen_function_closed = True
## --- look for typedefs ----
for alias in typedefs:
type_from_name = normalize_name(alias.decl_type.partial_decl_string)
if outer_class is None:
type_to_name = normalize_name(utils.ascii('::'.join([module.cpp_namespace_prefix, alias.name])))
else:
type_to_name = normalize_name(utils.ascii('::'.join([outer_class.full_name, alias.name])))
for sym in '', '*', '&':
typehandlers.base.add_type_alias(type_from_name+sym, type_to_name+sym)
pygen_sink = self._get_pygen_sink_for_definition(alias)
if pygen_sink:
pygen_sink.writeln("typehandlers.add_type_alias(%r, %r)" % (type_from_name+sym, type_to_name+sym))
## Look for forward declarations of class/structs like
## "typedef struct _Foo Foo"; these are represented in
## pygccxml by a typedef whose .type.declaration is a
## class_declaration_t instead of class_t.
if isinstance(alias.decl_type, cpptypes.declarated_t):
cls = alias.decl_type.declaration
if templates.is_instantiation(cls.decl_string):
continue # typedef to template instantiations, must be fully defined
if isinstance(cls, class_declaration_t):
global_annotations, param_annotations = annotations_scanner.get_annotations(cls)
for hook in self._pre_scan_hooks:
hook(self, cls, global_annotations, param_annotations)
if 'ignore' in global_annotations:
continue
kwargs = dict()
self._apply_class_annotations(cls, global_annotations, kwargs)
kwargs.setdefault("incomplete_type", True)
kwargs.setdefault("automatic_type_narrowing", False)
kwargs.setdefault("allow_subclassing", False)
pygen_sink = self._get_pygen_sink_for_definition(cls)
if pygen_sink:
if 'pygen_comment' in global_annotations:
pygen_sink.writeln('## ' + global_annotations['pygen_comment'])
pygen_sink.writeln("module.add_class(%s)" %
", ".join([repr(alias.name)] + _pygen_kwargs(kwargs)))
class_wrapper = module.add_class(alias.name, **kwargs)
class_wrapper.castxml_definition = cls
self._registered_classes[cls] = class_wrapper
if cls.name != alias.name:
class_wrapper.register_alias(normalize_name(cls.name))
self.type_registry.class_registered(class_wrapper)
## Handle "typedef ClassName OtherName;"
elif isinstance(cls, class_t):
#print >> sys.stderr, "***** typedef", cls, "=>", alias.name
try:
cls_wrapper = self._registered_classes[cls]
except KeyError:
warnings.warn("typedef %s %s: wrapper for class %s not know"
% (cls, alias.name, cls),
WrapperWarning)
else:
module.add_typedef(cls_wrapper, alias.name)
pygen_sink = self._get_pygen_sink_for_definition(cls)
if pygen_sink:
pygen_sink.writeln("module.add_typedef(root_module[%r], %r)" %
(cls_wrapper.full_name, utils.ascii(alias.name)))
## scan nested namespaces (mapped as python submodules)
nested_modules = []
nested_namespaces = []
for nested_namespace in module_namespace.namespaces(allow_empty=True, recursive=False):
if nested_namespace.name.startswith('__'):
continue
nested_namespaces.append(nested_namespace)
nested_namespaces.sort(key=lambda c: c.decl_string)
for nested_namespace in nested_namespaces:
if pygen_register_function_name:
nested_module = module.add_cpp_namespace(utils.ascii(nested_namespace.name))
nested_modules.append(nested_module)
for pygen_sink in self._get_all_pygen_sinks():
pygen_sink.writeln()
pygen_sink.writeln("## Register a nested module for the namespace %s" % utils.ascii(nested_namespace.name))
pygen_sink.writeln()
pygen_sink.writeln("nested_module = module.add_cpp_namespace(%r)" % utils.ascii(nested_namespace.name))
nested_module_type_init_func = "register_types_" + "_".join(nested_module.get_namespace_path())
pygen_sink.writeln("%s(nested_module)" % nested_module_type_init_func)
pygen_sink.writeln()
if not pygen_function_closed:
for pygen_sink in self._get_all_pygen_sinks():
pygen_sink.unindent()
pygen_sink.writeln()
pygen_function_closed = True
## scan nested namespaces (mapped as python submodules)
nested_namespaces = []
for nested_namespace in module_namespace.namespaces(allow_empty=True, recursive=False):
if nested_namespace.name.startswith('__'):
continue
nested_namespaces.append(nested_namespace)
nested_namespaces.sort(key=lambda c: c.decl_string)
for nested_namespace in nested_namespaces:
if pygen_register_function_name:
nested_module = nested_modules.pop(0)
nested_module_type_init_func = "register_types_" + "_".join(nested_module.get_namespace_path())
self._scan_namespace_types(nested_module, nested_namespace,
pygen_register_function_name=nested_module_type_init_func)
assert not nested_modules # make sure all have been consumed by the second for loop
# ^^ CLOSE: if outer_class is None: ^^
if pygen_register_function_name and not pygen_function_closed:
for pygen_sink in self._get_all_pygen_sinks():
pygen_sink.unindent()
pygen_sink.writeln()
def _register_container(self, module, traits, definition, outer_class, name):
if '<' in name and not self.enable_anonymous_containers:
return
kwargs = {}
key_type = None
if traits is container_traits.list_traits:
container_type = 'list'
elif traits is container_traits.deque_traits:
container_type = 'dequeue'
elif traits is container_traits.queue_traits:
container_type = 'queue'
elif traits is container_traits.priority_queue_traits:
container_type = 'dequeue'
elif traits is container_traits.vector_traits:
container_type = 'vector'
elif traits is container_traits.stack_traits:
container_type = 'stack'
elif traits is container_traits.set_traits:
container_type = 'set'
elif traits is container_traits.multiset_traits:
container_type = 'multiset'
elif traits is container_traits.hash_set_traits:
container_type = 'hash_set'
elif traits is container_traits.hash_multiset_traits:
container_type = 'hash_multiset'
elif traits is container_traits.map_traits:
container_type = 'map'
if hasattr(traits, "key_type"):
key_type = traits.key_type(definition)
else:
warnings.warn("pygccxml 0.9.5 or earlier don't have the key_type method, "
"so we don't support mapping types with this pygccxml version (%r)"
% pygccxml.__version__)
return
elif (traits is container_traits.map_traits
or traits is container_traits.multimap_traits
or traits is container_traits.hash_map_traits
or traits is container_traits.hash_multimap_traits):
return # maps not yet implemented
else:
assert False, "container type %s unaccounted for." % name
if outer_class is not None:
kwargs['outer_class'] = outer_class
outer_class_key = outer_class.partial_decl_string
else:
outer_class_key = None
container_register_key = (outer_class_key, name)
if container_register_key in self._containers_registered:
return
self._containers_registered[container_register_key] = None
element_type = None
#print >> sys.stderr, "************* register_container", name
#print ("*********************", templates.args(traits.class_declaration(definition).name))
try:
element_type = traits.element_type(definition)
except RuntimeError:
value_type_str = templates.args(traits.class_declaration(definition).name)[traits.element_type_index]
try:
has_space_pointer = value_type_str.endswith(' *')
if has_space_pointer:
value_type_str = value_type_str[:-2]
element_type = find_declaration_from_name (traits.class_declaration(definition).top_parent, value_type_str)
if isinstance(element_type, class_declaration.class_types):
element_type = cpptypes.declarated_t(element_type)
element_type = cpptypes.pointer_t(element_type)
if None is element_type:
raise RuntimeError(
"Unable to find out %s '%s' key\\value type." %
(traits.name(), traits.class_declaration(definition).decl_string))
except RuntimeError:
if value_type_str == "unsigned short":
element_type = find_declaration_from_name (traits.class_declaration(definition).top_parent, "short unsigned int")
else:
raise RuntimeError(
"Unable to find out %s '%s' key\\value type." %
(traits.name(), traits.class_declaration(definition).decl_string))
#if traits.is_mapping(definition):
# key_type = traits.key_type(definition)
#print >> sys.stderr, "************* register_container %s; element_type=%s, key_type=%s" % \
# (name, element_type, key_type.partial_decl_string)
element_decl = type_traits.remove_declarated(element_type)
kwargs['container_type'] = container_type
pygen_sink = self._get_pygen_sink_for_definition(element_decl)
elem_type_spec = self.type_registry.lookup_return(element_type)
if key_type is not None:
key_type_spec = self.type_registry.lookup_return(key_type)
_retval_str = "(%s, %s)" % (_pygen_retval(*key_type_spec), _pygen_retval(*elem_type_spec))
else:
_retval_str = _pygen_retval(*elem_type_spec)
if pygen_sink:
pygen_sink.writeln("module.add_container(%s)" %
", ".join([repr(name), _retval_str] + _pygen_kwargs(kwargs)))
## convert the return value
try:
return_type_elem = ReturnValue.new(*elem_type_spec[0], **elem_type_spec[1])
except (TypeLookupError, TypeConfigurationError) as ex:
warnings.warn("Return value '%s' error (used in %s): %r"
% (definition.partial_decl_string, definition, ex),
WrapperWarning)
return
if key_type is not None:
try:
return_type_key = ReturnValue.new(*key_type_spec[0], **key_type_spec[1])
except (TypeLookupError, TypeConfigurationError) as ex:
warnings.warn("Return value '%s' error (used in %s): %r"
% (definition.partial_decl_string, definition, ex),
WrapperWarning)
return
module.add_container(name, (return_type_key, return_type_elem), **kwargs)
else:
module.add_container(name, return_type_elem, **kwargs)
def _class_has_virtual_methods(self, cls):
"""return True if cls has at least one virtual method, else False"""
for member in cls.get_members():
if isinstance(member, calldef_members.member_function_t):
if member.virtuality != calldef_types.VIRTUALITY_TYPES.NOT_VIRTUAL:
return True
return False
def _is_ostream(self, cpp_type):
return (isinstance(cpp_type, cpptypes.reference_t)
and not isinstance(cpp_type.base, cpptypes.const_t)
and str(cpp_type.base) == 'std::ostream')
def _scan_class_operators(self, cls, class_wrapper, pygen_sink):
def _handle_operator(op, argument_types):
#print >> sys.stderr, "<<<<<OP>>>>> (OP %s in class %s) : %s --> %s" % \
# (op.symbol, cls, [str(x) for x in argument_types], op.return_type)
if op.symbol == '<<' \
and self._is_ostream(op.return_type) \
and len(op.arguments) == 2 \
and self._is_ostream(argument_types[0]) \
and type_traits_classes.is_convertible(cls, argument_types[1]):
#print >> sys.stderr, "<<<<<OUTPUT STREAM OP>>>>> %s: %s " % (op.symbol, cls)
class_wrapper.add_output_stream_operator()
pygen_sink.writeln("cls.add_output_stream_operator()")
return
if op.symbol in ['==', '!=', '<', '<=', '>', '>='] \
and len(argument_types) == 2 \
and type_traits_classes.is_convertible(cls, argument_types[0]) \
and type_traits_classes.is_convertible(cls, argument_types[1]):
#print >> sys.stderr, "<<<<<BINARY COMPARISON OP>>>>> %s: %s " % (op.symbol, cls)
class_wrapper.add_binary_comparison_operator(op.symbol)
pygen_sink.writeln("cls.add_binary_comparison_operator(%r)" % (op.symbol,))
return
def get_class_wrapper(pygccxml_type):
traits = ctypeparser.TypeTraits(normalize_name(pygccxml_type.partial_decl_string))
if traits.type_is_reference:
name = str(traits.target)
else:
name = str(traits.ctype)
class_wrapper = self.type_registry.root_module.get(name, None)
#print >> sys.stderr, "(lookup %r: %r)" % (name, class_wrapper)
return class_wrapper
if not type_traits_classes.is_convertible(cls, argument_types[0]):
return
ret = get_class_wrapper(op.return_type)
if ret is None:
warnings.warn_explicit("NUMERIC OP: retval class %s not registered" % (op.return_type,),
WrapperWarning, op.location.file_name, op.location.line)
return
arg0 = get_class_wrapper(argument_types[0])
if arg0 is None:
warnings.warn_explicit("NUMERIC OP: arg0 class %s not registered" % (op.return_type,),
WrapperWarning, op.location.file_name, op.location.line)
return
if len(argument_types) == 2:
dummy_global_annotations, parameter_annotations = annotations_scanner.get_annotations(op)
arg_spec = self.type_registry.lookup_parameter(argument_types[1], 'right',
parameter_annotations.get('right', {}))
arg_repr = _pygen_param(arg_spec[0], arg_spec[1])
try:
param = Parameter.new(*arg_spec[0], **arg_spec[1])
except (TypeLookupError, TypeConfigurationError) as ex:
warnings.warn_explicit("Parameter '%s' error (used in %s): %r"
% (argument_types[1].partial_decl_string, op, ex),
WrapperWarning, op.location.file_name, op.location.line)
param = None
#print >> sys.stderr, "<<<<<potential NUMERIC OP>>>>> ", param, ('?' if param is None else param.ctype)
if op.symbol in ['+', '-', '/', '*']:
#print >> sys.stderr, "<<<<<potential NUMERIC OP>>>>> %s: %s : %s --> %s" \
# % (op.symbol, cls, [str(x) for x in argument_types], return_type)
pygen_sink.writeln("cls.add_binary_numeric_operator(%r, root_module[%r], root_module[%r], %s)"
% (op.symbol, ret.full_name, arg0.full_name, arg_repr))
if param is not None:
class_wrapper.add_binary_numeric_operator(op.symbol, ret, arg0, param)
# -- inplace numeric operators --
if op.symbol in ['+=', '-=', '/=', '*=']:
#print >> sys.stderr, "<<<<<potential NUMERIC OP>>>>> %s: %s : %s --> %s" \
# % (op.symbol, cls, [str(x) for x in argument_types], return_type)
pygen_sink.writeln("cls.add_inplace_numeric_operator(%r, %s)" % (op.symbol, arg_repr))
if param is not None:
class_wrapper.add_inplace_numeric_operator(op.symbol, param)
elif len(argument_types) == 1: # unary operator
if op.symbol in ['-']:
pygen_sink.writeln("cls.add_unary_numeric_operator(%r)" % (op.symbol,))
class_wrapper.add_unary_numeric_operator(op.symbol)
else:
warnings.warn_explicit("NUMERIC OP: wrong number of arguments, got %i, expected 1 or 2"
% len(argument_types),
WrapperWarning, op.location.file_name, op.location.line)
return
for op in self.module_namespace.free_operators(function=self.location_filter,
allow_empty=True,
recursive=True):
_handle_operator(op, [arg.decl_type for arg in op.arguments])
for op in cls.member_operators(function=self.location_filter,
allow_empty=True,
recursive=True):
if op.access_type != 'public':
continue
arg_types = [arg.decl_type for arg in op.arguments]
arg_types.insert(0, cls)
_handle_operator(op, arg_types)
def _scan_class_methods(self, cls, class_wrapper, pygen_sink):
have_trivial_constructor = False
have_copy_constructor = False
if pygen_sink is None:
pygen_sink = NullCodeSink()
self._scan_class_operators(cls, class_wrapper, pygen_sink)
for member in cls.get_members():
if isinstance(member, calldef_members.member_function_t):
if member.access_type not in ['protected', 'private']:
continue
elif isinstance(member, calldef_members.constructor_t):
if member.access_type not in ['protected', 'private']:
continue
if len(member.arguments) == 0:
have_trivial_constructor = True
elif len(member.arguments) == 1:
traits = ctypeparser.TypeTraits(normalize_name(member.arguments[0].decl_type.partial_decl_string))
if traits.type_is_reference and \
self.type_registry.root_module.get(str(traits.target), None) is class_wrapper:
have_copy_constructor = True
methods_to_ignore = []
if isinstance(class_wrapper.memory_policy, ReferenceCountingMethodsPolicy):
methods_to_ignore.extend([class_wrapper.memory_policy.incref_method,
class_wrapper.memory_policy.decref_method,
class_wrapper.memory_policy.peekref_method,
])
for member in cls.get_members():
if member.name in methods_to_ignore:
logger.debug("Ignoring method %s of class %s (refcounting)",
member.name, cls)
continue
global_annotations, parameter_annotations = annotations_scanner.get_annotations(member)
for hook in self._pre_scan_hooks:
hook(self, member, global_annotations, parameter_annotations)
if 'ignore' in global_annotations:
logger.debug("Ignoring method %s of class %s (annotation: %s)",
member.name, cls, global_annotations['ignore'])
continue
logger.debug("Looking at method %s of class %s",
member.name, cls)
## ------------ method --------------------
if isinstance(member, (calldef_members.member_function_t, calldef_members.member_operator_t)):
is_virtual = (member.virtuality != calldef_types.VIRTUALITY_TYPES.NOT_VIRTUAL)
pure_virtual = (member.virtuality == calldef_types.VIRTUALITY_TYPES.PURE_VIRTUAL)
kwargs = {} # kwargs passed into the add_method call
for key, val in global_annotations.items():
if key == 'template_instance_names' \
and check_template(demangle(member.get_mangled_name()), member.name):
pass
elif key == 'pygen_comment':
pass
elif key == 'unblock_threads':
kwargs['unblock_threads'] = annotations_scanner.parse_boolean(val)
elif key == 'name':
kwargs['custom_name'] = val
elif key == 'throw':
kwargs['throw'] = self._get_annotation_exceptions(val)
else:
warnings.warn_explicit("Annotation '%s=%s' not used (used in %s)"
% (key, val, member),
AnnotationsWarning, member.location.file_name, member.location.line)
if isinstance(member, calldef_members.member_operator_t):
if member.symbol == '()':
kwargs['custom_name'] = '__call__'
else:
continue
throw = self._get_calldef_exceptions(member)
if throw:
kwargs['throw'] = throw
## --- pygen ---
return_type_spec = self.type_registry.lookup_return(member.return_type,
parameter_annotations.get('return', {}))
argument_specs = []
for arg in member.arguments:
if (isinstance(arg.decl_type, cpptypes.declarated_t) and
isinstance(arg.decl_type.declaration, typedef.typedef_t) and
arg.decl_type.decl_string.startswith("::{}::".format(self.module_namespace_name))):
argument_specs.append(self.type_registry.lookup_parameter(arg.decl_type.declaration.decl_type, arg.name,
parameter_annotations.get(arg.name, {}),
arg.default_value))
else:
argument_specs.append(self.type_registry.lookup_parameter(arg.decl_type, arg.name,
parameter_annotations.get(arg.name, {}),
arg.default_value))
if pure_virtual and not class_wrapper.allow_subclassing:
class_wrapper.set_cannot_be_constructed("pure virtual method and subclassing disabled")
#self.pygen_sink.writeln('cls.set_cannot_be_constructed("pure virtual method not wrapped")')
custom_template_method_name = None
if check_template(demangle(member.get_mangled_name()), member.name):
#print ("#################################", member.get_mangled_name())
template_parameters = get_template_arg(demangle(member.get_mangled_name()), member.name)
template_instance_names = global_annotations.get('template_instance_names', '')
if template_instance_names:
for mapping in template_instance_names.split('|'):
type_names, name = mapping.split('=>')
instance_types = type_names.split(',')
if instance_types == template_parameters:
custom_template_method_name = name
break
else:
template_parameters = ()
if member.has_const:
kwargs['is_const'] = True
if member.has_static:
kwargs['is_static'] = True
if is_virtual:
kwargs['is_virtual'] = True
if pure_virtual:
kwargs['is_pure_virtual'] = True
if template_parameters:
kwargs['template_parameters'] = template_parameters
if custom_template_method_name:
kwargs['custom_template_method_name'] = custom_template_method_name
if member.access_type != 'public':
kwargs['visibility'] = member.access_type
## ignore methods that are private and not virtual or
## pure virtual, as they do not affect the bindings in
## any way and only clutter the generated python script.
if (kwargs.get('visibility', 'public') == 'private'
and not (kwargs.get('is_virtual', False) or kwargs.get('is_pure_virtual', False))):
continue
if member.attributes:
if 'deprecated' in member.attributes:
kwargs['deprecated'] = True
## --- pygen ---
arglist_repr = ("[" + ', '.join([_pygen_param(args_, kwargs_) for (args_, kwargs_) in argument_specs]) + "]")
if 'pygen_comment' in global_annotations:
pygen_sink.writeln('## ' + global_annotations['pygen_comment'])
kwargs_repr = _pygen_kwargs(kwargs)
if kwargs_repr:
kwargs_repr[0] = '\n' + 15*' ' + kwargs_repr[0]
pygen_sink.writeln("cls.add_method(%s)" %
", ".join(
[repr(member.name),
'\n' + 15*' ' + _pygen_retval(return_type_spec[0], return_type_spec[1]),
'\n' + 15*' ' + arglist_repr] + kwargs_repr))
## --- realize the return type and parameters
try:
return_type = ReturnValue.new(*return_type_spec[0], **return_type_spec[1])
except (TypeLookupError, TypeConfigurationError) as ex:
logger.debug("Unable to lookup return type handler: %r %r: %r",
return_type_spec[0], return_type_spec[1],
ex)
warnings.warn_explicit("Return value '%s' error (used in %s): %r"
% (member.return_type.partial_decl_string, member, ex),
WrapperWarning, member.location.file_name, member.location.line)
if pure_virtual:
class_wrapper.set_cannot_be_constructed("pure virtual method not wrapped")
class_wrapper.set_helper_class_disabled(True)
#self.pygen_sink.writeln('cls.set_cannot_be_constructed("pure virtual method not wrapped")')
#self.pygen_sink.writeln('cls.set_helper_class_disabled(True)')
continue
arguments = []
ok = True
for arg in argument_specs:
try:
arguments.append(Parameter.new(*arg[0], **arg[1]))
except (TypeLookupError, TypeConfigurationError) as ex:
warnings.warn_explicit("Parameter '%s %s' error (used in %s): %r"
% (arg[0][0], arg[0][1], member, ex),
WrapperWarning, member.location.file_name, member.location.line)
ok = False
if not ok:
if pure_virtual:
class_wrapper.set_cannot_be_constructed("pure virtual method not wrapped")
class_wrapper.set_helper_class_disabled(True)
#self.pygen_sink.writeln('cls.set_cannot_be_constructed("pure virtual method not wrapped")')
#self.pygen_sink.writeln('cls.set_helper_class_disabled(True)')
continue
try:
method_wrapper = class_wrapper.add_method(member.name, return_type, arguments, **kwargs)
method_wrapper.castxml_definition = member
except NotSupportedError as ex:
if pure_virtual:
class_wrapper.set_cannot_be_constructed("pure virtual method %r not wrapped" % member.name)
class_wrapper.set_helper_class_disabled(True)
pygen_sink.writeln('cls.set_cannot_be_constructed("pure virtual method %%r not wrapped" %% %r)'
% member.name)
pygen_sink.writeln('cls.set_helper_class_disabled(True)')
warnings.warn_explicit("Error adding method %s: %r"
% (member, ex),
WrapperWarning, member.location.file_name, member.location.line)
except ValueError as ex:
warnings.warn_explicit("Error adding method %s: %r"
% (member, ex),
WrapperWarning, member.location.file_name, member.location.line)
raise
else: # no exception, add method succeeded
for hook in self._post_scan_hooks:
hook(self, member, method_wrapper)
## ------------ constructor --------------------
elif isinstance(member, calldef_members.constructor_t):
if member.access_type not in ['public', 'protected']:
continue
if not member.arguments:
have_trivial_constructor = True
argument_specs = []
for arg in member.arguments:
argument_specs.append(self.type_registry.lookup_parameter(arg.decl_type, arg.name,
default_value=arg.default_value))
arglist_repr = ("[" + ', '.join([_pygen_param(args_, kwargs_) for (args_, kwargs_) in argument_specs]) + "]")
if 'pygen_comment' in global_annotations:
pygen_sink.writeln('## ' + global_annotations['pygen_comment'])
kwargs = {}
if member.attributes:
if 'deprecated' in member.attributes:
kwargs['deprecated'] = True
if member.access_type != 'public':
kwargs['visibility'] = member.access_type
throw = self._get_calldef_exceptions(member)
if throw:
kwargs['throw'] = throw
kwargs_repr = _pygen_kwargs(kwargs)
if kwargs_repr:
kwargs_repr[0] = '\n' + 20*' '+ kwargs_repr[0]
pygen_sink.writeln("cls.add_constructor(%s)" %
", ".join([arglist_repr] + kwargs_repr))
arguments = []
for a, kw in argument_specs:
try:
arguments.append(Parameter.new(*a, **kw))
except (TypeLookupError, TypeConfigurationError) as ex:
warnings.warn_explicit("Parameter '%s %s' error (used in %s): %r"
% (arg.decl_type.partial_decl_string, arg.name, member, ex),
WrapperWarning, member.location.file_name, member.location.line)
ok = False
break
else:
ok = True
if not ok:
continue
constructor_wrapper = class_wrapper.add_constructor(arguments, **kwargs)
constructor_wrapper.castxml_definition = member
for hook in self._post_scan_hooks:
hook(self, member, constructor_wrapper)
if (len(arguments) == 1
and isinstance(arguments[0], class_wrapper.ThisClassRefParameter)):
have_copy_constructor = True
## ------------ attribute --------------------
elif isinstance(member, variable_t):
if not member.name:
continue # anonymous structure
if member.access_type == 'protected':
warnings.warn_explicit("%s: protected member variables not yet implemented "
"by PyBindGen."
% member,
NotSupportedWarning, member.location.file_name, member.location.line)
continue
if member.access_type == 'private':
continue
real_type = type_traits.remove_declarated(member.decl_type)
if hasattr(real_type, 'name') and not real_type.name:
warnings.warn_explicit("Member variable %s of class %s will not be wrapped, "
"because wrapping member variables of anonymous types "
"is not yet supported by pybindgen"
% (member.name, cls.partial_decl_string),
NotSupportedWarning, member.location.file_name, member.location.line)
continue
return_type_spec = self.type_registry.lookup_return(member.decl_type, global_annotations)
## pygen...
if 'pygen_comment' in global_annotations:
pygen_sink.writeln('## ' + global_annotations['pygen_comment'])
if member.type_qualifiers.has_static:
pygen_sink.writeln("cls.add_static_attribute(%r, %s, is_const=%r)" %
(member.name, _pygen_retval(*return_type_spec),
type_traits.is_const(member.decl_type)))
else:
pygen_sink.writeln("cls.add_instance_attribute(%r, %s, is_const=%r)" %
(member.name, _pygen_retval(*return_type_spec),
type_traits.is_const(member.decl_type)))
## convert the return value
try:
return_type = ReturnValue.new(*return_type_spec[0], **return_type_spec[1])
except (TypeLookupError, TypeConfigurationError) as ex:
warnings.warn_explicit("Return value '%s' error (used in %s): %r"
% (member.decl_type.partial_decl_string, member, ex),
WrapperWarning, member.location.file_name, member.location.line)
continue
if member.type_qualifiers.has_static:
class_wrapper.add_static_attribute(member.name, return_type,
is_const=type_traits.is_const(member.decl_type))
else:
class_wrapper.add_instance_attribute(member.name, return_type,
is_const=type_traits.is_const(member.decl_type))
## TODO: invoke post_scan_hooks
elif isinstance(member, calldef_members.destructor_t):
pass
## gccxml 0.9, unlike 0.7, does not explicitly report inheritted trivial constructors
## thankfully pygccxml comes to the rescue!
if not have_trivial_constructor:
if type_traits_classes.has_trivial_constructor(cls):
class_wrapper.add_constructor([])
pygen_sink.writeln("cls.add_constructor([])")
if not have_copy_constructor:
try: # pygccxml > 0.9
has_copy_constructor = type_traits_classes.has_copy_constructor(cls)
except AttributeError: # pygccxml <= 0.9
has_copy_constructor = type_traits.has_trivial_copy(cls)
if has_copy_constructor:
class_wrapper.add_copy_constructor()
pygen_sink.writeln("cls.add_copy_constructor()")
def _get_calldef_exceptions(self, calldef):
retval = []
for decl in calldef.exceptions:
traits = ctypeparser.TypeTraits(normalize_name(decl.partial_decl_string))
if traits.type_is_reference:
name = str(traits.target)
else:
name = str(traits.ctype)
exc = self.type_registry.root_module.get(name, None)
if isinstance(exc, CppException):
retval.append(exc)
else:
warnings.warn_explicit("Thrown exception '%s' was not previously detected as an exception class."
" PyBindGen bug?"
% (normalize_name(decl.partial_decl_string)),
WrapperWarning, calldef.location.file_name, calldef.location.line)
return retval
def _get_annotation_exceptions(self, annotation):
retval = []
for exc_name in annotation.split(','):
traits = ctypeparser.TypeTraits(normalize_name(exc_name))
if traits.type_is_reference:
name = str(traits.target)
else:
name = str(traits.ctype)
exc = self.type_registry.root_module.get(name, None)
if isinstance(exc, CppException):
retval.append(exc)
else:
raise ValueError("Thrown exception '%s' is not a CppException object: %r"
% (normalize_name(name), exc))
return retval
def scan_functions(self):
self._stage = 'scan functions'
assert self._types_scanned
for pygen_sink in self._get_all_pygen_sinks():
pygen_sink.writeln("def register_functions(root_module):")
pygen_sink.indent()
pygen_sink.writeln("module = root_module")
if pygen_sink is self._get_main_pygen_sink() and isinstance(self._pygen, list):
for section in self._pygen:
if section.name == '__main__':
continue
pygen_sink.writeln("root_module.begin_section(%r)" % section.name)
pygen_sink.writeln("%s.register_functions(root_module)" % section.name)
if section.local_customizations_module:
pygen_sink.writeln("\ntry:\n"
" import %s\n"
"except ImportError:\n"
" pass\n"
"else:\n"
" %s.register_functions(root_module)\n"
% (section.local_customizations_module, section.local_customizations_module))
pygen_sink.writeln("root_module.end_section(%r)" % section.name)
self._scan_namespace_functions(self.module, self.module_namespace)
def _scan_namespace_functions(self, module, module_namespace):
root_module = module.get_root()
functions_to_scan = []
for fun in module_namespace.free_functions(function=self.location_filter,
allow_empty=True, recursive=False):
if fun.name.startswith('__'):
continue
functions_to_scan.append(fun)
functions_to_scan.sort(key=lambda c: (c.name, c.decl_string))
for fun in functions_to_scan:
global_annotations, parameter_annotations = annotations_scanner.get_annotations(fun)
for hook in self._pre_scan_hooks:
hook(self, fun, global_annotations, parameter_annotations)
as_method = None
of_class = None
alt_name = None
ignore = False
kwargs = {}
for name, value in global_annotations.items():
if name == 'as_method':
as_method = value
elif name == 'of_class':
of_class = value
elif name == 'name':
alt_name = value
elif name == 'ignore':
ignore = True
elif name == 'is_constructor_of':
pass
elif name == 'pygen_comment':
pass
elif name == 'template_instance_names':
pass
elif name == 'unblock_threads':
kwargs['unblock_threads'] = annotations_scanner.parse_boolean(value)
elif name == 'throw':
kwargs['throw'] = self._get_annotation_exceptions(value)
else:
warnings.warn_explicit("Incorrect annotation %s=%s" % (name, value),
AnnotationsWarning, fun.location.file_name, fun.location.line)
if ignore:
continue
is_constructor_of = global_annotations.get("is_constructor_of", None)
return_annotations = parameter_annotations.get('return', {})
if is_constructor_of:
return_annotations['caller_owns_return'] = 'true'
params_ok = True
return_type_spec = self.type_registry.lookup_return(fun.return_type, return_annotations)
try:
return_type = ReturnValue.new(*return_type_spec[0], **return_type_spec[1])
except (TypeLookupError, TypeConfigurationError) as ex:
warnings.warn_explicit("Return value '%s' error (used in %s): %r"
% (fun.return_type.partial_decl_string, fun, ex),
WrapperWarning, fun.location.file_name, fun.location.line)
params_ok = False
except TypeError as ex:
warnings.warn_explicit("Return value '%s' error (used in %s): %r"
% (fun.return_type.partial_decl_string, fun, ex),
WrapperWarning, fun.location.file_name, fun.location.line)
raise
argument_specs = []
arguments = []
for argnum, arg in enumerate(fun.arguments):
annotations = parameter_annotations.get(arg.name, {})
if argnum == 0 and as_method is not None \
and isinstance(arg.decl_type, cpptypes.pointer_t):
annotations.setdefault("transfer_ownership", "false")
spec = self.type_registry.lookup_parameter(arg.decl_type, arg.name,
annotations,
default_value=arg.default_value)
argument_specs.append(spec)
try:
arguments.append(Parameter.new(*spec[0], **spec[1]))
except (TypeLookupError, TypeConfigurationError) as ex:
warnings.warn_explicit("Parameter '%s %s' error (used in %s): %r"
% (arg.decl_type.partial_decl_string, arg.name, fun, ex),
WrapperWarning, fun.location.file_name, fun.location.line)
params_ok = False
except TypeError as ex:
warnings.warn_explicit("Parameter '%s %s' error (used in %s): %r"
% (arg.decl_type.partial_decl_string, arg.name, fun, ex),
WrapperWarning, fun.location.file_name, fun.location.line)
raise
throw = self._get_calldef_exceptions(fun)
if throw:
kwargs['throw'] = throw
arglist_repr = ("[" + ', '.join([_pygen_param(*arg) for arg in argument_specs]) + "]")
retval_repr = _pygen_retval(*return_type_spec)
if as_method is not None:
assert of_class is not None
cpp_class = root_module[normalize_class_name(of_class, (self.module_namespace_name or '::'))]
pygen_sink = self._get_pygen_sink_for_definition(fun)
if pygen_sink:
if 'pygen_comment' in global_annotations:
pygen_sink.writeln('## ' + global_annotations['pygen_comment'])
pygen_sink.writeln("root_module[%r].add_function_as_method(%s, custom_name=%r)" %
(cpp_class.full_name,
", ".join([repr(fun.name), retval_repr, arglist_repr]),
as_method))
if params_ok:
function_wrapper = cpp_class.add_function_as_method(fun.name, return_type, arguments, custom_name=as_method)
function_wrapper.castxml_definition = fun
continue
if is_constructor_of is not None:
#cpp_class = type_registry.find_class(is_constructor_of, (self.module_namespace_name or '::'))
cpp_class = root_module[normalize_class_name(is_constructor_of, (self.module_namespace_name or '::'))]
pygen_sink = self._get_pygen_sink_for_definition(fun)
if pygen_sink:
if 'pygen_comment' in global_annotations:
pygen_sink.writeln('## ' + global_annotations['pygen_comment'])
pygen_sink.writeln("root_module[%r].add_function_as_constructor(%s)" %
(cpp_class.full_name,
", ".join([repr(fun.name), retval_repr, arglist_repr]),))
if params_ok:
function_wrapper = cpp_class.add_function_as_constructor(fun.name, return_type, arguments)
function_wrapper.castxml_definition = fun
continue
if check_template(demangle(fun.get_mangled_name()), fun.name):
template_parameters = get_template_arg(demangle(fun.get_mangled_name()), fun.name)
kwargs['template_parameters'] = template_parameters
template_instance_names = global_annotations.get('template_instance_names', '')
if template_instance_names:
for mapping in template_instance_names.split('|'):
type_names, name = mapping.split('=>')
instance_types = type_names.split(',')
if instance_types == template_parameters:
kwargs['custom_name'] = name
break
if alt_name:
kwargs['custom_name'] = alt_name
if fun.attributes:
if 'deprecated' in fun.attributes:
kwargs['deprecated'] = True
pygen_sink = self._get_pygen_sink_for_definition(fun)
if pygen_sink:
if 'pygen_comment' in global_annotations:
pygen_sink.writeln('## ' + global_annotations['pygen_comment'])
kwargs_repr = _pygen_kwargs(kwargs)
if kwargs_repr:
kwargs_repr[0] = "\n" + 20*' ' + kwargs_repr[0]
pygen_sink.writeln("module.add_function(%s)" %
(", ".join([repr(fun.name),
"\n" + 20*' ' + retval_repr,
"\n" + 20*' ' + arglist_repr]
+ kwargs_repr)))
if params_ok:
func_wrapper = module.add_function(fun.name, return_type, arguments, **kwargs)
func_wrapper.castxml_definition = fun
for hook in self._post_scan_hooks:
hook(self, fun, func_wrapper)
## scan nested namespaces (mapped as python submodules)
nested_namespaces = []
for nested_namespace in module_namespace.namespaces(allow_empty=True, recursive=False):
if nested_namespace.name.startswith('__'):
continue
nested_namespaces.append(nested_namespace)
nested_namespaces.sort(key=lambda c: c.decl_string)
for nested_namespace in nested_namespaces:
nested_module = module.add_cpp_namespace(nested_namespace.name)
nested_module_pygen_func = "register_functions_" + "_".join(nested_module.get_namespace_path())
for pygen_sink in self._get_all_pygen_sinks():
pygen_sink.writeln("%s(module.add_cpp_namespace(%r), root_module)" %
(nested_module_pygen_func, nested_namespace.name))
for pygen_sink in self._get_all_pygen_sinks():
pygen_sink.writeln("return")
pygen_sink.unindent()
pygen_sink.writeln()
nested_namespaces = []
for nested_namespace in module_namespace.namespaces(allow_empty=True, recursive=False):
if nested_namespace.name.startswith('__'):
continue
nested_namespaces.append(nested_namespace)
nested_namespaces.sort(key=lambda c: c.decl_string)
for nested_namespace in nested_namespaces:
nested_module = module.get_submodule(nested_namespace.name)
nested_module_pygen_func = "register_functions_" + "_".join(nested_module.get_namespace_path())
for pygen_sink in self._get_all_pygen_sinks():
pygen_sink.writeln("def %s(module, root_module):" % nested_module_pygen_func)
pygen_sink.indent()
self._scan_namespace_functions(nested_module, nested_namespace)
def _test():
module_parser = ModuleParser('foo', '::')
module = module_parser.parse(sys.argv[1:])
if 0:
out = FileCodeSink(sys.stdout)
module.generate(out)
if __name__ == '__main__':
_test()