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