Beispiel #1
0
 def transform(self, tree):
     if is_captured_value(tree):
         return tree  # don't recurse!
     if type(tree) is Name and tree.id == "__ar_":
         tree.id = our_lambda_argname
     elif type(tree) is arg and tree.arg == "__ar_":
         tree.arg = our_lambda_argname
     return self.generic_visit(tree)
Beispiel #2
0
        def transform(self, tree):
            # Ignore hygienically captured values, and don't recurse in them.
            # In `mcpyrate`, they are represented by Call nodes that match
            # `mcpyrate.quotes.is_captured_value`.
            if is_captured_value(tree):
                return tree

            hascurry = self.state.hascurry
            if type(tree) is Call:
                # Don't auto-curry some calls we know not to need it. This is both a performance optimization
                # and allows other macros (particularly `lazify`) to be able to see the original calls.
                # (It also generates cleaner expanded output.)
                #   - `Values(...)` accepts any args and kwargs, so currying it does not make sense.
                #   - `(chain_conts(cc1, cc2))(...)` handles a return value in `with continuations`.
                #     This has the effect that in `with continuations`, the tail-calls to continuation
                #     functions won't be curried, but perhaps that's ok. This allows the Pytkell dialect's
                #     `with lazify, autocurry` combo to work with an inner `with continuations`.
                if (isx(tree.func, "Values")
                        or (type(tree.func) is Call
                            and isx(tree.func.func, "chain_conts"))):
                    # However, *do* auto-curry in the positional and named args of the call.
                    tree.args = self.visit(tree.args)
                    tree.keywords = self.visit(tree.keywords)
                    return tree
                else:  # general case
                    if has_curry(
                            tree):  # detect decorated lambda with manual curry
                        # the lambda inside the curry(...) is the next Lambda node we will descend into.
                        hascurry = True
                    if not isx(tree.func, _iscurry):
                        tree.args = [tree.func] + tree.args
                        tree.func = q[h[currycall]]
                    if hascurry:  # this must be done after the edit because the edit changes the children
                        self.generic_withstate(tree, hascurry=True)

            elif type(tree) in (FunctionDef, AsyncFunctionDef):
                if not any(
                        isx(item, _iscurry) for item in
                        tree.decorator_list):  # no manual curry already
                    k = suggest_decorator_index("curry", tree.decorator_list)
                    if k is not None:
                        tree.decorator_list.insert(k, q[h[curryf]])
                    else:  # couldn't determine insert position; just plonk it at the end and hope for the best
                        tree.decorator_list.append(q[h[curryf]])

            elif type(tree) is Lambda:
                if not hascurry:
                    thelambda = tree
                    tree = q[h[curryf](
                        a[thelambda]
                    )]  # plonk it as innermost, we'll sort them later
                    # don't recurse on the lambda we just moved, but recurse inside it.
                    self.withstate(thelambda.body, hascurry=False)
                    thelambda.body = self.visit(thelambda.body)
                    return tree

            return self.generic_visit(tree)
Beispiel #3
0
 def transform(self, tree):
     if is_captured_value(tree):
         return tree  # don't recurse!
     expr = isdelete(tree)
     if expr:
         if type(expr) is not Name:
             raise SyntaxError("delete[...] takes exactly one name"
                               )  # pragma: no cover
         self.collect(expr.id)
         return q[a[envdel](
             u[expr.id])]  # `delete[x]` --> `e.pop('x')`
     return tree  # don't recurse!
Beispiel #4
0
 def examine(self, tree):
     if is_captured_value(tree):
         return  # don't recurse!
     elif is_let_syntax(tree) or is_abbrev(tree):
         return  # don't recurse!
     elif ismatch(tree):
         # Expand the stray helper macro invocation, to trigger its `SyntaxError`
         # with a useful message, and *make the expander generate a use site traceback*.
         #
         # (If we just `raise` here directly, the expander won't see the use site
         #  of the `with expr` or `with block`, but just that of the `do[]`.)
         dyn._macro_expander.visit(tree)
     self.generic_visit(tree)
