Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ which gives the following output:

![example_tree](example_tree.png)

See [this notebook](examples/TreeView.ipynb) for usage examples.
See [this notebook](examples/TreeView usage.ipynb) for usage examples.

## Installation prerequisites

Expand Down
1 change: 1 addition & 0 deletions REQUIRE
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ julia 0.5
LightGraphs 0.7
TikzGraphs 0.3
MacroTools 0.3
CommonSubexpressions
649 changes: 397 additions & 252 deletions examples/TreeView.ipynb → examples/TreeView usage.ipynb

Large diffs are not rendered by default.

101 changes: 10 additions & 91 deletions src/TreeView.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,111 +2,30 @@ module TreeView

using LightGraphs, TikzGraphs
using MacroTools
using CommonSubexpressions

export LabelledTree, walk_tree, walk_tree!, draw, @tree, @tree_with_call,
tikz_representation

immutable LabelledTree
g::Graph
labels::Vector{String}
end

add_numbered_vertex!(g) = (add_vertex!(g); top = nv(g)) # returns the number of the new vertex

# latex treats # as a special character, so we have to escape it. See:
# https://github.com/sisl/TikzGraphs.jl/issues/12
latex_escape(s::String) = replace(s, "#", "\\#")

"Convert the current node into a label"
function label(sym)
sym == :(^) && return "\\textasciicircum" # TikzGraphs chokes on ^

return latex_escape(string("\\texttt{", sym, "}"))
end


"""
walk_tree!(g, labels, ex, show_call=true)

Walk the abstract syntax tree (AST) of the given expression `ex`.
Builds up the graph `g` and the set of `labels`
for each node, both modified in place

`show_call` specifies whether to include `call` nodes in the graph.
Including them represents the Julia AST more precisely, but adds visual noise.

Returns the number of the top vertex.
"""

function walk_tree!(g, labels, ex, show_call=true)

top_vertex = add_numbered_vertex!(g)

start_argument = 1 # which argument to start with

if !(show_call) && ex.head == :call
f = ex.args[1] # the function name
push!(labels, label(f))

start_argument = 2 # drop "call" from tree

else
push!(labels, label(ex.head))
end
export make_dag, @dag, @dag_cse


for i in start_argument:length(ex.args)
abstract LabelledDiGraph

if isa(ex.args[i], Expr)

child = walk_tree!(g, labels, ex.args[i], show_call)
add_edge!(g, top_vertex, child)

else
n = add_numbered_vertex!(g)
add_edge!(g, top_vertex, n)

push!(labels, label(ex.args[i]))

end
end

return top_vertex

end

function walk_tree(ex::Expr, show_call=false)
g = Graph()
labels = String[]

walk_tree!(g, labels, ex, show_call)

return LabelledTree(g, labels)

end

tikz_representation(tree) = TikzGraphs.plot(tree.g, tree.labels)

import Base.show
function show(io::IO, mime::MIME"image/svg+xml", tree::LabelledTree)
p = tikz_representation(tree) # TikzPicture object
show(io, mime, p)
immutable LabelledTree <: LabelledDiGraph
g::DiGraph
labels::Vector{Any}
end

add_numbered_vertex!(g) = (add_vertex!(g); top = nv(g)) # returns the number of the new vertex


function draw(tree::LabelledTree)
TikzGraphs.plot(tree.g, tree.labels)
end

include("tree.jl")
include("dag.jl")
include("display.jl")

macro tree(ex::Expr)
walk_tree(ex)
end

macro tree_with_call(ex::Expr)
walk_tree(ex, true)
end


end # module
146 changes: 146 additions & 0 deletions src/dag.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# Make a DAG (Directed Acyclic Graph) by storing references to each symbol

"""
Structure representing a DAG.
Maintains a `symbol_map` giving the currently-known symbols and the corresponding
vertex number in the graph.
"""
immutable DirectedAcyclicGraph <: LabelledDiGraph
g::DiGraph
labels::Vector{Any}
symbol_map::Dict{Symbol, Int}
end

DirectedAcyclicGraph() = DirectedAcyclicGraph(DiGraph(), Symbol[], Dict())

"""
Adds a symbol to the DAG if it doesn't already exist.
Returns the vertex number
"""

# Make numbers unique:
function add_symbol!(dag::DirectedAcyclicGraph, s) # number
vertex = add_numbered_vertex!(dag.g)
push!(dag.labels, s)
return vertex
end

function lookup!(dag::DirectedAcyclicGraph, s)
add_symbol!(dag, s)
end

"""
Look up a symbol to see if it has already been seen.
"""
function lookup!(dag::DirectedAcyclicGraph, s::Symbol)
if haskey(dag.symbol_map, s)
return dag.symbol_map[s]

