Exemple #1
0
    def transform(tree, *, forcing_mode, stop, **kw):
        def rec(tree,
                forcing_mode=forcing_mode
                ):  # shorthand that defaults to current mode
            return transform.recurse(tree, forcing_mode=forcing_mode)

        # Forcing references (Name, Attribute, Subscript):
        #   x -> f(x)
        #   a.x -> f(force1(a).x)
        #   a.b.x -> f(force1(force1(a).b).x)
        #   a[j] -> f((force1(a))[force(j)])
        #   a[j][k] -> f(force1(force1(a)[force(j)])[force(k)])
        #
        # where f is force, force1 or identity (optimized away) depending on
        # where the term appears; j and k may be indices or slices.
        #
        # Whenever not in Load context, f is identity.
        #
        # The idea is to apply just the right level of forcing to be able to
        # resolve the reference, and then decide what to do with the resolved
        # reference based on where it appears.
        #
        # For example, when subscripting a list, force1 it to unwrap it from
        # a promise if it happens to be inside one, but don't force its elements
        # just for the sake of resolving the reference. Then, apply f to the
        # whole subscript term (forcing the accessed slice of the list, if necessary).
        def f(tree):
            if type(tree.ctx) is Load:
                if forcing_mode == "full":
                    return hq[force(ast_literal[tree])]
                elif forcing_mode == "flat":
                    return hq[force1(ast_literal[tree])]
                # else forcing_mode == "off"
            return tree

        if type(tree) in (FunctionDef, AsyncFunctionDef, Lambda):
            if type(tree) is Lambda and id(tree) not in userlambdas:
                pass  # ignore macro-introduced lambdas
            else:
                stop()

                # mark this definition as lazy, and insert the interface wrapper
                # to allow also strict code to call this function
                if type(tree) is Lambda:
                    lam = tree
                    tree = hq[passthrough_lazy_args(ast_literal[tree])]
                    # TODO: This doesn't really do anything; we don't here see the chain
                    # TODO: of Call nodes (decorators) that surround the Lambda node.
                    tree = sort_lambda_decorators(tree)
                    lam.body = rec(lam.body)
                else:
                    k = suggest_decorator_index("passthrough_lazy_args",
                                                tree.decorator_list)
                    # Force the decorators only after `suggest_decorator_index`
                    # has suggested us where to put ours.
                    # TODO: could make `suggest_decorator_index` ignore a `force()` wrapper.
                    tree.decorator_list = rec(tree.decorator_list)
                    if k is not None:
                        tree.decorator_list.insert(k,
                                                   hq[passthrough_lazy_args])
                    else:
                        # passthrough_lazy_args should generally be as innermost as possible
                        # (so that e.g. the curry decorator will see the function as lazy)
                        tree.decorator_list.append(hq[passthrough_lazy_args])
                    tree.body = rec(tree.body)

        elif type(tree) is Call:

            def transform_arg(tree):
                # add any needed force() invocations inside the tree,
                # but leave the top level of simple references untouched.
                isref = type(tree) in (Name, Attribute, Subscript)
                tree = rec(tree, forcing_mode=("off" if isref else "full"))
                if not isref:  # (re-)thunkify expr; a reference can be passed as-is.
                    tree = lazyrec(tree)
                return tree

            def transform_starred(tree, dstarred=False):
                isref = type(tree) in (Name, Attribute, Subscript)
                tree = rec(tree, forcing_mode=("off" if isref else "full"))
                # lazify items if we have a literal container
                # we must avoid lazifying any other exprs, since a Lazy cannot be unpacked.
                if is_literal_container(tree, maps_only=dstarred):
                    tree = lazyrec(tree)
                return tree

            # let bindings have a role similar to function arguments, so auto-lazify there
            # (LHSs are always new names, so no infinite loop trap for the unwary)
            if islet(tree):
                stop()
                view = ExpandedLetView(tree)
                if view.mode == "let":
                    for b in view.bindings.elts:  # b = (name, value)
                        b.elts[1] = transform_arg(b.elts[1])
                else:  # view.mode == "letrec":
                    for b in view.bindings.elts:  # b = (name, (lambda e: ...))
                        thelambda = b.elts[1]
                        thelambda.body = transform_arg(thelambda.body)
                if view.body:  # let decorators have no body inside the Call node
                    thelambda = view.body
                    thelambda.body = rec(thelambda.body)
            # namelambda() is used by let[] and do[]
            # Lazy() is a strict function, takes a lambda, constructs a Lazy object
            # _autoref_resolve doesn't need any special handling
            elif (isdo(tree) or is_decorator(tree.func, "namelambda")
                  or any(isx(tree.func, s) for s in _ctorcalls_all)
                  or isx(tree.func, isLazy) or any(
                      isx(tree.func, s)
                      for s in ("_autoref_resolve", "AutorefMarker"))):
                # here we know the operator (.func) to be one of specific names;
                # don't transform it to avoid confusing lazyrec[] (important if this
                # is an inner call in the arglist of an outer, lazy call, since it
                # must see any container constructor calls that appear in the args)
                stop()
                # TODO: correct forcing mode for `rec`? We shouldn't need to forcibly use "full",
                # since maybe_force_args() already fully forces any remaining promises
                # in the args when calling a strict function.
                tree.args = rec(tree.args)
                tree.keywords = rec(tree.keywords)
                # Python 3.4
                if hasattr(tree,
                           "starargs"):  # pragma: no cover, Python 3.4 only.
                    tree.starargs = rec(tree.starargs)
                if hasattr(tree,
                           "kwargs"):  # pragma: no cover, Python 3.4 only.
                    tree.kwargs = rec(tree.kwargs)
            else:
                stop()
                ln, co = tree.lineno, tree.col_offset
                thefunc = rec(tree.func)

                adata = []
                for x in tree.args:
                    if type(x) is Starred:  # *args in Python 3.5+
                        v = transform_starred(x.value)
                        v = Starred(value=q[ast_literal[v]],
                                    lineno=ln,
                                    col_offset=co)
                    else:
                        v = transform_arg(x)
                    adata.append(v)

                kwdata = []
                for x in tree.keywords:
                    if x.arg is None:  # **kwargs in Python 3.5+
                        v = transform_starred(x.value, dstarred=True)
                    else:
                        v = transform_arg(x.value)
                    kwdata.append((x.arg, v))

                # Construct the call
                mycall = Call(func=hq[maybe_force_args],
                              args=[q[ast_literal[thefunc]]] +
                              [q[ast_literal[x]] for x in adata],
                              keywords=[
                                  keyword(arg=k, value=q[ast_literal[x]])
                                  for k, x in kwdata
                              ],
                              lineno=ln,
                              col_offset=co)

                if hasattr(
                        tree,
                        "starargs"):  # *args in Python 3.4  # pragma: no cover
                    if tree.starargs is not None:
                        mycall.starargs = transform_starred(tree.starargs)
                    else:
                        mycall.starargs = None
                if hasattr(tree, "kwargs"
                           ):  # **kwargs in Python 3.4  # pragma: no cover
                    if tree.kwargs is not None:
                        mycall.kwargs = transform_starred(tree.kwargs,
                                                          dstarred=True)
                    else:
                        mycall.kwargs = None

                tree = mycall

        elif type(tree) is Subscript:  # force only accessed part of obj[...]
            stop()
            tree.slice = rec(tree.slice, forcing_mode="full")
            # resolve reference to the actual container without forcing its items.
            tree.value = rec(tree.value, forcing_mode="flat")
            tree = f(tree)

        elif type(tree) is Attribute:
            #   a.b.c --> f(force1(force1(a).b).c)  (Load)
            #         -->   force1(force1(a).b).c   (Store)
            #   attr="c", value=a.b
            #   attr="b", value=a
            # Note in case of assignment to a compound, only the outermost
            # Attribute is in Store context.
            #
            # Recurse in flat mode. Consider lst = [[1, 2], 3]
            #   lst[0] --> f(force1(lst)[0]), but
            #   lst[0].append --> force1(force1(force1(lst)[0]).append)
            # Hence, looking up an attribute should only force **the object**
            # so that we can perform the attribute lookup on it, whereas
            # looking up values should finally f() the whole slice.
            # (In the above examples, we have omitted f() when it is identity;
            #  in reality there is always an f() around the whole expr.)
            stop()
            tree.value = rec(tree.value, forcing_mode="flat")
            tree = f(tree)

        elif type(tree) is Name and type(tree.ctx) is Load:
            stop()  # must not recurse when a Name changes into a Call.
            tree = f(tree)

        return tree
