DebugClients/Python/BreakpointWatch.py

branch
debugger speed
changeset 5178
878ce843ca9f
parent 5174
8c48f5e0cd92
child 5389
9b1c800daff3
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/DebugClients/Python/BreakpointWatch.py	Mon Sep 19 22:47:52 2016 +0200
@@ -0,0 +1,332 @@
+# -*- 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]
+    breakInFrameCache = {}
+    
+    def __init__(self, filename, lineno, temporary=False, cond=None):
+        """
+        Constructor
+        
+        @param filename file name where a breakpoint is set
+        @type str
+        @param lineno line number of the breakpoint
+        @type int
+        @keyparam temporary flag to indicate a temporary breakpoint
+        @type bool
+        @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 = True
+        self.ignore = 0
+        self.hits = 0
+        self.breaks[filename, lineno] = self
+        lines = self.breakInFile.setdefault(filename, [])
+        if lineno not in lines:
+            lines.append(lineno)
+        self.breakInFrameCache.clear()
+
+    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 = True
+
+    def disable(self):
+        """
+        Public method to disable this breakpoint.
+        """
+        self.enabled = False
+
+    @staticmethod
+    def clear_break(filename, lineno):
+        """
+        Public method reimplemented from bdb.py to clear a breakpoint.
+        
+        @param filename file name of the bp to retrieve
+        @type str
+        @param lineno line number of the bp to retrieve
+        @type int
+        """
+        bp = Breakpoint.breaks.get((filename, lineno))
+        if bp:
+            bp.deleteMe()
+        Breakpoint.breakInFrameCache.clear()
+    
+    @staticmethod
+    def clear_all_breaks():
+        """
+        Public method to clear all breakpoints.
+        """
+        for bp in Breakpoint.breaks.copy():
+            bp.deleteMe()
+        Breakpoint.breakInFrameCache.clear()
+
+    @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 file name of the bp to retrieve
+        @type str
+        @param lineno line number 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 file name of the bp to retrieve
+        @type str
+        @param lineno line number 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 not b.enabled:
+            return (None, False)
+        
+        # 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, False)
+            else:
+                # breakpoint and marker that's ok
+                # to delete if temporary
+                return (b, True)
+        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, True)
+                # 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, False)
+        return (None, False)
+
+
+class Watch:
+    """
+    Watch class.
+
+    Implements temporary watches, ignore counts, disabling and
+    (re)-enabling, and conditionals.
+    """
+    watches = []
+
+    def __init__(self, cond, compiledCond, flag, temporary=False):
+        """
+        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 bool
+        """
+        # Should not occur
+        if not cond:
+            return
+        
+        self.cond = cond
+        self.compiledCond = compiledCond
+        self.temporary = temporary
+        
+        self.enabled = True
+        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 = True
+
+    def disable(self):
+        """
+        Public method to disable this watch.
+        """
+        self.enabled = False
+
+    @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 clear_all_watches():
+        """
+        Public method to clear all watch expressions.
+        """
+        del Watch.watches[:]
+
+    @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 not b.enabled:
+                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, True)
+                    
+                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, True)
+                    
+                elif val:
+                    if b.ignore > 0:
+                        b.ignore -= 1
+                        continue
+                    else:
+                        return (b, True)
+            except Exception:
+                continue
+        return (None, False)
+
+
+#
+# eflag: noqa = M702

eric ide

mercurial