Beispiel #5
0
 def transform(self, tree):
     if is_captured_value(tree):
         return tree  # don't recurse!
     # Don't recurse into nested `f[]`.
     # TODO: This would benefit from macro destructuring in the expander.
     # TODO: See https://github.com/Technologicat/mcpyrate/issues/3
     if type(tree) is Subscript and type(
             tree.value) is Name and tree.value.id in mynames:
         return tree
     elif type(tree) is Name and tree.id == "_":
         name = gensym("_")
         tree.id = name
         self.collect(name)
     return self.generic_visit(tree)
Beispiel #6
0
 def transform(self, tree):
     if is_captured_value(tree):
         return tree  # don't recurse!
     expr = islocaldef(tree)
     if expr:
         if not isenvassign(expr):
             raise SyntaxError(
                 "local[...] takes exactly one expression of the form 'name << value'"
             )  # pragma: no cover
         view = UnexpandedEnvAssignView(expr)
         self.collect(view.name)
         view.value = self.visit(
             view.value
         )  # nested local[] (e.g. from `do0[local[y << 5],]`)
         return expr  # `local[x << 21]` --> `x << 21`; compiling *that* makes the env-assignment occur.
     return tree  # don't recurse!
Beispiel #7
0
 def transform(self, tree):
     if is_captured_value(tree):
         return tree  # don't recurse!
     # we can robustly sort only decorators for which we know the correct ordering.
     if is_decorated_lambda(tree, mode="known"):
         decorator_list, thelambda = destructure_decorated_lambda(tree)
         # We can just swap the func attributes of the nodes.
         ordered_decorator_list = sorted(decorator_list, key=prioritize)
         ordered_funcs = [x.func for x in ordered_decorator_list]
         for thecall, newfunc in zip(decorator_list, ordered_funcs):
             thecall.func = newfunc
         # don't recurse on the tail of the "decorator list" (call chain),
         # but recurse into the lambda body.
         thelambda.body = self.visit(thelambda.body)
         return tree
     return self.generic_visit(tree)
Beispiel #8
0
 def transform(self, tree):
     if is_captured_value(tree):
         return tree  # don't recurse!
     # discard Expr wrapper (identifying a statement position) at use site
     # when performing a block substitution
     if mode == "block" and type(tree) is Expr and isthisfunc(
             tree.value):
         tree = subst(tree.value)
         return tree
     elif isthisfunc(tree):
         if mode == "block":
             raise SyntaxError(
                 f"cannot substitute block '{name}' into expression position"
             )  # pragma: no cover
         tree = subst(tree)
         return self.generic_visit(tree)
     return self.generic_visit(tree)
Beispiel #9
0
 def transform(self, tree):
     if is_captured_value(tree):
         return tree  # don't recurse!
     if type(tree) is Call and type(
             tree.func) is Name and tree.func.id == pname:
         names = [q[u[unparse(node)]] for node in tree.args
                  ]  # x --> "x"; (1 + 2) --> "(1 + 2)"; ...
         names = q[t[names]]
         values = q[t[tree.args]]
         tree.args = [names, values]
         # can't use inspect.stack in the printer itself because we want the line number *before macro expansion*.
         lineno = tree.lineno if hasattr(tree, "lineno") else None
         tree.keywords += [
             keyword(arg="filename", value=q[h[callsite_filename]()]),
             keyword(arg="lineno", value=q[u[lineno]])
         ]
         tree.func = pfunc
     return self.generic_visit(tree)