Exemple #2
0
    def transform(tree, *, quotelevel, set_ctx, stop, **kw):
        # Not tuples but syntax: leave alone the:
        #  - binding pair "tuples" of let, letseq, letrec, their d*, b* variants,
        #    and let_syntax, abbrev
        #  - subscript part of an explicit do[], do0[]
        # but recurse inside them.
        #
        # let and do have not expanded yet when prefix runs (better that way!).
        if islet(tree, expanded=False):
            stop()
            view = UnexpandedLetView(tree)
            for binding in view.bindings:
                if type(binding) is not Tuple:
                    assert False, "prefix: expected a tuple in let binding position"  # pragma: no cover
                _, value = binding.elts  # leave name alone, recurse into value
                binding.elts[1] = transform.recurse(value, quotelevel=quotelevel)
            if view.body:
                view.body = transform.recurse(view.body, quotelevel=quotelevel)
            return tree
        elif isdo(tree, expanded=False):
            stop()
            view = UnexpandedDoView(tree)
            view.body = [transform.recurse(expr, quotelevel=quotelevel) for expr in view.body]
            return tree

        # integration with testing framework
        if isunexpandedtestmacro(tree):
            stop()
            if type(tree.slice) is not Index:
                assert False, "prefix: Slice and ExtSlice not implemented in analysis of testing macro arguments"  # pragma: no cover
            body = tree.slice.value
            if type(body) is Tuple:
                # skip the transformation of the argument tuple itself, but transform its elements
                body.elts = [transform.recurse(expr, quotelevel=quotelevel) for expr in body.elts]
            else:
                tree.slice.value = transform.recurse(tree.slice.value, quotelevel=quotelevel)
            return tree

        # general case
        # macro-created nodes might not have a ctx, but we run in the first pass.
        if not (type(tree) is Tuple and type(tree.ctx) is Load):
            return tree
        op, *data = tree.elts
        while True:
            if isunquote(op):
                if quotelevel < 1:
                    assert False, "unquote while not in quote"  # pragma: no cover
                quotelevel -= 1
            elif isquote(op):
                quotelevel += 1
            else:
                break
            set_ctx(quotelevel=quotelevel)
            if not len(data):
                assert False, "a prefix tuple cannot contain only quote/unquote operators"  # pragma: no cover
            op, *data = data
        if quotelevel > 0:
            quoted = [op] + data
            if any(iskwargs(x) for x in quoted):
                assert False, "kw(...) may only appear in a prefix tuple representing a function call"  # pragma: no cover
            return q[(ast_literal[quoted],)]
        # (f, a1, ..., an) --> f(a1, ..., an)
        posargs = [x for x in data if not iskwargs(x)]
        # TODO: tag *args and **kwargs in a kw() as invalid, too (currently just ignored)
        invalids = list(flatmap(lambda x: x.args, filter(iskwargs, data)))
        if invalids:
            assert False, "kw(...) may only specify named args"  # pragma: no cover
        kwargs = flatmap(lambda x: x.keywords, filter(iskwargs, data))
        kwargs = list(rev(uniqify(rev(kwargs), key=lambda x: x.arg)))  # latest wins, but keep original ordering
        thecall = Call(func=op, args=posargs, keywords=list(kwargs))
        if PYTHON34:  # pragma: no cover, Python 3.4 only
            thecall.starargs = None
            thecall.kwargs = None
        return thecall