|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2016 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing the breakpoint and watch class. |
|
8 """ |
|
9 |
|
10 import os |
|
11 |
|
12 |
|
13 class Breakpoint: |
|
14 """ |
|
15 Breakpoint class. |
|
16 |
|
17 Implements temporary breakpoints, ignore counts, disabling and |
|
18 (re)-enabling, and conditionals. |
|
19 |
|
20 Breakpoints are indexed by the file,line tuple using breaks. It |
|
21 points to a single Breakpoint instance. This is rather different to |
|
22 the original bdb, since there may be more than one breakpoint per line. |
|
23 |
|
24 To test for a specific line in a file there is another dict breakInFile, |
|
25 which is indexed only by filename and holds all line numbers where |
|
26 breakpoints are. |
|
27 """ |
|
28 breaks = {} # indexed by (filename, lineno) tuple: Breakpoint |
|
29 breakInFile = {} # indexed by filename: [lineno] |
|
30 breakInFrameCache = {} |
|
31 |
|
32 def __init__(self, filename, lineno, temporary=False, cond=None): |
|
33 """ |
|
34 Constructor |
|
35 |
|
36 @param filename file name where a breakpoint is set |
|
37 @type str |
|
38 @param lineno line number of the breakpoint |
|
39 @type int |
|
40 @keyparam temporary flag to indicate a temporary breakpoint |
|
41 @type bool |
|
42 @keyparam cond Python expression which dynamically enables this bp |
|
43 @type str |
|
44 """ |
|
45 filename = os.path.abspath(filename) |
|
46 self.file = filename |
|
47 self.line = lineno |
|
48 self.temporary = temporary |
|
49 self.cond = cond |
|
50 self.enabled = True |
|
51 self.ignore = 0 |
|
52 self.hits = 0 |
|
53 self.breaks[filename, lineno] = self |
|
54 lines = self.breakInFile.setdefault(filename, []) |
|
55 if lineno not in lines: |
|
56 lines.append(lineno) |
|
57 self.breakInFrameCache.clear() |
|
58 |
|
59 def deleteMe(self): |
|
60 """ |
|
61 Public method to clear this breakpoint. |
|
62 """ |
|
63 try: |
|
64 del self.breaks[(self.file, self.line)] |
|
65 self.breakInFile[self.file].remove(self.line) |
|
66 if not self.breakInFile[self.file]: |
|
67 del self.breakInFile[self.file] |
|
68 except KeyError: |
|
69 pass |
|
70 |
|
71 def enable(self): |
|
72 """ |
|
73 Public method to enable this breakpoint. |
|
74 """ |
|
75 self.enabled = True |
|
76 |
|
77 def disable(self): |
|
78 """ |
|
79 Public method to disable this breakpoint. |
|
80 """ |
|
81 self.enabled = False |
|
82 |
|
83 @staticmethod |
|
84 def clear_break(filename, lineno): |
|
85 """ |
|
86 Public method reimplemented from bdb.py to clear a breakpoint. |
|
87 |
|
88 @param filename file name of the bp to retrieve |
|
89 @type str |
|
90 @param lineno line number of the bp to retrieve |
|
91 @type int |
|
92 """ |
|
93 bp = Breakpoint.breaks.get((filename, lineno)) |
|
94 if bp: |
|
95 bp.deleteMe() |
|
96 Breakpoint.breakInFrameCache.clear() |
|
97 |
|
98 @staticmethod |
|
99 def clear_all_breaks(): |
|
100 """ |
|
101 Public method to clear all breakpoints. |
|
102 """ |
|
103 for bp in Breakpoint.breaks.copy(): |
|
104 bp.deleteMe() |
|
105 Breakpoint.breakInFrameCache.clear() |
|
106 |
|
107 @staticmethod |
|
108 def get_break(filename, lineno): |
|
109 """ |
|
110 Public method to get the breakpoint of a particular line. |
|
111 |
|
112 Because eric6 supports only one breakpoint per line, this |
|
113 method will return only one breakpoint. |
|
114 |
|
115 @param filename file name of the bp to retrieve |
|
116 @type str |
|
117 @param lineno line number of the bp to retrieve |
|
118 @type int |
|
119 @return Breakpoint or None, if there is no bp |
|
120 @rtype Breakpoint object or None |
|
121 """ |
|
122 return Breakpoint.breaks.get((filename, lineno)) |
|
123 |
|
124 @staticmethod |
|
125 def effectiveBreak(filename, lineno, frame): |
|
126 """ |
|
127 Public method to determine which breakpoint for this filename:lineno |
|
128 is to be acted upon. |
|
129 |
|
130 Called only if we know there is a bpt at this |
|
131 location. Returns breakpoint that was triggered and a flag |
|
132 that indicates if it is ok to delete a temporary bp. |
|
133 |
|
134 @param filename file name of the bp to retrieve |
|
135 @type str |
|
136 @param lineno line number of the bp to retrieve |
|
137 @type int |
|
138 @param frame the current execution frame |
|
139 @type frame object |
|
140 @return tuple of Breakpoint and a flag to indicate, that a |
|
141 temporary breakpoint may be deleted |
|
142 @rtype tuple of Breakpoint, bool |
|
143 """ |
|
144 b = Breakpoint.breaks[filename, lineno] |
|
145 if not b.enabled: |
|
146 return (None, False) |
|
147 |
|
148 # Count every hit when bp is enabled |
|
149 b.hits += 1 |
|
150 if not b.cond: |
|
151 # If unconditional, and ignoring, |
|
152 # go on to next, else break |
|
153 if b.ignore > 0: |
|
154 b.ignore -= 1 |
|
155 return (None, False) |
|
156 else: |
|
157 # breakpoint and marker that's ok |
|
158 # to delete if temporary |
|
159 return (b, True) |
|
160 else: |
|
161 # Conditional bp. |
|
162 # Ignore count applies only to those bpt hits where the |
|
163 # condition evaluates to true. |
|
164 try: |
|
165 val = eval(b.cond, frame.f_globals, frame.f_locals) |
|
166 if val: |
|
167 if b.ignore > 0: |
|
168 b.ignore -= 1 |
|
169 # continue |
|
170 else: |
|
171 return (b, True) |
|
172 # else: |
|
173 # continue |
|
174 except Exception: |
|
175 # if eval fails, most conservative |
|
176 # thing is to stop on breakpoint |
|
177 # regardless of ignore count. |
|
178 # Don't delete temporary, |
|
179 # as another hint to user. |
|
180 return (b, False) |
|
181 return (None, False) |
|
182 |
|
183 |
|
184 class Watch: |
|
185 """ |
|
186 Watch class. |
|
187 |
|
188 Implements temporary watches, ignore counts, disabling and |
|
189 (re)-enabling, and conditionals. |
|
190 """ |
|
191 watches = [] |
|
192 |
|
193 def __init__(self, cond, compiledCond, flag, temporary=False): |
|
194 """ |
|
195 Constructor |
|
196 |
|
197 @param cond condition as string with flag |
|
198 @type str |
|
199 @param compiledCond precompiled condition |
|
200 @type code object |
|
201 @param flag indicates type of watch (created or changed) |
|
202 @type str |
|
203 @keyparam temporary flag for temporary watches |
|
204 @type bool |
|
205 """ |
|
206 # Should not occur |
|
207 if not cond: |
|
208 return |
|
209 |
|
210 self.cond = cond |
|
211 self.compiledCond = compiledCond |
|
212 self.temporary = temporary |
|
213 |
|
214 self.enabled = True |
|
215 self.ignore = 0 |
|
216 |
|
217 self.created = False |
|
218 self.changed = False |
|
219 if flag == '??created??': |
|
220 self.created = True |
|
221 elif flag == '??changed??': |
|
222 self.changed = True |
|
223 |
|
224 self.values = {} |
|
225 self.watches.append(self) |
|
226 |
|
227 def deleteMe(self): |
|
228 """ |
|
229 Public method to clear this watch expression. |
|
230 """ |
|
231 try: |
|
232 del self.watches[self] |
|
233 except ValueError: |
|
234 pass |
|
235 |
|
236 def enable(self): |
|
237 """ |
|
238 Public method to enable this watch. |
|
239 """ |
|
240 self.enabled = True |
|
241 |
|
242 def disable(self): |
|
243 """ |
|
244 Public method to disable this watch. |
|
245 """ |
|
246 self.enabled = False |
|
247 |
|
248 @staticmethod |
|
249 def clear_watch(cond): |
|
250 """ |
|
251 Public method to clear a watch expression. |
|
252 |
|
253 @param cond expression of the watch expression to be cleared |
|
254 @type str |
|
255 """ |
|
256 try: |
|
257 Watch.watches.remove(Watch.get_watch(cond)) |
|
258 except ValueError: |
|
259 pass |
|
260 |
|
261 @staticmethod |
|
262 def clear_all_watches(): |
|
263 """ |
|
264 Public method to clear all watch expressions. |
|
265 """ |
|
266 del Watch.watches[:] |
|
267 |
|
268 @staticmethod |
|
269 def get_watch(cond): |
|
270 """ |
|
271 Public method to get a watch expression. |
|
272 |
|
273 @param cond expression of the watch expression to be cleared |
|
274 @type str |
|
275 @return reference to the watch point |
|
276 @rtype Watch or None |
|
277 """ |
|
278 for b in Watch.watches: |
|
279 if b.cond == cond: |
|
280 return b |
|
281 |
|
282 @staticmethod |
|
283 def effectiveWatch(frame): |
|
284 """ |
|
285 Public method to determine, if a watch expression is effective. |
|
286 |
|
287 @param frame the current execution frame |
|
288 @type frame object |
|
289 @return tuple of watch expression and a flag to indicate, that a |
|
290 temporary watch expression may be deleted |
|
291 @rtype tuple of Watch, int |
|
292 """ |
|
293 for b in Watch.watches: |
|
294 if not b.enabled: |
|
295 continue |
|
296 try: |
|
297 val = eval(b.compiledCond, frame.f_globals, frame.f_locals) |
|
298 if b.created: |
|
299 if frame in b.values: |
|
300 continue |
|
301 else: |
|
302 b.values[frame] = [1, val, b.ignore] |
|
303 return (b, True) |
|
304 |
|
305 elif b.changed: |
|
306 try: |
|
307 if b.values[frame][1] != val: |
|
308 b.values[frame][1] = val |
|
309 else: |
|
310 continue |
|
311 except KeyError: |
|
312 b.values[frame] = [1, val, b.ignore] |
|
313 |
|
314 if b.values[frame][2] > 0: |
|
315 b.values[frame][2] -= 1 |
|
316 continue |
|
317 else: |
|
318 return (b, True) |
|
319 |
|
320 elif val: |
|
321 if b.ignore > 0: |
|
322 b.ignore -= 1 |
|
323 continue |
|
324 else: |
|
325 return (b, True) |
|
326 except Exception: |
|
327 continue |
|
328 return (None, False) |
|
329 |
|
330 |
|
331 # |
|
332 # eflag: noqa = M702 |