Exemple #1
0
    def rename(self, n):
        new_name = self.rename_types.get(n, None)
        if new_name:
            arg_type_order = self.wrapper_order.get(new_name, None)
            class_order = self.wrapper_order.get(self.current_class_name, None)

            # arg type is defined after current class
            if arg_type_order and class_order and arg_type_order > class_order:
                return T.Constant(new_name)

            if new_name == self.current_class_name:
                return T.Constant(new_name)

            return T.Name(new_name)
        return n
Exemple #2
0
    def parse_int(self, val):
        if val.startswith('0x'):
            return T.Constant(int(val, base=16))

        try:
            return T.Constant(int(val))
        except ValueError:
            pass

        try:
            return T.Constant(float(val))
        except ValueError:
            pass

        return None
Exemple #3
0
    def generate_function(self, elem: Cursor, depth=0, **kwargs):
        """Generate a type alias

        Examples
        --------
        >>> from tide.generators.clang_utils import parse_clang
        >>> tu, index = parse_clang('float add(float a, float b);')
        >>> module = BindingGenerator().generate(tu)
        >>> print(compact(unparse(module)))
        <BLANKLINE>
        add = _bind('add', [c_float, c_float], c_float, arg_names=['a', 'b'])
        <BLANKLINE>
        """
        log.debug(f'{d(depth)}Generate function `{elem.spelling}`')
        definition: Cursor = elem.get_definition()

        if definition is None:
            definition = elem

        rtype = self.generate_type(definition.result_type, depth + 1)
        args = definition.get_arguments()
        docstring = get_comment(elem)

        pyargs = []
        arg_names = []
        for a in args:
            atype = self.generate_type(a.type, depth + 1)
            pyargs.append(atype)
            if a.spelling != '':
                arg_names.append(T.Constant(a.spelling))

        funnane = definition.spelling
        if not pyargs:
            pyargs = T.Name('None')
        else:
            pyargs = T.List(elts=pyargs)

        kwargs = []
        if len(docstring) > 0:
            kwargs.append(
                T.Keyword('docstring', T.Constant(docstring, docstring=True)))

        if arg_names:
            kwargs.append(T.Keyword('arg_names', T.List(arg_names)))

        binding_call = T.Call(T.Name('_bind'),
                              [T.Constant(funnane), pyargs, rtype], kwargs)
        return T.Assign([T.Name(funnane)], binding_call)
Exemple #4
0
    def class_definition(self, class_def: T.ClassDef, depth):
        assert class_def.name in self.ctypes

        # Opaque struct: move on
        # if class_def.name[0] == '_':
        #    return class_def

        self_wrap = self.wrappers.get(class_def.name)
        if self_wrap is None:
            if T.Name('enumeration') in class_def.decorator_list:
                return self.clean_up_enumeration(class_def)

            _, names = parse_sdl_name(class_def.name)

            cl_name = class_name(*names)
            self_wrap = T.ClassDef(cl_name)
            self.new_code.append((class_def.name, self_wrap))
            self.wrappers[class_def.name] = self_wrap
            self.wrappers_2_ctypes[self_wrap.name] = class_def.name
            self.wrapper_order[self_wrap.name] = len(self.wrappers)

            self_type = T.Call(T.Name('POINTER'), [T.Name(class_def.name)])

            self_wrap.body.append(
                T.AnnAssign(T.Name('handle'), self_type, T.Name('None')))

            # Factory to build the wrapper form the ctype
            # create an uninitialized version of the object and set the handle
            from_ctype = T.FunctionDef('from_handle',
                                       decorator_list=[T.Name('staticmethod')])
            from_ctype.args = T.Arguments(args=[T.Arg('a', self_type)])
            from_ctype.returns = T.Constant(cl_name)
            from_ctype.body = [
                T.Assign([T.Name('b')],
                         T.Call(T.Attribute(T.Name('object'), '__new__'),
                                [T.Name(cl_name)])),
                T.Assign([T.Attribute(T.Name('b'), 'handle')], T.Name('a')),
                T.Return(T.Name('b'))
            ]
            self_wrap.body.append(from_ctype)

            if not self.is_opaque_container(class_def.name):
                # default init allocate a dummy object for it
                default_init = T.FunctionDef('__init__')
                default_init.args = T.Arguments(args=[T.Arg('self')])
                default_init.body = [
                    T.Assign([T.Attribute(T.Name('self'), '_value')],
                             T.Call(T.Name(class_def.name), [])),
                    T.Assign([T.Attribute(T.Name('self'), 'handle')],
                             T.Call(T.Name('byref'),
                                    [T.Attribute(T.Name('self'), '_value')]))
                ]

                self_wrap.body.append(default_init)

            self.rename_types[T.Call(T.Name('POINTER'),
                                     [T.Name(class_def.name)])] = cl_name

        return class_def
Exemple #5
0
    def generate_integer(self, elem: Cursor, **kwargs):
        try:
            val = list(elem.get_tokens())[0].spelling

            val = val.replace('u', '')
            val = val.replace('ll', '')
            val = val.replace('U', '')
            val = val.replace('L', '')

            base = 10
            if '0x' in val:
                base = 16

            return T.Constant(int(val, base=base))
        except IndexError:
            # this integer comes from __LINE__ macro
            # this is not correct at all, this should return the line on which it is called
            # but we cannot really do that in python I think
            return T.Constant(int(elem.location.line))
