Skip to content

Commit 2fc0667

Browse files
committed
Initial code with BrainStack/LLVM examples
1 parent 2b07e58 commit 2fc0667

File tree

5 files changed

+613
-1
lines changed

5 files changed

+613
-1
lines changed

src/DebuggerFramework.jl

Lines changed: 208 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,212 @@
1+
__precompile__()
12
module DebuggerFramework
3+
using TerminalUI
4+
5+
abstract StackFrame
26

3-
# package code goes here
7+
function print_var(io::IO, name, val::Nullable, undef_callback)
8+
print(" | ")
9+
if isnull(val)
10+
@assert false
11+
else
12+
val = get(val)
13+
T = typeof(val)
14+
val = repr(val)
15+
if length(val) > 150
16+
val = Suppressed("$(length(val)) bytes of output")
17+
end
18+
println(io, name, "::", T, " = ", val)
19+
end
20+
end
21+
22+
function print_locals(io::IO, args...)
23+
end
24+
25+
print_locdesc(io, frame) = println(io, locdesc(frame))
26+
function print_frame(_, io::IO, num, frame)
27+
print(io, "[$num] ")
28+
print_locdesc(io, frame)
29+
print_locals(io, frame)
30+
end
31+
32+
function print_backtrace(state)
33+
for (num, frame) in enumerate(state.stack)
34+
print_frame(state, Base.pipe_writer(state.terminal), num, frame)
35+
end
36+
end
37+
38+
print_backtrace(state, _::Void) = nothing
39+
40+
function execute_command(state, frame, ::Val{:bt}, cmd)
41+
print_backtrace(state)
42+
println()
43+
return false
44+
end
45+
46+
function execute_command(state, interp, ::Union{Val{:f},Val{:fr}}, command)
47+
subcmds = split(command,' ')[2:end]
48+
if isempty(subcmds) || subcmds[1] == "v"
49+
print_frame(state, Base.pipe_writer(state.terminal), state.level, state.stack[state.level])
50+
return false
51+
else
52+
new_level = parse(Int, subcmds[1])
53+
new_stack_idx = length(state.stack)-(new_level-1)
54+
if new_stack_idx > length(state.stack) || new_stack_idx < 1
55+
print_with_color(:red, STDERR, "Not a valid frame index\n")
56+
return false
57+
end
58+
state.level = new_level
59+
end
60+
return true
61+
end
62+
63+
"""
64+
Start debugging the specified code in the the specified environment.
65+
The second argument should default to the global environment if such
66+
an environment exists for the language in question.
67+
"""
68+
function debug
69+
end
70+
71+
type DebuggerState
72+
stack
73+
level
74+
main_mode
75+
terminal
76+
end
77+
78+
function print_status_synthtic(io, state, frame, lines_before, total_lines)
79+
return 0
80+
end
81+
82+
haslocinfo(frame) = false
83+
locdesc(frame) = "unknown function"
84+
85+
print_status(io, state) = print_status(io, state, state.stack[state.level])
86+
function print_status(io, state, frame)
87+
# Buffer to avoid flickering
88+
outbuf = IOBuffer()
89+
print_with_color(:bold, outbuf, "In ", locdesc(frame), "\n")
90+
if haslocinfo(frame)
91+
# Print location here
92+
else
93+
buf = IOBuffer()
94+
active_line = print_status_synthtic(buf, state, frame, 2, 5)
95+
code = split(String(take!(buf)),'\n')
96+
@assert active_line <= length(code)
97+
for (lineno, line) in enumerate(code)
98+
if lineno == active_line
99+
print_with_color(:yellow, outbuf, "=> ", bold = true); println(outbuf, line)
100+
else
101+
print_with_color(:bold, outbuf, "? "); println(outbuf, line)
102+
end
103+
end
104+
end
105+
print(io, String(take!(outbuf)))
106+
end
107+
108+
immutable AbstractDiagnostic; end
109+
110+
function execute_command
111+
end
112+
113+
using Base: LineEdit, REPL
114+
function RunDebugger(stack, terminal = Base.active_repl.t)
115+
promptname(level, name) = "$level|$name > "
116+
117+
repl = Base.active_repl
118+
state = DebuggerState(stack, 1, nothing, terminal)
119+
120+
# Setup debug panel
121+
panel = LineEdit.Prompt(promptname(state.level, "debug");
122+
prompt_prefix="\e[38;5;166m",
123+
prompt_suffix=Base.text_colors[:white],
124+
on_enter = s->true)
125+
126+
# For now use the regular REPL completion provider
127+
replc = Base.REPL.REPLCompletionProvider()
128+
129+
130+
panel.hist = REPL.REPLHistoryProvider(Dict{Symbol,Any}(:debug => panel))
131+
Base.REPL.history_reset_state(panel.hist)
132+
133+
search_prompt, skeymap = Base.LineEdit.setup_search_keymap(panel.hist)
134+
search_prompt.complete = Base.REPL.LatexCompletions()
135+
136+
state.main_mode = panel
137+
138+
panel.on_done = (s,buf,ok)->begin
139+
line = String(take!(buf))
140+
old_level = state.level
141+
if !ok || strip(line) == "q"
142+
LineEdit.transition(s, :abort)
143+
LineEdit.reset_state(s)
144+
return false
145+
end
146+
if isempty(strip(line)) && length(panel.hist.history) > 0
147+
command = panel.hist.history[end]
148+
else
149+
command = strip(line)
150+
end
151+
do_print_status = true
152+
cmd1 = split(command,' ')[1]
153+
do_print_status = try
154+
execute_command(state, state.stack[state.level], Val{Symbol(cmd1)}(), command)
155+
catch err
156+
isa(err, AbstractDiagnostic) || rethrow(err)
157+
caught = false
158+
for interp_idx in length(state.top_interp.stack):-1:1
159+
if process_exception!(state.top_interp.stack[interp_idx], err, interp_idx == length(top_interp.stack))
160+
interp = state.top_interp = state.top_interp.stack[interp_idx]
161+
resize!(state.top_interp.stack, interp_idx)
162+
caught = true
163+
break
164+
end
165+
end
166+
!caught && rethrow(err)
167+
display_diagnostic(STDERR, state.interp.code, err)
168+
println(STDERR)
169+
LineEdit.reset_state(s)
170+
return true
171+
end
172+
if old_level != state.level
173+
panel.prompt = promptname(state.level,"debug")
174+
end
175+
LineEdit.reset_state(s)
176+
if isempty(state.stack)
177+
LineEdit.transition(s, :abort)
178+
LineEdit.reset_state(s)
179+
return false
180+
end
181+
if do_print_status
182+
print_status(Base.pipe_writer(terminal), state)
183+
end
184+
return true
185+
end
186+
187+
const all_commands = ("q", "s", "si", "finish", "bt", "loc", "ind", "shadow",
188+
"up", "down", "ns", "nc", "n", "se")
189+
190+
const repl_switch = Dict{Any,Any}(
191+
'`' => function (s,args...)
192+
if isempty(s) || position(LineEdit.buffer(s)) == 0
193+
prompt = language_specific_prompt(state, state.interp)
194+
buf = copy(LineEdit.buffer(s))
195+
LineEdit.transition(s, prompt) do
196+
LineEdit.state(s, prompt).input_buffer = buf
197+
end
198+
else
199+
LineEdit.edit_insert(s,key)
200+
end
201+
end
202+
)
203+
204+
b = Dict{Any,Any}[skeymap, LineEdit.history_keymap, LineEdit.default_keymap, LineEdit.escape_defaults]
205+
panel.keymap_dict = LineEdit.keymap([repl_switch;b])
206+
207+
# Skip evaluated values (e.g. constants)
208+
print_status(Base.pipe_writer(terminal), state)
209+
Base.REPL.run_interface(terminal, LineEdit.ModalInterface([panel,search_prompt]))
210+
end
4211

