Skip to content

Commit a8c8f80

Browse files
committed
parametric blocks in let_syntax
1 parent b0867ab commit a8c8f80

4 files changed

Lines changed: 77 additions & 48 deletions

File tree

macro_extras/main.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,15 @@ def verylongfunctionname(x=1):
475475
assert lst == [4, 5, 6]
476476
assert snd == 5
477477

478+
with block(a, b, c) as makeabc:
479+
lst = [a, b, c]
480+
makeabc(3 + 4, 2**3, 3 * 3)
481+
assert lst == [7, 8, 9]
482+
with expr(n) as nth:
483+
lst[n]
484+
assert nth(2) == 9
485+
# TODO: add more tests
486+
478487
# nesting: each "with let_syntax" is a lexical scope for syntactic substitutions
479488
with let_syntax:
480489
with block as makelst:

unpythonic/syntax/__init__.py

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -502,13 +502,19 @@ def let_syntax(tree, **kw):
502502
Usage - block variant::
503503
504504
with let_syntax:
505-
with block as xs: # capture a block of statements
505+
with block as xs: # capture a block of statements - bare name
506+
...
507+
with block(a, ...) as xs: # capture a block of statements - template
508+
...
509+
with expr as x: # capture a single expression - bare name
510+
...
511+
with expr(a, ...) as x: # capture a single expression - template
506512
...
507-
with expr as x: # capture a single expression
508-
... # (can explicitly use do[] here if necessary)
509513
body0
510514
...
511515
516+
A single expression can be a ``do[]`` if multiple expressions are needed.
517+
512518
The bindings are applied **at macro expansion time**, substituting
513519
the expression on the RHS for each instance of the corresponding LHS.
514520
Each substitution gets a fresh copy.
@@ -517,28 +523,33 @@ def let_syntax(tree, **kw):
517523
expansion time (with zero run-time overhead), or to splice in several
518524
(possibly parametric) instances of a common pattern.
519525
520-
The LHS may be:
526+
In the expression variant, ``lhs`` may be:
521527
522528
- A bare name (e.g. ``x``), or
523529
524530
- A simple template of the form ``f(x, ...)``. The names inside the
525531
parentheses declare the formal parameters of the template.
526532
527-
Templates support only positional arguments, with no default values.
533+
In the block variant:
534+
535+
- The name of the LHS goes in the **as-part**.
536+
537+
- If a template, the formal parameters are declared on the ``block``
538+
or ``expr``. Parameters are always expressions (because they use the
539+
function-call syntax).
540+
541+
**Templates**
528542
529-
In the body, a template is used like a function call. Just like in an
530-
actual function call, when the template is substituted, any instances
531-
of its formal parameters on its RHS get replaced by the argument values
532-
from the "call" site; but ``let_syntax`` performs this at macro-expansion
533-
time. Note each instance of the same formal parameter gets a fresh copy
534-
of the corresponding argument value.
543+
To make parametric substitutions, use templates.
535544
536-
In the block variant, the **as-part** is the LHS, and the body of the
537-
``with block as ...`` or ``with expr as ...`` block is the RHS.
545+
Templates support only positional arguments, with no default values.
538546
539-
**CAUTION**: In a block-variant template, all formal parameters are also
540-
handled in block mode, as statements. (This is subject to change in a
541-
future version.)
547+
In the body, a template is used like a function call. Just like in an
548+
actual function call, when the template is substituted, any instances
549+
of its formal parameters on its RHS get replaced by the argument values
550+
from the "call" site; but ``let_syntax`` performs this at macro-expansion
551+
time. Note each instance of the same formal parameter gets a fresh copy
552+
of the corresponding argument value.
542553
543554
**Substitution order**
544555
@@ -576,6 +587,7 @@ def abbrev(tree, args, gen_sym, **kw):
576587
with dyn.let(gen_sym=gen_sym): # gen_sym is only needed by the implicit do.
577588
yield let_syntax_expr(bindings=args, body=tree)
578589

