#!/usr/bin/python
# -*- coding: utf-8 -*-

DEBUG = False

import sys
import os.path
import warnings
import re
import pygccxml
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 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 cpptypes
from pygccxml.declarations import calldef
from pygccxml.declarations import templates
from pygccxml.declarations import container_traits
from pygccxml.declarations.declaration import declaration_t
from pygccxml.declarations.class_declaration import class_declaration_t, class_t
import settings
import utils

#from pygccxml.declarations.calldef import \
#    destructor_t, constructor_t, member_function_t
from pygccxml.declarations.variable import variable_t


###
### 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.type_traits.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, "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),
                                   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.iteritems():
        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.iteritems():
            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.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, 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
        
        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()),
                                                   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, long)):
            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.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",
                                           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, str)
        self.name = name
        assert isinstance(code_sink, CodeSink)
        self.code_sink = code_sink
        assert local_customizations_module is None or isinstance(local_customizations_module, str)
        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: gccxml 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.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 # (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 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, pygen_classifier=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.
        """
        self.parse_init(header_files, include_paths, whitelist_paths, includes, pygen_sink,
                        pygen_classifier, gccxml_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,
                   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 gccxml_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 gccxml 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 gccxml_options: extra options to pass into the
            :class:`pygccxml.parser.config.gccxml_configuration_t` object as keyword
            arguments for more information).

        :type gccxml_options: dict

        """
        assert isinstance(header_files, list)
        assert isinstance(includes, (list, tuple))
        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 gccxml_options is None:
            gccxml_options = {}

        if include_paths is not None:
            assert isinstance(include_paths, list)
            warnings.warn("Parameter include_paths is deprecated, use gccxml_options instead", DeprecationWarning,
                          stacklevel=2)
            self.gccxml_config = parser.config_t(include_paths=include_paths, **gccxml_options)
        else:
            self.gccxml_config = parser.config_t(**gccxml_options)

        self.declarations = parser.parse(header_files, self.gccxml_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.gccxml_definition, class_declaration_t):
                continue # skip classes not fully defined
            if isinstance(class_wrapper, CppException):
                continue # exceptions cannot have methods (yet)
            pygen_sink =  self._get_pygen_sink_for_definition(class_wrapper.gccxml_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.gccxml_definition, class_declaration_t):
                continue # skip classes not fully defined
            if isinstance(class_wrapper, CppException):
                continue # exceptions cannot have methods (yet)
            register_methods_func = "register_%s_methods"  % (class_wrapper.mangled_full_name,)

            pygen_sink =  self._get_pygen_sink_for_definition(class_wrapper.gccxml_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.gccxml_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
        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('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
            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

        return is_exception
                

    def _has_public_destructor(self, cls):
        for member in cls.get_members():
            if isinstance(member, calldef.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):
        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):
            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.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, ),
                #                           NotSupportedWarning, enum.location.file_name, enum.location.line)
                #    continue
                enums.append(enum)

        for enum in enums:

            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 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.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)

            for typedef in outer_class.gccxml_definition.typedefs(function=self.location_filter,
                                                                  recursive=False, allow_empty=True):
                if outer_class.gccxml_definition.find_out_member_access_type(typedef) != 'public':
                    continue
                if typedef.name.startswith('__'):
                    continue
                typedefs.append(typedef)

        def cls_cmp(a, b):
            return cmp(a.decl_string, b.decl_string)
        unregistered_classes.sort(cls_cmp)

        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 >> sys.stderr, ">>> class %s is being postponed (%s)" % (str(cls), reason)
            unregistered_classes.append(cls)

        while unregistered_classes:
            cls = unregistered_classes.pop(0)
            if DEBUG:
                print >> sys.stderr, ">>> looking at class ", str(cls)
            typedef = None

            kwargs = {}
            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

            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
                

            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
                
            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

            ## 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)

            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])

            template_parameters_decls = [find_declaration_from_name(self.global_ns, templ_param)
                                         for templ_param in template_parameters]

            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)))

            ## 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))

            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.gccxml_definition = cls
            self._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)

            # 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))

        # -- 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

        if outer_class is None:
            
            ## --- look for typedefs ----
            for alias in module_namespace.typedefs(function=self.location_filter,
                                                   recursive=False, allow_empty=True):

                type_from_name = normalize_name(str(alias.type))
                type_to_name = normalize_name(utils.ascii('::'.join([module.cpp_namespace_prefix, 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.type, cpptypes.declarated_t):
                    cls = alias.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.gccxml_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
                        cls_wrapper = self._registered_classes[cls]
                        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)

            def decl_cmp(a, b):
                return cmp(a.decl_string, b.decl_string)
            nested_namespaces.sort(decl_cmp)

            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)

            def decl_cmp(a, b):
                return cmp(a.decl_string, b.decl_string)
            nested_namespaces.sort(decl_cmp)

            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

        #print >> sys.stderr, "************* register_container", name

        element_type = traits.element_type(definition)
        #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), 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), 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.member_function_t):
                if member.virtuality != calldef.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.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.is_convertible(cls, argument_types[0]) \
                    and type_traits.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.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), 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.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.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.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:
                    traits = ctypeparser.TypeTraits(normalize_name(member.arguments[0].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:
                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:
                continue

            ## ------------ method --------------------
            if isinstance(member, (calldef.member_function_t, calldef.member_operator_t)):
                is_virtual = (member.virtuality != calldef.VIRTUALITY_TYPES.NOT_VIRTUAL)
                pure_virtual = (member.virtuality == calldef.VIRTUALITY_TYPES.PURE_VIRTUAL)

                kwargs = {} # kwargs passed into the add_method call

                for key, val in global_annotations.iteritems():
                    if key == 'template_instance_names' \
                            and templates.is_instantiation(member.demangled_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
                    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.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:
                    argument_specs.append(self.type_registry.lookup_parameter(arg.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 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 = ()

                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), 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), 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.gccxml_definition = member
                except NotSupportedError, 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, 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.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.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), ex:
                        warnings.warn_explicit("Parameter '%s %s' error (used in %s): %r"
                                               % (arg.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.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

            ## ------------ 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.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.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.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.type)))
                    
                ## convert the return value
                try:
                    return_type = ReturnValue.new(*return_type_spec[0], **return_type_spec[1])
                except (TypeLookupError, TypeConfigurationError), ex:
                    warnings.warn_explicit("Return value '%s' error (used in %s): %r"
                                           % (member.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.type))
                else:
                    class_wrapper.add_instance_attribute(member.name, return_type,
                                                         is_const=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([])
                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_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 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)

        def fun_cmp(a, b):
            name_cmp = cmp(a.name, b.name)
            # if function names differ, compare by name, else compare by the full declaration
            if name_cmp != 0:
                return name_cmp
            else:
                return cmp(a.decl_string, b.decl_string)
        functions_to_scan.sort(fun_cmp)

        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.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
                elif name == 'pygen_comment':
                    pass
                elif name == 'template_instance_names':
                    pass
                elif name == 'unblock_threads':
                    kwargs['unblock_threads'] = annotations_scanner.parse_boolean(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), 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, 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.type, cpptypes.pointer_t):
                    annotations.setdefault("transfer_ownership", "false")
                    
                spec = self.type_registry.lookup_parameter(arg.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), ex:
                    warnings.warn_explicit("Parameter '%s %s' error (used in %s): %r"
                                           % (arg.type.partial_decl_string, arg.name, fun, ex),
                                           WrapperWarning, fun.location.file_name, fun.location.line)

                    params_ok = False
                except TypeError, ex:
                    warnings.warn_explicit("Parameter '%s %s' error (used in %s): %r"
                                           % (arg.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.gccxml_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.gccxml_definition = fun

                continue

            if templates.is_instantiation(fun.demangled_name):
                template_parameters = templates.args(fun.demangled_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.gccxml_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)

        def decl_cmp(a, b):
            return cmp(a.decl_string, b.decl_string)
        nested_namespaces.sort(decl_cmp)
        
        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("%s(module.get_submodule(%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)

        def decl_cmp(a, b):
            return cmp(a.decl_string, b.decl_string)
        nested_namespaces.sort(decl_cmp)

        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()
