"""
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)
"""
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)
"""
x = a + b
y = a * b
return x, y, x<y, x>y
def test_function(n, m):
"""
Used for doctesting the notebook debugger.
EXAMPLES::
>>> sage.interacts.debugger.test_function(2, 3)
(5, 6, True, False)
"""
return test_function2(m, n)
class Debug:
"""
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
3
"""
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 instance at 0x...>
"""
import inspect, sys, traceback
try:
self._stack = inspect.getinnerframes(sys.last_traceback)
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, b')")
'm n'
"""
locals = self.curframe().f_locals
globals = self.curframe().f_globals
try:
code = compile(line + '\n', '<stdin>', 'single')
exec code in globals, locals
except:
import sys
t, v = sys.exc_info()[:2]
if type(t) == type(''):
exc_type_name = t
else:
exc_type_name = t.__name__
print '***', 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">devel/sage/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)")
3... # call another function so the stack is bigger
--> ... test_function2(m, n)
...
<hr>> <a href="/src/interacts/debugger.py" target="_new">devel/sage/sage/interacts/debugger.py</a>
"""
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)'
from sagenb.notebook.cell import TRACEBACK
code = code.replace(TRACEBACK, TRACEBACK[:-1])
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">devel/sage/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
"""
from sage.interacts.library import library_interact
from sagenb.notebook.interact import slider, input_box, selector
self._last = None
@library_interact
def dbg(frame = slider(vmin=0, vmax=len(self._stack)-1-5, step_size=1, default=len(self._stack)-1-5, 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)
):
frame = frame + 5
if self._last is None:
self._last = {'command':command, 'button':button, 'lines':lines, 'frame':frame}
if self._last['lines'] != lines:
pass
elif self._last['command'] != command and command.strip():
self.evaluate(command)
elif self._last['frame'] != frame:
self._curframe_index = frame
elif command:
self.evaluate(command)
print '<html><hr>' + self.listing(lines//2) + '</html>'
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.
"""
from sage.plot.plot import EMBEDDED_MODE
if not EMBEDDED_MODE:
print "You should use %debug on the command line."
else:
Debug().interact()