590+
# TODO: abbrev does nest, but not in the lexical-scoping way. Update explanation!
579591
@macros.block
580592
def abbrev(tree, **kw):
581593
"""Exactly like ``let_syntax``, but expands in the first pass, outside in.

unpythonic/syntax/letsyntax.py

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,10 @@
66

77
from functools import partial
88
from copy import deepcopy
9-
from ast import Name, Call, Starred, If, Num, Expr
9+
from ast import Name, Call, Starred, If, Num, Expr, With
1010

1111
from macropy.core.walkers import Walker
1212

13-
from unpythonic.syntax.util import isnamedwith
1413
from unpythonic.syntax.letdo import implicit_do
1514

1615
def let_syntax_expr(bindings, body): # bindings: sequence of ast.Tuple: (k1, v1), (k2, v2), ..., (kn, vn)
@@ -38,30 +37,43 @@ def register_bindings():
3837

3938
# -----------------------------------------------------------------------------
4039

41-
# TODO: parametric block, expr (currently doesn't work, assignment to something
42-
# TODO: that looks like a function call is syntactically invalid in Python)
43-
4440
# block version:
4541
#
4642
# with let_syntax:
47-
# with block as xs: # capture a block of statements
43+
# with block as xs:
44+
# ...
45+
# with block(a, ...) as xs:
46+
# ...
47+
# with expr as x:
48+
# ...
49+
# with expr(a, ...) as x:
4850
# ...
49-
# with expr as x: # capture a single expression
50-
# ... # can explicitly use do[] here if necessary
5151
# body0
5252
# ...
5353
#
5454
def let_syntax_block(block_body):
5555
names_seen = set()
5656
templates = []
5757
barenames = []
58-
def register_binding(withstmt, mode): # "with block:" or "with expr:"
58+
def register_binding(withstmt, mode, kind):
59+
assert mode in ("block", "expr")
60+
assert kind in ("barename", "template")
61+
ctxmanager = withstmt.items[0].context_expr
5962
optvars = withstmt.items[0].optional_vars
6063
if not optvars:
61-
assert False, "'with {}:': expected a name (e.g. x) or a template (e.g. f(x, ...)) as the as-part".format(mode)
62-
name, args = _analyze_lhs(optvars)
64+
assert False, "'with {}:': expected an as-part".format(mode)
65+
if type(optvars) is not Name:
66+
assert False, "'with {}:': expected exactly one name in the as-part".format(mode)
67+
68+
name = optvars.id
6369
if name in names_seen:
6470
assert False, "duplicate '{}'; as-parts in the same let_syntax block must be unique".format(name)
71+
72+
if kind == "template":
73+
_, args = _analyze_lhs(ctxmanager) # syntactic limitation, can't place formal parameter list on the as-part
74+
else: # kind == "barename":
75+
args = []
76+
6577
if mode == "block":
6678
value = If(test=Num(n=1),
6779
body=withstmt.body,
@@ -72,20 +84,28 @@ def register_binding(withstmt, mode): # "with block:" or "with expr:"
7284
assert False, "'with expr:' expected a one-item body (use a do[] if need more)"
7385
theexpr = withstmt.body[0]
7486
if type(theexpr) is not Expr:
75-
assert False, "'with expr:' expected an expression in body, got a statement"
87+
assert False, "'with expr:' expected an expression body, got a statement"
7688
value = theexpr.value # discard Expr wrapper in definition
7789
names_seen.add(name)
7890
target = templates if args else barenames
7991
target.append((name, args, value, mode))
8092

81-
iswithblock = partial(isnamedwith, name="block") # "with block as ...:"
82-
iswithexpr = partial(isnamedwith, name="expr") # "with expr as ...:"
93+
def isbinding(tree):
94+
for mode in ("block", "expr"):
95+
if not (type(tree) is With and len(tree.items) == 1):
96+
continue
97+
ctxmanager = tree.items[0].context_expr
98+
if type(ctxmanager) is Name and ctxmanager.id == mode:
99+
return mode, "barename"
100+
if type(ctxmanager) is Call and type(ctxmanager.func) is Name and ctxmanager.func.id == mode:
101+
return mode, "template"
102+
return False
103+
83104
new_block_body = []
84105
for stmt in block_body:
85-
if iswithblock(stmt):
86-
register_binding(stmt, "block")
87-
elif iswithexpr(stmt):
88-
register_binding(stmt, "expr")
106+
binding_data = isbinding(stmt)
107+
if binding_data:
108+
register_binding(stmt, *binding_data)
89109
else:
90110
stmt = _substitute_templates(templates, stmt)
91111
stmt = _substitute_barenames(barenames, stmt)
@@ -161,9 +181,8 @@ def splice(tree, *, stop, **kw):
161181
# make a fresh deep copy of the RHS to avoid destroying the template.
162182
tree = deepcopy(value) # expand the f itself in f(x, ...)
163183
for k, v in zip(formalparams, theargs): # expand the x, ... in the expanded form of f
164-
# TODO: Currently all args of a block substitution are handled in block mode (as statements).
165-
# TODO: Some configurability may be needed here.
166-
tree = _substitute_barename(k, v, tree, mode)
184+
# can't put statements in a Call, so always treat args as expressions.
185+
tree = _substitute_barename(k, v, tree, "expr")
167186
return tree
168187
tree = splice.recurse(tree)
169188
return tree

unpythonic/syntax/util.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,6 @@ def isx(tree, x, accept_attr=True):
3333
(type(tree) is Captured and tree.name == x) or \
3434
(accept_attr and type(tree) is Attribute and tree.attr == x)
3535

36-
def isnamedwith(tree, name):
37-
"""Test whether tree is ``with name:``.
38-
39-
Only a simple with-block with only one context manager with a bare name
40-
is supported.
41-
"""
42-
if not (type(tree) is With and len(tree.items) == 1):
43-
return False
44-
ctxmanager = tree.items[0].context_expr
45-
return type(ctxmanager) is Name and ctxmanager.id == name
46-
4736
def islet(tree, expanded=True):
4837
"""Test whether tree is a ``let[]``, ``letseq[]`` or ``letrec[]``.
4938

0 commit comments

Comments
 (0)