else # make new one:
vertex = add_numbered_vertex!(dag.g)
push!(dag.labels, s)
dag.symbol_map[s] = vertex
return vertex
end
end


make_dag!(dag::DirectedAcyclicGraph, s) = lookup!(dag, s)

"""
Update a Directed Acyclic Graph with the result of traversing the given `Expr`ession.
"""
function make_dag!(dag::DirectedAcyclicGraph, ex::Expr)

local top

if ex.head == :block
for arg in ex.args
make_dag!(dag, arg)
end
return -1

elseif ex.head == :(=) # treat assignment as just giving pointers to the tree
local_var = ex.args[1]

top = make_dag!(dag, ex.args[2])

dag.symbol_map[local_var] = top # add an alias to the corresponding tree node

return top

end


where_start = 1 # which argument to start with

if ex.head == :call
f = ex.args[1] # the function name
top = add_symbol!(dag, f)

where_start = 2 # drop "call" from tree


else
@show ex.head
top = add_symbol!(dag, ex.head)
end

# @show top

for arg in ex.args[where_start:end]

# @show arg, typeof(arg)

if isa(arg, Expr)

child = make_dag!(dag, arg)
# @show "Expr", top, child
add_edge!(dag.g, top, child)

else
child = lookup!(dag, arg)
# @show top, child
add_edge!(dag.g, top, child)

end
end

return top

end

"""
Make a Directed Acyclic Graph (DAG) from a Julia expression.
"""
function make_dag(ex::Expr)

dag = DirectedAcyclicGraph()

make_dag!(dag, MacroTools.striplines(ex))

return dag

end

"""
Make a Directed Acyclic Graph (DAG) from a Julia expression.
"""
macro dag(ex::Expr)
make_dag(ex)
end

"""
Perform common subexpression elimination on a Julia `Expr`ession,
and make a Directed Acyclic Graph (DAG) of the result.
"""
macro dag_cse(ex::Expr)
make_dag(cse(ex)) # common subexpression elimination
end


import Base.show
function show(io::IO, mime::MIME"image/svg+xml", dag::DirectedAcyclicGraph)
p = tikz_representation(dag) # TikzPicture object
show(io, mime, p)
end
30 changes: 30 additions & 0 deletions src/display.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@

# latex treats # as a special character, so we have to escape it. See:
# https://github.com/sisl/TikzGraphs.jl/issues/12

latex_escape(s::String) = replace(s, "#", "\\#")

"Convert a symbol or into a LaTeX label"
function latex_label(sym)
sym == :(^) && return "\\textasciicircum" # TikzGraphs chokes on ^

return latex_escape(string("\\texttt{", sym, "}"))
end


"""
Return a Tikz representation of a tree object.
The tree object must have fields `g` (the graph) and `labels`.
"""
function tikz_representation(tree::LabelledDiGraph)
labels = String[latex_label(x) for x in tree.labels]
return TikzGraphs.plot(tree.g, labels)
end


function Base.show(io::IO, mime::MIME"image/svg+xml", tree::LabelledTree)

p = tikz_representation(tree) # TikzPicture object
show(io, mime, p)

end
75 changes: 75 additions & 0 deletions src/tree.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""
walk_tree!(g, labels, ex, show_call=true)

Walk the abstract syntax tree (AST) of the given expression `ex`.
Builds up the graph `g` and the set of `labels`
for each node, both modified in place

`show_call` specifies whether to include `call` nodes in the graph.
Including them represents the Julia AST more precisely, but adds visual noise.

Returns the number of the top vertex.
"""

function walk_tree!(g, labels, ex, show_call=true)

top_vertex = add_numbered_vertex!(g)

where_start = 1 # which argument to start with

if !(show_call) && ex.head == :call
f = ex.args[1] # the function name
push!(labels, f)

where_start = 2 # drop "call" from tree

else
push!(labels, ex.head)
end


for i in where_start:length(ex.args)

if isa(ex.args[i], Expr)

child = walk_tree!(g, labels, ex.args[i], show_call)
add_edge!(g, top_vertex, child)

else
n = add_numbered_vertex!(g)
add_edge!(g, top_vertex, n)

push!(labels, ex.args[i])

end
end

return top_vertex

end

function walk_tree(ex::Expr, show_call=false)
g = DiGraph()
labels = Any[]

walk_tree!(g, labels, ex, show_call)

return LabelledTree(g, labels)

end

"""
Make a tree from a Julia `Expr`ession.
Omits `call`.
"""
macro tree(ex::Expr)
walk_tree(ex)
end

"""
Make a tree from a Julia `Expr`ession.
Includes `call`.
"""
macro tree_with_call(ex::Expr)
walk_tree(ex, true)
end
Loading