DebugClients/Python/DebugBase.py

branch
jsonrpc
changeset 5132
a094eee9f862
parent 4683
1ba6ba86b383
--- a/DebugClients/Python/DebugBase.py	Sat Sep 03 18:01:19 2016 +0200
+++ b/DebugClients/Python/DebugBase.py	Sat Sep 03 18:02:37 2016 +0200
@@ -14,9 +14,7 @@
 import atexit
 import inspect
 import ctypes
-
-from DebugProtocol import ResponseClearWatch, ResponseClearBreak, \
-    ResponseLine, ResponseSyntax, ResponseException, CallTrace
+from inspect import CO_GENERATOR
 
 gRecursionLimit = 64
 
@@ -57,7 +55,7 @@
         bdb.Bdb.__init__(self)
 
         self._dbgClient = dbgClient
-        self._mainThread = 1
+        self._mainThread = True
         
         self.breaks = self._dbgClient.breakpoints
         
@@ -200,6 +198,7 @@
             if not self.__skip_it(fromFrame) and not self.__skip_it(toFrame):
                 if event in ["call", "return"]:
                     fr = fromFrame
+                    # TODO: change from and to info to a dictionary
                     fromStr = "%s:%s:%s" % (
                         self._dbgClient.absPath(self.fix_frame_filename(fr)),
                         fr.f_lineno,
@@ -209,8 +208,7 @@
                         self._dbgClient.absPath(self.fix_frame_filename(fr)),
                         fr.f_lineno,
                         fr.f_code.co_name)
-                    self._dbgClient.write("%s%s@@%s@@%s\n" % (
-                        CallTrace, event[0], fromStr, toStr))
+                    self._dbgClient.sendCallTrace(event, fromStr, toStr)
     
     def trace_dispatch(self, frame, event, arg):
         """
@@ -279,6 +277,9 @@
         @exception bdb.BdbQuit raised to indicate the end of the debug session
         """
         if self.stop_here(frame) or frame == self.returnframe:
+            # Ignore return events in generator except when stepping.
+            if self.stopframe and frame.f_code.co_flags & CO_GENERATOR:
+                return self.trace_dispatch
             self.user_return(frame, arg)
             if self.quitting and not self._dbgClient.passive:
                 raise bdb.BdbQuit
@@ -294,9 +295,27 @@
         @exception bdb.BdbQuit raised to indicate the end of the debug session
         """
         if not self.__skip_it(frame):
+            # When stepping with next/until/return in a generator frame,
+            # skip the internal StopIteration exception (with no traceback)
+            # triggered by a subiterator run with the 'yield from'
+            # statement.
+            if not (frame.f_code.co_flags & CO_GENERATOR and
+                    arg[0] is StopIteration and arg[2] is None):
+                self.user_exception(frame, arg)
+                if self.quitting:
+                    raise bdb.BdbQuit
+        
+        # Stop at the StopIteration or GeneratorExit exception when the user
+        # has set stopframe in a generator by issuing a return command, or a
+        # next/until command at the last statement in the generator before the
+        # exception.
+        elif (self.stopframe and frame is not self.stopframe and
+                self.stopframe.f_code.co_flags & CO_GENERATOR and
+                arg[0] in (StopIteration, GeneratorExit)):
             self.user_exception(frame, arg)
             if self.quitting:
                 raise bdb.BdbQuit
+        
         return self.trace_dispatch
 
     def set_trace(self, frame=None):
@@ -318,9 +337,9 @@
         # Modified version of the one found in bdb.py
         # Here we only set a new stop frame if it is a normal continue.
         if not special:
-            self.stopframe = self.botframe
-        self.returnframe = None
-        self.quitting = 0
+            self._set_stopinfo(self.botframe, None)
+        else:
+            self._set_stopinfo(self.stopframe, None)
 
     def set_quit(self):
         """
@@ -355,10 +374,14 @@
            frame.f_globals['__file__'] and \
            frame.f_globals['__file__'] == frame.f_code.co_filename:
             root, ext = os.path.splitext(frame.f_globals['__file__'])