Exemple #6
0
    def parse_expression(self, depth=0):
        tok = self.peek()
        log.debug(f'{d(depth)} parse_expression {tok.kind} {tok.spelling}')

        if tok.spelling == '\\\n{':
            body = []
            self.next()

            while tok.spelling != '\\\n}':
                p = T.Expr(self.parse_expression(depth + 1))
                body.append(p)

                tok = self.peek()
                if tok.spelling == ';':
                    self.next()
                    tok = self.peek()

            self.next()
            return T.If(T.Constant(True), body)

        if is_operator(tok.spelling) and is_unary_operator(tok.spelling):
            p = self.parse_unary(depth + 1)
        else:
            p = self.parse_primary(depth + 1)

        tok = self.peek()
        if tok is None:
            return p

        # we are doing a cast or call (type(expr))
        if tok.spelling == '(':
            self.next()
            expr = self.parse_call(p, depth + 1)

            if self.peek().spelling == ')':
                self.next()
            return expr

        # argument list do not try to parse operators
        if tok.spelling == ',' and self.is_call[-1]:
            return p

        if tok.spelling == ')':
            return p

        # cast (expr) <expr>
        if not is_operator(tok.spelling):
            return self.parse_cast(p, depth + 1)

        # if tok.spelling == ')':
        #    return p

        return self.parse_expression_1(p, 0, depth + 1)
Exemple #7
0
    def process_builtin_macros(self, cursor: Cursor):
        tokens = list(cursor.get_tokens())
        name, tok_args, tok_body = parse_macro(tokens)

        if len(tok_body) == 0:
            self.definitions[name.spelling] = T.Constant(True)
            return

        try:
            bods = {t.spelling for t in tok_body}
            if not bods.isdisjoint(self.unsupported_macros):
                raise UnsupportedExpression()

            py_body = parse_macro2(name, tok_args, tok_body)

        except UnsupportedExpression:
            self.unsupported_macros.add(name.spelling)

        self.definitions[name.spelling] = py_body
Exemple #8
0
    def parse_literal(self, tok: Token, depth):
        log.debug(f'{d(depth)} parse_literal {tok.kind} {tok.spelling}')
        val = None

        # 3243212132u
        # 0x13223u
        l = self.is_int_annotated(tok.spelling)
        if l > 0:
            val = self.parse_int(tok.spelling[:-l])

        # 1232133 | 123.123
        if val is None:
            val = self.parse_int(tok.spelling)

        # the rest
        if val is None:
            val = T.Constant(str(tok.spelling))

        return val
Exemple #9
0
    def generate_field(self, body, attrs, attr, anonymous_renamed, depth,
                       **kwargs):
        if attr.kind == CursorKind.FIELD_DECL:
            # Rename anonymous types
            uid = self.get_underlying_type_uid(attr.type)

            log.debug(
                f'{d(depth)}uid: {uid} {attr.type.spelling} {anonymous_renamed}'
            )
            if uid in anonymous_renamed:
                parent, name = anonymous_renamed[uid]
                if parent:
                    typename = T.Attribute(T.Name(parent), name)
                else:
                    typename = T.Name(name)

                if attr.type.kind == TypeKind.POINTER:
                    typename = T.Call(T.Name('POINTER'), [typename])
            else:
                typename = self.generate_type(attr.type, depth + 1)

            pair = T.Tuple()
            pair.elts = [T.Constant(attr.spelling), typename]
            attrs.elts.append(pair)

        elif attr.kind in (CursorKind.UNION_DECL, CursorKind.STRUCT_DECL):
            nested_struct = self.generate_struct_union(
                attr, depth + 1, nested=True, rename=anonymous_renamed)

            body.append(nested_struct)
        elif attr.kind == CursorKind.PACKED_ATTR:
            for attr2 in attr.get_children():
                self.generate_field(body, attrs, attr2, anonymous_renamed,
                                    depth + 1)
                # attrs.append(field)
        else:
            show_elem(attr)
            print('NESTED ', attr.kind)
            raise RuntimeError('')
Exemple #10
0
 def generate_string(self, elem, **kwargs):
     return T.Constant(elem.spelling)