5212
end # module

test/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
expected.out
2+
failed.out

test/brainstack.jl

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
using Base.Test
2+
using DebuggerFramework
3+
reload("DebuggerFramework")
4+
5+
# This file will define a minimalistic programming language and implement a
6+
# debugger for this language. The language is not supposed to be useful, it
7+
# is a proving ground for the DebuggerFramework APIs.
8+
9+
# Language semantics:
10+
# Every line has to start with its line number relative to the current program,
11+
# followed by a colon. The colon is followed by a whitespace separated list of
12+
# integers, indicating either a line to call, or 0 which indicates a return.
13+
# This language has no conditional branches, so all loops are infinite. The
14+
# computed value is the number of calls that were made
15+
16+
module BrainStack
17+
using DebuggerFramework
18+
immutable BrainStackAST
19+
stmts::Vector{Vector{Int}}
20+
end
21+
22+
function Base.parse(::Type{BrainStackAST}, data)
23+
BrainStackAST(map(enumerate(split(data, '\n', keep=false))) do args
24+
lineno, line = args
25+
parts = split(line, r"\s", keep=false)
26+
@assert parts[1] == "$lineno:"
27+
map(x->parse(Int,x), parts[2:end])
28+
end)
29+
end
30+
31+
type BrainStackInterpreterState
32+
ast::BrainStackAST
33+
stack::Vector{Tuple{Int, Int}}
34+
idx::Int
35+
pos::Int
36+
ncalls::Int
37+
end
38+
39+
# Helpfully split up the interpreter to be re-usable from the debugger below
40+
enter(ast::BrainStackAST) = BrainStackInterpreterState(ast, [(0,0)], 1, 1, 0)
41+
function step_one!(state::BrainStackInterpreterState)
42+
if state.idx == 0
43+
return false
44+
end
45+
op = state.ast.stmts[state.idx][state.pos]
46+
if op == 0
47+
state.idx, state.pos = pop!(state.stack)
48+
state.pos += 1
49+
else
50+
push!(state.stack, (state.idx, state.pos))
51+
state.idx, state.pos = op, 1
52+
state.ncalls += 1
53+
end
54+
return true
55+
end
56+
function interpret(ast::BrainStackAST)
57+
state = enter(ast)
58+
while step_one!(state); end
59+
state.ncalls
60+
end
61+
62+
63+
# Debugger support
64+
immutable BrainStackStackRef <: DebuggerFramework.StackFrame
65+
state::BrainStackInterpreterState
66+
idx::Int
67+
end
68+
69+
function DebuggerFramework.debug(ast::BrainStackAST, args...)
70+
state = enter(ast)
71+
stack = [BrainStackStackRef(state, 0)]
72+
DebuggerFramework.RunDebugger(stack, args...)
73+
end
74+
75+
function idxpos(frame)
76+
if frame.idx == 0
77+
idx, pos = frame.state.idx, frame.state.pos
78+
else
79+
idx, pos = frame.state.stack[end-(frame.idx-1)]
80+
end
81+
idx, pos
82+
end
83+
84+
function DebuggerFramework.print_status_synthtic(io::IO, state, frame::BrainStackStackRef, lines_before, total_lines)
85+
ast = frame.state.ast
86+
idx, pos = idxpos(frame)
87+
print(io, "$idx: ")
88+
for (opno, op) in enumerate(ast.stmts[idx])
89+
if opno == pos
90+
print_with_color(:yellow, io, string(op); bold = true)
91+
else
92+
print(io, op)
93+
end
94+
(opno != length(ast.stmts[idx])) && print(io, ' ')
95+
end
96+
end
97+
98+
DebuggerFramework.locdesc(frame::BrainStackStackRef) = "statement $(idxpos(frame)[1])"
99+
100+
function update_stack!(ds, state, stack)
101+
ds.stack = [BrainStackStackRef(state, i) for i = 0:length(stack)-1]
102+
if ds.level > length(ds.stack)
103+
ds.level = length(ds.stack)
104+
end
105+
end
106+
107+
function DebuggerFramework.execute_command(state, frame::BrainStackStackRef, cmd::Val{:si}, command)
108+
step_one!(frame.state)
109+
update_stack!(state, frame.state, frame.state.stack)
110+
return true
111+
end
112+
end
113+
114+
proga = """
115+
1: 2 3 0
116+
2: 0
117+
3: 0
118+
"""
119+
120+
asta = parse(BrainStack.BrainStackAST, proga)
121+
@test BrainStack.interpret(asta) == 2
122+
123+
include(Pkg.dir("VT100","test","TerminalRegressionTests.jl"))
124+
125+
const thisdir = dirname(@__FILE__)
126+
TerminalRegressionTests.automated_test(
127+
joinpath(thisdir,"brainstack/simple.multiout"),
128+
["si\n", "f 2\n", "si\n", "si\n", "\n", "\n"]) do emuterm
129+
DebuggerFramework.debug(asta, emuterm)
130+
end

