Skip to content

Commit 75960ef

Browse files
committed
port code using quasiquotes to mcpyrate
1 parent 69ccf3e commit 75960ef

20 files changed

+337
-340
lines changed

doc/macros.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1801,7 +1801,7 @@ The `the[]` mechanism is smart enough to skip reporting trivialities for literal
18011801

18021802
If nothing but such trivialities were captured, the failure message will instead report the value of the whole expression. (The captures still remain inspectable in the exception instance.)
18031803

1804-
To make testing/debugging macro code more convenient, the `the[]` mechanism automatically unparses an AST value into its source code representation for display in the test failure message. This is meant for debugging macro utilities, to which a test case hands some quoted code (i.e. code lifted into its AST representation using MacroPy's `q[]` macro). See [`unpythonic.syntax.test.test_letdoutil`](unpythonic/syntax/test/test_letdoutil.py) for some examples. (Note the unparsing is done for display only; the raw value remains inspectable in the exception instance.)
1804+
To make testing/debugging macro code more convenient, the `the[]` mechanism automatically unparses an AST value into its source code representation for display in the test failure message. This is meant for debugging macro utilities, to which a test case hands some quoted code (i.e. code lifted into its AST representation using mcpyrate's `q[]` macro). See [`unpythonic.syntax.test.test_letdoutil`](unpythonic/syntax/test/test_letdoutil.py) for some examples. (Note the unparsing is done for display only; the raw value remains inspectable in the exception instance.)
18051805

18061806
**CAUTION**: The source code is back-converted from the AST representation; hence its surface syntax may look slightly different to the original (e.g. extra parentheses). See ``macropy.core.unparse``.
18071807

unpythonic/dynassign.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,9 @@ def main():
135135
# tracks dynamic state; in a manner of speaking, it has one foot on the
136136
# call stack.
137137
#
138-
# But we can't prevent pickling, because MacroPy's hygienic quasiquotes (`hq[]`)
139-
# build on `pickle`. If `dyn` fails to pickle, some macros in `unpythonic.syntax`
140-
# (notably `autoref` and `lazify`) crash, because they need both `dyn` and `hq[]`.
138+
# But we can't prevent pickling, because mcpyrate's hygienic unquote (`h[]`)
139+
# builds on `pickle`. If `dyn` fails to pickle, some macros in `unpythonic.syntax`
140+
# (notably `autoref` and `lazify`) crash, because they need both `dyn` and `h[]`.
141141
#
142142
# Fortunately, no state is saved in the `dyn` singleton instance itself, so
143143
# it doesn't matter that the default `__setstate__` clobbers the `__dict__`

unpythonic/syntax/autoref.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44
from ast import (Name, Assign, Load, Call, Lambda, With, Constant, arg,
55
Attribute, Subscript, Store, Del)
66

7-
from macropy.core.quotes import macros, q, u, name, ast_literal
8-
from macropy.core.hquotes import macros, hq # noqa: F811, F401
7+
from mcpyrate.quotes import macros, q, u, n, a, h # noqa: F401
8+
99
from macropy.core.walkers import Walker
1010

1111
from mcpyrate import gensym
1212

1313
from .astcompat import getconstant, Str
14+
from .nameutil import isx
1415
from .util import wrapwith, AutorefMarker
1516
from .letdoutil import isdo, islet, ExpandedDoView, ExpandedLetView
1617

@@ -89,13 +90,16 @@ def autoref(block_body, args, asname):
8990

9091
o = asname.id if asname else gensym("_o") # Python itself guarantees asname to be a bare Name.
9192

93+
# TODO: We can't use `unpythonic.syntax.util.ismarker` here, because it
94+
# TODO: doesn't currently understand markers with arguments. Extend it?
95+
#
9296
# with AutorefMarker("_o42"):
9397
def isexpandedautorefblock(tree):
9498
if not (type(tree) is With and len(tree.items) == 1):
9599
return False
96100
ctxmanager = tree.items[0].context_expr
97101
return (type(ctxmanager) is Call and
98-
type(ctxmanager.func) is Name and ctxmanager.func.id == "AutorefMarker" and
102+
isx(ctxmanager.func, "AutorefMarker") and
99103
len(ctxmanager.args) == 1 and type(ctxmanager.args[0]) in (Constant, Str)) # Python 3.8+: ast.Constant
100104
def getreferent(tree):
101105
return getconstant(tree.items[0].context_expr.args[0])
@@ -104,7 +108,7 @@ def getreferent(tree):
104108
def isautoreference(tree):
105109
return (type(tree) is Call and
106110
len(tree.args) == 1 and type(tree.args[0]) is Call and
107-
type(tree.args[0].func) is Name and tree.args[0].func.id == "_autoref_resolve" and
111+
isx(tree.args[0].func, "_autoref_resolve") and
108112
type(tree.func) is Lambda and len(tree.func.args.args) == 1 and
109113
tree.func.args.args[0].arg.startswith("_ar"))
110114
def get_resolver_list(tree): # (p, o, "x")
@@ -116,7 +120,7 @@ def add_to_resolver_list(tree, objnode):
116120
# x --> the autoref code above.
117121
def makeautoreference(tree):
118122
assert type(tree) is Name and (type(tree.ctx) is Load or not tree.ctx)
119-
newtree = hq[(lambda __ar_: __ar_[1] if __ar_[0] else ast_literal[tree])(_autoref_resolve((name[o], u[tree.id])))]
123+
newtree = q[(lambda __ar_: __ar_[1] if __ar_[0] else a[tree])(h[_autoref_resolve]((n[o], u[tree.id])))]
120124
our_lambda_argname = gensym("_ar")
121125
@Walker
122126
def renametmp(tree, **kw):
@@ -182,10 +186,10 @@ def transform(tree, *, referents, set_ctx, stop, **kw):
182186

183187
# remove autoref lookup for an outer referent, inserted early by an inner autoref block
184188
# (that doesn't know that any outer block exists)
185-
tree = q[name[thename]] # (lambda ...)(_autoref_resolve((p, "o"))) --> o
189+
tree = q[n[thename]] # (lambda ...)(_autoref_resolve((p, "o"))) --> o
186190
else:
187-
add_to_resolver_list(tree, q[name[o]]) # _autoref_resolve((p, "x")) --> _autoref_resolve((p, o, "x"))
188-
elif type(tree) is Call and type(tree.func) is Name and tree.func.id == "AutorefMarker": # nested autorefs
191+
add_to_resolver_list(tree, q[n[o]]) # _autoref_resolve((p, "x")) --> _autoref_resolve((p, o, "x"))
192+
elif type(tree) is Call and isx(tree.func, "AutorefMarker"): # nested autorefs
189193
stop()
190194
elif type(tree) is Name and (type(tree.ctx) is Load or not tree.ctx) and tree.id not in referents:
191195
stop()
@@ -203,10 +207,10 @@ def transform(tree, *, referents, set_ctx, stop, **kw):
203207
# test framework stuff
204208
'unpythonic_assert', 'unpythonic_assert_signals', 'unpythonic_assert_raises',
205209
'callsite_filename', 'returns_normally']
206-
newbody = [Assign(targets=[q[name[o]]], value=args[0])]
210+
newbody = [Assign(targets=[q[n[o]]], value=args[0])]
207211
for stmt in block_body:
208212
newbody.append(transform.recurse(stmt, referents=always_skip + [o]))
209213

210-
return wrapwith(item=hq[AutorefMarker(u[o])],
214+
return wrapwith(item=q[h[AutorefMarker](u[o])],
211215
body=newbody,
212216
locref=block_body[0])

unpythonic/syntax/curry.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33

44
from ast import Call, Lambda, FunctionDef, AsyncFunctionDef, Name
55

6-
from macropy.core.quotes import macros, ast_literal
7-
from macropy.core.hquotes import macros, hq # noqa: F811, F401
6+
from mcpyrate.quotes import macros, q, a, h # noqa: F401
7+
88
from macropy.core.walkers import Walker
99

1010
from .util import (suggest_decorator_index, isx, make_isxpred, has_curry,
@@ -24,17 +24,17 @@ def transform(tree, *, hascurry, set_ctx, stop, **kw):
2424
set_ctx(hascurry=True) # the lambda inside the curry(...) is the next Lambda node we will descend into.
2525
if not isx(tree.func, _iscurry):
2626
tree.args = [tree.func] + tree.args
27-
tree.func = hq[currycall]
27+
tree.func = q[h[currycall]]
2828
elif type(tree) in (FunctionDef, AsyncFunctionDef):
2929
if not any(isx(item, _iscurry) for item in tree.decorator_list): # no manual curry already
3030
k = suggest_decorator_index("curry", tree.decorator_list)
3131
if k is not None:
32-
tree.decorator_list.insert(k, hq[curryf])
32+
tree.decorator_list.insert(k, q[h[curryf]])
3333
else: # couldn't determine insert position; just plonk it at the end and hope for the best
34-
tree.decorator_list.append(hq[curryf])
34+
tree.decorator_list.append(q[h[curryf]])
3535
elif type(tree) is Lambda:
3636
if not hascurry:
37-
tree = hq[curryf(ast_literal[tree])] # plonk it as innermost, we'll sort them later
37+
tree = q[h[curryf](a[tree])] # plonk it as innermost, we'll sort them later
3838
# don't recurse on the lambda we just moved, but recurse inside it.
3939
stop()
4040
tree.args[0].body = transform.recurse(tree.args[0].body, hascurry=False)

unpythonic/syntax/dbg.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88

99
from ast import Call, Name, Tuple, keyword
1010

11-
from macropy.core.quotes import macros, q, u, ast_literal
12-
from macropy.core.hquotes import macros, hq # noqa: F811, F401
11+
from mcpyrate.quotes import macros, q, u, a, h # noqa: F401
12+
1313
from macropy.core.walkers import Walker
14-
from macropy.core import unparse
14+
from mcpyrate import unparse
1515

1616
from ..misc import callsite_filename
1717

@@ -75,7 +75,7 @@ def dbg_block(body, args):
7575
p = args[0]
7676
pname = p.id # name of the print function as it appears in the user code
7777
else:
78-
p = hq[dbgprint_block]
78+
p = q[h[dbgprint_block]]
7979
pname = "print"
8080

8181
@Walker
@@ -86,9 +86,9 @@ def transform(tree, **kw):
8686
values = Tuple(elts=tree.args, lineno=tree.lineno, col_offset=tree.col_offset)
8787
tree.args = [names, values]
8888
# can't use inspect.stack in the printer itself because we want the line number *before macro expansion*.
89-
tree.keywords += [keyword(arg="filename", value=hq[callsite_filename()]),
89+
tree.keywords += [keyword(arg="filename", value=q[h[callsite_filename]()]),
9090
keyword(arg="lineno", value=(q[u[tree.lineno]] if hasattr(tree, "lineno") else q[None]))]
91-
tree.func = q[ast_literal[p]]
91+
tree.func = q[a[p]]
9292
return tree
9393

9494
return [transform.recurse(stmt) for stmt in body]
@@ -128,9 +128,9 @@ def dbgprint_expr(k, v, *, filename, lineno):
128128
fill in the ``lineno`` attribute of the AST node.
129129
"""
130130
print(f"[{filename}:{lineno}] {k}: {v}")
131-
return v # IMPORTANT!
131+
return v # IMPORTANT! (passthrough; debug printing is a side effect)
132132

133133
def dbg_expr(tree):
134134
ln = q[u[tree.lineno]] if hasattr(tree, "lineno") else q[None]
135-
filename = hq[callsite_filename()]
136-
return q[dbgprint_expr(u[unparse(tree)], ast_literal[tree], filename=ast_literal[filename], lineno=ast_literal[ln])]
135+
filename = q[h[callsite_filename]()]
136+
return q[dbgprint_expr(u[unparse(tree)], a[tree], filename=a[filename], lineno=a[ln])]

unpythonic/syntax/forall.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33

44
from ast import Tuple, arg
55

6-
from macropy.core.quotes import macros, q, u, ast_literal, name
7-
from macropy.core.hquotes import macros, hq # noqa: F811, F401
6+
from mcpyrate.quotes import macros, q, u, n, a, h # noqa: F401
87

98
from .util import splice
109
from .letdoutil import isenvassign, UnexpandedEnvAssignView
@@ -41,9 +40,9 @@ def build(lines, tree):
4140
k, v = "_ignored", line
4241
islast = not rest
4342
# don't unpack on last line to allow easily returning a tuple as a result item
44-
Mv = hq[monadify(ast_literal[v], u[not islast])]
43+
Mv = q[h[monadify](a[v], u[not islast])]
4544
if not islast:
46-
body = q[ast_literal[Mv] >> (lambda: name["_here_"])] # monadic bind: >>
45+
body = q[a[Mv] >> (lambda: n["_here_"])] # monadic bind: >>
4746
body.right.args.args = [arg(arg=k)]
4847
else:
4948
body = Mv
@@ -52,4 +51,4 @@ def build(lines, tree):
5251
else:
5352
newtree = body
5453
return build(rest, newtree)
55-
return hq[tuple(ast_literal[build(exprs.elts, None)])]
54+
return q[h[tuple](a[build(exprs.elts, None)])]

unpythonic/syntax/ifexprs.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33

44
from ast import Tuple
55

6-
from macropy.core.quotes import macros, q, ast_literal
7-
from macropy.core.hquotes import macros, hq # noqa: F811, F401
6+
from mcpyrate.quotes import macros, q, a # noqa: F811, F401
87

98
from .letdo import implicit_do, let
109

@@ -25,8 +24,8 @@ def __repr__(self): # pragma: no cover, we have a repr just in case one of thes
2524

2625
def aif(tree):
2726
test, then, otherwise = [implicit_do(x) for x in tree.elts]
28-
bindings = [q[(it, ast_literal[test])]]
29-
body = q[ast_literal[then] if it else ast_literal[otherwise]]
27+
bindings = [q[(it, a[test])]]
28+
body = q[a[then] if it else a[otherwise]]
3029
return let(bindings, body)
3130

3231
def cond(tree):
@@ -40,5 +39,5 @@ def build(elts):
4039
test, then, *more = elts
4140
test = implicit_do(test)
4241
then = implicit_do(then)
43-
return hq[ast_literal[then] if ast_literal[test] else ast_literal[build(more)]]
42+
return q[a[then] if a[test] else a[build(more)]]
4443
return build(tree.elts)

unpythonic/syntax/lambdatools.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
from copy import deepcopy
88
import sys
99

10-
from macropy.core.quotes import macros, q, u, ast_literal, name
11-
from macropy.core.hquotes import macros, hq # noqa: F811, F401
10+
from mcpyrate.quotes import macros, q, u, n, a, h # noqa: F401
11+
1212
from macropy.core.walkers import Walker
1313
from macropy.quick_lambda import f, _ # _ for re-export only # noqa: F401
1414

@@ -36,7 +36,7 @@ def transform(tree, *, stop, **kw):
3636
# by the "do" we are inserting here
3737
# - for each item, "do" internally inserts a lambda to delay execution,
3838
# as well as to bind the environment
39-
# - we must do() instead of hq[do[...]] for pickling reasons
39+
# - we must do() instead of q[h[do][...]] for pickling reasons
4040
# - but recurse manually into each *do item*; these are explicit
4141
# user-provided code so we should transform them
4242
stop()
@@ -84,7 +84,7 @@ def nameit(myname, tree):
8484
if isautocurrywithfinallambda(tree): # "currycall(..., curryf(lambda ...: ...))"
8585
match = True
8686
thelambda = tree.args[-1].args[-1]
87-
tree.args[-1].args[-1] = hq[namelambda(u[myname])(ast_literal[thelambda])]
87+
tree.args[-1].args[-1] = q[h[namelambda](u[myname])(a[thelambda])]
8888
elif type(tree) is Lambda or d or c:
8989
match = True
9090
if d:
@@ -93,7 +93,7 @@ def nameit(myname, tree):
9393
thelambda = tree.args[-1]
9494
else:
9595
thelambda = tree
96-
tree = hq[namelambda(u[myname])(ast_literal[tree])] # plonk it as outermost and hope for the best
96+
tree = q[h[namelambda](u[myname])(a[tree])] # plonk it as outermost and hope for the best
9797
return tree, thelambda, match
9898

9999
@Walker
@@ -180,7 +180,7 @@ def underscore_search(tree, collect, **kw):
180180
collect(name)
181181
return tree
182182
tree, used_names = underscore_search.recurse_collect(tree)
183-
new_tree = q[lambda: ast_literal[tree]]
183+
new_tree = q[lambda _: a[tree]] # noqa: F811, it's a placeholder overwritten at the next line.
184184
new_tree.args.args = [arg(arg=x) for x in used_names]
185185
return new_tree
186186

@@ -239,13 +239,13 @@ def isourupdate(thecall):
239239
argnames = getargs(tree)
240240
if argnames:
241241
# prepend env init to function body, update bindings
242-
kws = [keyword(arg=k, value=q[name[k]]) for k in argnames] # "x" --> x
242+
kws = [keyword(arg=k, value=q[n[k]]) for k in argnames] # "x" --> x
243243
newbindings = bindings.copy()
244244
if type(tree) in (FunctionDef, AsyncFunctionDef):
245245
ename = gensym("e")
246-
theenv = hq[_envify()]
246+
theenv = q[h[_envify]()]
247247
theenv.keywords = kws
248-
assignment = Assign(targets=[q[name[ename]]],
248+
assignment = Assign(targets=[q[n[ename]]],
249249
value=theenv)
250250
assignment = copy_location(assignment, tree)
251251
tree.body.insert(0, assignment)
@@ -256,15 +256,15 @@ def isourupdate(thecall):
256256
# the name should revert to mean the formal parameter.
257257
#
258258
# inject a do[] and reuse its env
259-
tree.body = do(List(elts=[q[name["_here_"]],
259+
tree.body = do(List(elts=[q[n["_here_"]],
260260
tree.body]))
261261
view = ExpandedDoView(tree.body) # view.body: [(lambda e14: ...), ...]
262262
ename = view.body[0].args.args[0].arg # do[] environment name
263-
theupdate = Attribute(value=q[name[ename]], attr="update")
264-
thecall = q[ast_literal[theupdate]()]
263+
theupdate = Attribute(value=q[n[ename]], attr="update")
264+
thecall = q[a[theupdate]()]
265265
thecall.keywords = kws
266-
tree.body = splice(tree.body, thecall, "_here_")
267-
newbindings.update({k: Attribute(value=q[name[ename]], attr=k) for k in argnames}) # "x" --> e.x
266+
tree.body = splice(tree.body, thecall, "_here_") # TODO: mcpyrate.splicing.splice_expression
267+
newbindings.update({k: Attribute(value=q[n[ename]], attr=k) for k in argnames}) # "x" --> e.x
268268
set_ctx(enames=enames + [ename])
269269
set_ctx(bindings=newbindings)
270270
else:
@@ -276,11 +276,11 @@ def isourupdate(thecall):
276276
view = UnexpandedEnvAssignView(tree)
277277
if view.name in bindings.keys():
278278
envset = Attribute(value=bindings[view.name].value, attr="set")
279-
return q[ast_literal[envset](u[view.name], ast_literal[view.value])]
279+
return q[a[envset](u[view.name], a[view.value])]
280280
# transform references to currently active bindings
281281
elif type(tree) is Name and tree.id in bindings.keys():
282282
# We must be careful to preserve the Load/Store/Del context of the name.
283-
# The default lets MacroPy fix it later.
283+
# The default lets mcpyrate fix it later.
284284
ctx = tree.ctx if hasattr(tree, "ctx") else None
285285
out = deepcopy(bindings[tree.id])
286286
out.ctx = ctx

0 commit comments

Comments
 (0)