Exemple #11
0
    def generate_enum(self, elem: Cursor, depth=0, **kwargs):
        """ Generate a enum class

        Notes
        -----
        we cannot use enum.Enum because we need the class to be a ctype/c_int when they are used
        a arguments

        Examples
        --------
        >>> from tide.generators.clang_utils import parse_clang
        >>> tu, index = parse_clang('enum Colors { Red, Green, Blue;};')
        >>> module = BindingGenerator().generate(tu)
        >>> print(compact(unparse(module)))
        <BLANKLINE>
        @enumeration
        class Colors(c_int):
            Red = 0
            Green = 1
            Blue = 2
        <BLANKLINE>
        """
        log.debug(f'{d(depth)}Generate Enum')
        name = self.get_name(elem)
        enum = []
        ctypes = self.type_registry.get('int')
        common = self.find_duplicate_name(elem)

        if name == '':
            name = common
            if name[-1] == '_':
                name = name[:-1]

        if self.global_enum:
            enum.append(T.Assign([T.Name(name)],
                                 self.type_registry.get('int')))

        def shorten_name(n):
            if len(n) == len(common):
                return n

            if self.short_enum_names:
                new_name = n[len(common):]

                if not new_name[0].isalpha():
                    return n

                # rename SDL_PIXELTYPE_UNKNOWN to SDL_PIXELTYPE.UNKNOWN
                # useful to get editor auto-completion to show relevant values
                self.renaming[n] = T.Attribute(T.Name(name), new_name)
                return new_name
            return n

        for value in elem.get_children():
            if value.kind == CursorKind.ENUM_CONSTANT_DECL:
                n = shorten_name(self.get_name(value))
                enum.append(T.Assign([T.Name(n)],
                                     T.Constant(value.enum_value)))
            else:
                log.error(f'Unexpected children {value.kind}')
                raise RuntimeError()

        if not self.global_enum:
            # this require renaming the code where the enum were used
            # not implemented yet
            self.import_enum = True
            enum_class = T.ClassDef(name, bases=[ctypes])
            enum_class.body = enum
            enum_class.decorator_list.append(T.Name('enumeration'))
            return enum_class

        return enum
Exemple #12
0
    def generate_struct_union(self,
                              elem: Cursor,
                              depth=1,
                              nested=False,
                              rename=None,
                              **kwargs):
        """Generate a struct or union alias

        Examples
        --------
        >>> from tide.generators.clang_utils import parse_clang
        >>> tu, index = parse_clang('struct Point { float x, y;};')
        >>> module = BindingGenerator().generate(tu)
        >>> print(compact(unparse(module)))
        <BLANKLINE>
        class Point(Structure):
            pass
        <BLANKLINE>
        Point._fields_ = [('x', c_float), ('y', c_float)]
        <BLANKLINE>


        >>> from tide.generators.clang_utils import parse_clang
        >>> tu, index = parse_clang('union Point { float x; int y;};')
        >>> module = BindingGenerator().generate(tu)
        >>> print(compact(unparse(module)))
        <BLANKLINE>
        class Point(Union):
            pass
        <BLANKLINE>
        Point._fields_ = [('x', c_float), ('y', c_int)]
        <BLANKLINE>
        """

        log.debug(f'{d(depth)}Generate struct `{elem.spelling}`')
        pyname = self.get_name(elem, rename=rename, depth=depth + 1)

        base = 'Structure'
        if elem.kind == CursorKind.UNION_DECL:
            base = 'Union'

        # For recursive data structure
        #  log.debug(pyname)
        self.type_registry[f'struct {pyname}'] = T.Name(pyname)
        self.type_registry[f'const struct {pyname}'] = T.Name(pyname)

        parent = pyname
        anonymous_renamed = self.find_anonymous_fields(elem,
                                                       parent,
                                                       depth=depth + 1)

        # Docstring is the first element of the body
        # T.Constant(get_comment(elem))
        body = []
        docstring = get_comment(elem)
        if docstring:
            body.append(T.Expr(T.Constant(docstring, docstring=True)))

        attrs = T.List()

        need_pass = len(body)
        attr: Cursor
        for attr in elem.get_children():
            self.generate_field(body, attrs, attr, anonymous_renamed, depth)

        # insert pass even if we have a docstring because
        # we will remove the docstring later
        if need_pass == len(body):
            body.append(ast.Pass())

        if not attrs.elts:
            return T.ClassDef(name=pyname, bases=[T.Name(base)], body=body)

        fields = T.Assign([T.Attribute(T.Name(pyname), '_fields_')], attrs)
        return [
            T.ClassDef(name=pyname, bases=[T.Name(base)], body=body), fields
        ]