test/brainstack/simple.multiout

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
++++++++++++++++++++++++++++++++++++++++++++++++++++++
2+
|In statement 1
3+
|=> 1: 2 3 0
4+
|1|debug >
5+
+1++++++++++++++++++++++++++++++++++++++++++++++++++++
6+
|1|debug > si
7+
|In statement 2
8+
|=> 2: 0
9+
|1|debug >
10+
+1++++++++++++++++++++++++++++++++++++++++++++++++++++
11+
|1|debug > f 2
12+
|In statement 1
13+
|=> 1: 2 3 0
14+
|2|debug >
15+
+1++++++++++++++++++++++++++++++++++++++++++++++++++++
16+
|2|debug > si
17+
|In statement 1
18+
|=> 1: 2 3 0
19+
|1|debug >
20+
+1++++++++++++++++++++++++++++++++++++++++++++++++++++
21+
|1|debug > si
22+
|In statement 3
23+
|=> 3: 0
24+
|1|debug >
25+
+1++++++++++++++++++++++++++++++++++++++++++++++++++++
26+
|1|debug >
27+
|In statement 1
28+
|=> 1: 2 3 0
29+
|1|debug >
30+
++++++++++++++++++++++++++++++++++++++++++++++++++++++

0 commit comments

Comments
 (0)