Skip to content

Commit

Permalink
Add DAGs (#5)
Browse files Browse the repository at this point in the history
* Add basic DAG construction

* DAG improvements, but gets confused with :block

* Working DAGs!

* Rename LabelledTree -> LabelledDigraph

* Refactor into separate files

* Export at-dag_cse

* Add test for at-dag_cse

* Reduce examples to single notebook

* Add CommonSubexpressions to REQUIRE

* Add source files
  • Loading branch information
dpsanders authored Jan 31, 2017
1 parent ebfe75d commit 1f40e0f
Show file tree
Hide file tree
Showing 8 changed files with 675 additions and 348 deletions.
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

0 comments on commit 1f40e0f

Please sign in to comment.