Exemple #13
0
    def _generate_type(self, type: Type, depth=0):
        # print(type.kind, type.spelling, self.type_registry.get(type.spelling, 'NOT FOUND'))
        if type.kind == TypeKind.VOID:
            return T.Name('None')

        if type.kind == TypeKind.INT:
            return T.Name('c_int')

        if type.kind == TypeKind.POINTER and type.get_pointee(
        ).kind == TypeKind.VOID:
            return T.Name('c_void_p')

        if type.kind == TypeKind.POINTER and type.get_pointee(
        ).kind == TypeKind.CHAR_S:
            return T.Name('c_char_p')

        if type.kind == TypeKind.POINTER:
            pointee: Type = type.get_pointee()

            # Typedef use the name that it is aliased to
            if pointee.kind is TypeKind.TYPEDEF:
                pointee = get_typename(pointee)

            elif pointee.kind != TypeKind.VOID:
                pointee = self.generate_type(pointee, depth + 1)
            else:
                pointee = T.Name('c_void_p')

            # Function pointer do not need to be decorated by POINTER call
            if isinstance(pointee, T.Call) and isinstance(
                    pointee.func, T.Name) and pointee.func.id == 'CFUNCTYPE':
                return pointee

            # for native types we need to keep ref because python will copy them
            return T.Call(T.Name('POINTER'), [pointee])

        if type.kind == TypeKind.CHAR_S:
            return T.Name('c_char_p')

        # if type.is_const_qualified():
        #     return T.Name(get_typename(type))

        if type.kind == TypeKind.TYPEDEF:
            return get_typename(type)

        if type.kind == TypeKind.FUNCTIONPROTO or (
                type.kind == TypeKind.UNEXPOSED
                and type.get_canonical().kind == TypeKind.FUNCTIONPROTO):
            # SDL_HitTest = CFUNCTYPE(SDL_HitTestResult, POINTER(SDL_Window), POINTER(SDL_Point), c_void_p)
            canon = type
            if type.kind == TypeKind.UNEXPOSED:
                canon = type.get_canonical()

            rtype = canon.get_result()

            args = []
            for arg in canon.argument_types():
                args.append(self.generate_type(arg, depth + 1))

            returntype = self.generate_type(rtype, depth + 1)

            cargs = [returntype]
            cargs.extend(args)
            return T.Call(T.Name('CFUNCTYPE'), args=cargs)

        if type.kind == TypeKind.CONSTANTARRAY:
            t = self.generate_type(type.element_type, depth + 1)
            return T.BinOp(t, ast.Mult(), T.Constant(type.element_count))

        # Represents a C array with an unspecified size.
        if type.kind == TypeKind.INCOMPLETEARRAY:
            element_type = self.generate_type(type.element_type, depth + 1)
            # char *[] -> char **
            return T.Call(T.Name('POINTER'), [element_type])

        # struct <TYPENAME>
        if type.kind == TypeKind.ELABORATED:
            return T.Name(get_typename(type).id.replace('struct', '').strip())

        if type.kind == TypeKind.ENUM:
            return get_typename(type)

        # print('gentype')
        show_elem(type, print_fun=log.debug)
        return get_typename(type)
Exemple #14
0
 def generate_float(self, elem, **kwargs):
     toks = [t.spelling for t in elem.get_tokens()]
     assert len(toks) == 1
     return T.Constant(float(toks[0]))
Exemple #15
0
    def parse_expression_1(self, lhs, min_precedence, depth):
        lookahead = self.peek()
        precedence = fetch_precedence(lookahead.spelling)
        log.debug(
            f'{d(depth)} parse_expression_1 {lhs} {lookahead.kind} {lookahead.spelling}'
        )

        while lookahead and precedence is not None and precedence >= min_precedence:
            op = lookahead
            self.next()

            rhs = self.parse_primary(depth + 1)
            lookahead = self.peek()

            if lookahead is None:
                break

            is_binary = is_binary_operator(lookahead.spelling)
            lookahead_pred = fetch_precedence(lookahead.spelling)
            is_right_asso = is_right_associative(lookahead.spelling)

            while lookahead and (is_binary and lookahead_pred > precedence
                                 ) or (is_right_asso
                                       and lookahead_pred == precedence):
                rhs = self.parse_expression_1(rhs, lookahead_pred, depth + 1)

                lookahead = self.peek()

                is_binary = is_binary_operator(lookahead.spelling)
                lookahead_pred = fetch_precedence(lookahead.spelling)
                is_right_asso = is_right_associative(lookahead.spelling)

                if lookahead.spelling == ')':
                    break

            # the result of applying op with operands lhs and rhs
            pyop = fetch_python_op(op.spelling)

            if pyop is not None and not isinstance(pyop, str):
                if is_comparison(op.spelling):
                    lhs = T.Compare(left=lhs, ops=[pyop()], comparators=[rhs])
                elif is_bool(op.spelling):
                    lhs = T.BoolOp(op=pyop(), values=[lhs, rhs])
                else:
                    lhs = T.BinOp(left=lhs, op=pyop(), right=rhs)

            elif pyop == 'if':
                raise UnsupportedExpression()

            elif pyop == 'assign':
                lhs = T.Assign(targets=[lhs], value=rhs)

            elif op.spelling == '[':
                lhs = T.Subscript(value=lhs,
                                  slice=T.Index(value=rhs),
                                  ctx=T.Load())
                tok = self.peek()
                assert tok.spelling == ']'
                self.next()

            elif op.spelling == '->':
                if isinstance(rhs, T.Name):
                    rhs = rhs.id

                lhs = T.Attribute(lhs, rhs, ctx=T.Load())

            elif op.spelling == ',':
                lhs = T.If(T.Constant(True), [T.Expr(lhs), T.Return(rhs)])
            else:
                show_elem(op)
                assert False

            precedence = fetch_precedence(lookahead.spelling)
        return lhs
Exemple #16
0
    tu = index.parse(filename, options=0x01)

    for diag in tu.diagnostics:
        print(diag.format())

    for elem in tu.cursor.get_children():
        show_elem(elem)

        if elem.spelling == '__GNUC__':
            break

        pass


if __name__ == '__main__':
    # show_file('/usr/include/SDL2/SDL.h')
    from tide.generators.unparser_patch import unparse
    import tide.generators.nodes as T

    import ast
    from astunparse import dump

    mod = ast.parse("""
\"\"\"test\"\"\"
""")

    mod = T.Constant("abc", docstring=True)

    # print(dump(mod))
    print(unparse(mod))
