Skip to content

Commit dda2c02

Browse files
committed
syntax: split utilities related to let and do forms into their own module
1 parent d9844b9 commit dda2c02

File tree

5 files changed

+277
-267
lines changed

5 files changed

+277
-267
lines changed

unpythonic/syntax/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from .tailtools import autoreturn as _autoreturn, tco as _tco, \
2727
continuations as _continuations, with_cc
2828

29-
from .util import UnexpandedLetView
29+
from .letdoutil import UnexpandedLetView
3030
from ..dynassign import dyn, make_dynvar
3131

3232
from macropy.core.macros import Macros

unpythonic/syntax/letdoutil.py

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
# -*- coding: utf-8 -*-
2+
"""Detect let and do forms, and destructure them writably.
3+
4+
Separate from letdo.py for dependency reasons.
5+
Separate from util.py due to the length.
6+
"""
7+
8+
from ast import Call, Name, Subscript, Index, Compare, In, Tuple, List
9+
10+
def islet(tree, expanded=True):
11+
"""Test whether tree is a ``let[]``, ``letseq[]``, ``letrec[]``,
12+
``let_syntax[]``, or ``abbrev[]``.
13+
14+
Return a truthy value if it is, ``False`` if not.
15+
16+
expanded: if ``True``, test for the already expanded form.
17+
If ``False``, test for the form that exists prior to macro expansion.
18+
19+
Note ``let_syntax[]`` and ``abbrev[]`` are completely eliminated by
20+
macro expansion, so they are seen only if ``expanded=False``.
21+
"""
22+
if expanded:
23+
# name must match what ``unpythonic.syntax.letdo._letimpl`` uses in its output.
24+
if type(tree) is Call and type(tree.func) is Name and tree.func.id == "letter":
25+
return ("expanded", None) # TODO: detect let/letseq/letrec mode also for expanded forms (from kwargs)
26+
return False
27+
# dlet((k0, v0), ...) (call, usually in a decorator list)
28+
deconames = ("dlet", "dletseq", "dletrec",
29+
"blet", "bletseq", "bletrec")
30+
if type(tree) is Call and type(tree.func) is Name:
31+
s = tree.func.id
32+
if any(s == x for x in deconames):
33+
return ("decorator", s)
34+
# otherwise we should have an expr macro invocation
35+
if not (type(tree) is Subscript and type(tree.slice) is Index):
36+
return False
37+
macro = tree.value
38+
expr = tree.slice.value
39+
exprnames = ("let", "letseq", "letrec", "let_syntax", "abbrev")
40+
# let((k0, v0), ...)[body]
41+
if type(macro) is Call and type(macro.func) is Name:
42+
s = macro.func.id
43+
if any(s == x for x in exprnames):
44+
return ("lispy_expr", s)
45+
# The haskelly syntaxes are only available as a let expression (no decorator form).
46+
elif type(macro) is Name:
47+
s = macro.id
48+
if not any(s == x for x in exprnames):
49+
return False
50+
h = _ishaskellylet(expr)
51+
if h:
52+
return (h, s)
53+
return False
54+
55+
def _ishaskellylet(tree):
56+
"""Test whether tree is the content of a haskelly let.
57+
58+
Return a truthy value if it is, ``False`` if not.
59+
60+
In other words, detect the part inside the brackets in::
61+
62+
let[((k0, v0), ...) in body]
63+
let[body, where((k0, v0), ...)]
64+
65+
To detect the full expression including the ``let[]``, use ``islet`` instead.
66+
"""
67+
# let[((k0, v0), ...) in body]
68+
if type(tree) is Compare and \
69+
len(tree.ops) == 1 and type(tree.ops[0]) is In and \
70+
type(tree.left) is Tuple:
71+
bindings = tree.left
72+
if all((type(b) is Tuple and len(b.elts) == 2 and type(b.elts[0]) is Name)
73+
for b in bindings.elts):
74+
return "in_expr"
75+
# let[body, where((k0, v0), ...)]
76+
elif type(tree) is Tuple and len(tree.elts) == 2 and type(tree.elts[1]) is Call:
77+
thecall = tree.elts[1]
78+
if type(thecall.func) is Name and thecall.func.id == "where":
79+
return "where_expr"
80+
return False
81+
82+
def isdo(tree, expanded=True):
83+
"""Detect whether tree is a ``do[]`` or ``do0[]``.
84+
85+
expanded: if ``True``, test for the already expanded form.
86+
If ``False``, test for the form that exists prior to macro expansion.
87+
"""
88+
if expanded:
89+
# name must match what ``unpythonic.syntax.letdo.do`` uses in its output.
90+
return type(tree) is Call and type(tree.func) is Name and tree.func.id == "dof"
91+
# TODO: detect also do[] with a single expression inside? (now requires a comma)
92+
return type(tree) is Subscript and \
93+
type(tree.value) is Name and any(tree.value.id == x for x in ("do", "do0")) and \
94+
type(tree.slice) is Index and type(tree.slice.value) is Tuple
95+
96+
# TODO: kwargs support for let(x=42)[...] if implemented later
97+
class UnexpandedLetView:
98+
"""Destructure a let form, writably.
99+
100+
If ``tree`` cannot be interpreted as a ``let`` form, then ``TypeError``
101+
is raised.
102+
103+
For in-place modification of ``bindings`` or ``body``. Use before the ``let``
104+
form is expanded away.
105+
106+
**Supported formats**::
107+
108+
dlet((k0, v0), ...) # decorator
109+
let((k0, v0), ...)[body] # lispy expression
110+
let[((k0, v0), ...) in body] # haskelly expression
111+
let[body, where((k0, v0), ...)] # haskelly expression, inverted
112+
113+
In addition, we also support *just the bracketed part* of the haskelly
114+
formats. This is to make it easier for the macro interface to destructure
115+
these forms (for sending into the ``let`` syntax transformer). So these
116+
forms are supported, too::
117+
118+
((k0, v0), ...) in body
119+
(body, where((k0, v0), ...))
120+
121+
This is a data abstraction that hides the detailed structure of the AST,
122+
since there are three alternate syntaxes that can be used for a ``let``
123+
expression.
124+
125+
For the decorator forms, ``tree`` should be the decorator call. In this case
126+
only ``bindings`` is available (the body is then the body of the function
127+
being decorated).
128+
129+
**Attributes**:
130+
131+
``bindings`` is a ``list`` of ``ast.Tuple``, where each item is of the form
132+
``(k, v)``, where ``k`` is an ``ast.Name``. Writing to ``bindings`` updates
133+
the original.
134+
135+
``body`` (when available) is an AST representing an expression. If the
136+
outermost layer is an ``ast.List``, it means an implicit ``do[]``
137+
(handled by the ``let`` expander), allowing a multiple-expression body.
138+
Writing to ``body`` updates the original.
139+
140+
When not available, ``body is None``.
141+
142+
``mode`` is one of ``let``, ``letseq``, ``letrec``; for information only
143+
(this essentially says what the ``bindings`` mean).
144+
145+
If ``tree`` is just the bracketed part of a haskelly let, then ``mode`` is
146+
``None``, because the mode information is contained in the surrounding
147+
subscript form (expr macro invocation) and hence not accessible from here.
148+
"""
149+
def __init__(self, tree):
150+
data = islet(tree, expanded=False)
151+
if not data:
152+
# the macro interface only gets the bracketed part as tree,
153+
# so we jump through hoops to make this usable both from
154+
# syntax transformers (which have access to the full AST)
155+
# and the macro interface (which needs to destructure bindings and body
156+
# from the given tree, to send them to the let transformer).
157+
h = _ishaskellylet(tree)
158+
if not h:
159+
# check a common mistake, missing trailing comma after a single binding ((k, v),)
160+
if type(tree) is Compare and len(tree.ops) == 1 and type(tree.ops[0]) is In:
161+
bindings = tree.left
162+
if type(bindings) is Tuple and len(bindings.elts) == 2 and type(bindings.elts[0]) is Name:
163+
raise TypeError("expected a tree representing a let; maybe missing trailing comma after a single binding?")
164+
raise TypeError("expected a tree representing a let, got {}".format(tree))
165+
data = (h, None) # cannot detect mode, no access to the surrounding subscript form
166+
self._tree = tree
167+
self._type, self.mode = data
168+
if self._type == "decorator":
169+
self.body = None
170+
171+
def _getbindings(self):
172+
t = self._type
173+
if t == "decorator": # bare Call
174+
return self._tree.args
175+
elif t == "lispy_expr": # Call inside a Subscript
176+
return self._tree.value.args
177+
else: # haskelly let
178+
# self.mode is set if the Subscript container is present.
179+
theexpr = self._tree.slice.value if self.mode else self._tree
180+
if t == "in_expr":
181+
return theexpr.left.elts
182+
elif t == "where_expr":
183+
return theexpr.elts[1].args
184+
raise NotImplementedError("unknown let form type '{}'".format(t))
185+
def _setbindings(self, newbindings):
186+
t = self._type
187+
if t == "decorator":
188+
self._tree.args = newbindings
189+
elif t == "lispy_expr":
190+
self._tree.value.args = newbindings
191+
else:
192+
theexpr = self._tree.slice.value if self.mode else self._tree
193+
if t == "in_expr":
194+
theexpr.left.elts = newbindings
195+
elif t == "where_expr":
196+
theexpr.elts[1].args = newbindings
197+
raise NotImplementedError("unknown let form type '{}'".format(t))
198+
bindings = property(fget=_getbindings, fset=_setbindings, doc="The bindings subform of the let. Writable.")
199+
200+
def _getbody(self):
201+
t = self._type
202+
if t == "decorator":
203+
# not reached, but let's leave this here for documentation.
204+
raise TypeError("the body of a decorator let form is the body of decorated function, not a subform of the let.")
205+
elif t == "lispy_expr":
206+
return self._tree.slice.value
207+
else:
208+
theexpr = self._tree.slice.value if self.mode else self._tree
209+
if t == "in_expr":
210+
return theexpr.comparators[0]
211+
elif t == "where_expr":
212+
return theexpr.elts[0]
213+
raise NotImplementedError("unknown let form type '{}'".format(t))
214+
def _setbody(self, newbody):
215+
t = self._type
216+
if t == "decorator":
217+
# not reached, but let's leave this here for documentation.
218+
raise TypeError("the body of a decorator let form is the body of decorated function, not a subform of the let.")
219+
elif t == "lispy_expr":
220+
self._tree.slice.value = newbody
221+
else:
222+
theexpr = self._tree.slice.value if self.mode else self._tree
223+
if t == "in_expr":
224+
theexpr.comparators[0] = newbody
225+
elif t == "where_expr":
226+
theexpr.elts[0] = newbody
227+
raise NotImplementedError("unknown let form type '{}'".format(t))
228+
body = property(fget=_getbody, fset=_setbody, doc="The body subform of the let (only for expr forms). Writable.")
229+
230+
class UnexpandedDoView:
231+
"""Destructure a do form, writably.
232+
233+
If ``tree`` cannot be interpreted as a ``do`` form, then ``TypeError``
234+
is raised.
235+
236+
For easy in-place modification of ``body``. Use before the ``do`` form
237+
is expanded away.
238+
239+
**Supported formats**:
240+
241+
do[body0, ...]
242+
do0[body0, ...]
243+
[...]
244+
245+
The list format is for convenience, for viewing an implicit ``do[]`` in the
246+
body of a ``let`` form.
247+
248+
**Attributes**:
249+
250+
``body`` is a ``list`` of the expressions in the body of the ``do[]``.
251+
Writing to it updates the original.
252+
"""
253+
def __init__(self, tree):
254+
self._implicit = False
255+
if not isdo(tree, expanded=False):
256+
if type(tree) is not List: # for implicit do[]
257+
raise TypeError("expected a tree representing a do, got {}".format(tree))
258+
self._implicit = True
259+
self._tree = tree
260+
261+
def _getbody(self):
262+
return self._tree.slice.value.elts if not self._implicit else self._tree.elts
263+
def _setbody(self, newbody):
264+
if not self._implicit:
265+
self._tree.slice.value.elts = newbody
266+
else:
267+
self._tree.elts = newbody
268+
body = property(fget=_getbody, fset=_setbody, doc="The body of the do. Writable.")

