Пример #1
0
    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
Пример #2
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
Пример #3
0
    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
Пример #4
0
    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
Пример #5
0
    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
Пример #6
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)
Пример #7
0
    def generate_c_cast(self, elem, **kwargs):
        show_elem(elem)
        children = list(elem.get_children())

        if len(children) == 2:
            type, expr = children
            traverse(type, print_fun=log.debug)
            traverse(expr, print_fun=log.debug)

            typecast = self.dispatch(type, **kwargs)
            if isinstance(typecast, str):
                typecast = T.Name(typecast)

            return T.Call(typecast, [self.dispatch(expr, **kwargs)])

        if len(children) == 1:
            return self.dispatch(children[0], **kwargs)

        return None
Пример #8
0
    def parse_call(self, expr, depth):
        log.debug(f'{d(depth)} parse_call {expr}')

        args = []

        tok = self.peek()
        self.is_call.append(True)
        while tok.spelling != ')':
            args.append(self.parse_expression(depth + 1))
            tok = self.peek()

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

        self.is_call.pop()
        assert tok.spelling == ')'
        self.next()
        return T.Call(expr, args=args)
Пример #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('')
Пример #10
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)
Пример #11
0
    def generate_function(self, elem: Cursor):
        """Generate the Python function corresponding to the c function

        Examples
        --------
         >>> from tide.generators.clang_utils import parse_clang
        >>> tu, index = parse_clang('double add(double a, double b);')
        >>> modules = APIGenerator().generate(tu)
        >>> for k, m in modules.items():
        ...     print(f'# {k}')
        ...     print(unparse(m))
        # temporary_buffer_1234.c
        <BLANKLINE>
        <BLANKLINE>
        <BLANKLINE>
        def add(a: double, b: double) -> double:
            return add(a, b)
        <BLANKLINE>
        """
        definition: Cursor = elem.get_definition()

        if definition is None:
            definition = elem

        log.debug(f'Generate function {definition.spelling}')
        rtype = self.get_typename(definition.result_type)
        args = definition.get_arguments()

        pyargs = []
        cargs = []
        for a in args:
            atype = self.get_typename(a.type)
            aname = a.displayname
            pyargs.append(T.Arg(arg=aname, annotation=T.Name(atype)))
            cargs.append(T.Name(aname))

        c_function = definition.spelling
        module, names = self.parse_name(c_function)

        fundef = T.FunctionDef(self.topyfunname(names), T.Arguments(args=pyargs))
        fundef.returns = T.Name(rtype)
        fundef.body = [
            # this is docstring but it is not unparsed correctly
            # T.Expr(T.Str(get_comment(elem))),
            T.Return(T.Call(T.Name(c_function), cargs))
        ]

        # This could be a method
        if len(pyargs) > 0:
            # log.debug(f'Looking for {pyargs[0].annotation} in {self.type_registry}')

            selftype = pyargs[0].annotation
            classdef = None

            # For class grouping the first arg needs to be a reference to the class type
            lookuptype = selftype.id
            if isinstance(lookuptype, Ref):
                lookuptype = lookuptype.base
                classdef: T.ClassDef = self.type_registry.get(lookuptype)

                if isinstance(classdef, T.ClassDef):
                    log.debug(f'found {classdef} for {lookuptype}')
                else:
                    classdef = None

            if classdef is not None:
                # Remove annotation for `self`
                pyargs[0].arg = 'self'
                pyargs[0].annotation = None
                # replace the object by self.handle
                cargs[0] = T.Attribute(T.Name('self'), 'handle')

                # Generate Accessor properties
                if len(pyargs) == 1 and names[0] == 'get':
                    # @property
                    # def x(self):
                    #     return SDL_GetX(self.handle)
                    offset = 1
                    if names[1] == 'set':
                        offset = 2

                    fundef.name = self.topyfunname(names[offset:])
                    fundef.decorator_list.append(T.Name('property'))

                if len(pyargs) == 2 and (names[0] == 'get' or names[1] == 'get') and is_ref(pyargs[1].annotation):
                    rtype = pyargs[1].annotation
                    cargs[1] = T.Name('result')

                    offset = 1
                    if names[1] == 'get':
                        offset = 2

                    fundef.name = self.topyfunname(names[offset:])
                    fundef.decorator_list.append(T.Name('property'))
                    fundef.returns = rtype

                    fundef.args.args = [fundef.args.args[0]]
                    fundef.body = [
                        # T.Expr(T.Str(get_comment(elem))),
                        T.Assign([T.Name('result')], T.Call(rtype)),
                        T.Expr(T.Call(T.Name(c_function), cargs)),
                        # T.Assign([T.Name('err')], T.Call(T.Name(c_function), cargs)),
                        # T.If(T.Name('err'), T.Return(T.Name('None'))),
                        T.Return(T.Name('result'))
                    ]

                if len(pyargs) == 2 and (names[0] == 'set' or names[1] == 'set'):
                    # @x.setter
                    # def x(self, x):
                    #     return SDL_SetX(self.handle, x)
                    offset = 1
                    if names[1] == 'set':
                        offset = 2

                    fundef.name = self.topyfunname(names[offset:])
                    fundef.decorator_list.append(T.Attribute(T.Name(fundef.name), 'setter'))

                # Standard method
                log.debug(f'Adding method to {classdef.name}')
                classdef.body.append(fundef)
                return None

        return fundef
Пример #12
0
    def parse_cast(self, cast_expr, depth):
        tok = self.peek()
        log.debug(f'{d(depth)} parse_cast {tok.kind} {tok.spelling}')

        expr = self.parse_expression(depth + 1)
        return T.Call(cast_expr, [expr])