Beispiel #10
0
def getname(tree, accept_attr=True):
    """The cousin of ``isx``.

    From the same types of trees, extract the name as str.

    If no match on ``tree``, return ``None``.
    """
    if isinstance(tree, Done):
        return getname(tree.body, accept_attr=accept_attr)
    if type(tree) is Name:
        return tree.id
    key = is_captured_value(tree)  # AST -> (name, frozen_value) or False
    if key:  # TODO: Python 3.8+: use walrus assignment here
        name, frozen_value = key
        return name
    if accept_attr and type(tree) is Attribute:
        return tree.attr
    return None
 def transform(self, tree):
     if is_captured_value(tree):
         return tree  # don't recurse!
     # Respect the boundaries of nested test constructs (don't recurse there).
     if isunexpandedtestmacro(tree):
         return tree
     elif _is_important_subexpr_mark(tree):
         if sys.version_info >= (
                 3, 9, 0):  # Python 3.9+: the Index wrapper is gone.
             thing = tree.slice
         else:
             thing = tree.slice.value
         self.collect(
             thing
         )  # or anything really; value not used, we just count them.
         # Handle any nested the[] subexpressions
         subtree = self.visit(thing)
         return _inject_value_recorder(envname, subtree)
     else:
         return self.generic_visit(tree)  # recurse
Beispiel #12
0
            def transform(self, tree):
                if is_captured_value(tree):
                    return tree  # don't recurse!

                def subst():
                    # Copy just to be on the safe side. Different instances may be
                    # edited differently by other macros expanded later.
                    return deepcopy(value)

                # discard Expr wrapper (identifying a statement position) at use site
                # when performing a block substitution
                if mode == "block" and type(tree) is Expr and isthisname(
                        tree.value):
                    tree = subst()
                    return tree
                elif isthisname(tree):
                    if mode == "block":
                        raise SyntaxError(
                            f"cannot substitute block '{name}' into expression position"
                        )  # pragma: no cover
                    tree = subst()
                    return self.generic_visit(tree)
                return self.generic_visit(tree)
Beispiel #13
0
 def transform(self, tree):
     if is_captured_value(tree):
         return tree  # don't recurse!
     if type(tree) is Lambda:
         tree.body = _implicit_do(tree.body)
     return self.generic_visit(tree)
Beispiel #14
0
        def transform(self, tree):
            if is_captured_value(tree):
                return tree  # don't recurse!

            bindings = self.state.bindings
            enames = self.state.enames

            def isourupdate(thecall):
                if type(thecall.func) is not Attribute:
                    return False
                return thecall.func.attr == "update" and any(
                    isx(thecall.func.value, x) for x in enames)

            if isfunctionoruserlambda(tree):
                argnames = getargs(tree)
                if argnames:
                    # prepend env init to function body, update bindings
                    kws = [keyword(arg=k, value=q[n[k]])
                           for k in argnames]  # "x" --> x
                    newbindings = bindings.copy()
                    if type(tree) in (FunctionDef, AsyncFunctionDef):
                        ename = gensym("e")
                        theenv = q[h[_envify]()]
                        theenv.keywords = kws
                        with q as quoted:
                            n[ename] = a[theenv]
                        assignment = quoted[0]
                        tree.body.insert(0, assignment)
                    elif type(tree) is Lambda and id(tree) in userlambdas:
                        # We must in general inject a new do[] even if one is already there,
                        # due to scoping rules. If the user code writes to the same names in
                        # its do[] env, this shadows the formals; if it then pops one of its names,
                        # the name should revert to mean the formal parameter.
                        #
                        # inject a do[] and reuse its env
                        tree.body = _do(q[n["_here_"], a[tree.body]])
                        view = ExpandedDoView(
                            tree.body)  # view.body: [(lambda e14: ...), ...]
                        ename = view.body[0].args.args[
                            0].arg  # do[] environment name
                        theupdate = q[n[f"{ename}.update"]]
                        thecall = q[a[theupdate]()]
                        thecall.keywords = kws
                        tree.body = splice_expression(thecall, tree.body,
                                                      "_here_")
                    newbindings.update(
                        {k: q[n[f"{ename}.{k}"]]
                         for k in argnames})  # "x" --> e.x
                    self.generic_withstate(tree,
                                           enames=(enames + [ename]),
                                           bindings=newbindings)
            else:
                # leave alone the _envify() added by us
                if type(tree) is Call and (isx(tree.func, "_envify")
                                           or isourupdate(tree)):
                    # don't recurse
                    return tree
                # transform env-assignments into our envs
                elif isenvassign(tree):
                    view = UnexpandedEnvAssignView(tree)
                    if view.name in bindings.keys():
                        # Grab the envname from the actual binding of "varname", of the form `e.varname`
                        # (so it's the `id` of a `Name` that is the `value` of an `Attribute`).
                        envset = q[n[f"{bindings[view.name].value.id}.set"]]
                        newvalue = self.visit(view.value)
                        return q[a[envset](u[view.name], a[newvalue])]
                # transform references to currently active bindings
                # x --> e14.x
                # It doesn't matter if this hits an already expanded inner `with envify`,
                # because the gensymmed environment name won't be in our bindings, and the "x"
                # has become the `attr` in an `Attribute` node.
                elif type(tree) is Name and tree.id in bindings.keys():
                    # We must be careful to preserve the Load/Store/Del context of the name.
                    # The default lets `mcpyrate` fix it later.
                    ctx = tree.ctx if hasattr(tree, "ctx") else None
                    out = deepcopy(bindings[tree.id])
                    out.ctx = ctx
                    return out

            return self.generic_visit(tree)
