Skip to content

Commit b43d6b0

Browse files
committed
enh: add autoref block macro, 'with autoref(obj)' allows writing just 'x' instead of 'obj.x' for any name in Load context
1 parent cbd2b55 commit b43d6b0

File tree

4 files changed

+102
-1
lines changed

4 files changed

+102
-1
lines changed

macro_extras/README.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ Of the `python3` command-line options, the `macropy3` bootstrapper supports only
5656
- [**Convenience features**](#convenience-features)
5757
- [``cond``: the missing ``elif`` for ``a if p else b``](#cond-the-missing-elif-for-a-if-p-else-b)
5858
- [``aif``: anaphoric if](#aif-anaphoric-if), the test result is ``it``
59+
- [``autoref``: implicitly reference attributes of an object](#autoref-implicitly-reference-attributes-of-an-object)
5960
- *Changed in v0.13.1.* The ``fup[]`` macro is gone, and has been replaced with the ``fup`` function, with slightly changed syntax to accommodate.
6061

6162
- [**Other**](#other)
@@ -1484,6 +1485,26 @@ aif[[pre, ..., test],
14841485
To denote a single expression that is a literal list, use an extra set of brackets: ``[[1, 2, 3]]``.
14851486

14861487

1488+
### ``autoref``: implicitly reference attributes of an object
1489+
1490+
*Added in v0.14.0.*
1491+
1492+
Ever wished you could ``with(obj)`` to write ``x`` instead of ``obj.x`` to read attributes of an object? Enter the ``autoref`` block macro:
1493+
1494+
```python
1495+
from unpythonic.syntax import macros, autoref
1496+
from unpythonic import env
1497+
1498+
e = env(a=1, b=2)
1499+
c = 3
1500+
with autoref(e):
1501+
assert a == 1 # a --> e.a
1502+
assert b == 2 # b --> e.b
1503+
assert c == 3 # no c in e, so just c
1504+
```
1505+
1506+
The transformation is ``x --> o.x if hasattr(o, "x") else x``, applied at each use site. It is applied for names in ``Load`` context only. ``Store`` and ``Del`` are not redirected. This can be convenient e.g. with the ``.mat`` file loader of SciPy.
1507+
14871508

14881509
## Other
14891510

@@ -1496,7 +1517,7 @@ Stuff that didn't fit elsewhere.
14961517
Mix regular code with math-notebook-like code in a ``.py`` file. To enable notebook mode, ``with nb``:
14971518

14981519
```python
1499-
from unpythonic.syntax import nb
1520+
from unpythonic.syntax import macros, nb
15001521
from sympy import symbols, pprint
15011522

15021523
with nb:

unpythonic/syntax/__init__.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
# insist, deny, it, f, _, force, force1, local, delete, block, expr, call_cc
1212
# are just for passing through to the client code that imports us.
13+
from .autoref import autoref as _autoref
1314
from .curry import curry as _curry
1415
from .forall import forall as _forall, insist, deny
1516
from .ifexprs import aif as _aif, it, cond as _cond
@@ -50,6 +51,33 @@ def nogensym(*args, **kwargs):
5051

5152
# -----------------------------------------------------------------------------
5253

54+
@macros.block
55+
def autoref(tree, args, *, gen_sym, **kw):
56+
"""Implicitly reference attributes of an object.
57+
58+
Example::
59+
60+
e = env(a=1, b=2)
61+
c = 3
62+
with autoref(e):
63+
a
64+
b
65+
c
66+
67+
The transformation is::
68+
69+
x --> o.x if hasattr(o, "x") else x
70+
71+
The transformation is applied in ``Load`` context only. ``Store`` and ``Del``
72+
are not redirected.
73+
74+
Useful e.g. with the ``.mat`` file loader of SciPy.
75+
"""
76+
with dyn.let(gen_sym=gen_sym):
77+
return _autoref(block_body=tree, args=args)
78+
79+
# -----------------------------------------------------------------------------
80+
5381
@macros.expr
5482
def aif(tree, *, gen_sym, **kw):
5583
"""[syntax, expr] Anaphoric if.

unpythonic/syntax/autoref.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# -*- coding: utf-8 -*-
2+
"""Implicitly reference attributes of an object."""
3+
4+
from ast import Name, Assign, Attribute, Load
5+
6+
from macropy.core.quotes import macros, q, u, name, ast_literal
7+
from macropy.core.walkers import Walker
8+
9+
from ..dynassign import dyn
10+
11+
def autoref(block_body, args):
12+
assert len(args) == 1, "expected exactly one argument, the object to implicitly reference"
13+
assert block_body, "expected at least one statement in the 'with autoref' block"
14+
15+
# assign the implicit object to a temporary name, to resolve a computed reference only once
16+
gen_sym = dyn.gen_sym
17+
ref = gen_sym("r")
18+
19+
@Walker
20+
def transform(tree, *, stop, **kw):
21+
if not (type(tree) is Name and type(tree.ctx) is Load):
22+
return tree
23+
stop()
24+
x = tree.id
25+
theattr = Attribute(value=q[name[ref]], attr=x)
26+
newtree = q[ast_literal[theattr] if hasattr(name[ref], u[x]) else ast_literal[tree]]
27+
return newtree
28+
29+
newbody = [Assign(targets=[q[name[ref]]], value=args[0])]
30+
for stmt in block_body:
31+
newbody.append(transform.recurse(stmt))
32+
33+
return newbody
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# -*- coding: utf-8 -*-
2+
"""Implicitly reference attributes of an object."""
3+
4+
from ...syntax import macros, autoref
5+
6+
from ...env import env
7+
8+
def test():
9+
e = env(a=1, b=2)
10+
c = 3
11+
with autoref(e):
12+
assert a == 1 # a --> e.a
13+
assert b == 2 # b --> e.b
14+
assert c == 3 # no c in e, so just c
15+
16+
print("All tests PASSED")
17+
18+
if __name__ == '__main__':
19+
test()

0 commit comments

Comments
 (0)