Skip to content

Commit c45bc01

Browse files
committed
enh: prefix macro now works together with let and do
1 parent ada93cc commit c45bc01

File tree

2 files changed

+53
-2
lines changed

2 files changed

+53
-2
lines changed

macro_extras/main.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1031,6 +1031,30 @@ def g(*args, **kwargs):
10311031
a, b = (q, b, a) # pythonic swap in prefix syntax; must quote RHS
10321032
assert a == 200 and b == 100
10331033

1034+
# prefix has no effect on the let binding syntax ((name0, value0), ...)
1035+
a = let((x, 42))[x << x + 1]
1036+
assert a == 43
1037+
1038+
# but the RHSs of the bindings are transformed normally
1039+
def double(x):
1040+
return 2*x
1041+
a = let((x, (double, 21)))[x << x + 1]
1042+
assert a == 43
1043+
1044+
# similarly, prefix leaves the "body tuple" of a do alone
1045+
# (syntax, not semantically a tuple), but recurses into it:
1046+
a = do[1, 2, 3]
1047+
assert a == 3
1048+
a = do[1, 2, (double, 3)]
1049+
assert a == 6
1050+
1051+
# the extra bracket syntax has no danger of confusion, as it's a list, not tuple
1052+
a = let((x, 3))[[
1053+
1,
1054+
2,
1055+
(double, x)]]
1056+
assert a == 6
1057+
10341058
# Introducing LisThEll:
10351059
with prefix, curry: # important: apply prefix first, then curry
10361060
mymap = lambda f: (foldr, (compose, cons, f), nil)

unpythonic/syntax/prefix.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
Experimental, not for use in production code.
55
"""
66

7-
from ast import Name, Call, Tuple, Load
7+
from ast import Name, Call, Tuple, Load, Subscript, Index
88

99
from macropy.core.quotes import macros, q, u, ast_literal
1010
from macropy.core.walkers import Walker
@@ -16,7 +16,34 @@ def prefix(block_body):
1616
isunquote = lambda tree: type(tree) is Name and tree.id == "u"
1717
iskwargs = lambda tree: type(tree) is Call and type(tree.func) is Name and tree.func.id == "kw"
1818
@Walker
19-
def transform(tree, *, quotelevel, set_ctx, **kw):
19+
def transform(tree, *, quotelevel, set_ctx, stop, **kw):
20+
# Not tuples but syntax: leave alone the:
21+
# - bindings blocks of let, letseq, letrec, and the d*, b* variants
22+
# - subscript part of an explicit do[]
23+
# but recurse inside them.
24+
#
25+
# let and do have not expanded yet when prefix runs (better that way!),
26+
# so we can't use the (expanded-form) detectors islet, isdo.
27+
if type(tree) is Call and type(tree.func) is Name and \
28+
any(tree.func.id == x for x in ("let", "letseq", "letrec",
29+
"dlet", "dletseq", "dletrec",
30+
"blet", "bletseq", "bletrec")):
31+
# let((x, 42))[...] appears as Subscript(value=Call(...), ...)
32+
stop()
33+
for binding in tree.args: # TODO: kwargs support for let(x=42)[...] if implemented later
34+
_, value = binding.elts # leave name alone, recurse into value
35+
binding.elts[1] = transform.recurse(value, quotelevel=quotelevel)
36+
return tree
37+
elif type(tree) is Subscript and type(tree.value) is Name and \
38+
any(tree.value.id == x for x in ("do", "do0")) and \
39+
type(tree.slice) is Index and type(tree.slice.value) is Tuple:
40+
stop()
41+
newelts = []
42+
for expr in tree.slice.value.elts:
43+
newelts.append(transform.recurse(expr, quotelevel=quotelevel))
44+
tree.slice.value.elts = newelts
45+
return tree
46+
# general case
2047
if not (type(tree) is Tuple and type(tree.ctx) is Load):
2148
return tree
2249
op, *data = tree.elts

0 commit comments

Comments
 (0)