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 Display information when matchers that are bound to variables are called.
32
33 This is possible thanks to a neat trick suggested by Carl Banks on c.l.p
34 '''
35
36 from __future__ import generators, print_function
37 from contextlib import contextmanager
38 from sys import stderr, _getframe
39
40 from lepl.stream.core import s_debug, s_line, s_kargs
41 from lepl.matchers.support import trampoline_matcher_factory
42 from lepl.support.lib import fmt, str
43
44
45 @trampoline_matcher_factory()
46 -def NamedResult(name, matcher, out=stderr):
57
58 def record_success(count, stream_in, result):
59 (value, stream_out) = result
60 count_desc = fmt(' ({0})', count) if count > 1 else ''
61
62 print(fmt('{0}{1} = {2}\n {3} -> {4}',
63 name, count_desc, value,
64 fmt_stream(stream_in), fmt_stream(stream_out)),
65 file=out, end=str('\n'))
66
67 def record_failure(count, stream_in):
68
69 print(fmt('! {0} (after {1} matches)\n {2}', name, count,
70 fmt_stream(stream_in)),
71 file=out, end=str('\n'))
72
73 def match(support, stream):
74 count = 0
75 generator = matcher._match(stream)
76 try:
77 while True:
78 value = yield generator
79 count += 1
80 record_success(count, stream, value)
81 yield value
82 except StopIteration:
83 record_failure(count, stream)
84
85 return match
86
87
88 -def _adjust(text, width, pad=False, left=False):
98
99
100 -def name(name, show_failures=True, width=80, out=stderr):
101
102 left = 3 * width // 5 - 1
103 right = 2 * width // 5 - 1
104
105 def namer(stream_in, matcher):
106 try:
107 (result, stream_out) = matcher()
108 except StopIteration:
109 if show_failures:
110 stream = \
111 _adjust(fmt('stream = {rest}', **s_kargs(stream_in)),
112 right)
113 str_name = _adjust(name, left // 4, True, True)
114 match = _adjust(fmt(' {0} failed', str_name), left, True)
115
116 print(match + ' ' + stream, file=out, end=str('\n'))
117 raise StopIteration
118 else:
119 try:
120 try:
121 rest = fmt('{rest}', **s_kargs(stream_out))
122 except StopIteration:
123 rest = '<EOS>'
124 stream = _adjust(fmt('stream = {0}', rest), right)
125 str_name = _adjust(name, left // 4, True, True)
126 match = _adjust(fmt(' {0} = {1}', str_name, result), left, True)
127
128 print(match + ' ' + stream, file=out, end=str('\n'))
129 return (result, stream_out)
130 except Exception as e:
131 print('Error in trace', file=out, end=str('\n'))
132 print(repr(e), file=out, end=str('\n'))
133 return (result, stream_out)
134
135 return namer
136
137
138 @contextmanager
139 -def TraceVariables(on=True, show_failures=True, width=80, out=stderr):
140 '''
141 Add this as a context (`with TraceVariables():`) and you will see
142 debug logging indicating how variables are bound during matching.
143 '''
144 if on:
145 before = _getframe(2).f_locals.copy()
146 yield None
147 if on:
148 after = _getframe(2).f_locals
149 for key in after:
150 value = after[key]
151 if key not in before or value != before[key]:
152 try:
153 try:
154 value.wrapper.append(name(key, show_failures, width, out))
155 except AttributeError:
156 value.trace_variables = name(key, show_failures, width, out)
157 except:
158 print('Unfortunately the following matchers cannot '
159 'be tracked:', end=str('\n'))
160 print(fmt(' {0} = {1}', key, value), end=str('\n'))
161