src/eric7/DebugClients/Python/BreakpointWatch.py

branch
eric7-maintenance
changeset 9264
18a7312cfdb3
parent 9221
bf71ee032bb4
child 9473
3f23dbf37dbe
equal deleted inserted replaced
9241:d23e9854aea4 9264:18a7312cfdb3
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2016 - 2022 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the breakpoint and watch class.
8 """
9
10 import os
11 import contextlib
12
13
14 class Breakpoint:
15 """
16 Breakpoint class.
17
18 Implements temporary breakpoints, ignore counts, disabling and
19 (re)-enabling, and conditionals.
20
21 Breakpoints are indexed by the file,line tuple using breaks. It
22 points to a single Breakpoint instance. This is rather different to
23 the original bdb, since there may be more than one breakpoint per line.
24
25 To test for a specific line in a file there is another dict breakInFile,
26 which is indexed only by filename and holds all line numbers where
27 breakpoints are.
28 """
29
30 breaks = {} # indexed by (filename, lineno) tuple: Breakpoint
31 breakInFile = {} # indexed by filename: [lineno]
32 breakInFrameCache = {}
33
34 def __init__(self, filename, lineno, temporary=False, cond=None):
35 """
36 Constructor
37
38 @param filename file name where a breakpoint is set
39 @type str
40 @param lineno line number of the breakpoint
41 @type int
42 @param temporary flag to indicate a temporary breakpoint
43 @type bool
44 @param cond Python expression which dynamically enables this bp
45 @type str
46 """
47 filename = os.path.abspath(filename)
48 self.file = filename
49 self.line = lineno
50 self.temporary = temporary
51 self.cond = cond
52 self.enabled = True
53 self.ignore = 0
54 self.hits = 0
55 Breakpoint.breaks[(filename, lineno)] = self
56 lines = Breakpoint.breakInFile.setdefault(filename, [])
57 if lineno not in lines:
58 lines.append(lineno)
59 Breakpoint.breakInFrameCache.clear()
60
61 def deleteMe(self):
62 """
63 Public method to clear this breakpoint.
64 """
65 with contextlib.suppress(KeyError):
66 del Breakpoint.breaks[(self.file, self.line)]
67 Breakpoint.breakInFile[self.file].remove(self.line)
68 if not Breakpoint.breakInFile[self.file]:
69 del Breakpoint.breakInFile[self.file]
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 Static 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 Static method to clear all breakpoints.
102 """
103 Breakpoint.breaks.clear()
104 Breakpoint.breakInFile.clear()
105 Breakpoint.breakInFrameCache.clear()
106
107 @staticmethod
108 def get_break(filename, lineno):
109 """
110 Static method to get the breakpoint of a particular line.
111
112 Because eric 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 Static 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) # secok
166 if val:
167 if b.ignore > 0:
168 b.ignore -= 1
169 # continue
170 else:
171 return (b, True)
172 except Exception:
173 # if eval fails, most conservative
174 # thing is to stop on breakpoint
175 # regardless of ignore count.
176 # Don't delete temporary,
177 # as another hint to user.
178 return (b, False)
179 return (None, False)
180
181
182 class Watch:
183 """
184 Watch class.
185
186 Implements temporary watches, ignore counts, disabling and
187 (re)-enabling, and conditionals.
188 """
189
190 watches = []
191
192 def __init__(self, cond, compiledCond, flag, temporary=False):
193 """
194 Constructor
195
196 @param cond condition as string with flag
197 @type str
198 @param compiledCond precompiled condition
199 @type code object
200 @param flag indicates type of watch (created or changed)
201 @type str
202 @param temporary flag for temporary watches
203 @type bool
204 """
205 # Should not occur
206 if not cond:
207 return
208
209 self.cond = cond
210 self.compiledCond = compiledCond
211 self.temporary = temporary
212
213 self.enabled = True
214 self.ignore = 0
215
216 self.created = False
217 self.changed = False
218 if flag == "??created??":
219 self.created = True
220 elif flag == "??changed??":
221 self.changed = True
222
223 self.values = {}
224 Watch.watches.append(self)
225
226 def deleteMe(self):
227 """
228 Public method to clear this watch expression.
229 """
230 with contextlib.suppress(ValueError):
231 del Watch.watches[self]
232
233 def enable(self):
234 """
235 Public method to enable this watch.
236 """
237 self.enabled = True
238
239 def disable(self):
240 """
241 Public method to disable this watch.
242 """
243 self.enabled = False
244
245 @staticmethod
246 def clear_watch(cond):
247 """
248 Static method to clear a watch expression.
249
250 @param cond expression of the watch expression to be cleared
251 @type str
252 """
253 with contextlib.suppress(ValueError):
254 Watch.watches.remove(Watch.get_watch(cond))
255
256 @staticmethod
257 def clear_all_watches():
258 """
259 Static method to clear all watch expressions.
260 """
261 del Watch.watches[:]
262
263 @staticmethod
264 def get_watch(cond):
265 """
266 Static method to get a watch expression.
267
268 @param cond expression of the watch expression to be cleared
269 @type str
270 @return reference to the watch point
271 @rtype Watch or None
272 """
273 for b in Watch.watches:
274 if b.cond == cond:
275 return b
276
277 return None
278
279 @staticmethod
280 def effectiveWatch(frame):
281 """
282 Static method to determine, if a watch expression is effective.
283
284 @param frame the current execution frame
285 @type frame object
286 @return tuple of watch expression and a flag to indicate, that a
287 temporary watch expression may be deleted
288 @rtype tuple of Watch, int
289 """
290 for b in Watch.watches:
291 if not b.enabled:
292 continue
293 try:
294 val = eval(b.compiledCond, frame.f_globals, frame.f_locals)
295 # secok
296 if b.created:
297 if frame in b.values:
298 continue
299 else:
300 b.values[frame] = [1, val, b.ignore]
301 return (b, True)
302
303 elif b.changed:
304 try:
305 if b.values[frame][1] != val:
306 b.values[frame][1] = val
307 else:
308 continue
309 except KeyError:
310 b.values[frame] = [1, val, b.ignore]
311
312 if b.values[frame][2] > 0:
313 b.values[frame][2] -= 1
314 continue
315 else:
316 return (b, True)
317
318 elif val:
319 if b.ignore > 0:
320 b.ignore -= 1
321 continue
322 else:
323 return (b, True)
324 except Exception: # secok
325 continue
326 return (None, False)

eric ide

mercurial