Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
""" Interactive Debugger for the Sage Notebook
Start the debugger in the Sage Notebook by running the ``debug()`` function. You can run several debuggers at once.
AUTHOR:
- William Stein (2012) """ from __future__ import print_function from six.moves import range
# Below all tests are done using sage0, which is a pexpect interface # to Sage itself. This allows us to test exploring a stack traceback # using the doctest framework.
def test_function2(a, b): """ Used for doctesting the notebook debugger.
EXAMPLES::
>>> sage.interacts.debugger.test_function2(2, 3) # using normal prompt would confuse tests below. (5, 6, True, False) """
def test_function(n, m,level=10): """ Used for doctesting the notebook debugger.
EXAMPLES::
>>> sage.interacts.debugger.test_function(2, 3) (5, 6, True, False) """ # call another function so the stack is bigger else:
class Debug(object): """ Create a debugger for the most recent stack trace.
NOTES:
- Input is not preparsed. - You can define and work with many debug interacts at the same time.
TESTS::
The current position in the stack frame is self._curframe_index::
sage: a = sage0.eval("sage.interacts.debugger.test_function('n', 'm')") sage: d = sage0('sage.interacts.debugger.Debug()') sage: d._curframe_index 8 """ def __init__(self): """ Create the debugger object from the most recent traceback.
TESTS::
sage: a = sage0.eval("sage.interacts.debugger.test_function('n', 'm')") sage: sage0('sage.interacts.debugger.Debug()') <sage.interacts.debugger.Debug object at 0x...> """ import inspect import sys import traceback try: tb = sys.last_traceback #we strip off the 5 outermost frames, since those relate only to #the notebook, not user code for i in range(5): tb = tb.tb_next self._stack = inspect.getinnerframes(tb) except AttributeError: raise RuntimeError("no traceback has been produced; nothing to debug") self._curframe_index = len(self._stack) - 1
def curframe(self): """ Return the current frame object. This defines the local and global variables at a point in the stack trace, and the code.
OUTPUT:
- frame object
TESTS::
sage: a = sage0.eval("sage.interacts.debugger.test_function('n', 'm')") sage: d = sage0('sage.interacts.debugger.Debug()') sage: d.curframe() <frame object at 0x...> """ return self._stack[self._curframe_index][0]
def evaluate(self, line): """ Evaluate the input string ``line`` in the scope of the current position in the stack.
INPUT:
- ``line`` -- string; the code to exec
OUTPUT:
- string (the output)
TESTS::
sage: _ = sage0.eval("sage.interacts.debugger.test_function('n', 'm')") sage: _ = sage0.eval('d = sage.interacts.debugger.Debug()') sage: sage0.eval("d.evaluate('print(a);print(b)')") 'm\nn' """ locals = self.curframe().f_locals globals = self.curframe().f_globals try: code = compile(line + '\n', '<stdin>', 'single') exec(code, globals, locals) except Exception: import sys t, v = sys.exc_info()[:2] if isinstance(t, type('')): exc_type_name = t else: exc_type_name = t.__name__ print('*** {}: {}'.format(exc_type_name, v))
def listing(self, n=5): """ Return HTML display of the lines (with numbers and a pointer at the current line) in the code listing for the current frame, with `n` lines before and after of context.
INPUT:
- `n` -- integer (default: 5)
OUTPUT:
- list of strings
TESTS::
sage: _ = sage0.eval("sage.interacts.debugger.test_function('n', 'm')") sage: _ = sage0.eval('d = sage.interacts.debugger.Debug()') sage: print(sage0("d.listing(1)")) 2... x = a + b --> ... y = a * b ....: return x, y, x<y, x>y # < to ensure HTML is properly escaped <hr>> <a href="/src/interacts/debugger.py" target="_new">src/sage/interacts/debugger.py</a> sage: print(sage0("d.listing()")) 2... ... ....: x = a + b --> ... y = a * b ....: return x, y, x<y, x>y # < to ensure HTML is properly escaped ... sage: _ = sage0.eval('d._curframe_index -= 1') sage: print(sage0("d.listing(1)")) 4... else: --> ... test_function2(m, n) ... <hr>> <a href="/src/interacts/debugger.py" target="_new">src/sage/interacts/debugger.py</a> """ # TODO: Currently, just as with ipdb on the command line, # there is no support for displaying code in Cython files. # This shouldn't be too hard to add. curframe = self.curframe() filename = curframe.f_code.co_filename lineno = curframe.f_lineno import linecache w = [] for i in range(lineno-n, lineno+n+1): z = linecache.getline(filename, i, curframe.f_globals) if z: w.append(('--> ' if i ==lineno else ' ') + '%-5s'%i + z) code = ''.join(w) if not code.strip(): code = '(code not available)'
# This is a hideous hack to get around how the notebook "works". # If the output of anything contains the string TRACEBACK then # it will get mangled. So we replace TRACEBACK in our code block # by the harmless version with the colon missing. This sucks. from sagenb.notebook.cell import TRACEBACK code = code.replace(TRACEBACK, TRACEBACK[:-1])
# Create a hyperlink to the file, if possible. i = filename.rfind('site-packages/sage') if i != -1: fname = filename[i+len('site-packages/sage')+1:].rstrip('/') file = '<a href="/src/%s" target="_new">src/sage/%s</a>'%(fname,fname) else: file = filename
import cgi t = """%s<hr>> %s"""%(cgi.escape(code), file) return t
def interact(self): """ Start the interact debugger.
TESTS::
sage: _ = sage0.eval("sage.interacts.debugger.test_function('n', 'm')") sage: _ = sage0.eval('d = sage.interacts.debugger.Debug()') sage: _ = sage0.eval('d.interact()') # only works in the notebook """ # We use a library_interact instead of a normal interact here, # since this is an interact in the library, and a normal # "@interact" is all mangled.
from sage.interacts.library import library_interact from sagenb.notebook.interact import slider, input_box, selector
# self._last holds the last state of all controls. This allows # us to deduce which control changed to cause the update, or that # nothing changed, in which case we assume the user requested to # re-evaluate the input box (for some reason -- currently there is # no point in doing so). It is a shortcoming of @interact that # we have to do this. self._last = None
# two sliders and a box to put in commands with an evaluate button. @library_interact def dbg(frame = slider(vmin=0, vmax=len(self._stack)-1, step_size=1, default=len(self._stack)-1, label='stack frame'), lines = slider(vmin=3, vmax=99, step_size=2, default=11, label='lines of context'), command = input_box("", label="", type=str), button = selector(['Evaluate'], label='', buttons=True) ):
if self._last is None: self._last = {'command':command, 'button':button, 'lines':lines, 'frame':frame}
if self._last['lines'] != lines: # they dragged the number-of-lines slider, so done pass elif self._last['command'] != command and command.strip(): # they changed the command, so evaluate that self.evaluate(command) elif self._last['frame'] != frame: # they dragged the frame slider. self._curframe_index = frame elif command: # must have hit the evaluate button self.evaluate(command)
print('<html><hr>{}</html>'.format(self.listing(lines//2))) # save control state for next time around self._last = {'command':command, 'button':button, 'lines':lines, 'frame':frame}
dbg()
def debug(): """ If you get a traceback in the Sage notebook, use the ``debug()`` command to start an interactive debugger. Using %debug on the command line.
Using the debugger you can move up and down the stack frame and evaluate arbitrary code anywhere in the call stack.
.. warning::
If you type in a command, execute it, switch to a different frame and press enter again, nothing happens: it doesn't execute the command in the new frame. You can get the command to execute by hitting the Evaluate button though. This is a fundamental limitation of the Sage notebook in Sage-5.0.
EXAMPLES::
sage: debug() # only works in the notebook You should use %debug on the command line. """ # "EMBEDDED_MODE" is True precisely when the Sage notebook is running. # Must be the command line, so suggest using the IPython debugger. else: # Create the Debug object and make it interactive. Debug().interact() |