Breakpoint and Watch and thier basic methods in new classes extracted. debugger speed

Sun, 17 Jul 2016 22:40:53 +0200

author
T.Rzepka <Tobias.Rzepka@gmail.com>
date
Sun, 17 Jul 2016 22:40:53 +0200
branch
debugger speed
changeset 5041
f00a4c8bcbbd
parent 5012
be693f11da53
child 5044
630b9f290a77

Breakpoint and Watch and thier basic methods in new classes extracted.

DebugClients/Python/BreakpointWatch.py file | annotate | diff | comparison | revisions
DebugClients/Python/DebugBase.py file | annotate | diff | comparison | revisions
DebugClients/Python/DebugClientBase.py file | annotate | diff | comparison | revisions
DebugClients/Python3/BreakpointWatch.py file | annotate | diff | comparison | revisions
DebugClients/Python3/DebugBase.py file | annotate | diff | comparison | revisions
DebugClients/Python3/DebugClientBase.py file | annotate | diff | comparison | revisions
eric6.e4p file | annotate | diff | comparison | revisions
--- /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>

eric ide

mercurial