Beispiel #15
0
        def transform(self, tree):
            if is_captured_value(tree):
                return tree  # don't recurse!

            # 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):
                view = UnexpandedLetView(tree)
                newbindings = []
                for binding in view.bindings:
                    if type(binding) is not Tuple:
                        raise SyntaxError(
                            "prefix: expected a tuple in let binding position"
                        )  # pragma: no cover
                    _, value = binding.elts  # leave name alone, recurse into value
                    binding.elts[1] = self.visit(value)
                    newbindings.append(binding)
                view.bindings = newbindings  # write the new bindings (important!)
                if view.body:
                    view.body = self.visit(view.body)
                return tree
            elif isdo(tree, expanded=False):
                view = UnexpandedDoView(tree)
                view.body = self.visit(view.body)
                return tree

            # Integration with other macros, including the testing framework.
            # Macros may take a tuple as the top-level expr, but typically don't take slice syntax.
            #
            # Up to Python 3.8, a top-level tuple is packed into an Index:
            #     ast.parse("a[1, 2]").body[0].value.slice        # --> <_ast.Index at 0x7fd57505f208>
            #     ast.parse("a[1, 2]").body[0].value.slice.value  # --> <_ast.Tuple at 0x7fd590962ef0>
            # The structure is for this example is
            #     Module
            #       Expr
            #         Subscript
            if type(tree) is Subscript:
                if sys.version_info >= (
                        3, 9, 0):  # Python 3.9+: the Index wrapper is gone.
                    body = tree.slice
                else:
                    body = tree.slice.value

                if type(body) is Tuple:
                    # Skip the transformation of the expr tuple itself, but transform its elements.
                    # This skips the transformation of the macro argument tuple, too, because
                    # that's a nested Subscript (`(macro[a0, ...])[expr]`).
                    body.elts = self.visit(body.elts)
                    tree.value = self.visit(tree.value)
                    return tree
                # in any other case, continue processing normally

            # general case
            # macro-created nodes might not have a ctx, but we run outside in.
            if not (type(tree) is Tuple and type(tree.ctx) is Load):
                return self.generic_visit(tree)
            op, *data = tree.elts
            quotelevel = self.state.quotelevel
            while True:
                if isunquote(op):
                    if quotelevel < 1:
                        raise SyntaxError(
                            "unquote while not in quote")  # pragma: no cover
                    quotelevel -= 1
                elif isquote(op):
                    quotelevel += 1
                else:
                    break

                if not len(data):
                    raise SyntaxError(
                        "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):
                    raise SyntaxError(
                        "kw(...) may only appear in a prefix tuple representing a function call"
                    )  # pragma: no cover
                self.withstate(quoted, quotelevel=quotelevel)
                return q[t[self.visit(quoted)]]
            # (f, a1, ..., an) --> f(a1, ..., an)
            posargs = [x for x in data if not iskwargs(x)]
            kwargs_calls = [x for x in data if iskwargs(x)]
            # In Python 3.5+, this tags *args as invalid, too, because those are Starred items inside `args`.
            invalids = list(
                flatmap(lambda tree: tree.args,
                        kwargs_calls))  # no positional args allowed in kw()
            kwargs = list(flatmap(lambda x: x.keywords, kwargs_calls))
            invalids += [x for x in kwargs
                         if type(x) is Starred]  # reject **kwargs
            if invalids:
                raise SyntaxError(
                    "kw(...) may only specify individual named args"
                )  # pragma: no cover
            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))
            self.withstate(thecall, quotelevel=quotelevel)
            return self.visit(thecall)