unpythonic/syntax/prefix.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from macropy.core.quotes import macros, q, u, ast_literal
1010
from macropy.core.walkers import Walker
1111

12-
from .util import islet, isdo, UnexpandedLetView, UnexpandedDoView
12+
from .letdoutil import islet, isdo, UnexpandedLetView, UnexpandedDoView
1313

1414
from ..it import flatmap, rev, uniqify
1515

unpythonic/syntax/tailtools.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
arguments, arg, keyword, \
1010
List, Tuple, \
1111
Subscript, Index, \
12-
Call, Name, Starred, Num, NameConstant, \
12+
Call, Name, Starred, NameConstant, \
1313
BoolOp, And, Or, \
1414
With, If, IfExp, Try, Assign, Return, Expr, \
1515
copy_location
@@ -20,10 +20,11 @@
2020
from macropy.core.hquotes import macros, hq
2121
from macropy.core.walkers import Walker
2222

23-
from .util import isx, isec, isdo, islet, \
23+
from .util import isx, isec, \
2424
detect_callec, detect_lambda, \
25-
has_tco, is_decorator, sort_lambda_decorators, \
25+
has_tco, sort_lambda_decorators, \
2626
suggest_decorator_index
27+
from .letdoutil import isdo, islet
2728
from .ifexprs import aif
2829
from .letdo import let
2930

0 commit comments

Comments
 (0)