Exemple #17
0
class APIPass:
    """Generate a more friendly API for C bindings

    Notes
    -----
    * removes the explicit library namespace from the name of functions and struct
    * Rewrites functions as method if first argument is a pointer to struct
    * Method use original c argument names
    * Method has the origin c docstring
    * Method has a short name removing the object name and the library namespace
    * enum values are now scoped inside an enum class
    * enum values have shorter names since name clashing cannot happen anymore
    * rewrites function that takes pointer arguments to return multiple values
    """
    def __init__(self):
        self.dispatcher = {
            'Expr': self.expression,
            'Assign': self.assign,
            'ClassDef': self.class_definition,
            'FunctionDef': self.function_definition
        }
        self.pre_dispatcher = {
            'Expr': self.pre_expression,
            'Assign': self.pre_assign,
            'ClassDef': self.pre_class_definition,
        }
        self.ctypes = dict()
        self.wrappers = dict()
        self.wrappers_2_ctypes = dict()
        # keep the order to know if we can annotate with Name of with string
        self.wrapper_order = dict()
        # we will remove wrappers that do not have methods
        # i.e they are data struct only
        self.wrapper_method_count = defaultdict(int)
        self.wrapper_ctor = defaultdict(list)
        self.ctypes_fields = dict()
        self.new_code = []
        self.names = Trie()
        self.rename_types = dict()
        self.current_class_name = None
        self.new_names = set()

    def pre_expression(self, expr: T.Expr, depth):
        values = self.preprocess(expr.value, depth)
        if isinstance(values, (tuple, list)):
            return [T.Expr(v) for v in values]

        return T.Expr(values)

    def pre_class_definition(self, class_def: T.ClassDef, depth=0):
        self.ctypes[class_def.name] = class_def

    def pre_assign(self, expr: T.Assign, depth):
        # <c-type>._fields_ = []
        if match(expr.targets[0], 'Attribute') and match(
                expr.value, 'List') and expr.targets[0].attr == '_fields_':
            ctype_name = expr.targets[0].value.id
            data = []
            self.ctypes_fields[ctype_name] = data

            for elem in expr.value.elts:
                name = elem.elts[0].value
                ctype = elem.elts[1]
                data.append((name, ctype))

    def preprocess(self, expr, depth):
        handler = self.pre_dispatcher.get(expr.__class__.__name__, None)

        if handler is not None:
            handler(expr, depth)

    def preprocessor(self, module: T.Module, depth=0):
        for expr in module.body:
            self.preprocess(expr, depth)

    def post_process_class_defintion(self, class_def: T.ClassDef):
        """Make a final pass over the generated class to improve function names"""

        # move the documentation over to our new class
        c_class_name = self.wrappers_2_ctypes[class_def.name]
        c_class_def = self.ctypes[c_class_name]
        docstring = fetch_docstring(c_class_def)

        if docstring:
            class_def.body.insert(0, docstring)
        # --

        class_name = class_def.name.lower()
        names = defaultdict(int)

        for expr in class_def.body:
            if not isinstance(expr, T.FunctionDef):
                continue

            for n in expr.name.split('_'):
                if len(n) > 0:
                    names[n] += 1

        names = list(names.items())

        # remove names that are not used that often
        names = sorted(filter(lambda x: x[1] > 2, names), key=lambda x: x[1])

        # this is the magic filter that makes thing work
        # by forcing the name we are removing to be part of the type name
        # we are almost sure to remove duplicated data that is not useful
        # even when it appears multiple times it can make sense to have it duplicated
        # Example:
        #   read_le16, read_le32, read_le64
        #   write_le16, write_le32, write_le64

        names = list(
            map(lambda x: x[0], filter(lambda x: x[0] in class_name, names)))

        for expr in class_def.body:
            if not isinstance(expr, T.FunctionDef):
                continue

            for useless_name in names:
                expr.name = clean_name(expr.name, useless_name)

    def group_constant_to_enums(self):
        pass

    def clean_up_enumeration(self, class_def: T.ClassDef):
        """Removes superfluous prefix"""
        _, names = parse_sdl_name(class_def.name)
        cl_name = class_name(*names)

        # make a short alias of the enum (without the hardcoded c namespace)
        if cl_name != class_def.name:
            self.new_code.append(
                (None, T.Assign([T.Name(cl_name)], T.Name(class_def.name))))

        t = Trie()
        counts = 0
        for expr in class_def.body:
            if match(expr, 'Assign') and match(expr.targets[0], 'Name'):
                constant_name = expr.targets[0].id
                t.insert(constant_name)
                counts += 1

        _ = list(t.redundant_prefix())
        if len(_) > 1:
            # select the shortest prefix that is common to all
            most_likely = _[0]
            for c, n in _:

                if len(n) < len(most_likely[1]):
                    most_likely = (c, n)

            # check that the prefix is common to all
            good = True
            for c, n in _:
                if not most_likely[1] in n:
                    good = False
                    break

            if good:
                _ = [most_likely]
            else:
                _ = []

        if len(_) == 1:
            c, namespace = _[0]

            # SDL insert a None/Max enum that does not follow the pattern
            if counts != c + 1:
                return class_def

            for expr in class_def.body:
                if match(expr, 'Assign') and match(expr.targets[0], 'Name'):
                    constant_name = expr.targets[0].id
                    expr.targets[0].id = clean_name(constant_name, namespace)

        return class_def

    def is_opaque_container(self, name):
        # does this class define fields
        return self.ctypes_fields.get(name, None) is None

    def class_definition(self, class_def: T.ClassDef, depth):
        assert class_def.name in self.ctypes

        # Opaque struct: move on
        # if class_def.name[0] == '_':
        #    return class_def

        self_wrap = self.wrappers.get(class_def.name)
        if self_wrap is None:
            if T.Name('enumeration') in class_def.decorator_list:
                return self.clean_up_enumeration(class_def)

            _, names = parse_sdl_name(class_def.name)

            cl_name = class_name(*names)
            self_wrap = T.ClassDef(cl_name)
            self.new_code.append((class_def.name, self_wrap))
            self.wrappers[class_def.name] = self_wrap
            self.wrappers_2_ctypes[self_wrap.name] = class_def.name
            self.wrapper_order[self_wrap.name] = len(self.wrappers)

            self_type = T.Call(T.Name('POINTER'), [T.Name(class_def.name)])

            self_wrap.body.append(
                T.AnnAssign(T.Name('handle'), self_type, T.Name('None')))

            # Factory to build the wrapper form the ctype
            # create an uninitialized version of the object and set the handle
            from_ctype = T.FunctionDef('from_handle',
                                       decorator_list=[T.Name('staticmethod')])
            from_ctype.args = T.Arguments(args=[T.Arg('a', self_type)])
            from_ctype.returns = T.Constant(cl_name)
            from_ctype.body = [
                T.Assign([T.Name('b')],
                         T.Call(T.Attribute(T.Name('object'), '__new__'),
                                [T.Name(cl_name)])),
                T.Assign([T.Attribute(T.Name('b'), 'handle')], T.Name('a')),
                T.Return(T.Name('b'))
            ]
            self_wrap.body.append(from_ctype)

            if not self.is_opaque_container(class_def.name):
                # default init allocate a dummy object for it
                default_init = T.FunctionDef('__init__')
                default_init.args = T.Arguments(args=[T.Arg('self')])
                default_init.body = [
                    T.Assign([T.Attribute(T.Name('self'), '_value')],
                             T.Call(T.Name(class_def.name), [])),
                    T.Assign([T.Attribute(T.Name('self'), 'handle')],
                             T.Call(T.Name('byref'),
                                    [T.Attribute(T.Name('self'), '_value')]))
                ]

                self_wrap.body.append(default_init)

            self.rename_types[T.Call(T.Name('POINTER'),
                                     [T.Name(class_def.name)])] = cl_name

        return class_def

    def function_definition(self, function_def: T.FunctionDef, depth):
        return function_def

    SELF_HANDLE = T.Attribute(T.Name('self'), 'handle')

    def rename(self, n):
        new_name = self.rename_types.get(n, None)
        if new_name:
            arg_type_order = self.wrapper_order.get(new_name, None)
            class_order = self.wrapper_order.get(self.current_class_name, None)

            # arg type is defined after current class
            if arg_type_order and class_order and arg_type_order > class_order:
                return T.Constant(new_name)

            if new_name == self.current_class_name:
                return T.Constant(new_name)

            return T.Name(new_name)
        return n

    def make_wrapping_call(
            self,
            call: BindCall,
            replace_arg_names: Optional[List] = None,
            replace_args: Optional[List] = None) -> Tuple[T.Arguments, T.Call]:
        if replace_arg_names is None:
            replace_arg_names = []

        if replace_args is None:
            replace_args = []

        assert len(replace_args) == len(replace_arg_names)

        fun_name = call.function_name
        offset = len(replace_args)
        ctype_args = list(call.arguments[offset:])
        arg_names = list(call.argument_names[offset:])

        fun_args = T.Arguments(
            args=replace_arg_names +
            [T.Arg(n, self.rename(t)) for n, t in zip(arg_names, ctype_args)])
        fun_call = T.Call(
            T.Name(fun_name), replace_args +
            [T.Name(arg_names[i]) for i in range(len(ctype_args))])

        return fun_args, fun_call

    def add_docstring(self, new_fun: T.FunctionDef, binding: BindCall,
                      indent_level: int):
        docstring = binding.docstring

        if docstring:
            docstring.value = docstring.value.replace(
                '\n    ', '\n' + '    ' * indent_level)
            new_fun.body.insert(0, T.Expr(docstring))

    def generate_constructor(self, class_def: T.ClassDef, call: BindCall):
        self.current_class_name = class_def.name
        cl_name = class_def.name

        # because we can have multiple constructors we have a to generate a constructor as a static method
        fun_name = call.function_name
        ctype_return = call.return_type
        _, names = parse_sdl_name(fun_name)

        args, c_call = self.make_wrapping_call(call)

        new_fun = T.FunctionDef(function_name(*names),
                                decorator_list=[T.Name('staticmethod')])
        new_fun.returns = self.rename(ctype_return)
        new_fun.args = args
        new_fun.body = [
            T.Assign([T.Name('b')],
                     T.Call(T.Attribute(T.Name('object'), '__new__'),
                            [T.Name(cl_name)])),
            T.Assign([T.Attribute(T.Name('b'), 'handle')], c_call),
            T.Return(T.Name('b'))
        ]

        self.add_docstring(new_fun, call, 2)
        call.clear_kwargs()
        class_def.body.append(new_fun)
        self.current_class_name = None

    def get_arg_names(self, call: T.Call):
        arg_names = get_kwarg_arg('arg_names', call.keywords, None)

        if arg_names is not None:
            return [a.value.lower() for a in arg_names.elts]
        else:
            return self.ARGS

    def generate_method(self, call: BindCall, self_wrap: T.ClassDef):
        fun_name = call.function_name
        _, names = parse_sdl_name(fun_name)

        self.current_class_name = self_wrap.name

        args, c_call = self.make_wrapping_call(
            call,
            replace_arg_names=[T.Arg('self')],
            replace_args=[self.SELF_HANDLE])

        new_fun = T.FunctionDef(function_name(*names))
        new_fun.returns = self.rename(call.return_type)
        new_fun.args = args

        # does the return type need to be wrapped
        if new_fun.returns != call.return_type:
            cast_to = new_fun.returns

            if isinstance(new_fun.returns, T.Constant):
                cast_to = T.Name(new_fun.returns.value)

            c_call = T.Call(T.Attribute(cast_to, 'from_handle'), [c_call])

        new_fun.body = [T.Return(c_call)]

        self.add_docstring(new_fun, call, 2)
        call.clear_kwargs()

        if self.is_multi_output(new_fun, offset=1):
            new_fun = self.rewrite_multi_output_function(new_fun, offset=1)

        self_wrap.body.append(new_fun)

        # we cannot automatically add a destructor because we do not know
        # which object is owning what
        # # try to find destructor and constructor functions
        # for i, n in enumerate(names):
        #     arg_n = len(new_fun.args.args)
        #
        #     # check if the function is named destroy + class_name
        #     if arg_n == 1 and n == 'destroy' and i + 2 == len(names) and names[i + 1] == self_wrap.name.lower():
        #         destructor = copy.deepcopy(new_fun)
        #         destructor.name = '__del__'
        #         self_wrap.body.append(destructor)
        #         break
        #
        #     # sdl is not consistent with that one
        #     if n == 'free':
        #         destructor = copy.deepcopy(new_fun)
        #         destructor.name = '__del__'
        #         self_wrap.body.append(destructor)

        self.wrapper_method_count[self.current_class_name] += 1
        self.current_class_name = None

    def generate_function_wrapper(self, call: BindCall):
        fun_name = call.function_name
        _, names = parse_sdl_name(fun_name)

        args, c_call = self.make_wrapping_call(call)

        new_name = function_name(*names)

        if new_name not in self.new_names:
            self.new_names.add(new_name)
        else:
            log.warning(
                f'{new_name} already exist in this scope cannot rename {fun_name}'
            )
            new_name = fun_name

        new_fun = T.FunctionDef(new_name)
        new_fun.returns = self.rename(call.return_type)
        new_fun.args = args
        new_fun.body = [T.Return(c_call)]

        self.add_docstring(new_fun, call, 1)
        call.clear_kwargs()

        if self.is_multi_output(new_fun, offset=0):
            new_fun = self.rewrite_multi_output_function(new_fun, offset=0)

        self.new_code.append((None, new_fun))
        return

    def process_bind_call(self, call: BindCall, parent):
        """Try to find the class this function belongs to"""
        fun_name = call.function_name

        # constructor
        self_name = call.is_constructor()
        if self_name and call.is_method() is None:
            class_def: T.ClassDef = self.wrappers.get(self_name)
            if class_def:
                return self.generate_constructor(class_def, call)

            return self.generate_function_wrapper(call)
        # ---

        self_name = call.is_method()
        # not a method
        if self_name is None:
            self.generate_function_wrapper(call)
            return parent

        self_def = self.ctypes.get(self_name)
        if not self_def:
            log.debug(f'Method `{fun_name}` does not have c-class')
            self.generate_function_wrapper(call)
            return parent

        # not a known class
        if not match(self_def, 'ClassDef'):
            self.generate_function_wrapper(call)
            return parent

        # Generate our new python class that will wrap the ctypes
        self_wrap: T.ClassDef = self.wrappers.get(self_name)
        if self_wrap is None and match(self_def, 'ClassDef'):
            log.debug(
                f'Method `{fun_name}` does not have wrapped class `{self_name}`'
            )
            return parent

        self.generate_method(call, self_wrap)
        return parent

    handle_is_not_none = T.Assert(test=T.Compare(
        left=T.Attribute(value=T.Name(id='self', ctx=T.Load()),
                         attr='handle',
                         ctx=T.Load()),
        ops=[T.IsNot()],
        comparators=[T.Constant(value=None, kind=None)]),
                                  msg=None)

    def is_multi_output(self, func: T.FunctionDef, offset):
        expr = func.body[0]

        if not match(expr, 'Expr', ('value', 'Constant')):
            return False

        expr: T.Constant = func.body[0].value

        if not expr.docstring:
            return False

        # if doc string specify it returns an error
        # or if function is known to return nothing
        if (':return 0 on success, or -1' not in expr.value) and (match(
                func.returns, 'Name') and func.returns.id != 'None'):
            return False

        if len(func.args.args[offset:]) == 0:
            return False

        count = 0
        # multi output should group all the output at the end of the function call
        previous_was_ptr = False

        for arg in func.args.args[offset:]:
            if match(arg.annotation, 'Call',
                     ('func', 'Name')) and arg.annotation.func.id == 'POINTER':
                count += 1
                previous_was_ptr = True

            elif previous_was_ptr:
                return False

        if count > 0:
            log.debug(f'{func.name} has multiple outputs')
            return True

        return False

    def rewrite_multi_output_function(self, func: T.FunctionDef, offset):
        """
        Notes
        -----
        Outputs should be grouped at the end of the function to differentiate between an output and an
        array input
        """
        new_func = T.FunctionDef(func.name)
        new_func.body = [func.body[0]]

        arg_len = len(func.args.args[offset:])
        if arg_len == 0:
            return func

        output_args = []
        remaining_args = []
        arg_type = dict()

        # Filter output arguments
        for i, arg in enumerate(func.args.args[offset:]):
            if match(arg.annotation, 'Call',
                     ('func', 'Name')) and arg.annotation.func.id == 'POINTER':
                # Generate the result argument
                var_type = arg.annotation.args[0]
                new_func.body.append(
                    T.Assign([T.Name(arg.arg)], T.Call(var_type)))
                output_args.append(arg)
                arg_type[i] = 'O'
            else:
                remaining_args.append(arg)
                arg_type[i] = 'I'

        original_call: T.Call = func.body[1].value

        for i in range(len(original_call.args[offset:])):
            if arg_type[i] == 'O':
                original_call.args[i + offset] = T.Call(
                    T.Name('byref'), [T.Name(func.args.args[i + offset].arg)])

        new_func.body.append(T.Assign([T.Name('error')], original_call))

        returns = [T.Name(arg.arg) for arg in output_args]
        returns_types = [arg.annotation.args[0] for arg in output_args]

        if len(returns) == 1:
            new_func.body.append(T.Return(returns[0]))
            new_func.returns = returns_types[0]
        else:
            new_func.body.append(T.Return(T.Tuple(returns)))
            # new_func.returns = T.Call(T.Name('Tuple'), returns_types)
            new_func.returns = T.Subscript(T.Name('Tuple'),
                                           T.ExtSlice(dims=returns_types),
                                           T.Load())

        if offset == 1:
            remaining_args = [T.Arg('self')] + remaining_args

        new_func.args = T.Arguments(args=remaining_args)

        return new_func

    def struct_fields(self, expr: T.Assign):
        ctype_name = expr.targets[0].value.id
        assert ctype_name in self.ctypes_fields

    def assign(self, expr: T.Assign, depth):
        # <function> = _bind('c-function', [cargs], rtype, docstring=, arg_names=)
        if BindCall.is_binding_call(expr.value):
            expr = self.process_bind_call(BindCall(expr.value), expr)

        # <c-type>._fields_ = []
        elif match(expr.targets[0], 'Attribute') and match(
                expr.value, 'List') and expr.targets[0].attr == '_fields_':
            self.struct_fields(expr)

        # <Name> = <Name>
        elif match(expr.value, 'Name') and match(expr.targets[0], 'Name'):
            alias_name = expr.targets[0].id
            aliased_name = expr.value.id

            aliased_ctype = self.ctypes.get(aliased_name)
            aliased_wrapped = self.wrappers.get(aliased_name)

            self.rename_types[aliased_name] = alias_name
            self.ctypes[alias_name] = aliased_ctype
            self.wrappers[alias_name] = aliased_wrapped

        return expr

    def expression(self, expr: T.Expr, depth):
        values = self.dispatch(expr.value, depth)
        if isinstance(values, (tuple, list)):
            return [T.Expr(v) for v in values]

        return T.Expr(values)

    def generate(self, module: T.Module, depth=0):
        new_module: T.Module = ast.Module()
        new_module.body = []

        self.preprocessor(module)

        for expr in module.body:
            self.dispatch(expr, depth + 1)

        # insert our new bindings at the end
        for k, v in self.new_code:
            if isinstance(v, T.ClassDef):
                if self.wrapper_method_count.get(v.name, 0) > 0:
                    self.post_process_class_defintion(v)
                    module.body.append(v)

                elif v.name in self.wrappers_2_ctypes:
                    c_struct = self.wrappers_2_ctypes.get(v.name)
                    # make it an alias for the ctype
                    module.body.append(
                        T.Expr(T.Assign([T.Name(v.name)], T.Name(c_struct))))
            else:
                module.body.append(T.Expr(v))

        return module

    def dispatch(self, expr, depth) -> T.Expr:
        handler = self.dispatcher.get(expr.__class__.__name__, None)

        if handler is None:
            print(expr)
            assert False

        return handler(expr, depth)