Beispiel #16
0
        def transform(self, tree):
            if is_captured_value(tree):
                return tree  # don't recurse!

            referents = self.state.referents
            if type(tree) in (Attribute, Subscript, Name) and type(tree.ctx) in (Store, Del):
                return tree
            # skip autoref lookup for let/do envs
            elif islet(tree):
                view = ExpandedLetView(tree)
                self.generic_withstate(tree, referents=referents + [view.body.args.args[0].arg])  # lambda e14: ...
            elif isdo(tree):
                view = ExpandedDoView(tree)
                self.generic_withstate(tree, referents=referents + [view.body[0].args.args[0].arg])  # lambda e14: ...
            elif isinstance(tree, ExpandedAutorefMarker):
                self.generic_withstate(tree, referents=referents + [tree.varname])
            elif isautoreference(tree):  # generated by an inner already expanded autoref block
                thename = getconstant(get_resolver_list(tree)[-1])
                if thename in referents:
                    # This case is tricky to trigger, so let's document it here. This code:
                    #
                    # with autoref[e]:
                    #     with autoref[e2]:
                    #         e
                    #
                    # expands to:
                    #
                    # $ASTMarker<ExpandedAutorefMarker>:
                    #     varname: '_o5'
                    #     body:
                    #         _o5 = e
                    #         $ASTMarker<ExpandedAutorefMarker>:
                    #             varname: '_o4'
                    #             body:
                    #                 _o4 = (lambda _ar13: (_ar13[1] if _ar13[0] else e2))(_autoref_resolve((_o5, 'e2')))
                    #                 (lambda _ar9: (_ar9[1] if _ar9[0] else e))(_autoref_resolve((_o4, _o5, 'e')))
                    #
                    # so there's no "e" as referent; the actual referent has a gensymmed name.
                    # Inside the body of the inner autoref, looking up "e" in e2 before falling
                    # back to the outer "e" is exactly what `autoref` is expected to do.
                    #
                    # Where is this used, then? The named variant `with autoref[...] as ...`:
                    #
                    # with step_expansion:
                    #     with autoref[e] as outer:
                    #         with autoref[e2] as inner:
                    #             outer
                    #
                    # expands to:
                    #
                    # $ASTMarker<ExpandedAutorefMarker>:
                    #     varname: 'outer'
                    #     body:
                    #         outer = e
                    #         $ASTMarker<ExpandedAutorefMarker>:
                    #             varname: 'inner'
                    #             body:
                    #                 inner = (lambda _ar17: (_ar17[1] if _ar17[0] else e2))(_autoref_resolve((outer, 'e2')))
                    #                 outer  # <-- !!!
                    #
                    # Now this case is triggered; we get a bare `outer` inside the inner body.
                    # TODO: Whether this wart is a good idea is another question...

                    # remove autoref lookup for an outer referent, inserted early by an inner autoref block
                    # (that doesn't know that any outer block exists)
                    tree = q[n[thename]]  # (lambda ...)(_autoref_resolve((p, "o"))) --> o
                else:
                    add_to_resolver_list(tree, q[n[o]])  # _autoref_resolve((p, "x")) --> _autoref_resolve((p, o, "x"))
                return tree
            elif isinstance(tree, ExpandedAutorefMarker):  # nested autorefs
                return tree
            elif type(tree) is Name and (type(tree.ctx) is Load or not tree.ctx) and tree.id not in referents:
                tree = makeautoreference(tree)
                return tree
            # Attribute works as-is, because a.b.c --> Attribute(Attribute(a, "b"), "c"), so Name "a" gets transformed.
            # Subscript similarly, a[1][2] --> Subscript(Subscript(a, 1), 2), so Name "a" gets transformed.
            return self.generic_visit(tree)
