Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/interacts/debugger.py
8817 views
1
"""
2
Interactive Debugger for the Sage Notebook
3
4
Start the debugger in the Sage Notebook by running the ``debug()``
5
function. You can run several debuggers at once.
6
7
AUTHOR:
8
9
- William Stein (2012)
10
"""
11
# Below all tests are done using sage0, which is a pexpect interface
12
# to Sage itself. This allows us to test exploring a stack traceback
13
# using the doctest framework.
14
15
def test_function2(a, b):
16
"""
17
Used for doctesting the notebook debugger.
18
19
EXAMPLES::
20
21
>>> sage.interacts.debugger.test_function2(2, 3) # using normal prompt would confuse tests below.
22
(5, 6, True, False)
23
"""
24
x = a + b
25
y = a * b
26
return x, y, x<y, x>y # < to ensure HTML is properly escaped
27
28
def test_function(n, m,level=10):
29
"""
30
Used for doctesting the notebook debugger.
31
32
EXAMPLES::
33
34
>>> sage.interacts.debugger.test_function(2, 3)
35
(5, 6, True, False)
36
"""
37
# call another function so the stack is bigger
38
if level > 0:
39
return test_function(n,m,level=level-1)
40
else:
41
return test_function2(m, n)
42
43
44
class Debug:
45
"""
46
Create a debugger for the most recent stack trace.
47
48
NOTES:
49
50
- Input is not preparsed.
51
- You can define and work with many debug interacts at the same time.
52
53
TESTS::
54
55
The current position in the stack frame is self._curframe_index::
56
57
sage: a = sage0.eval("sage.interacts.debugger.test_function('n', 'm')")
58
sage: d = sage0('sage.interacts.debugger.Debug()')
59
sage: d._curframe_index
60
8
61
"""
62
def __init__(self):
63
"""
64
Create the debugger object from the most recent traceback.
65
66
TESTS::
67
68
sage: a = sage0.eval("sage.interacts.debugger.test_function('n', 'm')")
69
sage: sage0('sage.interacts.debugger.Debug()')
70
<sage.interacts.debugger.Debug instance at 0x...>
71
"""
72
import inspect, sys, traceback
73
try:
74
tb=sys.last_traceback
75
#we strip off the 5 outermost frames, since those relate only to
76
#the notebook, not user code
77
for i in xrange(5):
78
tb=tb.tb_next
79
self._stack = inspect.getinnerframes(tb)
80
except AttributeError:
81
raise RuntimeError, "no traceback has been produced; nothing to debug"
82
self._curframe_index = len(self._stack) - 1
83
84
def curframe(self):
85
"""
86
Return the current frame object. This defines the local and
87
global variables at a point in the stack trace, and the code.
88
89
OUTPUT:
90
91
- frame object
92
93
TESTS::
94
95
sage: a = sage0.eval("sage.interacts.debugger.test_function('n', 'm')")
96
sage: d = sage0('sage.interacts.debugger.Debug()')
97
sage: d.curframe()
98
<frame object at 0x...>
99
"""
100
return self._stack[self._curframe_index][0]
101
102
def evaluate(self, line):
103
"""
104
Evaluate the input string ``line`` in the scope of the current
105
position in the stack.
106
107
INPUT:
108
109
- ``line`` -- string; the code to exec
110
111
OUTPUT:
112
113
- string (the output)
114
115
TESTS::
116
117
sage: _ = sage0.eval("sage.interacts.debugger.test_function('n', 'm')")
118
sage: _ = sage0.eval('d = sage.interacts.debugger.Debug()')
119
sage: sage0.eval("d.evaluate('print a, b')")
120
'm n'
121
"""
122
locals = self.curframe().f_locals
123
globals = self.curframe().f_globals
124
try:
125
code = compile(line + '\n', '<stdin>', 'single')
126
exec code in globals, locals
127
except Exception:
128
import sys
129
t, v = sys.exc_info()[:2]
130
if type(t) == type(''):
131
exc_type_name = t
132
else:
133
exc_type_name = t.__name__
134
print '***', exc_type_name + ':', v
135
136
def listing(self, n=5):
137
"""
138
Return HTML display of the lines (with numbers and a pointer
139
at the current line) in the code listing for the current
140
frame, with `n` lines before and after of context.
141
142
INPUT:
143
144
- `n` -- integer (default: 5)
145
146
OUTPUT:
147
148
- list of strings
149
150
TESTS::
151
152
sage: _ = sage0.eval("sage.interacts.debugger.test_function('n', 'm')")
153
sage: _ = sage0.eval('d = sage.interacts.debugger.Debug()')
154
sage: print sage0("d.listing(1)")
155
2... x = a + b
156
--&gt; ... y = a * b
157
... return x, y, x&lt;y, x&gt;y # &lt; to ensure HTML is properly escaped
158
<hr>> <a href="/src/interacts/debugger.py" target="_new">devel/sage/sage/interacts/debugger.py</a>
159
sage: print sage0("d.listing()")
160
2...
161
...
162
... x = a + b
163
--&gt; ... y = a * b
164
... return x, y, x&lt;y, x&gt;y # &lt; to ensure HTML is properly escaped
165
...
166
sage: _ = sage0.eval('d._curframe_index -= 1')
167
sage: print sage0("d.listing(1)")
168
4... else:
169
--&gt; ... test_function2(m, n)
170
...
171
<hr>> <a href="/src/interacts/debugger.py" target="_new">devel/sage/sage/interacts/debugger.py</a>
172
"""
173
# TODO: Currently, just as with ipdb on the command line,
174
# there is no support for displaying code in Cython files.
175
# This shouldn't be too hard to add.
176
curframe = self.curframe()
177
filename = curframe.f_code.co_filename
178
lineno = curframe.f_lineno
179
import linecache
180
w = []
181
for i in range(lineno-n, lineno+n+1):
182
z = linecache.getline(filename, i, curframe.f_globals)
183
if z: w.append(('--> ' if i ==lineno else ' ') + '%-5s'%i + z)
184
code = ''.join(w)
185
if not code.strip():
186
code = '(code not available)'
187
188
# This is a hideous hack to get around how the notebook "works".
189
# If the output of anything contains the string TRACEBACK then
190
# it will get mangled. So we replace TRACEBACK in our code block
191
# by the harmless version with the colon missing. This sucks.
192
from sagenb.notebook.cell import TRACEBACK
193
code = code.replace(TRACEBACK, TRACEBACK[:-1])
194
195
# Create a hyperlink to the file, if possible.
196
i = filename.rfind('site-packages/sage')
197
if i != -1:
198
fname = filename[i+len('site-packages/sage')+1:].rstrip('/')
199
file = '<a href="/src/%s" target="_new">devel/sage/sage/%s</a>'%(fname,fname)
200
else:
201
file = filename
202
203
import cgi
204
t = """%s<hr>> %s"""%(cgi.escape(code), file)
205
return t
206
207
def interact(self):
208
"""
209
Start the interact debugger.
210
211
TESTS::
212
213
sage: _ = sage0.eval("sage.interacts.debugger.test_function('n', 'm')")
214
sage: _ = sage0.eval('d = sage.interacts.debugger.Debug()')
215
sage: _ = sage0.eval('d.interact()') # only works in the notebook
216
"""
217
# We use a library_interact instead of a normal interact here,
218
# since this is an interact in the library, and a normal
219
# "@interact" is all mangled.
220
221
from sage.interacts.library import library_interact
222
from sagenb.notebook.interact import slider, input_box, selector
223
224
# self._last holds the last state of all controls. This allows
225
# us to deduce which control changed to cause the update, or that
226
# nothing changed, in which case we assume the user requested to
227
# re-evaluate the input box (for some reason -- currently there is
228
# no point in doing so). It is a shortcoming of @interact that
229
# we have to do this.
230
self._last = None
231
232
# two sliders and a box to put in commands with an evaluate button.
233
@library_interact
234
def dbg(frame = slider(vmin=0, vmax=len(self._stack)-1, step_size=1, default=len(self._stack)-1, label='stack frame'),
235
lines = slider(vmin=3, vmax=99, step_size=2, default=11, label='lines of context'),
236
command = input_box("", label="", type=str),
237
button = selector(['Evaluate'], label='', buttons=True)
238
):
239
240
if self._last is None:
241
self._last = {'command':command, 'button':button, 'lines':lines, 'frame':frame}
242
243
if self._last['lines'] != lines:
244
# they dragged the number-of-lines slider, so done
245
pass
246
elif self._last['command'] != command and command.strip():
247
# they changed the command, so evaluate that
248
self.evaluate(command)
249
elif self._last['frame'] != frame:
250
# they dragged the frame slider.
251
self._curframe_index = frame
252
elif command:
253
# must have hit the evaluate button
254
self.evaluate(command)
255
256
print '<html><hr>' + self.listing(lines//2) + '</html>'
257
# save control state for next time around
258
self._last = {'command':command, 'button':button, 'lines':lines, 'frame':frame}
259
260
dbg()
261
262
def debug():
263
"""
264
If you get a traceback in the Sage notebook, use the ``debug()``
265
command to start an interactive debugger. Using %debug on the
266
command line.
267
268
Using the debugger you can move up and down the stack frame and
269
evaluate arbitrary code anywhere in the call stack.
270
271
.. warning::
272
273
If you type in a command, execute it, switch to a different
274
frame and press enter again, nothing happens: it doesn't
275
execute the command in the new frame. You can get the command
276
to execute by hitting the Evaluate button though. This is a
277
fundamental limitation of the Sage notebook in Sage-5.0.
278
279
EXAMPLES::
280
281
sage: debug() # only works in the notebook
282
You should use %debug on the command line.
283
"""
284
# "EMBEDDED_MODE" is True precisely when the Sage notebook is running.
285
from sage.plot.plot import EMBEDDED_MODE
286
if not EMBEDDED_MODE:
287
# Must be the command line, so suggest using the IPython debugger.
288
print "You should use %debug on the command line."
289
else:
290
# Create the Debug object and make it interactive.
291
Debug().interact()
292
293