Esempio n. 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
Esempio n. 2
0
    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
Esempio n. 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
Esempio n. 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
Esempio n. 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
Esempio n. 6
0
    def generate_macro_definition(self, elem: Cursor, **kwargs):
        """Transform a macro into a function if possible

        Examples
        --------
        >>> from tide.generators.clang_utils import parse_clang
        >>> tu, index = parse_clang('#define PI 3.14')
        >>> module = BindingGenerator().generate(tu)
        >>> print(compact(unparse(module)))
        <BLANKLINE>
        PI = 3.14
        <BLANKLINE>

        >>> tu, index = parse_clang(''
        ... '#define SDL_AUDIO_ALLOW_FREQUENCY_CHANGE    0x00000001\\n'
        ... '#define SDL_AUDIO_ALLOW_FORMAT_CHANGE       0x00000002\\n'
        ... '#define SDL_AUDIO_ALLOW_CHANNELS_CHANGE     0x00000004\\n'
        ... '#define SDL_AUDIO_ALLOW_ANY_CHANGE          (SDL_AUDIO_ALLOW_FREQUENCY_CHANGE|SDL_AUDIO_ALLOW_FORMAT_CHANGE|SDL_AUDIO_ALLOW_CHANNELS_CHANGE)\\n'
        ... )
        >>> module = BindingGenerator().generate(tu)
        >>> print(compact(unparse(module)))
        <BLANKLINE>
        SDL_AUDIO_ALLOW_FREQUENCY_CHANGE = 1
        <BLANKLINE>
        SDL_AUDIO_ALLOW_FORMAT_CHANGE = 2
        <BLANKLINE>
        SDL_AUDIO_ALLOW_CHANNELS_CHANGE = 4
        <BLANKLINE>
        SDL_AUDIO_ALLOW_ANY_CHANGE = (SDL_AUDIO_ALLOW_FREQUENCY_CHANGE | (SDL_AUDIO_ALLOW_FORMAT_CHANGE | SDL_AUDIO_ALLOW_CHANNELS_CHANGE))
        <BLANKLINE>
        """
        # builtin macros
        if elem.location.file is None:
            return

        log.debug(f'Macro definition {elem.spelling}')

        args = list(elem.get_arguments())
        children = list(elem.get_children())
        tokens = list(elem.get_tokens())

        if len(args) != 0:
            for arg in args:
                show_elem(arg, print_fun=log.debug)
                assert False

        if len(children) != 0:
            for child in children:
                show_elem(child, print_fun=log.debug)
                assert False

        if len(tokens) == 1:
            return

        name, tok_args, tok_body = parse_macro(tokens)

        if len(tok_body) == 0:
            return

        if name.spelling == 'NULL':
            return T.Assign([T.Name('NULL')], T.Name('None'))

        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, self.definitions,
                                   self.type_registry, self.renaming)

        except UnsupportedExpression:
            self.unsupported_macros.add(name.spelling)
            body = [b.spelling for b in tok_body]
            log.warning(
                f'Unsupported expression, cannot transform macro {name.spelling} {"".join(body)}'
            )
            return

        name = name.spelling

        # if name == 'SDL_TOUCH_MOUSEID':
        #     print(py_body)
        #     assert False

        if len(tok_args) == 0 and not isinstance(py_body, T.If):
            return T.Assign([T.Name(name)], py_body)

        func = T.FunctionDef(
            name, T.Arguments(args=[T.Arg(arg=a.spelling) for a in tok_args]))

        if isinstance(py_body, T.Expr):
            py_body = py_body.value

        # we use ifs as a makeshift Body expression
        if isinstance(py_body, T.If) and isinstance(py_body.test, T.Constant) \
                and py_body.test.value is True:
            func.body = py_body.body
        else:
            func.body = [T.Return(py_body)]

        return func
Esempio n. 7
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
Esempio n. 8
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