/ld27

To get this branch, use:
bzr branch /bzr/ld27
35 by Josh C
cluke009 zoetrope + my spritebatch changes
1
-- Class: DebugStepper
2
-- This lets you pause execution of the app and step through it line by line.
3
-- This adds a function, debugger.breakpt(), that triggers this intrument.
4
-- Until this is called, the instrument remains hidden.
5
6
DebugStepper = DebugInstrument:extend
7
{
8
	visible = false,
9
	lineContext = 10,
10
	width = 'wide',
11
	_fileCache = {},
12
13
	onNew = function (self)
14
		self.stepIntoButton = self:add(DebugInstrumentButton:new
15
		{
16
			label = 'Step Into',
17
			onMouseUp = function (self)
18
				debugger._stepPaused = false
19
				debugger._stepFilter = nil
20
			end
21
		})
22
23
		self.stepOverButton = self:add(DebugInstrumentButton:new
24
		{
25
			label = 'Step Over',
26
			onMouseUp = function (self)
27
				debugger._stepPaused = false
28
				local prevStack = debugger._stepStack()
29
30
				debugger._stepFilter = function (stack)
31
					for i = 1, #stack - #prevStack do
32
						local match = true
33
34
						for j = 1, #prevStack do
35
							if stack[i + j] ~= prevStack[j] then
36
								match = false
37
								break
38
							end
39
						end
40
41
						-- we are now executing a sub-call;
42
						-- temporarily disable our line hook until
43
						-- we return to the previous function
44
45
						if match then
46
							debug.sethook(function()
47
								local state = debug.getinfo(3, 'f')
48
49
								if state.func == prevStack[1] then 
50
									-- we're at least on our old function, but is
51
									-- the stack depth the same?
52
53
									local depth = 1
54
									
55
									while true do
56
										state = debug.getinfo(3 + depth, 'f')
57
										if not state then break end
58
										depth = depth + 1
59
									end
60
61
									if depth == #prevStack then
62
										debug.sethook(debugger._stepLine, 'l')
63
									end
64
								end
65
							end, 'r')
66
							return false
67
						end
68
					end
69
70
					return true
71
				end
72
			end
73
		})
74
75
		self.stepOutButton = self:add(DebugInstrumentButton:new
76
		{
77
			label = 'Step Out',
78
			onMouseUp = function (self)
79
				debugger._stepPaused = false
80
				local prevStack = debugger._stepStack()
81
82
				-- disable our line hook until the current function returns
83
84
				debug.sethook(function()
85
					local depth = 2
86
					local match = false
87
88
					while true do
89
						local state = debug.getinfo(depth, 'f')
90
91
						if not state then
92
							match = true
93
							break
94
						elseif state.func ~= prevStack[depth - 1] then
95
							break
96
						end
97
98
						depth = depth + 1
99
					end
100
101
					if match then
102
						debug.sethook(debugger._stepLine, 'l')
103
					end
104
				end, 'r')
105
			end
106
		})
107
108
		self.continueButton = self:add(DebugInstrumentButton:new
109
		{
110
			label = 'Continue',
111
			onMouseUp = function (self)
112
				debugger._stepPaused = false
113
				debugger.endBreakpt()
114
			end
115
		})
116
117
		self.lineHighlight = self:add(Fill:new{ fill = {64, 64, 64}, height = 0, width = 0 })
118
119
		self.sourceLines = self:add(Text:new
120
		{
121
			font = self.font,
122
			width = 20,
123
			align = 'right',
124
			wordWrap = false,
125
		})
126
127
		self.sourceView = self:add(Text:new{ font = self.font, wordWrap = false })
128
		self.lineHeight = self.sourceView._fontObj:getHeight()
129
130
		self.title.text = 'Source'
131
		self.contentHeight = self.lineHeight * (self.lineContext + 1) * 2 + self.spacing * 3 +
132
		                     DebugInstrumentButton.height
133
134
		debugger.breakpt = function()
135
			local print = debugger.unsourcedPrint or print
136
			local caller = debug.getinfo(2, 'S')
137
138
			debugger.showConsole()
139
			self.visible = true
140
141
			print('\n' .. string.rep('=', 40))
142
			print('Breakpoint, ' .. caller.short_src .. ', ' .. caller.linedefined)
143
			print(string.rep('=', 40))
144
			debug.sethook(debugger._stepLine, 'l')
145
		end
146
147
		debugger.endBreakpt = function()
148
			self.visible = false
149
			if debugger.hideStack then debugger.hideStack() end
150
			if debugger.hideLocals then debugger.hideLocals() end
151
			debugger.hideConsole()
152
			debug.sethook()
153
		end
154
155
		-- hook to handle stepping over source
156
157
		debugger._stepLine = function (_, line)
158
			local state = debug.getinfo(2, 'Sl')
159
160
			if string.find(state.source, 'zoetrope/debug') or
161
			   (debugger._stepFilter and not debugger._stepFilter(debugger._stepStack())) then
162
				--print('skipping', state.source, state.currentline, #debugger._stepStack())
163
				return
164
			end
165
166
			if debugger.showStack then debugger.showStack(4) end
167
			if debugger.showLocals then debugger.showLocals(4) end
168
169
			local file = string.match(state.source, '^@(.*)')
170
			self:showLine(file, line)
171
172
			debugger._stepPaused = true
173
			local quit = false
174
175
			while debugger._stepPaused and not quit do
176
				quit = debugger._miniEventLoop()
177
			end
178
179
			if quit then
180
				debug.sethook()
181
				love.event.quit()
182
			end
183
		end
184
185
		-- returns a table representing the call stack during a source step
186
187
		debugger._stepStack = function()
188
			local level = 2
189
			local result = {}
190
			local info = {}
191
			local afterHook = false
192
193
			while true do
194
				info = debug.getinfo(level, 'f')
195
				if not info then break end
196
197
				if afterHook then
198
					table.insert(result, info.func)
199
				elseif info.func == debugger._stepLine then
200
					afterHook = true
201
				end
202
203
				level = level + 1
204
			end
205
206
			return result
207
		end
208
	end,
209
210
	onResize = function (self, x, y, width, height)
211
		self.sourceLines.x = x + self.spacing
212
		self.sourceLines.y = y + self.spacing
213
		self.sourceLines.height = height - self.sourceLines.y - self.spacing
214
		
215
		self.sourceView.x = self.sourceLines.x + self.sourceLines.width + self.spacing
216
		self.sourceView.y = self.sourceLines.y
217
		self.sourceView.width = width - self.sourceView.x - self.spacing * 2
218
		self.sourceView.height = self.sourceLines.height
219
220
		self.lineHighlight.x = x + self.spacing
221
		self.lineHighlight.y = self.sourceLines.y + self.lineHeight * self.lineContext
222
		self.lineHighlight.width = width - self.spacing * 2
223
		self.lineHighlight.height = self.lineHeight
224
225
		self.stepIntoButton.x = self.sourceLines.x
226
		self.stepIntoButton.y = self.sourceView.y + self.sourceView.height + self.spacing
227
228
		self.stepOverButton.x = self.stepIntoButton.x + self.stepIntoButton.width + self.spacing
229
		self.stepOverButton.y = self.stepIntoButton.y
230
231
		self.stepOutButton.x = self.stepOverButton.x + self.stepOverButton.width + self.spacing
232
		self.stepOutButton.y = self.stepOverButton.y
233
234
		self.continueButton.x = width - self.stepOverButton.width
235
		self.continueButton.y = self.stepOverButton.y
236
	end,
237
238
	showLine = function (self, file, line)
239
		if file then
240
			self.sourceLines.text = ''
241
			self.sourceView.text = ''
242
243
			for i = line - self.lineContext, line + self.lineContext + 1 do
244
				local source = debugger.sourceLine(file, i)
245
246
				if source then
247
					self.sourceLines.text = self.sourceLines.text .. i .. '\n'
248
					self.sourceView.text = self.sourceView.text .. string.gsub(debugger.sourceLine(file, i), '\t', string.rep(' ', 4)) .. '\n'
249
				end
250
			end
251
252
			self.title.text = file .. ':' .. line
253
		else
254
			self.title.text = 'Source'
255
			self.sourceView.text = '(source not available)'
256
		end
257
	end
258
}
259