bzr branch
http://9ix.org/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 |