eric7/DebugClients/Python/BreakpointWatch.py

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

eric ide

mercurial