-            if ext == '.pyc' or ext == '.py' or ext == '.pyo':
+            if ext in ['.pyc', '.py', '.py2', '.pyo']:
                 fixedName = root + '.py'
                 if os.path.exists(fixedName):
                     return fixedName
+                
+                fixedName = root + '.py2'
+                if os.path.exists(fixedName):
+                    return fixedName
 
         return frame.f_code.co_filename
 
@@ -420,7 +443,7 @@
         @param cond expression of the watch expression to be cleared (string)
         """
         self.clear_watch(cond)
-        self._dbgClient.write('%s%s\n' % (ResponseClearWatch, cond))
+        self._dbgClient.sendClearTemporaryWatch(cond)
 
     def __effective(self, frame):
         """
@@ -446,7 +469,7 @@
                         if b.values[frame][0] == 0:
                             b.values[frame][0] = 1
                             b.values[frame][1] = val
-                            return (b, 1)
+                            return (b, True)
                         else:
                             continue
                     b.values[frame][0] = 1
@@ -457,7 +480,7 @@
                                 b.values[frame][2] -= 1
                                 continue
                             else:
-                                return (b, 1)
+                                return (b, True)
                         else:
                             continue
                     continue
@@ -474,7 +497,7 @@
                     except KeyError:
                         b.values[frame] = [0, None, b.ignore]
                 continue
-        return (None, None)
+        return (None, False)
     
     def break_here(self, frame):
         """
@@ -488,29 +511,34 @@
         """
         filename = self.canonic(self.fix_frame_filename(frame))
         if filename not in self.breaks and "Watch" not in self.breaks:
-            return 0
+            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]:
-                # flag says ok to delete temp. bp
+                # flag says ok to delete temp. breakpoint
                 (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
+                    return True
         
         if "Watch" in self.breaks:
-            # flag says ok to delete temp. bp
+            # flag says ok to delete temp. watch
             (bp, flag) = self.__effective(frame)
             if bp:
                 self.currentbp = bp.number
                 if (flag and bp.temporary):
                     self.__do_clearWatch(bp.cond)
-                return 1
+                return True
         
-        return 0
+        return False
 
     def break_anywhere(self, frame):
         """
@@ -551,8 +579,7 @@
         @param lineno linenumber of the bp
         """
         self.clear_break(filename, lineno)
-        self._dbgClient.write('%s%s,%d\n' % (ResponseClearBreak, filename,
-                                             lineno))
+        self._dbgClient.sendClearTemporaryBreakpoint(filename, lineno)
 
     def getStack(self):
         """
@@ -612,7 +639,9 @@
                 return
             fr = frame
             while (fr is not None and
-                    fr.f_code != self._dbgClient.handleLine.func_code):
+                   fr.f_code not in [
+                        self._dbgClient.handleLine.func_code,
+                        self._dbgClient.handleJsonCommand.func_code]):
                 self._dbgClient.mainFrame = fr
                 fr = fr.f_back
 
@@ -654,7 +683,7 @@
         
         self.__isBroken = True
         
-        self._dbgClient.write('%s%s\n' % (ResponseLine, unicode(stack)))
+        self._dbgClient.sendResponseLine(stack)
         self._dbgClient.eventLoop()
 
     def user_exception(self, frame, (exctype, excval, exctb), unhandled=0):
@@ -674,29 +703,44 @@
         if exctype in [SystemExit, bdb.BdbQuit]:
             atexit._run_exitfuncs()
             if excval is None:
-                excval = 0
+                exitcode = 0
+                message = ""
             elif isinstance(excval, (unicode, str)):
-                self._dbgClient.write(excval)
-                excval = 1
-            if isinstance(excval, int):
-                self._dbgClient.progTerminated(excval)
+                exitcode = 1
+                message = excval
+            elif isinstance(excval, int):
+                exitcode = excval
+                message = ""
+            elif isinstance(excval, SystemExit):
+                code = excval.code
+                if isinstance(code, (unicode, str)):
+                    exitcode = 1
+                    message = code
+                elif isinstance(code, int):
+                    exitcode = code
+                    message = ""
+                else:
+                    exitcode = 1
+                    message = str(code)
             else:
-                self._dbgClient.progTerminated(excval.code)
+                exitcode = 1
+                message = str(excval)
+            self._dbgClient.progTerminated(exitcode, message)
             return
         
         if exctype in [SyntaxError, IndentationError]:
             try:
-                message, (filename, linenr, charnr, text) = excval
+                message, (filename, lineno, charno, text) = excval
             except ValueError:
-                exclist = []
+                message = ""
+                filename = ""
+                lineno = 0
+                charno = 0
                 realSyntaxError = True
-            else:
-                exclist = [message, [filename, linenr, charnr]]
-                realSyntaxError = os.path.exists(filename)
             
             if realSyntaxError:
-                self._dbgClient.write("%s%s\n" % (ResponseSyntax,
-                                                  unicode(exclist)))
+                self._dbgClient.sendSyntaxError(
+                    message, filename, lineno, charno)
                 self._dbgClient.eventLoop()
                 return
         
@@ -712,11 +756,11 @@
         else:
             exctypetxt = unicode(exctype)
         try:
-            exclist = [exctypetxt,
-                       unicode(excval).encode(self._dbgClient.getCoding())]
+            excvaltxt = unicode(excval).encode(self._dbgClient.getCoding())
         except TypeError:
-            exclist = [exctypetxt, str(excval)]
+            excvaltxt = str(excval)
         
+        stack = []
         if exctb:
             frlist = self.__extract_stack(exctb)
             frlist.reverse()
@@ -746,9 +790,9 @@
                 else:
                     fargs = ""
                 
-                exclist.append([filename, linenr, ffunc, fargs])
+                stack.append([filename, linenr, ffunc, fargs])
         
-        self._dbgClient.write("%s%s\n" % (ResponseException, unicode(exclist)))
+        self._dbgClient.sendException(exctypetxt, excvaltxt, stack)
         
         if exctb is None:
             return
@@ -798,7 +842,7 @@
         @return flag indicating whether the debugger should stop here
         """
         if self.__skip_it(frame):
-            return 0
+            return False
         return bdb.Bdb.stop_here(self, frame)
 
     def __skip_it(self, frame):
@@ -812,32 +856,33 @@
         @return flag indicating whether the debugger should skip this frame
         """
         if frame is None:
-            return 1
+            return True
         
         fn = self.fix_frame_filename(frame)
 
         # Eliminate things like <string> and <stdin>.
         if fn[0] == '<':
-            return 1
+            return True
 
         #XXX - think of a better way to do this.  It's only a convenience for
         #debugging the debugger - when the debugger code is in the current
         #directory.
         if os.path.basename(fn) in [
-            'AsyncFile.py', 'AsyncIO.py',
-            'DebugConfig.py', 'DCTestResult.py',
-            'DebugBase.py', 'DebugClientBase.py',
-            'DebugClientCapabilities.py', 'DebugClient.py',
-            'DebugClientThreads.py', 'DebugProtocol.py',
-            'DebugThread.py', 'FlexCompleter.py',
+            'AsyncFile.py', 'DCTestResult.py',
+            'DebugBase.py', 'DebugClient.py',
+            'DebugClientBase.py',
+            'DebugClientCapabilities.py',
+            'DebugClientThreads.py',
+            'DebugConfig.py', 'DebugThread.py',
+            'DebugUtilities.py', 'FlexCompleter.py',
             'PyProfile.py'] or \
            os.path.dirname(fn).endswith("coverage"):
-            return 1
+            return True
 
         if self._dbgClient.shouldSkip(fn):
-            return 1
+            return True
         
-        return 0
+        return False
     
     def isBroken(self):
         """

eric ide

mercurial