|
| 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.") |
0 commit comments