Exemplo n.º 1
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
Exemplo n.º 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
Exemplo n.º 3
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
Exemplo n.º 4
0
    def generate_struct_union(self, elem: Cursor, depth=1, nested=False, rename=None):
        """Generate the Python function corresponding to the c function

        Examples
        --------
        >>> from tide.generators.clang_utils import parse_clang
        >>> tu, index = parse_clang('struct Point { int x, y; };')
        >>> modules = APIGenerator().generate(tu)
        >>> for k, m in modules.items():
        ...     print(f'# {k}')
        ...     print(unparse(m))
        # temporary_buffer_1234.c
        <BLANKLINE>
        <BLANKLINE>
        <BLANKLINE>
        class Point(Structure):
            _fields_ = [('x', int), ('y', int)]
        <BLANKLINE>
        """

        dataclass_type = 'struct'
        if elem.kind == CursorKind.UNION_DECL:
            dataclass_type = 'union'

        # --
        c_name = self.get_name(elem, rename=rename)
        module, names = self.parse_name(c_name)

        pyname = self.topyclassname(names)
        class_def = T.ClassDef(pyname)

        # if this class is a C struct
        if True:
            c_fields = self.generate_struct_fields(elem)
            class_def.body.append(c_fields)
            class_def.bases.append(T.Name('Structure'))

        # Insert the def inside the registry so we can insert method to it
        self.type_registry[c_name] = class_def

        # If this class wraps a C struct
        if False:
            ctor = T.FunctionDef('__init__', args=T.Arguments(args=[T.Arg('self'), T.Arg('handle', T.Name(c_name))]))
            ctor.body = [
                T.Assign([T.Attribute(T.Name('self'), 'handle')], T.Name('handle'))
            ]
            class_def.body.append(ctor)

        return class_def
Exemplo n.º 5
0
        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
Exemplo n.º 6
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('')
Exemplo n.º 7
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
        ]
Exemplo n.º 8
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
Exemplo n.º 9
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)
Exemplo n.º 10
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