Permalink
Browse files

Imported the code.

  • Loading branch information...
1 parent b480d30 commit a911bbe9e44833f0aac1b7fe83a3abd197f94ab4 @toivoh committed Aug 15, 2012
Showing with 486 additions and 3 deletions.
  1. +1 −0 .gitignore
  2. +80 −3 README.md
  3. +259 −0 debug.jl
  4. +29 −0 test/test.jl
  5. +19 −0 test/test_collect.jl
  6. +27 −0 test/test_debug.jl
  7. +39 −0 test/test_debugeval.jl
  8. +32 −0 test/test_recode.jl
View
@@ -0,0 +1 @@
+*~
View
@@ -1,4 +1,81 @@
-julia-debugger
-==============
+julia-debugger v0.0
+===================
-Prototype interactive debugger for Julia
+Prototype interactive debugger for Julia
+
+This package is a first attempt towards an interactive debugger for
+[Julia](julialang.org).
+(It's not interactive yet, though :)
+
+Example
+-------
+The `@debug` macro instruments a piece of code with debug callbacks,
+which invoke the `debug_hook` function with a current line and scope.
+The `debug_eval` function attempts to `eval` an expression within a provided
+scope.
+
+The code (from `test/test.jl`)
+
+ @debug function f(n)
+ x = 0 # line 10
+ for k=1:n # line 11
+ x += k # line 12
+ end # line 13
+ x = x*x # line 14
+ end
+
+ function debug_hook(line::Int, file, scope::Scope)
+ print(line, " :")
+ if (line == 11) debug_eval(scope, :(x += 1)) end
+ if (line > 10) debug_eval(scope, :(print("\tx = ", x))) end
+ if (line == 12) debug_eval(scope, :(print("\tk = ", k))) end
+ println()
+ end
+
+ f(3)
+
+produces the output (from `debug_hook` above)
+
+ 10:
+ 11: x = 1
+ 12: x = 1 k = 1
+ 12: x = 2 k = 2
+ 12: x = 4 k = 3
+ 14: x = 7
+
+For slightly more elaborate examples, see the `test/` directory.
+
+Current implementation and limitations
+--------------------------------------
+
+ * `@debug` first tries to gather the set of symbols that is defined within
+ each scope in the instrumented code.
+ I've tried to encode the scoping rules of Julia, but I might very well
+ have missed something.
+ `@debug` should be used at global scope, or it might fail to capture some
+ variables that are visible to the instrumented code.
+
+ * Secondly, `@debug` instruments the code by inserting a call to `debug_hook`
+ before each expression within a `:block` expr in the AST (i e in most block
+ structures within the code).
+ The first instrumented line of each scope also creates a new `Scope` object,
+ with a dict that maps symbols to getter and setter closures,
+ and a link to the containing scope.
+
+ * `debug_eval(scope, ex)` attempts to translate `ex` to replace reads from and
+ writes to variables to use getters and setters from `scope`
+ where appropriate.
+ I've tried to encode Julia's assignment rules, but there could be
+ glitches.
+ Assignment to symbols that are not found in `scope` is prohibited,
+ rather than setting a global (without a corresponding `global` declaration
+ in the code).
+ This is meant to mimic the behavior of code within the local scope.
+
+ Mutating operators such as `x+=1` are first translated into e g `x=x+1`,
+ and then into e g `x_setter(x_getter()+1)`.
+
+Further limitations:
+ * No support for code that runs directly in a global scope.
+ * Not much tested yet.
+ * No actual interactive debug hook.
View
@@ -0,0 +1,259 @@
+
+module Debug
+import Base.*
+export @debug, debug_hook, debug_eval, Scope, getdefs, code_debug
+
+const doublecolon = symbol("::")
+
+quot(ex) = expr(:quote, ex)
+
+is_expr(ex, head::Symbol) = (isa(ex, Expr) && (ex.head == head))
+is_expr(ex, head::Symbol, n::Int) = is_expr(ex, head) && length(ex.args) == n
+
+is_symbol(ex::Symbol) = true
+is_symbol(ex::SymbolNode ) = true
+is_symbol(ex) = false
+
+get_symbol(ex::Symbol) = ex
+get_symbol(ex::SymbolNode) = ex.name
+
+is_linenumber(ex::LineNumberNode) = true
+is_linenumber(ex::Expr) = is(ex.head, :line)
+is_linenumber(ex) = false
+
+get_linenumber(ex::Expr) = ex.args[1]
+get_linenumber(ex::LineNumberNode) = ex.line
+
+
+# ---- getdefs: gather defined symbols by scope -------------------------------
+
+type DefinedSyms
+ scopes::ObjectIdDict
+ syms::Set{Symbol}
+end
+
+enter(c::DefinedSyms, ex) = enter(c.scopes, ex)
+function enter(scopes::ObjectIdDict, ex)
+ scopes[ex] = syms = Set{Symbol}()
+ DefinedSyms(scopes, syms)
+end
+
+getdefs(c::DefinedSyms, exs::Vector) = (for ex in exs; getdefs(c, ex); end)
+function getdefs(c::DefinedSyms, ex::Expr)
+ head = ex.head
+ args = ex.args
+
+ if contains([:line, :quote, :type], head) return end
+ if head === :let
+ body = args[1]
+ c_outer, c_inner = c, enter(c, body)
+ getdefs(c_inner, body)
+ for arg in args[2:end]
+ if is_expr(arg, :(=))
+ lhs, rhs = arg.args[1], arg.args[2]
+ getdefs(c_outer, rhs)
+ else
+ lhs = arg
+ end
+ getdefs_lhs(c_inner, lhs)
+ c.scopes[lhs] = c_inner.syms
+ end
+ return
+ end
+ if head === :try
+ try_body, catch_arg, catch_body = args[1], args[2], args[3]
+
+ getdefs(enter(c, try_body), try_body)
+
+ c = enter(c, catch_body)
+ getdefs_lhs(c, catch_arg)
+ getdefs(c, catch_body)
+ c.scopes[catch_arg] = c.syms
+ return
+ end
+
+ if contains([:function, :for, :while], head)
+ c = enter(c, ex)
+ end
+ if head === :(=)
+ getdefs_lhs(c, args[1])
+ getdefs(c, args[2])
+ elseif contains([:local, :global], head)
+ getdefs_lhs(c, args)
+ elseif head === :function
+ getdefs_lhs(c, args[1])
+ getdefs(c, args[2])
+ else
+ getdefs(c, args)
+ end
+end
+getdefs(c::DefinedSyms, ex) = nothing
+
+getdefs_lhs(c::DefinedSyms, exs::Vector) = (for e in exs; getdefs_lhs(c,e);end)
+function getdefs_lhs(c::DefinedSyms, ex::Expr)
+ head = ex.head
+ args = ex.args
+ nargs = length(args)
+
+ if head === doublecolon && nargs == 2
+ getdefs_lhs(c, args[1])
+ getdefs(c, args[2])
+ elseif head === :call
+ getdefs_lhs(c, args)
+ elseif head === :ref
+ getdefs(c, args)
+ else
+ error("getdefs_lhs: don't know how to handle head=$head")
+ end
+end
+getdefs_lhs(c::DefinedSyms, ex::Symbol) = (add(c.syms, ex); nothing)
+getdefs_lhs(c::DefinedSyms, ex::SymbolNode) = getdefs(c, ex.name)
+getdefs_lhs(c::DefinedSyms, ex) = nothing
+
+
+function getdefs(ex::Expr)
+ scopes = ObjectIdDict()
+ getdefs(enter(scopes, ex), ex)
+ scopes
+end
+
+
+# ---- Scope: runtime symbol table with getters and setters -------------------
+
+abstract Scope
+
+type NoScope <: Scope; end
+has(scope::NoScope, sym::Symbol) = false
+
+type LocalScope <: Scope
+ parent::Scope
+ syms::Dict
+end
+
+function has(scope::LocalScope, sym::Symbol)
+ has(scope.syms, sym) || has(scope.parent, sym)
+end
+function get_entry(scope::LocalScope, sym::Symbol)
+ has(scope.syms, sym) ? scope.syms[sym] : get_entry(scope.parent, sym)
+end
+
+get_getter(scope::LocalScope, sym::Symbol) = get_entry(scope, sym)[1]
+get_setter(scope::LocalScope, sym::Symbol) = get_entry(scope, sym)[2]
+ref( scope::LocalScope, sym::Symbol) = get_getter(scope, sym)()
+assign(scope::LocalScope, x, sym::Symbol) = get_setter(scope, sym)(x)
+
+function code_getset(sym::Symbol)
+ val = gensym(string(sym))
+ :( ()->($sym), ($val)->(($sym)=($val)) )
+end
+function code_scope(scope::Symbol, parent, syms)
+ pairs = {expr(:(=>), quot(sym), code_getset(sym)) for sym in syms}
+ :(local ($scope) = ($quot(LocalScope))(($parent), ($expr(:cell1d, pairs))))
+end
+
+
+# ---- @debug: instrument an AST with debug code ------------------------------
+
+type DbgShared
+ line::Integer
+ file
+ scopes::ObjectIdDict
+ DbgShared(scopes::ObjectIdDict) = new(-1, nothing, scopes)
+end
+
+type CodeDebug
+ shared::DbgShared
+ scope_sym
+ syms::Set{Symbol}
+end
+CodeDebug(shared::DbgShared, scope) = CodeDebug(shared, scope, Set{Symbol}())
+
+function enter(c::CodeDebug, ex)
+ CodeDebug(c.shared, c.scope_sym, union(c.syms, c.shared.scopes[ex]))
+end
+
+code_debug(c::CodeDebug, exs::Vector) = {code_debug(c, ex) for ex in exs}
+function code_debug(c::CodeDebug, ex::Expr)
+ scopes = c.shared.scopes
+ if has(scopes, ex); c = enter(c, ex); end
+
+ if ex.head === :block
+ args = {}
+ if !isempty(c.syms)
+ # emit Scope object
+ scope_sym = gensym("scope")
+ push(args, code_scope(scope_sym, c.scope_sym, c.syms))
+ del_all(c.syms)
+ c.scope_sym = scope_sym
+ end
+ for arg in ex.args
+ if is_linenumber(arg)
+ c.shared.line = get_linenumber(arg)
+ if is_expr(arg, :line, 2)
+ c.shared.file = arg.args[2]
+ end
+ push(args, arg)
+ continue
+ end
+ line, file, scope = c.shared.line, c.shared.file, c.scope_sym
+ push(args, :( debug_hook(($quot(line)), ($quot(file)), ($scope)) ))
+ push(args, code_debug(c, arg))
+ end
+ return expr(ex.head, args)
+ end
+
+ return expr(ex.head, code_debug(c, ex.args))
+end
+code_debug(c::CodeDebug, ex) = ex
+
+function code_debug(ex)
+ code_debug(CodeDebug(DbgShared(getdefs(ex)), quot(NoScope())), ex)
+end
+
+
+debug_hook(line::Int, scope::Scope) = nothing
+
+macro debug(ex)
+ esc(code_debug(ex))
+end
+
+
+# ---- debug_eval: eval an expression inside a Scope (by substitution) --------
+
+const updating_ops = {
+ :+= => :+, :-= => :-, :*= => :*, :/= => :/, ://= => ://, :.//= => :.//,
+:.*= => :.*, :./= => :./, :\= => :\, :.\= => :.\, :^= => :^, :.^= => :.^,
+ :%= => :%, :|= => :|, :&= => :&, :$= => :$, :<<= => :<<, :>>= => :>>,
+ :>>>= => :>>>}
+
+function resym(s::Scope, ex::Expr)
+ head, args = ex.head, ex.args
+ if head === :(=)
+ if is_symbol(args[1])
+ lhs = get_symbol(args[1])
+ if !has(s, lhs) error("Cannot assign to ($lhs): not in scope.") end
+ setter = get_setter(s, lhs)
+ rhs = resym(s, args[2])
+ expr(:call, quot(setter), rhs)
+ else
+ expr(head, {resym(s, arg) for arg in args})
+ end
+ elseif has(updating_ops, head) # Translate updating ops, e g x+=1 ==> x=x+1
+ op = updating_ops[head]
+ resym(s, :( ($args[1]) = ($op)(($args[1]), ($args[2])) ))
+ elseif (head === :quote) ex
+ else expr(head, {resym(s, arg) for arg in args})
+ end
+end
+resym(s::Scope, node::SymbolNode) = resym(s, node.name)
+resym(s::Scope, ex::Symbol) = has(s, ex) ? :(($quot(get_getter(s, ex)))()) : ex
+resym(s::Scope, ex) = ex
+
+debug_eval(scope::Scope, ex) = eval(resym(scope, ex))
+# function debug_eval(scope::Scope, ex)
+# ex = resym(scope, ex)
+# println("ex = ", ex)
+# eval(ex)
+# end
+
+end # module
View
@@ -0,0 +1,29 @@
+
+load("debug.jl")
+
+module TestDebugEval
+import Base.*
+import Debug.*
+
+
+@debug function f(n)
+ x = 0 # line 10
+ for k=1:n # line 11
+ x += k # line 12
+ end # line 13
+ x = x*x # line 14
+end
+
+function debug_hook(line::Int, file, scope::Scope)
+ print(line, ":")
+
+ if (line == 11) debug_eval(scope, :(x += 1)) end
+
+ if (line > 10) debug_eval(scope, :(print("\tx = ", x))) end
+ if (line == 12) debug_eval(scope, :(print("\tk = ", k))) end
+ println()
+end
+
+f(3)
+
+end
View
@@ -0,0 +1,19 @@
+
+load("debug.jl")
+
+module TestCollect
+import Base.*
+import Debug.*
+
+code = :(function f(n)
+ x = 0
+ for k=1:n
+ x += k
+ println("k = ", k)
+ end
+ x = x*x
+end)
+
+println(getdefs(code), '\n')
+
+end
Oops, something went wrong.

0 comments on commit a911bbe

Please sign in to comment.