Sun, 17 Jul 2016 22:40:53 +0200
Breakpoint and Watch and thier basic methods in new classes extracted.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/DebugClients/Python/BreakpointWatch.py Sun Jul 17 22:40:53 2016 +0200 @@ -0,0 +1,314 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the breakpoint and watch class. +""" + +import os + + +class Breakpoint: + """ + Breakpoint class. + + Implements temporary breakpoints, ignore counts, disabling and + (re)-enabling, and conditionals. + + Breakpoints are indexed by the file,line tuple using breaks. It + points to a single Breakpoint instance. This is rather different to + the original bdb, since there may be more than one breakpoint per line. + + To test for a specific line in a file there is another dict breakInFile, + which is indexed only by filename and holds all line numbers where + breakpoints are. + """ + breaks = {} # indexed by (filename, lineno) tuple: Breakpoint + breakInFile = {} # indexed by filename: [lineno] + + def __init__(self, filename, lineno, temporary=0, cond=None): + """ + Constructor + + @param filename the filename where a breakpoint is set + @type str + @param lineno the line number of the breakpoint + @type int + @keyparam temporary flag to indicate a temporary breakpoint + @type int + @keyparam cond Python expression which dynamically enables this bp + @type str + """ + filename = os.path.abspath(filename) + self.file = filename + self.line = lineno + self.temporary = temporary + self.cond = cond + self.enabled = 1 + self.ignore = 0 + self.hits = 0 + self.breaks[filename, lineno] = self + lines = self.breakInFile.setdefault(filename, []) + if lineno not in lines: + lines.append(lineno) + + def deleteMe(self): + """ + Public method to clear this breakpoint. + """ + try: + del self.breaks[(self.file, self.line)] + self.breakInFile[self.file].remove(self.line) + if not self.breakInFile[self.file]: + del self.breakInFile[self.file] + except KeyError: + pass + + def enable(self): + """ + Public method to enable this breakpoint. + """ + self.enabled = 1 + + def disable(self): + """ + Public method to disable this breakpoint. + """ + self.enabled = 0 + + @staticmethod + def clear_break(filename, lineno): + """ + Public method reimplemented from bdb.py to clear a breakpoint. + + @param filename the filename of the bp to retrieve + @type str + @param lineno the linenumber of the bp to retrieve + @type int + """ + bp = Breakpoint.breaks.get((filename, lineno)) + if bp: + bp.deleteMe() + + @staticmethod + def get_break(filename, lineno): + """ + Public method to get the breakpoint of a particular line. + + Because eric6 supports only one breakpoint per line, this + method will return only one breakpoint. + + @param filename the filename of the bp to retrieve + @type str + @param lineno the linenumber of the bp to retrieve + @type int + @return Breakpoint or None, if there is no bp + @rtype Breakpoint object or None + """ + return Breakpoint.breaks.get(filename, lineno) + + @staticmethod + def effectiveBreak(filename, lineno, frame): + """ + Public method to determine which breakpoint for this filename:lineno + is to be acted upon. + + Called only if we know there is a bpt at this + location. Returns breakpoint that was triggered and a flag + that indicates if it is ok to delete a temporary bp. + + @param filename the filename of the bp to retrieve + @type str + @param lineno the linenumber of the bp to retrieve + @type int + @param frame the current execution frame + @type frame object + @return tuple of Breakpoint and a flag to indicate, that a + temporary breakpoint may be deleted + @rtype tuple of Breakpoint, bool + """ + b = Breakpoint.breaks[filename, lineno] + if b.enabled == 0: + return (None, 0) + + # Count every hit when bp is enabled + b.hits += 1 + if not b.cond: + # If unconditional, and ignoring, + # go on to next, else break + if b.ignore > 0: + b.ignore -= 1 + return (None, 0) + else: + # breakpoint and marker that's ok + # to delete if temporary + return (b, 1) + else: + # Conditional bp. + # Ignore count applies only to those bpt hits where the + # condition evaluates to true. + try: + val = eval(b.cond, frame.f_globals, frame.f_locals) + if val: + if b.ignore > 0: + b.ignore -= 1 + # continue + else: + return (b, 1) + # else: + # continue + except Exception: + # if eval fails, most conservative + # thing is to stop on breakpoint + # regardless of ignore count. + # Don't delete temporary, + # as another hint to user. + return (b, 0) + return (None, 0) + + +class Watch: + """ + Watch class. + + Implements temporary watches, ignore counts, disabling and + (re)-enabling, and conditionals. + """ + watches = [] + + def __init__(self, cond, compiledCond, flag, temporary=0): + """ + Constructor + + @param cond condition as string with flag + @type str + @param compiledCond precompiled condition + @type code object + @param flag indicates type of watch (created or changed) + @type str + @keyparam temporary flag for temporary watches + @type int + """ + # Should not occur + if not cond: + return + + self.cond = cond + self.compiledCond = compiledCond + self.temporary = temporary + + self.enabled = 1 + self.ignore = 0 + + self.created = False + self.changed = False + if flag == '??created??': + self.created = True + elif flag == '??changed??': + self.changed = True + + self.values = {} + self.watches.append(self) + + def deleteMe(self): + """ + Public method to clear this watch expression. + """ + try: + del self.watches[self] + except ValueError: + pass + + def enable(self): + """ + Public method to enable this watch. + """ + self.enabled = 1 + + def disable(self): + """ + Public method to disable this watch. + """ + self.enabled = 0 + + @staticmethod + def clear_watch(cond): + """ + Public method to clear a watch expression. + + @param cond expression of the watch expression to be cleared + @type str + """ + try: + Watch.watches.remove(Watch.get_watch(cond)) + except ValueError: + pass + + @staticmethod + def get_watch(cond): + """ + Public method to get a watch expression. + + @param cond expression of the watch expression to be cleared + @type str + @return reference to the watch point + @rtype Watch or None + """ + for b in Watch.watches: + if b.cond == cond: + return b + + @staticmethod + def effectiveWatch(frame): + """ + Public method to determine, if a watch expression is effective. + + @param frame the current execution frame + @type frame object + @return tuple of watch expression and a flag to indicate, that a + temporary watch expression may be deleted + @rtype tuple of Watch, int + """ + for b in Watch.watches: + if b.enabled == 0: + continue + try: + val = eval(b.compiledCond, frame.f_globals, frame.f_locals) + if b.created: + if frame in b.values: + continue + else: + b.values[frame] = [1, val, b.ignore] + return (b, 1) + + elif b.changed: + try: + if b.values[frame][1] != val: + b.values[frame][1] = val + else: + continue + except KeyError: + b.values[frame] = [1, val, b.ignore] + + if b.values[frame][2] > 0: + b.values[frame][2] -= 1 + continue + else: + return (b, 1) + + elif val: + if b.ignore > 0: + b.ignore -= 1 + continue + else: + return (b, 1) + except Exception: + continue + return (None, 0) + + +# +# eflag: FileType = Python2 +# eflag: noqa = M601, M702
--- a/DebugClients/Python/DebugBase.py Sat Jul 02 21:58:09 2016 +0200 +++ b/DebugClients/Python/DebugBase.py Sun Jul 17 22:40:53 2016 +0200 @@ -17,6 +17,7 @@ from DebugProtocol import ResponseClearWatch, ResponseClearBreak, \ ResponseLine, ResponseSyntax, ResponseException, CallTrace +from BreakpointWatch import Breakpoint, Watch gRecursionLimit = 64 @@ -69,7 +70,6 @@ self._dbgClient = dbgClient self._mainThread = 1 - self.breaks = self._dbgClient.breakpoints self.tracePythonLibs(0) self.__isBroken = False @@ -261,7 +261,9 @@ self.botframe = frame.f_back return self.trace_dispatch - if not (self.stop_here(frame) or self.break_anywhere(frame)): + if not (self.stop_here(frame) or + self.break_anywhere(frame) or + Watch.watches != []): # No need to trace this function return if self.quitting: @@ -371,120 +373,6 @@ self._fnCache[fn] = fixedName return fixedName - def set_watch(self, cond, temporary=0): - """ - Public method to set a watch expression. - - @param cond expression of the watch expression (string) - @param temporary flag indicating a temporary watch expression (boolean) - """ - bp = bdb.Breakpoint("Watch", 0, temporary, cond) - if cond.endswith('??created??') or cond.endswith('??changed??'): - bp.condition, bp.special = cond.split() - else: - bp.condition = cond - bp.special = "" - bp.values = {} - if "Watch" not in self.breaks: - self.breaks["Watch"] = 1 - else: - self.breaks["Watch"] += 1 - - def clear_watch(self, cond): - """ - Public method to clear a watch expression. - - @param cond expression of the watch expression to be cleared (string) - """ - try: - possibles = bdb.Breakpoint.bplist["Watch", 0] - for i in range(0, len(possibles)): - b = possibles[i] - if b.cond == cond: - b.deleteMe() - self.breaks["Watch"] -= 1 - if self.breaks["Watch"] == 0: - del self.breaks["Watch"] - break - except KeyError: - pass - - def get_watch(self, cond): - """ - Public method to get a watch expression. - - @param cond expression of the watch expression to be cleared (string) - @return reference to the watch point - """ - possibles = bdb.Breakpoint.bplist["Watch", 0] - for i in range(0, len(possibles)): - b = possibles[i] - if b.cond == cond: - return b - - def __do_clearWatch(self, cond): - """ - Private method called to clear a temporary watch expression. - - @param cond expression of the watch expression to be cleared (string) - """ - self.clear_watch(cond) - self._dbgClient.write('%s%s\n' % (ResponseClearWatch, cond)) - - def __effective(self, frame): - """ - Private method to determine, if a watch expression is effective. - - @param frame the current execution frame - @return tuple of watch expression and a flag to indicate, that a - temporary watch expression may be deleted (bdb.Breakpoint, boolean) - """ - possibles = bdb.Breakpoint.bplist["Watch", 0] - for i in range(0, len(possibles)): - b = possibles[i] - if b.enabled == 0: - continue - if not b.cond: - # watch expression without expression shouldn't occur, - # just ignore it - continue - try: - val = eval(b.condition, frame.f_globals, frame.f_locals) - if b.special: - if b.special == '??created??': - if b.values[frame][0] == 0: - b.values[frame][0] = 1 - b.values[frame][1] = val - return (b, 1) - else: - continue - b.values[frame][0] = 1 - if b.special == '??changed??': - if b.values[frame][1] != val: - b.values[frame][1] = val - if b.values[frame][2] > 0: - b.values[frame][2] -= 1 - continue - else: - return (b, 1) - else: - continue - continue - if val: - if b.ignore > 0: - b.ignore -= 1 - continue - else: - return (b, 1) - except Exception: - if b.special: - try: - b.values[frame][0] = 0 - except KeyError: - b.values[frame] = [0, None, b.ignore] - continue - return (None, None) - def break_here(self, frame): """ Public method reimplemented from bdb.py to fix the filename from the @@ -493,33 +381,29 @@ See fix_frame_filename for more info. @param frame the frame object - @return flag indicating the break status (boolean) + @type frame object + @return flag indicating the break status + @rtype bool """ filename = self.fix_frame_filename(frame) - if filename not in self.breaks and "Watch" not in self.breaks: - return 0 - - if filename in self.breaks: - lineno = frame.f_lineno - if lineno in self.breaks[filename]: + if (filename, frame.f_lineno) in Breakpoint.breaks: + bp, flag = Breakpoint.effectiveBreak( + filename, frame.f_lineno, frame) + if bp: # flag says ok to delete temp. bp - (bp, flag) = bdb.effective(filename, lineno, frame) - if bp: - self.currentbp = bp.number - if (flag and bp.temporary): - self.__do_clear(filename, lineno) - return 1 + if flag and bp.temporary: + self.__do_clearBreak(filename, frame.f_lineno) + return True - if "Watch" in self.breaks: - # flag says ok to delete temp. bp - (bp, flag) = self.__effective(frame) + if Watch.watches != []: + bp, flag = Watch.effectiveWatch(frame) if bp: - self.currentbp = bp.number - if (flag and bp.temporary): + # flag says ok to delete temp. watch + if flag and bp.temporary: self.__do_clearWatch(bp.cond) - return 1 + return True - return 0 + return False def break_anywhere(self, frame): """ @@ -529,81 +413,35 @@ (see fix_frame_filename for more info). @param frame the frame object - @return flag indicating the break status (boolean) - """ - return \ - self.fix_frame_filename(frame) in self.breaks or \ - ("Watch" in self.breaks and self.breaks["Watch"]) - - def set_break(self, filename, lineno, temporary=0, cond=None, - funcname=None): - """ - Public method reimplemented from bdb.py to normalize the filename and - set as a breakpoint. - - @param filename the filename where a breakpoint is set - @type str - @param lineno the line number of the breakpoint - @type int - @keyparam temporary flag to indicate a temporary breakpoint - @type int - @keyparam cond Python expression which dynamically enables this bp - @type str - @keyparam funcname name of the function (unused) - @type str or None - """ - filename = os.path.abspath(filename) - list = self.breaks.setdefault(filename, []) - if lineno not in list: - list.append(lineno) - bdb.Breakpoint(filename, lineno, temporary, cond, funcname) - - def get_break(self, filename, lineno): + @type frame object + @return flag indicating the break status + @rtype bool """ - Public method reimplemented from bdb.py to get the first breakpoint of - a particular line. - - Because eric6 supports only one breakpoint per line, this overwritten - method will return this one and only breakpoint. - - @param filename the filename of the bp to retrieve - @type str - @param lineno the linenumber of the bp to retrieve - @type int - @return breakpoint or None, if there is no bp - @rtype breakpoint object or None - """ - return (lineno in self.breaks.get(filename, []) and - bdb.Breakpoint.bplist[filename, lineno][0] or None) - - def clear_break(self, filename, lineno): - """ - Public method reimplemented from bdb.py to clear a breakpoint. - - @param filename the filename of the bp to retrieve - @type str - @param lineno the linenumber of the bp to retrieve - @type int - """ - if (filename, lineno) not in bdb.Breakpoint.bplist: - return - # If there's only one bp in the list for that file,line - # pair, then remove the breaks entry - for bp in bdb.Breakpoint.bplist[filename, lineno][:]: - bp.deleteMe() - self._prune_breaks(filename, lineno) + return self.fix_frame_filename(frame) in Breakpoint.breakInFile - def __do_clear(self, filename, lineno): + def __do_clearBreak(self, filename, lineno): """ Private method called to clear a temporary breakpoint. @param filename name of the file the bp belongs to + @type str @param lineno linenumber of the bp + @type int """ - self.clear_break(filename, lineno) + Breakpoint.clear_break(filename, lineno) self._dbgClient.write('%s%s,%d\n' % (ResponseClearBreak, filename, lineno)) + def __do_clearWatch(self, cond): + """ + Private method called to clear a temporary watch expression. + + @param cond expression of the watch expression to be cleared + @type str + """ + Watch.clear_watch(cond) + self._dbgClient.write('%s%s\n' % (ResponseClearWatch, cond)) + def getStack(self): """ Public method to get the stack. @@ -887,6 +725,7 @@ """ return self.__isBroken + # # eflag: FileType = Python2 # eflag: noqa = M601, M702
--- a/DebugClients/Python/DebugClientBase.py Sat Jul 02 21:58:09 2016 +0200 +++ b/DebugClients/Python/DebugClientBase.py Sun Jul 17 22:40:53 2016 +0200 @@ -27,6 +27,7 @@ from AsyncFile import AsyncFile, AsyncPendingWrite from DebugConfig import ConfigVarTypeStrings from FlexCompleter import Completer +from BreakpointWatch import Breakpoint, Watch DebugClientInstance = None @@ -687,9 +688,9 @@ (DebugProtocol.ResponseBPConditionError, fn, line)) return - self.mainThread.set_break(fn, line, temporary, cond) + Breakpoint(fn, line, temporary, cond) else: - self.mainThread.clear_break(fn, line) + Breakpoint.clear_break(fn, line) return @@ -724,19 +725,24 @@ cond, temporary, set = arg.split('@@') set = int(set) temporary = int(temporary) - + + if cond.endswith(('??created??', '??changed??')): + compiledCond, flag = cond.split() + else: + compiledCond = cond + flag = '' + + try: + compiledCond = compile(compiledCond, '<string>', 'eval') + except SyntaxError: + self.write('%s%s\n' % ( + DebugProtocol.ResponseWPConditionError, cond)) + return + if set: - if not cond.endswith('??created??') and \ - not cond.endswith('??changed??'): - try: - compile(cond, '<string>', 'eval') - except SyntaxError: - self.write('%s%s\n' % ( - DebugProtocol.ResponseWPConditionError, cond)) - return - self.mainThread.set_watch(cond, temporary) + Watch(cond, compiledCond, flag, temporary) else: - self.mainThread.clear_watch(cond) + Watch.clear_watch(cond) return @@ -744,7 +750,7 @@ cond, enable = arg.split(',') enable = int(enable) - bp = self.mainThread.get_watch(cond) + bp = Watch.get_watch(cond) if bp is not None: if enable: bp.enable() @@ -757,7 +763,7 @@ cond, count = arg.split(',') count = int(count) - bp = self.mainThread.get_watch(cond) + bp = Watch.get_watch(cond) if bp is not None: bp.ignore = count
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/DebugClients/Python3/BreakpointWatch.py Sun Jul 17 22:40:53 2016 +0200 @@ -0,0 +1,313 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2016 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the breakpoint and watch class. +""" + +import os + + +class Breakpoint: + """ + Breakpoint class. + + Implements temporary breakpoints, ignore counts, disabling and + (re)-enabling, and conditionals. + + Breakpoints are indexed by the file,line tuple using breaks. It + points to a single Breakpoint instance. This is rather different to + the original bdb, since there may be more than one breakpoint per line. + + To test for a specific line in a file there is another dict breakInFile, + which is indexed only by filename and holds all line numbers where + breakpoints are. + """ + breaks = {} # indexed by (filename, lineno) tuple: Breakpoint + breakInFile = {} # indexed by filename: [lineno] + + def __init__(self, filename, lineno, temporary=0, cond=None): + """ + Constructor + + @param filename the filename where a breakpoint is set + @type str + @param lineno the line number of the breakpoint + @type int + @keyparam temporary flag to indicate a temporary breakpoint + @type int + @keyparam cond Python expression which dynamically enables this bp + @type str + """ + filename = os.path.abspath(filename) + self.file = filename + self.line = lineno + self.temporary = temporary + self.cond = cond + self.enabled = 1 + self.ignore = 0 + self.hits = 0 + self.breaks[filename, lineno] = self + lines = self.breakInFile.setdefault(filename, []) + if lineno not in lines: + lines.append(lineno) + + def deleteMe(self): + """ + Public method to clear this breakpoint. + """ + try: + del self.breaks[(self.file, self.line)] + self.breakInFile[self.file].remove(self.line) + if not self.breakInFile[self.file]: + del self.breakInFile[self.file] + except KeyError: + pass + + def enable(self): + """ + Public method to enable this breakpoint. + """ + self.enabled = 1 + + def disable(self): + """ + Public method to disable this breakpoint. + """ + self.enabled = 0 + + @staticmethod + def clear_break(filename, lineno): + """ + Public method reimplemented from bdb.py to clear a breakpoint. + + @param filename the filename of the bp to retrieve + @type str + @param lineno the linenumber of the bp to retrieve + @type int + """ + bp = Breakpoint.breaks.get((filename, lineno)) + if bp: + bp.deleteMe() + + @staticmethod + def get_break(filename, lineno): + """ + Public method to get the breakpoint of a particular line. + + Because eric6 supports only one breakpoint per line, this + method will return only one breakpoint. + + @param filename the filename of the bp to retrieve + @type str + @param lineno the linenumber of the bp to retrieve + @type int + @return Breakpoint or None, if there is no bp + @rtype Breakpoint object or None + """ + return Breakpoint.breaks.get(filename, lineno) + + @staticmethod + def effectiveBreak(filename, lineno, frame): + """ + Public method to determine which breakpoint for this filename:lineno + is to be acted upon. + + Called only if we know there is a bpt at this + location. Returns breakpoint that was triggered and a flag + that indicates if it is ok to delete a temporary bp. + + @param filename the filename of the bp to retrieve + @type str + @param lineno the linenumber of the bp to retrieve + @type int + @param frame the current execution frame + @type frame object + @return tuple of Breakpoint and a flag to indicate, that a + temporary breakpoint may be deleted + @rtype tuple of Breakpoint, bool + """ + b = Breakpoint.breaks[filename, lineno] + if b.enabled == 0: + return (None, 0) + + # Count every hit when bp is enabled + b.hits += 1 + if not b.cond: + # If unconditional, and ignoring, + # go on to next, else break + if b.ignore > 0: + b.ignore -= 1 + return (None, 0) + else: + # breakpoint and marker that's ok + # to delete if temporary + return (b, 1) + else: + # Conditional bp. + # Ignore count applies only to those bpt hits where the + # condition evaluates to true. + try: + val = eval(b.cond, frame.f_globals, frame.f_locals) + if val: + if b.ignore > 0: + b.ignore -= 1 + # continue + else: + return (b, 1) + # else: + # continue + except Exception: + # if eval fails, most conservative + # thing is to stop on breakpoint + # regardless of ignore count. + # Don't delete temporary, + # as another hint to user. + return (b, 0) + return (None, 0) + + +class Watch: + """ + Watch class. + + Implements temporary watches, ignore counts, disabling and + (re)-enabling, and conditionals. + """ + watches = [] + + def __init__(self, cond, compiledCond, flag, temporary=0): + """ + Constructor + + @param cond condition as string with flag + @type str + @param compiledCond precompiled condition + @type code object + @param flag indicates type of watch (created or changed) + @type str + @keyparam temporary flag for temporary watches + @type int + """ + # Should not occur + if not cond: + return + + self.cond = cond + self.compiledCond = compiledCond + self.temporary = temporary + + self.enabled = 1 + self.ignore = 0 + + self.created = False + self.changed = False + if flag == '??created??': + self.created = True + elif flag == '??changed??': + self.changed = True + + self.values = {} + self.watches.append(self) + + def deleteMe(self): + """ + Public method to clear this watch expression. + """ + try: + del self.watches[self] + except ValueError: + pass + + def enable(self): + """ + Public method to enable this watch. + """ + self.enabled = 1 + + def disable(self): + """ + Public method to disable this watch. + """ + self.enabled = 0 + + @staticmethod + def clear_watch(cond): + """ + Public method to clear a watch expression. + + @param cond expression of the watch expression to be cleared + @type str + """ + try: + Watch.watches.remove(Watch.get_watch(cond)) + except ValueError: + pass + + @staticmethod + def get_watch(cond): + """ + Public method to get a watch expression. + + @param cond expression of the watch expression to be cleared + @type str + @return reference to the watch point + @rtype Watch or None + """ + for b in Watch.watches: + if b.cond == cond: + return b + + @staticmethod + def effectiveWatch(frame): + """ + Public method to determine, if a watch expression is effective. + + @param frame the current execution frame + @type frame object + @return tuple of watch expression and a flag to indicate, that a + temporary watch expression may be deleted + @rtype tuple of Watch, int + """ + for b in Watch.watches: + if b.enabled == 0: + continue + try: + val = eval(b.compiledCond, frame.f_globals, frame.f_locals) + if b.created: + if frame in b.values: + continue + else: + b.values[frame] = [1, val, b.ignore] + return (b, 1) + + elif b.changed: + try: + if b.values[frame][1] != val: + b.values[frame][1] = val + else: + continue + except KeyError: + b.values[frame] = [1, val, b.ignore] + + if b.values[frame][2] > 0: + b.values[frame][2] -= 1 + continue + else: + return (b, 1) + + elif val: + if b.ignore > 0: + b.ignore -= 1 + continue + else: + return (b, 1) + except Exception: + continue + return (None, 0) + + +# +# eflag: noqa = M702
--- a/DebugClients/Python3/DebugBase.py Sat Jul 02 21:58:09 2016 +0200 +++ b/DebugClients/Python3/DebugBase.py Sun Jul 17 22:40:53 2016 +0200 @@ -18,6 +18,7 @@ from DebugProtocol import ResponseClearWatch, ResponseClearBreak, \ ResponseLine, ResponseSyntax, ResponseException, CallTrace from DebugUtilities import getargvalues, formatargvalues +from BreakpointWatch import Breakpoint, Watch gRecursionLimit = 64 @@ -70,7 +71,6 @@ self._dbgClient = dbgClient self._mainThread = True - self.breaks = self._dbgClient.breakpoints self.tracePythonLibs(0) self.__isBroken = False @@ -262,7 +262,9 @@ self.botframe = frame.f_back return self.trace_dispatch - if not (self.stop_here(frame) or self.break_anywhere(frame)): + if not (self.stop_here(frame) or + self.break_anywhere(frame) or + Watch.watches != []): # No need to trace this function return if self.quitting: @@ -391,121 +393,6 @@ self._fnCache[fn] = fixedName return fixedName - def set_watch(self, cond, temporary=False): - """ - Public method to set a watch expression. - - @param cond expression of the watch expression (string) - @param temporary flag indicating a temporary watch expression (boolean) - """ - bp = bdb.Breakpoint("Watch", 0, temporary, cond) - if cond.endswith('??created??') or cond.endswith('??changed??'): - bp.condition, bp.special = cond.split() - else: - bp.condition = cond - bp.special = "" - bp.values = {} - if "Watch" not in self.breaks: - self.breaks["Watch"] = 1 - else: - self.breaks["Watch"] += 1 - - def clear_watch(self, cond): - """ - Public method to clear a watch expression. - - @param cond expression of the watch expression to be cleared (string) - """ - try: - possibles = bdb.Breakpoint.bplist["Watch", 0] - for i in range(0, len(possibles)): - b = possibles[i] - if b.cond == cond: - b.deleteMe() - self.breaks["Watch"] -= 1 - if self.breaks["Watch"] == 0: - del self.breaks["Watch"] - break - except KeyError: - pass - - def get_watch(self, cond): - """ - Public method to get a watch expression. - - @param cond expression of the watch expression to be cleared (string) - @return reference to the watch point - """ - possibles = bdb.Breakpoint.bplist["Watch", 0] - for i in range(0, len(possibles)): - b = possibles[i] - if b.cond == cond: - return b - - def __do_clearWatch(self, cond): - """ - Private method called to clear a temporary watch expression. - - @param cond expression of the watch expression to be cleared (string) - """ - self.clear_watch(cond) - self._dbgClient.write('{0}{1}\n'.format(ResponseClearWatch, cond)) - - def __effective(self, frame): - """ - Private method to determine, if a watch expression is effective. - - @param frame the current execution frame - @return tuple of watch expression and a flag to indicate, that a - temporary watch expression may be deleted (bdb.Breakpoint, boolean) - """ - possibles = bdb.Breakpoint.bplist["Watch", 0] - for i in range(0, len(possibles)): - b = possibles[i] - if not b.enabled: - continue - if not b.cond: - # watch expression without expression shouldn't occur, - # just ignore it - continue - try: - val = eval(b.condition, frame.f_globals, frame.f_locals) - if b.special: - if b.special == '??created??': - if b.values[frame][0] == 0: - b.values[frame][0] = 1 - b.values[frame][1] = val - return (b, True) - else: - continue - b.values[frame][0] = 1 - if b.special == '??changed??': - if b.values[frame][1] != val: - b.values[frame][1] = val - if b.values[frame][2] > 0: - b.values[frame][2] -= 1 - continue - else: - return (b, True) - else: - continue - continue - if val: - if b.ignore > 0: - b.ignore -= 1 - continue - else: - return (b, True) - except Exception: - if b.special: - try: - b.values[frame][0] = 0 - except KeyError: - b.values[frame] = [0, None, b.ignore] - continue - - return (None, False) - def break_here(self, frame): """ Public method reimplemented from bdb.py to fix the filename from the @@ -514,34 +401,25 @@ See fix_frame_filename for more info. @param frame the frame object - @return flag indicating the break status (boolean) + @type frame object + @return flag indicating the break status + @rtype bool """ filename = self.fix_frame_filename(frame) - if filename not in self.breaks and "Watch" not in self.breaks: - return False - - if filename in self.breaks: - lineno = frame.f_lineno - if lineno not in self.breaks[filename]: - # The line itself has no breakpoint, but maybe the line is the - # first line of a function with breakpoint set by function - # name. - lineno = frame.f_code.co_firstlineno - if lineno in self.breaks[filename]: + if (filename, frame.f_lineno) in Breakpoint.breaks: + bp, flag = Breakpoint.effectiveBreak( + filename, frame.f_lineno, frame) + if bp: # flag says ok to delete temp. bp - (bp, flag) = bdb.effective(filename, lineno, frame) - if bp: - self.currentbp = bp.number - if (flag and bp.temporary): - self.__do_clear(filename, lineno) - return True + if flag and bp.temporary: + self.__do_clearBreak(filename, frame.f_lineno) + return True - if "Watch" in self.breaks: - # flag says ok to delete temp. bp - (bp, flag) = self.__effective(frame) + if Watch.watches != []: + bp, flag = Watch.effectiveWatch(frame) if bp: - self.currentbp = bp.number - if (flag and bp.temporary): + # flag says ok to delete temp. watch + if flag and bp.temporary: self.__do_clearWatch(bp.cond) return True @@ -555,80 +433,34 @@ (see fix_frame_filename for more info). @param frame the frame object - @return flag indicating the break status (boolean) - """ - return \ - self.fix_frame_filename(frame) in self.breaks or \ - ("Watch" in self.breaks and self.breaks["Watch"]) - - def set_break(self, filename, lineno, temporary=0, cond=None, - funcname=None): - """ - Public method reimplemented from bdb.py to normalize the filename and - set as a breakpoint. - - @param filename the filename where a breakpoint is set - @type str - @param lineno the line number of the breakpoint - @type int - @keyparam temporary flag to indicate a temporary breakpoint - @type int - @keyparam cond Python expression which dynamically enables this bp - @type str - @keyparam funcname name of the function (unused) - @type str or None - """ - filename = os.path.abspath(filename) - list = self.breaks.setdefault(filename, []) - if lineno not in list: - list.append(lineno) - bdb.Breakpoint(filename, lineno, temporary, cond, funcname) - - def get_break(self, filename, lineno): + @param frame object + @return flag indicating the break status + @rtype bool """ - Public method reimplemented from bdb.py to get the first breakpoint of - a particular line. - - Because eric6 supports only one breakpoint per line, this overwritten - method will return this one and only breakpoint. - - @param filename the filename of the bp to retrieve - @type str - @param lineno the linenumber of the bp to retrieve - @type int - @return breakpoint or None, if there is no bp - @rtype breakpoint object or None - """ - return (lineno in self.breaks.get(filename, []) and - bdb.Breakpoint.bplist[filename, lineno][0] or None) - - def clear_break(self, filename, lineno): - """ - Public method reimplemented from bdb.py to clear a breakpoint. - - @param filename the filename of the bp to retrieve - @type str - @param lineno the linenumber of the bp to retrieve - @type int - """ - if (filename, lineno) not in bdb.Breakpoint.bplist: - return - # If there's only one bp in the list for that file,line - # pair, then remove the breaks entry - for bp in bdb.Breakpoint.bplist[filename, lineno][:]: - bp.deleteMe() - self._prune_breaks(filename, lineno) + return self.fix_frame_filename(frame) in Breakpoint.breakInFile - def __do_clear(self, filename, lineno): + def __do_clearBreak(self, filename, lineno): """ Private method called to clear a temporary breakpoint. @param filename name of the file the bp belongs to + @type str @param lineno linenumber of the bp + @type int """ - self.clear_break(filename, lineno) - self._dbgClient.write('{0}{1},{2:d}\n'.format( - ResponseClearBreak, filename, lineno)) + Breakpoint.clear_break(filename, lineno) + self._dbgClient.write('%s%s,%d\n' % (ResponseClearBreak, filename, + lineno)) + + def __do_clearWatch(self, cond): + """ + Private method called to clear a temporary watch expression. + + @param cond expression of the watch expression to be cleared + @type str + """ + Watch.clear_watch(cond) + self._dbgClient.write('%s%s\n' % (ResponseClearWatch, cond)) def getStack(self): """
--- a/DebugClients/Python3/DebugClientBase.py Sat Jul 02 21:58:09 2016 +0200 +++ b/DebugClients/Python3/DebugClientBase.py Sun Jul 17 22:40:53 2016 +0200 @@ -27,6 +27,7 @@ from DebugConfig import ConfigVarTypeStrings from FlexCompleter import Completer from DebugUtilities import getargvalues, formatargvalues +from BreakpointWatch import Breakpoint, Watch DebugClientInstance = None @@ -695,9 +696,9 @@ DebugProtocol.ResponseBPConditionError, fn, line)) return - self.mainThread.set_break(fn, line, temporary, cond) + Breakpoint(fn, line, temporary, cond) else: - self.mainThread.clear_break(fn, line) + Breakpoint.clear_break(fn, line) return @@ -730,19 +731,24 @@ cond, temporary, set = arg.split('@@') set = int(set) temporary = int(temporary) - + + if cond.endswith(('??created??', '??changed??')): + compiledCond, flag = cond.split() + else: + compiledCond = cond + flag = '' + + try: + compiledCond = compile(compiledCond, '<string>', 'eval') + except SyntaxError: + self.write('{0}{1}\n'.format( + DebugProtocol.ResponseWPConditionError, cond)) + return + if set: - if not cond.endswith('??created??') and \ - not cond.endswith('??changed??'): - try: - compile(cond, '<string>', 'eval') - except SyntaxError: - self.write('{0}{1}\n'.format( - DebugProtocol.ResponseWPConditionError, cond)) - return - self.mainThread.set_watch(cond, temporary) + Watch(cond, compiledCond, flag, temporary) else: - self.mainThread.clear_watch(cond) + Watch.clear_watch(cond) return @@ -750,7 +756,7 @@ cond, enable = arg.split(',') enable = int(enable) - bp = self.mainThread.get_watch(cond) + bp = Watch.get_watch(cond) if bp is not None: if enable: bp.enable() @@ -763,7 +769,7 @@ cond, count = arg.split(',') count = int(count) - bp = self.mainThread.get_watch(cond) + bp = Watch.get_watch(cond) if bp is not None: bp.ignore = count
--- a/eric6.e4p Sat Jul 02 21:58:09 2016 +0200 +++ b/eric6.e4p Sun Jul 17 22:40:53 2016 +0200 @@ -28,6 +28,7 @@ <Source>DataViews/__init__.py</Source> <Source>DebugClients/Python/AsyncFile.py</Source> <Source>DebugClients/Python/AsyncIO.py</Source> + <Source>DebugClients/Python/BreakpointWatch.py</Source> <Source>DebugClients/Python/DCTestResult.py</Source> <Source>DebugClients/Python/DebugBase.py</Source> <Source>DebugClients/Python/DebugClient.py</Source> @@ -76,6 +77,7 @@ <Source>DebugClients/Python/getpass.py</Source> <Source>DebugClients/Python3/AsyncFile.py</Source> <Source>DebugClients/Python3/AsyncIO.py</Source> + <Source>DebugClients/Python3/BreakpointWatch.py</Source> <Source>DebugClients/Python3/DCTestResult.py</Source> <Source>DebugClients/Python3/DebugBase.py</Source> <Source>DebugClients/Python3/DebugClient.py</Source>