Hide keyboard shortcuts

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

""" 

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) 

""" 

x = a + b 

y = a * b 

return x, y, x<y, x>y # < to ensure HTML is properly escaped 

 

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 

if level > 0: 

return test_function(n,m,level=level-1) 

else: 

return test_function2(m, n) 

 

 

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 

--&gt; ... y = a * b 

....: return x, y, x&lt;y, x&gt;y # &lt; 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 

--&gt; ... y = a * b 

....: return x, y, x&lt;y, x&gt;y # &lt; to ensure HTML is properly escaped 

... 

sage: _ = sage0.eval('d._curframe_index -= 1') 

sage: print(sage0("d.listing(1)")) 

4... else: 

--&gt; ... 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. 

from sage.plot.plot import EMBEDDED_MODE 

if not EMBEDDED_MODE: 

# Must be the command line, so suggest using the IPython debugger. 

print("You should use %debug on the command line.") 

else: 

# Create the Debug object and make it interactive. 

Debug().interact()