Files
pybindgen/pybindgen/gccxmlparser.py
T

1384 lines
66 KiB
Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
import os.path
import warnings
import re
from pygccxml import parser
from pygccxml import declarations
from module import Module
from typehandlers.codesink import FileCodeSink, CodeSink, NullCodeSink
import typehandlers.base
from typehandlers.base import ReturnValue, Parameter, TypeLookupError, TypeConfigurationError, NotSupportedError
from pygccxml.declarations.enumeration import enumeration_t
from enum import Enum
from function import Function
from cppclass import CppClass, CppConstructor, CppMethod
from pygccxml.declarations import type_traits
from pygccxml.declarations import cpptypes
from pygccxml.declarations import calldef
from pygccxml.declarations import templates
from pygccxml.declarations.class_declaration import class_declaration_t, class_t
import settings
#from pygccxml.declarations.calldef import \
# destructor_t, constructor_t, member_function_t
from pygccxml.declarations.variable import variable_t
ALWAYS_USE_PARAMETER_NEW = True
## ------------------------
class ErrorHandler(settings.ErrorHandler):
def handle_error(self, wrapper, exception, traceback_):
if hasattr(wrapper, "gccxml_definition"):
definition = wrapper.gccxml_definition
elif hasattr(wrapper, "main_wrapper"):
try:
definition = wrapper.main_wrapper.gccxml_definition
except AttributeError:
definition = None
else:
definition = None
if definition is None:
print >> sys.stderr, "exception %r in wrapper %s" % (exception, wrapper)
else:
warnings.warn_explicit("exception %r in wrapper for %s"
% (exception, definition),
Warning, definition.location.file_name,
definition.location.line)
return True
settings.error_handler = ErrorHandler()
_digits = re.compile(r"\s*\d+\s*")
def normalize_name(decl_string):
if templates.is_instantiation(decl_string):
cls_name, template_parameters = templates.split(decl_string)
else:
cls_name = decl_string
template_parameters = None
if not _digits.match(cls_name):
if cls_name.startswith('::'):
cls_name = cls_name[2:]
if template_parameters is None:
return cls_name
else:
return "%s< %s >" % (cls_name, ', '.join([normalize_name(name) for name in template_parameters]))
def normalize_class_name(class_name, module_namespace):
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.iteritems():
if isinstance(val, CppClass):
l.append("%s=root_module[%r]" % (key, val.full_name))
else:
l.append("%s=%r" % (key, val))
return l
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)
self.ordered_classes.append(cpp_class)
def get_class_type_traits(self, type_info):
assert isinstance(type_info, cpptypes.type_t)
decomposed = type_traits.decompose_type(type_info)
base_type = decomposed.pop()
is_const = False
is_reference = False
is_pointer = False
pointer_is_const = False
if isinstance(base_type, cpptypes.declarated_t):
class_name = normalize_name(base_type.decl_string)
try:
cpp_class = self.root_module[class_name]
except KeyError:
return (None, is_const, is_pointer, is_reference, pointer_is_const)
if not isinstance(cpp_class, CppClass):
return (None, is_const, is_pointer, is_reference, pointer_is_const)
try:
type_tmp = decomposed.pop()
except IndexError:
return (cpp_class, is_const, is_pointer, is_reference, pointer_is_const)
if isinstance(type_tmp, cpptypes.const_t):
is_const = True
try:
type_tmp = decomposed.pop()
except IndexError:
return (cpp_class, is_const, is_pointer, is_reference, pointer_is_const)
if isinstance(type_tmp, cpptypes.reference_t):
is_reference = True
elif isinstance(type_tmp, cpptypes.pointer_t):
is_pointer = True
else:
raise AssertionError
try:
type_tmp = decomposed.pop()
except IndexError:
pass
else:
if isinstance(type_tmp, cpptypes.const_t):
assert is_pointer
pointer_is_const = True
elif isinstance(type_tmp, cpptypes.pointer_t):
return (None, is_const, is_pointer, is_reference, pointer_is_const)
else:
raise AssertionError, "found %r for type %s" % (type_tmp, type_info.decl_string)
assert len(decomposed) == 0
return (cpp_class, is_const, is_pointer, is_reference, pointer_is_const)
return (None, is_const, is_pointer, is_reference, pointer_is_const)
def _fixed_std_type_name(self, type_info):
decl = self._root_ns_rx.sub('', type_info.decl_string)
return decl
def lookup_return(self, type_info, annotations={}):
assert isinstance(type_info, cpptypes.type_t)
cpp_class, is_const, is_pointer, is_reference, pointer_is_const = \
self.get_class_type_traits(type_info)
kwargs = {}
for name, value in annotations.iteritems():
if name == 'caller_owns_return':
kwargs['caller_owns_return'] = annotations_scanner.parse_boolean(value)
elif name == 'custodian':
kwargs['custodian'] = int(value)
else:
warnings.warn("invalid annotation name %r" % name)
if is_const:
kwargs['is_const'] = True
if cpp_class is None:
if isinstance(type_traits.remove_declarated(type_info), enumeration_t):
name = normalize_name(type_info.decl_string)
else:
name = self._fixed_std_type_name(type_info)
retval = ReturnValue.new(name, **kwargs)
retval._pygen_repr = ("ReturnValue.new(%s)"
% (", ".join([repr(name)] + _pygen_kwargs(kwargs))))
return retval
## class value
if not is_pointer and not is_reference:
if ALWAYS_USE_PARAMETER_NEW:
retval = ReturnValue.new(cpp_class.full_name, **kwargs)
retval._pygen_repr = ("ReturnValue.new(%s)"
% (", ".join([repr(cpp_class.full_name)] + _pygen_kwargs(kwargs))))
else:
retval = cpp_class.ThisClassReturn(type_info.decl_string)
retval._pygen_repr = ("root_module[%r].ThisClassReturn(%r)" % (cpp_class.full_name, type_info.decl_string))
return retval
## pointer to class
if is_pointer and not is_reference:
if is_const and 'caller_owns_return' not in kwargs:
## a pointer to const object "usually" means caller_owns_return=False
## some guessing going on here, though..
kwargs['caller_owns_return'] = False
if ALWAYS_USE_PARAMETER_NEW:
retval = ReturnValue.new(cpp_class.full_name+'*', **kwargs)
retval._pygen_repr = ("ReturnValue.new(%s)"
% (", ".join([repr(cpp_class.full_name + '*')] + _pygen_kwargs(kwargs))))
else:
retval = cpp_class.ThisClassPtrReturn(type_info.decl_string, **kwargs)
retval._pygen_repr = ("root_module[%r].ThisClassPtrReturn(%s)"
% (cpp_class.full_name,
", ".join([repr(type_info.decl_string)] + _pygen_kwargs(kwargs))))
return retval
## reference of class
if not is_pointer and is_reference:
if ALWAYS_USE_PARAMETER_NEW:
retval = ReturnValue.new(cpp_class.full_name+'&', **kwargs)
retval._pygen_repr = ("ReturnValue.new(%s)"
% (", ".join([repr(cpp_class.full_name + '&')] + _pygen_kwargs(kwargs))))
else:
retval = cpp_class.ThisClassRefReturn(type_info.decl_string, **kwargs)
retval._pygen_repr = ("root_module[%r].ThisClassRefReturn(%s)"
% (cpp_class.full_name,
", ".join([repr(type_info.decl_string)] + _pygen_kwargs(kwargs))))
return retval
assert 0, "this line should not be reached"
def lookup_parameter(self, type_info, param_name, annotations={}, default_value=None):
assert isinstance(type_info, cpptypes.type_t)
kwargs = {}
for name, value in annotations.iteritems():
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)
elif name == 'custodian':
kwargs['custodian'] = int(value)
else:
warnings.warn("invalid annotation name %r" % name)
cpp_class, is_const, is_pointer, is_reference, dummy_pointer_is_const = \
self.get_class_type_traits(type_info)
if is_const:
kwargs['is_const'] = True
if default_value:
kwargs['default_value'] = default_value
if cpp_class is None:
if isinstance(type_traits.remove_declarated(type_info), enumeration_t):
type_name = normalize_name(type_info.decl_string)
else:
type_name = self._fixed_std_type_name(type_info)
retval = Parameter.new(type_name, param_name, **kwargs)
retval._pygen_repr = ("Parameter.new(%s)"
% (", ".join([repr(type_name), repr(param_name)]
+ _pygen_kwargs(kwargs))))
return retval
assert isinstance(cpp_class, CppClass)#, cpp_class.full_name
## class value
if not is_pointer and not is_reference:
if ALWAYS_USE_PARAMETER_NEW:
retval = Parameter.new(cpp_class.full_name, param_name, **kwargs)
retval._pygen_repr = ("Parameter.new(%s)"
% (", ".join([repr(cpp_class.full_name), repr(param_name)] + _pygen_kwargs(kwargs))))
else:
retval = cpp_class.ThisClassParameter(type_info.decl_string, param_name, **kwargs)
retval._pygen_repr = ("root_module[%r].ThisClassParameter(%s)"
% (cpp_class.full_name,
", ".join([repr(type_info.decl_string), repr(param_name)]
+ _pygen_kwargs(kwargs))))
return retval
## pointer to class
if is_pointer and not is_reference:
if is_const:
## a pointer to const object usually means transfer_ownership=False
kwargs.setdefault('transfer_ownership', False)
if ALWAYS_USE_PARAMETER_NEW:
retval = Parameter.new(cpp_class.full_name+'*', param_name, **kwargs)
retval._pygen_repr = ("Parameter.new(%s)"
% (", ".join([repr(cpp_class.full_name+'*'), repr(param_name)] + _pygen_kwargs(kwargs))))
else:
retval = cpp_class.ThisClassPtrParameter(type_info.decl_string, param_name, **kwargs)
retval._pygen_repr = ("root_module[%r].ThisClassPtrParameter(%s)"
% (cpp_class.full_name,
", ".join([repr(type_info.decl_string), repr(param_name)]
+ _pygen_kwargs(kwargs))))
return retval
## reference to class
if not is_pointer and is_reference:
try:
if ALWAYS_USE_PARAMETER_NEW:
retval = Parameter.new(cpp_class.full_name+'&', param_name, **kwargs)
retval._pygen_repr = ("Parameter.new(%s)"
% (", ".join([repr(cpp_class.full_name+'&'), repr(param_name)] + _pygen_kwargs(kwargs))))
else:
retval = cpp_class.ThisClassRefParameter(type_info.decl_string, param_name, **kwargs)
retval._pygen_repr = ("root_module[%r].ThisClassRefParameter(%s)"
% (cpp_class.full_name,
", ".join([repr(type_info.decl_string), repr(param_name)]
+ _pygen_kwargs(kwargs))))
return retval
except TypeError:
print >> sys.stderr, "** Error in %s class ref parameter" % cpp_class.full_name
raise
assert 0, "this line should not be reached"
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, file_name, line_number):
"""
file_name -- absolute file name where the definition is
line_number -- line number of where the definition is within the file
"""
try:
lines = self.files[file_name]
except KeyError:
lines = file(file_name, "rt").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()),
Warning, file_name, line_number)
continue
warnings.warn_explicit("could not parse %r" % (annotation_str),
Warning, file_name, line_number)
return global_annotations, parameter_annotations
def parse_boolean(self, 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.iteritems():
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",
Warning, file_name, line_number+1)
annotations_scanner = AnnotationsScanner()
## ------------------------
class ModuleParser(object):
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.gccxml_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 # pygccxml.declarations.namespace.namespace_t (as returned by pygccxml.parser.parse)
self._types_scanned = False
self._pre_scan_hooks = []
self._post_scan_hooks = []
self.pygen_sink = NullCodeSink()
self.type_registry = None
def add_pre_scan_hook(self, hook):
"""
Add a function to be called right before converting a gccxml
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 callable(hook):
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 gccxml 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 callable(hook):
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):
"""
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()
"""
self.parse_init(header_files, include_paths, whitelist_paths, includes, pygen_sink)
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):
"""
Prepares to parse a set of header files. The following
methods should then be called in order to finish the rest of
scanning process:
1. scan_types()
2. scan_methods()
2. scan_functions()
3. parse_finalize()
"""
assert isinstance(header_files, list)
assert isinstance(includes, (list, tuple))
assert pygen_sink is None or 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 pygen_sink is not None:
self.pygen_sink = pygen_sink
if whitelist_paths is not None:
assert isinstance(whitelist_paths, list)
self.whitelist_paths = [os.path.abspath(p) for p in whitelist_paths]
if include_paths is not None:
assert isinstance(include_paths, list)
self.gccxml_config = parser.config_t(include_paths=include_paths)
else:
self.gccxml_config = parser.config_t()
self.declarations = parser.parse(header_files, self.gccxml_config)
if self.module_namespace_name == '::':
self.module_namespace = declarations.get_global_namespace(self.declarations)
else:
self.module_namespace = declarations.get_global_namespace(self.declarations).\
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)
self.pygen_sink.writeln("import sys")
self.pygen_sink.writeln("from pybindgen import Module, FileCodeSink, write_preamble")
self.pygen_sink.writeln("from pybindgen.cppclass import CppClass")
self.pygen_sink.writeln("from pybindgen.cppmethod import CppMethod, CppConstructor, CppNoConstructor")
self.pygen_sink.writeln("from pybindgen import ReturnValue, Parameter, Function, Enum")
self.pygen_sink.writeln()
self.pygen_sink.writeln("def module_init():")
self.pygen_sink.indent()
self.pygen_sink.writeln("root_module = Module(%r, cpp_namespace=%r)"
% (self.module_name, self.module_namespace.decl_string))
for inc in includes:
self.pygen_sink.writeln("root_module.add_include(%r)" % inc)
self.pygen_sink.writeln("return root_module")
self.pygen_sink.unindent()
self.pygen_sink.writeln()
self.type_registry = GccXmlTypeRegistry(self.module)
def scan_types(self):
self._scan_namespace_types(self.module, self.module_namespace, pygen_register_function_name="register_types")
self._types_scanned = True
def scan_methods(self):
assert self._types_scanned
self.pygen_sink.writeln("def register_methods(root_module):")
self.pygen_sink.indent()
for class_wrapper in self.type_registry.ordered_classes:
if isinstance(class_wrapper.gccxml_definition, class_declaration_t):
continue # skip classes not fully defined
register_methods_func = "register_%s_methods" % (class_wrapper.mangled_full_name,)
self.pygen_sink.writeln("%s(root_module, root_module[%r])" % (register_methods_func, class_wrapper.full_name))
self.pygen_sink.unindent()
self.pygen_sink.writeln()
for class_wrapper in self.type_registry.ordered_classes:
if isinstance(class_wrapper.gccxml_definition, class_declaration_t):
continue # skip classes not fully defined
register_methods_func = "register_%s_methods" % (class_wrapper.mangled_full_name,)
self.pygen_sink.writeln("def %s(root_module, cls):" % (register_methods_func,))
self.pygen_sink.indent()
self._scan_class_methods(class_wrapper.gccxml_definition, class_wrapper)
self.pygen_sink.writeln("return")
self.pygen_sink.unindent()
self.pygen_sink.writeln()
def parse_finalize(self):
annotations_scanner.warn_unused_annotations()
self.pygen_sink.writeln("def main():")
self.pygen_sink.indent()
self.pygen_sink.writeln("out = FileCodeSink(sys.stdout)")
self.pygen_sink.writeln("root_module = module_init()")
self.pygen_sink.writeln("register_types(root_module)")
self.pygen_sink.writeln("register_methods(root_module)")
self.pygen_sink.writeln("register_functions(root_module)")
self.pygen_sink.writeln("write_preamble(out)")
self.pygen_sink.writeln("root_module.generate(out)")
self.pygen_sink.unindent()
self.pygen_sink.writeln()
self.pygen_sink.writeln("if __name__ == '__main__':\n main()")
self.pygen_sink.writeln()
return self.module
def _apply_class_annotations(self, cls, annotations, kwargs):
for name, value in annotations.iteritems():
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('incref_method', value)
elif name == 'decref_method':
kwargs.setdefault('decref_method', value)
elif name == 'peekref_method':
kwargs.setdefault('peekref_method', value)
elif name == 'automatic_type_narrowing':
kwargs.setdefault('automatic_type_narrowing', annotations_scanner.parse_boolean(value))
elif name == 'free_function':
kwargs.setdefault('free_function', value)
elif name == 'incref_function':
kwargs.setdefault('incref_function', value)
elif name == 'decref_function':
kwargs.setdefault('decref_function', value)
elif name == 'python_name':
kwargs.setdefault('python_name', value)
else:
warnings.warn_explicit("Class annotation %r ignored" % name,
Warning, cls.location.file_name, cls.location.line)
if isinstance(cls, class_t):
if self._class_has_virtual_methods(cls):
kwargs.setdefault('allow_subclassing', True)
if not self._class_has_public_destructor(cls):
kwargs.setdefault('is_singleton', True)
def _scan_namespace_types(self, module, module_namespace, outer_class=None, pygen_register_function_name=None):
root_module = module.get_root()
if pygen_register_function_name:
self.pygen_sink.writeln("def %s(module):" % pygen_register_function_name)
self.pygen_sink.indent()
self.pygen_sink.writeln("root_module = module.get_root()")
self.pygen_sink.writeln()
## scan enumerations
if outer_class is None:
enums = module_namespace.enums(function=self.location_filter,
recursive=False, allow_empty=True)
else:
enums = []
for enum in outer_class.gccxml_definition.enums(function=self.location_filter,
recursive=False, allow_empty=True):
if outer_class.gccxml_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, ),
Warning, enum.location.file_name, enum.location.line)
continue
enums.append(enum)
for enum in enums:
module.add_enum(enum.name, [name for name, dummy_val in enum.values],
outer_class=outer_class)
enum_values_repr = '[' + ', '.join([repr(name) for name, dummy_val in enum.values]) + ']'
l = [repr(enum.name), enum_values_repr]
if outer_class is not None:
l.append('outer_class=root_module[%r]' % outer_class.full_name)
self.pygen_sink.writeln('module.add_enum(%s)' % ', '.join(l))
registered_classes = {} # class_t -> CppClass
## 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.
for alias in module_namespace.typedefs(function=self.location_filter,
recursive=False, allow_empty=True):
## handle "typedef int Something;"
if isinstance(alias.type, cpptypes.int_t):
param_cls, dummy_transf = typehandlers.base.param_type_matcher.lookup('int')
for ctype in param_cls.CTYPES:
#print >> sys.stderr, "%s -> int" % ctype.replace('int', alias.name)
typehandlers.base.param_type_matcher.register(ctype.replace('int', alias.name), param_cls)
return_cls, dummy_transf = typehandlers.base.return_type_matcher.lookup('int')
for ctype in return_cls.CTYPES:
#print >> sys.stderr, "%s -> int" % ctype.replace('int', alias.name)
typehandlers.base.return_type_matcher.register(ctype.replace('int', alias.name), return_cls)
continue
if not isinstance(alias.type, cpptypes.declarated_t):
continue
cls = alias.type.declaration
if not isinstance(cls, class_declaration_t):
continue # fully defined classes handled further below
if templates.is_instantiation(cls.decl_string):
continue # typedef to template instantiations, must be fully defined
global_annotations, param_annotations = \
annotations_scanner.get_annotations(cls.location.file_name,
cls.location.line)
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)
class_wrapper = module.add_class(alias.name, **kwargs)
self.pygen_sink.writeln("module.add_class(%s)" %
", ".join([repr(alias.name)] + _pygen_kwargs(kwargs)))
class_wrapper.gccxml_definition = cls
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)
## 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('__')]
else:
unregistered_classes = []
for cls in outer_class.gccxml_definition.classes(function=self.location_filter,
recursive=False, allow_empty=True):
if outer_class.gccxml_definition.find_out_member_access_type(cls) != 'public':
continue
if cls.name.startswith('__'):
continue
unregistered_classes.append(cls)
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 >= 100:
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, cls._pybindgen_postpone_reason, reason))
cls._pybindgen_postpone_reason = reason
unregistered_classes.append(cls)
while unregistered_classes:
cls = unregistered_classes.pop(0)
typedef = None
kwargs = {}
global_annotations, param_annotations = \
annotations_scanner.get_annotations(cls.location.file_name,
cls.location.line)
for hook in self._pre_scan_hooks:
hook(self, cls, global_annotations, param_annotations)
if 'ignore' in global_annotations:
continue
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.type)
if typedef_type == cls:
break
else:
typedef = None
if len(cls.bases) > 1:
warnings.warn_explicit(("Class %s ignored because it uses multiple "
"inheritance (not yet supported by pybindgen)"
% cls.decl_string),
Warning, cls.location.file_name, cls.location.line)
continue
if cls.bases:
base_cls = cls.bases[0].related_class
try:
base_class_wrapper = 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 has a base class (%s) "
"which is not declared."
% (cls.decl_string, base_cls.decl_string),
Warning, cls.location.file_name, cls.location.line)
continue
postpone_class(cls, "waiting for base class %s to be registered first" % base_cls)
continue
else:
base_class_wrapper = None
## 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
try:
dummy = root_module[normalize_class_name(operator.return_type.decl_string, '::')]
except KeyError:
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.decl_string,)))
continue
self._apply_class_annotations(cls, global_annotations, kwargs)
custom_template_class_name = None
template_parameters = ()
if typedef is None:
alias = None
if templates.is_instantiation(cls.decl_string):
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:
cls_name = typedef.name
alias = '::'.join([module.cpp_namespace_prefix, cls.name])
if base_class_wrapper is not None:
kwargs["parent"] = base_class_wrapper
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_template_class_name"] = custom_template_class_name
class_wrapper = module.add_class(cls_name, **kwargs)
self.pygen_sink.writeln("module.add_class(%s)" %
", ".join([repr(cls_name)] + _pygen_kwargs(kwargs)))
class_wrapper.gccxml_definition = cls
registered_classes[cls] = class_wrapper
if alias:
class_wrapper.register_alias(normalize_name(alias))
self.type_registry.class_registered(class_wrapper)
for hook in self._post_scan_hooks:
hook(self, cls, class_wrapper)
del cls_name
## scan for nested classes/enums
self._scan_namespace_types(module, module_namespace, outer_class=class_wrapper)
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 = type_registry.find_class(operator.return_type.decl_string, '::')
other_class = root_module[normalize_class_name(operator.return_type.decl_string, '::')]
class_wrapper.implicitly_converts_to(other_class)
self.pygen_sink.writeln("root_module[%r].implicitly_converts_to(root_module[%r])"
% (class_wrapper.full_name, other_class.full_name))
pygen_function_closed = False
if outer_class is None:
## scan nested namespaces (mapped as python submodules)
nested_modules = []
for nested_namespace in module_namespace.namespaces(allow_empty=True, recursive=False):
if nested_namespace.name.startswith('__'):
continue
if pygen_register_function_name:
self.pygen_sink.writeln()
self.pygen_sink.writeln("## Register a nested module for the namespace %s" % nested_namespace.name)
self.pygen_sink.writeln()
nested_module = module.add_cpp_namespace(nested_namespace.name)
nested_modules.append(nested_module)
self.pygen_sink.writeln("nested_module = module.add_cpp_namespace(%r)" % nested_namespace.name)
nested_module_type_init_func = "register_types_" + "_".join(nested_module.get_namespace_path())
self.pygen_sink.writeln("%s(nested_module)" % nested_module_type_init_func)
self.pygen_sink.writeln()
self.pygen_sink.unindent()
self.pygen_sink.writeln()
pygen_function_closed = True
## scan nested namespaces (mapped as python submodules)
for nested_namespace in module_namespace.namespaces(allow_empty=True, recursive=False):
if nested_namespace.name.startswith('__'):
continue
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
if pygen_register_function_name and not pygen_function_closed:
self.pygen_sink.unindent()
self.pygen_sink.writeln()
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.member_function_t):
if member.virtuality != calldef.VIRTUALITY_TYPES.NOT_VIRTUAL:
return True
return False
def _class_has_public_destructor(self, cls):
"""return True if cls has a public destructor, else False"""
for member in cls.get_members('public'):
if isinstance(member, calldef.destructor_t):
return True
return False
def _scan_class_methods(self, cls, class_wrapper):
have_trivial_constructor = False
have_copy_constructor = False
for member in cls.get_members():
if isinstance(member, calldef.member_function_t):
if member.access_type not in ['protected', 'private']:
continue
elif isinstance(member, calldef.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:
(cpp_class, dummy_is_const, dummy_is_pointer,
is_reference, dummy_pointer_is_const) = \
self.type_registry.get_class_type_traits(member.arguments[0].type)
if cpp_class is class_wrapper and is_reference:
have_copy_constructor = True
for member in cls.get_members():
if member.name in [class_wrapper.incref_method, class_wrapper.decref_method,
class_wrapper.peekref_method]:
continue
global_annotations, parameter_annotations = \
annotations_scanner.get_annotations(member.location.file_name,
member.location.line)
for hook in self._pre_scan_hooks:
hook(self, member, global_annotations, parameter_annotations)
if 'ignore' in global_annotations:
continue
## ------------ method --------------------
if isinstance(member, calldef.member_function_t):
is_virtual = (member.virtuality != calldef.VIRTUALITY_TYPES.NOT_VIRTUAL)
pure_virtual = (member.virtuality == calldef.VIRTUALITY_TYPES.PURE_VIRTUAL)
for key, val in global_annotations.iteritems():
if key == 'template_instance_names' \
and templates.is_instantiation(member.demangled_name):
pass
else:
warnings.warn_explicit("Annotation '%s=%s' not used (used in %s)"
% (key, val, member),
Warning, member.location.file_name, member.location.line)
try:
return_type = self.type_registry.lookup_return(member.return_type,
parameter_annotations.get('return', {}))
except (TypeLookupError, TypeConfigurationError), ex:
warnings.warn_explicit("Return value '%s' error (used in %s): %r"
% (member.return_type.decl_string, member, ex),
Warning, 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 member.arguments:
try:
arguments.append(self.type_registry.lookup_parameter(arg.type, arg.name,
parameter_annotations.get(arg.name, {}),
arg.default_value))
assert hasattr(arguments[-1], "_pygen_repr")
except (TypeLookupError, TypeConfigurationError), ex:
warnings.warn_explicit("Parameter '%s %s' error (used in %s): %r"
% (arg.type.decl_string, arg.name, member, ex),
Warning, 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
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 templates.is_instantiation(member.demangled_name):
template_parameters = templates.args(member.demangled_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 = ()
kwargs = {}
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
def _pygen_method():
arglist_repr = ("[" + ', '.join([arg._pygen_repr for arg in arguments]) + "]")
self.pygen_sink.writeln("cls.add_method(%s)" %
", ".join(
[repr(member.name), return_type._pygen_repr, arglist_repr]
+ _pygen_kwargs(kwargs)))
try:
method_wrapper = class_wrapper.add_method(member.name, return_type, arguments, **kwargs)
method_wrapper.gccxml_definition = member
except NotSupportedError, ex:
_pygen_method()
if pure_virtual:
class_wrapper.set_cannot_be_constructed("pure virtual method %r not wrapped" % member.name)
class_wrapper.set_helper_class_disabled(True)
self.pygen_sink.writeln('cls.set_cannot_be_constructed("pure virtual method %%r not wrapped" %% %r)'
% member.name)
self.pygen_sink.writeln('cls.set_helper_class_disabled(True)')
warnings.warn_explicit("Error adding method %s: %r"
% (member, ex),
Warning, member.location.file_name, member.location.line)
except ValueError, ex:
warnings.warn_explicit("Error adding method %s: %r"
% (member, ex),
Warning, member.location.file_name, member.location.line)
raise
else: # no exception, add method succeeded
_pygen_method()
for hook in self._post_scan_hooks:
hook(self, member, method_wrapper)
## ------------ constructor --------------------
elif isinstance(member, calldef.constructor_t):
if member.access_type not in ['public', 'protected']:
continue
if not member.arguments:
have_trivial_constructor = True
arguments = []
for arg in member.arguments:
try:
arguments.append(self.type_registry.lookup_parameter(arg.type, arg.name,
default_value=arg.default_value))
except (TypeLookupError, TypeConfigurationError), ex:
warnings.warn_explicit("Parameter '%s %s' error (used in %s): %r"
% (arg.type.decl_string, arg.name, member, ex),
Warning, member.location.file_name, member.location.line)
ok = False
break
else:
ok = True
if not ok:
continue
constructor_wrapper = class_wrapper.add_constructor(arguments, visibility=member.access_type)
constructor_wrapper.gccxml_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
arglist_repr = ("[" + ', '.join([arg._pygen_repr for arg in arguments]) + "]")
self.pygen_sink.writeln("cls.add_constructor(%s)" %
", ".join([arglist_repr, "visibility=%r" % member.access_type]))
## ------------ attribute --------------------
elif isinstance(member, variable_t):
if member.access_type == 'protected':
warnings.warn_explicit("%s: protected member variables not yet implemented "
"by PyBindGen."
% member,
Warning, member.location.file_name, member.location.line)
continue
if member.access_type == 'private':
continue
try:
return_type = self.type_registry.lookup_return(member.type)
except (TypeLookupError, TypeConfigurationError), ex:
warnings.warn_explicit("Return value '%s' error (used in %s): %r"
% (member.type.decl_string, member, ex),
Warning, member.location.file_name, member.location.line)
continue
if member.type_qualifiers.has_static:
class_wrapper.add_static_attribute(return_type, member.name,
is_const=type_traits.is_const(member.type))
self.pygen_sink.writeln("cls.add_static_attribute(%s, %r, is_const=%r)" %
(return_type._pygen_repr, member.name, type_traits.is_const(member.type)))
else:
class_wrapper.add_instance_attribute(return_type, member.name,
is_const=type_traits.is_const(member.type))
self.pygen_sink.writeln("cls.add_instance_attribute(%s, %r, is_const=%r)" %
(return_type._pygen_repr, member.name, type_traits.is_const(member.type)))
## TODO: invoke post_scan_hooks
elif isinstance(member, calldef.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.has_trivial_constructor(cls):
class_wrapper.add_constructor([])
self.pygen_sink.writeln("cls.add_constructor([])")
if not have_copy_constructor:
try: # pygccxml > 0.9
has_copy_constructor = type_traits.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_constructor([
class_wrapper.ThisClassRefParameter("%s const &" % class_wrapper.full_name,
'ctor_arg', is_const=True)])
def scan_functions(self):
assert self._types_scanned
self.pygen_sink.writeln("def register_functions(root_module):")
self.pygen_sink.indent()
self.pygen_sink.writeln("module = root_module")
self._scan_namespace_functions(self.module, self.module_namespace)
def _scan_namespace_functions(self, module, module_namespace):
root_module = module.get_root()
for fun in module_namespace.free_functions(function=self.location_filter,
allow_empty=True, recursive=False):
if fun.name.startswith('__'):
continue
global_annotations, parameter_annotations = \
annotations_scanner.get_annotations(fun.location.file_name,
fun.location.line)
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
for name, value in global_annotations.iteritems():
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
else:
warnings.warn_explicit("Incorrect annotation %s=%s" % (name, value),
Warning, 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'
try:
return_type = self.type_registry.lookup_return(fun.return_type, return_annotations)
except (TypeLookupError, TypeConfigurationError), ex:
warnings.warn_explicit("Return value '%s' error (used in %s): %r"
% (fun.return_type.decl_string, fun, ex),
Warning, fun.location.file_name, fun.location.line)
continue
except TypeError, ex:
warnings.warn_explicit("Return value '%s' error (used in %s): %r"
% (fun.return_type.decl_string, fun, ex),
Warning, fun.location.file_name, fun.location.line)
raise
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.type, cpptypes.pointer_t):
annotations.setdefault("transfer_ownership", "false")
try:
arguments.append(self.type_registry.lookup_parameter(arg.type, arg.name,
annotations,
default_value=arg.default_value))
except (TypeLookupError, TypeConfigurationError), ex:
warnings.warn_explicit("Parameter '%s %s' error (used in %s): %r"
% (arg.type.decl_string, arg.name, fun, ex),
Warning, fun.location.file_name, fun.location.line)
ok = False
break
except TypeError, ex:
warnings.warn_explicit("Parameter '%s %s' error (used in %s): %r"
% (arg.type.decl_string, arg.name, fun, ex),
Warning, fun.location.file_name, fun.location.line)
raise
else:
ok = True
if not ok:
continue
arglist_repr = ("[" + ', '.join([arg._pygen_repr for arg in arguments]) + "]")
if as_method is not None:
assert of_class is not None
#cpp_class = type_registry.find_class(of_class, (self.module_namespace_name or '::'))
cpp_class = root_module[normalize_class_name(of_class, (self.module_namespace_name or '::'))]
function_wrapper = cpp_class.add_function_as_method(fun.name, return_type, arguments, custom_name=as_method)
function_wrapper.gccxml_definition = fun
self.pygen_sink.writeln("root_module[%r].add_function_as_method(%s, custom_name=%r)" %
(cpp_class.full_name,
", ".join([repr(fun.name), return_type._pygen_repr, arglist_repr]),
as_method))
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 '::'))]
function_wrapper = cpp_class.add_function_as_constructor(fun.name, return_type, arguments)
function_wrapper.gccxml_definition = fun
self.pygen_sink.writeln("root_module[%r].add_function_as_constructor(%s)" %
(cpp_class.full_name,
", ".join([repr(fun.name), return_type._pygen_repr, arglist_repr]),))
continue
kwargs = {}
if templates.is_instantiation(fun.demangled_name):
kwargs['template_parameters'] = templates.args(fun.demangled_name)
if alt_name:
kwargs['custom_name'] = alt_name
func_wrapper = module.add_function(fun.name, return_type, arguments, **kwargs)
func_wrapper.gccxml_definition = fun
self.pygen_sink.writeln("module.add_function(%s)" %
(", ".join([repr(fun.name), return_type._pygen_repr, arglist_repr] + _pygen_kwargs(kwargs))))
for hook in self._post_scan_hooks:
hook(self, fun, func_wrapper)
## scan nested namespaces (mapped as python submodules)
for nested_namespace in module_namespace.namespaces(allow_empty=True, recursive=False):
if nested_namespace.name.startswith('__'):
continue
nested_module = module.get_submodule(nested_namespace.name)
nested_module_pygen_func = "register_functions_" + "_".join(nested_module.get_namespace_path())
self.pygen_sink.writeln("%s(module.get_submodule(%r), root_module)" %
(nested_module_pygen_func, nested_namespace.name))
self.pygen_sink.writeln("return")
self.pygen_sink.unindent()
self.pygen_sink.writeln()
for nested_namespace in module_namespace.namespaces(allow_empty=True, recursive=False):
if nested_namespace.name.startswith('__'):
continue
nested_module = module.get_submodule(nested_namespace.name)
nested_module_pygen_func = "register_functions_" + "_".join(nested_module.get_namespace_path())
self.pygen_sink.writeln("def %s(module, root_module):" % nested_module_pygen_func)
self.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)
import utils
utils.write_preamble(out)
module.generate(out)
if __name__ == '__main__':
_test()