Beispiel #17
0
        def transform(self, tree):
            forcing_mode = self.state.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 q[h[force](a[tree])]
                    elif forcing_mode == "flat":
                        return q[h[force1](a[tree])]
                    # else forcing_mode == "off"
                return tree

            # Hygienic captures must be treated separately:
            if is_captured_value(tree):
                if forcing_mode in ("full", "flat"):
                    return q[h[force](a[tree])]
                # else forcing_mode == "off"
                return tree

            elif type(tree) in (FunctionDef, AsyncFunctionDef, Lambda):
                if type(tree) is Lambda and id(tree) not in userlambdas:
                    return self.generic_visit(
                        tree
                    )  # ignore macro-introduced lambdas (but recurse inside them)
                else:
                    # 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 = q[h[passthrough_lazy_args](a[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 = self.visit(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 = self.visit(tree.decorator_list)
                        if k is not None:
                            tree.decorator_list.insert(
                                k, q[h[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(
                                q[h[passthrough_lazy_args]])
                        tree.body = self.visit(tree.body)
                    return tree

            elif type(tree) is Call:
                # We don't need to expand in the output of `_lazyrec`,
                # because we don't recurse further into the args of the call,
                # so the `lazify` transformer never sees the confusing `Subscript`
                # instances that are actually macro invocations for `lazy[]`.
                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)
                    self.withstate(tree,
                                   forcing_mode=("off" if isref else "full"))
                    tree = self.visit(tree)
                    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)
                    self.withstate(tree,
                                   forcing_mode=("off" if isref else "full"))
                    tree = self.visit(tree)
                    # 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):
                    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 = self.visit(thelambda.body)
                    return tree

                # Don't lazify in calls to some specific functions we know to be strict.
                # Some of these are performance optimizations; others must be left as-is
                # for other macros to be able to see the original calls. (It also generates
                # cleaner expanded output.)
                #   - `namelambda` (emitted by `let[]`, `do[]`, and `test[]`)
                #   - All known container constructor calls (listed in `_ctorcalls_all`).
                #   - `Lazy` takes a lambda, constructs a `Lazy` object; if we're calling `Lazy`,
                #     the expression is already lazy.
                #   - `_autoref_resolve` does the name lookup in `with autoref` blocks.
                #
                # Don't lazify in calls to return-value utilities, because return values
                # are never implicitly lazy in `unpythonic`.
                #   - `Values` constructs a multiple-return-values and/or named return values.
                #   - `(chain_conts(cc1, cc2))(args)` handles a return value in `with continuations`.
                elif (isdo(tree) or is_decorator(tree.func, "namelambda")
                      or any(isx(tree.func, s) for s in _ctorcalls_all)
                      or isx(tree.func, _expanded_lazy_name)
                      or isx(tree.func, "_autoref_resolve")
                      or isx(tree.func, "Values")
                      or (type(tree.func) is Call
                          and isx(tree.func.func, "chain_conts"))):
                    # Here we know the operator (.func) to be one of specific names;
                    # don't transform it to avoid confusing `lazyrec[]`.
                    #
                    # This is especially 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.
                    #
                    # But *do* transform in the positional and named args of the call;
                    # doing so generates the code to force any promises that are passed
                    # to the function being called.
                    #
                    # TODO: correct forcing mode for recursion? 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.
                    # NOTE v0.15.0: In practice, using whatever is the currently active mode seems to be fine.
                    tree.args = self.visit(tree.args)
                    tree.keywords = self.visit(tree.keywords)
                    return tree

                else:  # general case
                    thefunc = self.visit(tree.func)

                    # Lazify the arguments of the call.
                    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[a[v]])
                        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=q[h[maybe_force_args]],
                        args=[q[a[thefunc]]] + [q[a[x]] for x in adata],
                        keywords=[
                            keyword(arg=k, value=q[a[x]]) for k, x in kwdata
                        ])
                    tree = mycall
                    return tree

            # NOTE: We must expand all inner macro invocations before we hit this, or we'll produce nonsense.
            # Hence it is easiest to have `lazify` expand inside-out.
            elif type(
                    tree) is Subscript:  # force only accessed part of obj[...]
                # force the slice expression; it is needed to extract the relevant items.
                self.withstate(tree.slice, forcing_mode="full")
                tree.slice = self.visit(tree.slice)
                # resolve reference to the actual container without forcing its items.
                self.withstate(tree.value, forcing_mode="flat")
                tree.value = self.visit(tree.value)
                # using the currently active forcing mode, force the value returned
                # by the subscript expression.
                tree = f(tree)
                return 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.)
                self.withstate(tree.value, forcing_mode="flat")
                tree.value = self.visit(tree.value)
                # using the currently active forcing mode, force the value returned
                # by the attribute expression.
                tree = f(tree)
                return tree

            elif type(tree) is Name and type(tree.ctx) is Load:
                # using the currently active forcing mode, force the value.
                tree = f(tree)
                # must not recurse when a Name changes into a Call.
                return tree

            return self.generic_visit(tree)
