-
Notifications
You must be signed in to change notification settings - Fork 3
/
quick_lambda.py
58 lines (49 loc) · 1.83 KB
/
quick_lambda.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# -*- coding: utf-8 -*-
"""
Implementation of quick lambdas in Pyccolo, similar to MacroPy's.
Ref: https://macropy3.readthedocs.io/en/latest/quick_lambda.html#quicklambda
Example:
```
with QuickLambdaTracer:
pyc.eval("f[_ + _](3, 4)")
>>> 7
```
"""
import ast
import copy
import pyccolo as pyc
from pyccolo.examples.quasiquote import Quasiquoter, is_macro
class _ArgReplacer(ast.NodeTransformer):
def __init__(self):
self.ctr = 0
def visit_Subscript(self, node: ast.Subscript) -> ast.Subscript:
if isinstance(node.value, ast.Name) and node.value.id == "f":
# defer visiting nested quick lambdas
return node
else:
return self.generic_visit(node) # type: ignore
def visit_Name(self, node: ast.Name) -> ast.Name:
if node.id == "_":
node.id = f"_{self.ctr}"
self.ctr += 1
return node
class QuickLambdaTracer(Quasiquoter):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.macros.add("f")
self._arg_replacer = _ArgReplacer()
@pyc.before_subscript_slice(when=is_macro("f"), reentrant=True)
def handle_quick_lambda(self, _ret, node, frame, *_, **__):
orig_ctr = self._arg_replacer.ctr
orig_lambda_body = node.slice
if isinstance(node.slice, ast.Index):
orig_lambda_body = orig_lambda_body.value
lambda_body = self._arg_replacer.visit( # noqa: F841
copy.deepcopy(orig_lambda_body)
)
num_lambda_args = self._arg_replacer.ctr - orig_ctr
lambda_args = ", ".join(
f"_{arg_idx}" for arg_idx in range(orig_ctr, orig_ctr + num_lambda_args)
)
ast_lambda = pyc.eval(f"q[lambda {lambda_args}: ast_literal[lambda_body]]")
return lambda: pyc.eval(ast_lambda, frame.f_globals, frame.f_locals)