-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathdbg.py
More file actions
143 lines (106 loc) · 5.6 KB
/
dbg.py
File metadata and controls
143 lines (106 loc) · 5.6 KB
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# -*- coding: utf-8 -*-
"""Debug printer, which prints both the expression source code and its value.
Both block and expression variants are provided.
The printing can be customized; see ``dbgprint_block`` and ``dbgprint_expr``.
"""
from ast import Call, Name, Tuple, keyword
from mcpyrate.quotes import macros, q, u, a, h # noqa: F401
from mcpyrate import unparse
from mcpyrate.quotes import is_captured_value
from mcpyrate.walkers import ASTTransformer
from ..dynassign import dyn, make_dynvar
from ..misc import callsite_filename
def dbgprint_block(ks, vs, *, filename=None, lineno=None, sep=", ", **kwargs):
"""Default debug printer for the ``dbg`` macro, block variant.
The default print format looks like::
[/home/developer/codes/foo.py:42] x: 2, y: 3, (17 + 23): 40
Parameters:
``ks``: ``tuple``
expressions as strings
``vs``: ``tuple``
the corresponding values
``filename``: ``str``
filename where the debug print call occurred
``lineno``: number or ``None``
line number where the debug print call occurred
``sep``: ``str``
separator as in built-in ``print``,
used between the expression/value pairs.
``kwargs``: anything
passed through to built-in ``print``
**Implementing a custom debug printer**:
When implementing a custom print function, it **must** accept two
positional arguments, ``ks`` and ``vs``, and two by-name arguments,
``filename`` and ``lineno``.
It may also accept other arguments (see built-in ``print``), or just
``**kwargs`` them through to the built-in ``print``, if you like.
Other arguments are only needed if the print calls in the ``dbg`` sections
of your client code use them. (To be flexible, this default debug printer
supports ``sep`` and passes everything else through.)
The ``lineno`` argument may be ``None`` if the input resulted from macro
expansion and the macro that generated it didn't bother to fill in the
``lineno`` attribute of the AST node.
"""
header = f"[{filename}:{lineno}] "
if "\n" in sep:
print(sep.join(f"{header}{k}: {v}" for k, v in zip(ks, vs)), **kwargs)
else:
print(header + sep.join(f"{k}: {v}" for k, v in zip(ks, vs)), **kwargs)
def dbg_block(body, args):
if args: # custom print function hook
# TODO: add support for Attribute to support using a method as a custom print function
# (the problem is we must syntactically find matches in the AST, and AST nodes don't support comparison)
if type(args[0]) is not Name: # pragma: no cover, let's not test the macro expansion errors.
raise SyntaxError("Custom debug print function must be specified by a bare name")
p = args[0]
pname = p.id # name of the print function as it appears in the user code
else:
p = q[h[dbgprint_block]]
pname = "print" # override standard print function within this block
class DbgBlockTransformer(ASTTransformer):
def transform(self, tree):
if is_captured_value(tree):
return tree # don't recurse!
if type(tree) is Call and type(tree.func) is Name and tree.func.id == pname:
names = [q[u[unparse(node)]] for node in tree.args] # x --> "x"; (1 + 2) --> "(1 + 2)"; ...
names = Tuple(elts=names, lineno=tree.lineno, col_offset=tree.col_offset)
values = Tuple(elts=tree.args, lineno=tree.lineno, col_offset=tree.col_offset)
tree.args = [names, values]
# can't use inspect.stack in the printer itself because we want the line number *before macro expansion*.
tree.keywords += [keyword(arg="filename", value=q[h[callsite_filename]()]),
keyword(arg="lineno", value=(q[u[tree.lineno]] if hasattr(tree, "lineno") else q[None]))]
tree.func = q[a[p]]
return self.generic_visit(tree)
return DbgBlockTransformer().visit(body)
def dbgprint_expr(k, v, *, filename, lineno):
"""Default debug printer for the ``dbg`` macro, expression variant.
The default print format looks like::
[/home/developer/codes/foo.py:42] (17 + 23): 40
Parameters:
``k``: ``str``
the expression source code
``v``: anything
the corresponding value
``filename``: ``str``
filename of the expression being debug-printed
``lineno``: number or ``None``
line number of the expression being debug-printed
**Implementing a custom debug printer**:
When implementing a custom print function, it **must** accept two
positional arguments, ``k`` and ``v``, and two by-name arguments,
``filename`` and ``lineno``.
It **must** return ``v``, because the ``dbg[]`` macro replaces the
original expression with the print call.
The ``lineno`` argument may be ``None`` if the input expression resulted
from macro expansion and the macro that generated it didn't bother to
fill in the ``lineno`` attribute of the AST node.
"""
print(f"[{filename}:{lineno}] {k}: {v}")
return v # IMPORTANT! (passthrough; debug printing is a side effect)
def dbg_expr(tree):
ln = q[u[tree.lineno]] if hasattr(tree, "lineno") else q[None]
filename = q[h[callsite_filename]()]
# Careful here! We must `h[]` the `dyn`, but not `dbgprint_expr` itself,
# because we want to look up that attribute dynamically.
return q[h[dyn].dbgprint_expr(u[unparse(tree)], a[tree], filename=a[filename], lineno=a[ln])]
make_dynvar(dbgprint_expr=dbgprint_expr)