Beispiel #18
0
 def transform(self, tree):
     if is_captured_value(tree):
         return tree  # don't recurse!
     if islet(tree, expanded=False):  # let bindings
         view = UnexpandedLetView(tree)
         newbindings = []
         for b in view.bindings:
             b.elts[1], thelambda, match = nameit(
                 getname(b.elts[0]), b.elts[1])
             if match:
                 thelambda.body = self.visit(thelambda.body)
             else:
                 b.elts[1] = self.visit(b.elts[1])
             newbindings.append(b)
         view.bindings = newbindings  # write the new bindings (important!)
         view.body = self.visit(view.body)
         return tree
     # assumption: no one left-shifts by a literal lambda :)
     elif isenvassign(tree):  # f << (lambda ...: ...)
         view = UnexpandedEnvAssignView(tree)
         view.value, thelambda, match = nameit(view.name, view.value)
         if match:
             thelambda.body = self.visit(thelambda.body)
         else:
             view.value = self.visit(view.value)
         return tree
     elif issingleassign(tree):  # f = lambda ...: ...
         tree.value, thelambda, match = nameit(getname(tree.targets[0]),
                                               tree.value)
         if match:
             thelambda.body = self.visit(thelambda.body)
         else:
             tree.value = self.visit(tree.value)
         return tree
     elif type(
             tree
     ) is NamedExpr:  # f := lambda ...: ...  (Python 3.8+, added in unpythonic 0.15.0)
         tree.value, thelambda, match = nameit(getname(tree.target),
                                               tree.value)
         if match:
             thelambda.body = self.visit(thelambda.body)
         else:
             tree.value = self.visit(tree.value)
         return tree
     elif iscallwithnamedargs(tree):  # foo(f=lambda: ...)
         for kw in tree.keywords:
             if kw.arg is None:  # **kwargs in Python 3.5+
                 kw.value = self.visit(kw.value)
                 continue
             # a single named arg
             kw.value, thelambda, match = nameit(kw.arg, kw.value)
             if match:
                 thelambda.body = self.visit(thelambda.body)
             else:
                 kw.value = self.visit(kw.value)
         tree.args = self.visit(tree.args)
         return tree
     elif type(tree) is Dict:  # {"f": lambda: ..., "g": lambda: ...}
         lst = list(zip(tree.keys, tree.values))
         for j in range(len(lst)):
             k, v = tree.keys[j], tree.values[j]
             if k is None:  # {..., **d, ...}
                 tree.values[j] = self.visit(v)
             else:
                 if type(k) in (Constant,
                                Str):  # Python 3.8+: ast.Constant
                     thename = getconstant(k)
                     tree.values[j], thelambda, match = nameit(
                         thename, v)
                     if match:
                         thelambda.body = self.visit(thelambda.body)
                     else:
                         tree.values[j] = self.visit(v)
                 else:
                     tree.keys[j] = self.visit(k)
                     tree.values[j] = self.visit(v)
         return tree
     return self.generic_visit(tree)