66
77from functools import partial
88from 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
1111from macropy .core .walkers import Walker
1212
13- from unpythonic .syntax .util import isnamedwith
1413from unpythonic .syntax .letdo import implicit_do
1514
1615def 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#
5454def 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
0 commit comments