예제 #1
0
파일: api_pass.py 프로젝트: Delaunay/tide
    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
파일: api_pass.py 프로젝트: Delaunay/tide
    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
예제 #3
0
파일: api_pass.py 프로젝트: Delaunay/tide
    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
예제 #4
0
파일: api_pass.py 프로젝트: Delaunay/tide
    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
예제 #5
0
    def generate_struct_fields(self, elem):
        fields = []
        for e in elem.get_children():
            if e.kind == CursorKind.FIELD_DECL:
                field_type = self.get_typename(e.type)

                if isinstance(field_type, str):
                    field_type = T.Name(field_type)

                fields.append(T.Tuple([T.Str(e.spelling), field_type]))

        return T.Assign([T.Name('_fields_')], T.List(fields))
예제 #6
0
파일: api_pass.py 프로젝트: Delaunay/tide
    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
예제 #7
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
예제 #8
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)
예제 #9
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
예제 #10
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
예제 #11
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
        ]
예제 #12
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
예제 #13
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