diff --git a/README.md b/README.md index 4091032..477a502 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/REQUIRE b/REQUIRE index 46ad0dd..bc26d86 100644 --- a/REQUIRE +++ b/REQUIRE @@ -2,3 +2,4 @@ julia 0.5 LightGraphs 0.7 TikzGraphs 0.3 MacroTools 0.3 +CommonSubexpressions diff --git a/examples/TreeView.ipynb b/examples/TreeView usage.ipynb similarity index 75% rename from examples/TreeView.ipynb rename to examples/TreeView usage.ipynb index 6b309bc..e02a401 100644 --- a/examples/TreeView.ipynb +++ b/examples/TreeView usage.ipynb @@ -1,19 +1,33 @@ { "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Usage of the TreeView package" + ] + }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 6, "metadata": { - "collapsed": false + "collapsed": true }, "outputs": [], "source": [ "using TreeView" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The `@tree` macro displays a given expression as a directed graph:" + ] + }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": { "collapsed": false }, @@ -87,21 +101,21 @@ "\n" ], "text/plain": [ - "TreeView.LabelledTree({7, 6} undirected graph,String[\"\\\\texttt{+}\",\"\\\\textasciicircum\",\"\\\\texttt{x}\",\"\\\\texttt{2}\",\"\\\\textasciicircum\",\"\\\\texttt{y}\",\"\\\\texttt{2}\"])" + "TreeView.LabelledTree({7, 6} directed graph,Any[:+,:^,:x,2,:^,:y,2])" ] }, - "execution_count": 3, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "tree = walk_tree(:(x^2 + y^2))" + "@tree x^2 + y^2" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "metadata": { "collapsed": false }, @@ -110,86 +124,286 @@ "data": { "image/svg+xml": [ "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + "\n", + "\n" + ], + "text/plain": [ + "TreeView.LabelledTree({4, 3} directed graph,Any[:f,:g,:x,:y])" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "@tree f(g(x, y))" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", "\n", "\n" ], "text/plain": [ - "TreeView.LabelledTree({7, 6} undirected graph,String[\"\\\\texttt{+}\",\"\\\\textasciicircum\",\"\\\\texttt{x}\",\"\\\\texttt{2}\",\"\\\\textasciicircum\",\"\\\\texttt{y}\",\"\\\\texttt{2}\"])" + "TreeView.LabelledTree({19, 18} directed graph,Any[:block,:line,2,Symbol(\"In[7]\"),:(=),:a,:+,:b,:c,:line,3,Symbol(\"In[7]\"),:(=),:d,:^,:-,:a,2,2])" ] }, - "execution_count": 5, + "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "@tree x^2 + y^2" + "@tree begin\n", + " a = b + c\n", + " d = (a - 2)^2\n", + "end" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Internally, function calls in `Expr`essions are represented using nodes with `call`, which can also be visualized:" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 3, "metadata": { "collapsed": false }, @@ -201,34 +415,34 @@ "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", "\n", @@ -253,53 +467,53 @@ "\n", "\n", "\n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", "\n", "\n" ], "text/plain": [ - "TreeView.LabelledTree({10, 9} undirected graph,String[\"\\\\texttt{call}\",\"\\\\texttt{+}\",\"\\\\texttt{call}\",\"\\\\textasciicircum\",\"\\\\texttt{x}\",\"\\\\texttt{2}\",\"\\\\texttt{call}\",\"\\\\textasciicircum\",\"\\\\texttt{y}\",\"\\\\texttt{2}\"])" + "TreeView.LabelledTree({10, 9} directed graph,Any[:call,:+,:call,:^,:x,2,:call,:^,:y,2])" ] }, - "execution_count": 5, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } @@ -308,9 +522,30 @@ "@tree_with_call x^2 + y^2" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Directed Acyclic Graphs (DAGs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Directed Acyclic Graphs (DAGs) give a higher-level view of the graph, with less Julia-specific detail." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the following, the expression `x+y` is repeated twice:" + ] + }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 9, "metadata": { "collapsed": false }, @@ -319,65 +554,97 @@ "data": { "image/svg+xml": [ "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", + "\n", + "\n", + " \n", "\n", "\n", "\n", "\n" ], "text/plain": [ - "TreeView.LabelledTree({4, 3} undirected graph,String[\"\\\\texttt{f}\",\"\\\\texttt{g}\",\"\\\\texttt{x}\",\"\\\\texttt{y}\"])" + "TreeView.DirectedAcyclicGraph({7, 8} directed graph,Any[:+,:^,:+,:x,:y,2,:+],Dict(:y=>5,:x=>4))" ] }, - "execution_count": 4, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "@tree f(g(x, y))" + "@dag (x + y)^2 + (x + y)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can apply **Common Subexpression Elimination** (CSE) from the [`CommonSubexpressions.jl` package](https://github.com/rdeits/CommonSubexpressions.jl/):" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 10, "metadata": { "collapsed": false }, @@ -386,207 +653,85 @@ "data": { "image/svg+xml": [ "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", + "\n", "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", - "\n", - "\n", + "\n", + "\n", "\n", - "\n", + "\n", "\n", "\n", - "\n", + "\n", "\n", "\n", "\n", "\n", "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", - " \n", - " \n", - " \n", + " \n", "\n", "\n", - " \n", + " \n", "\n", "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", - "\n", - "\n", - " \n", + " \n", "\n", "\n", "\n", "\n" ], "text/plain": [ - "TreeView.LabelledTree({19, 18} undirected graph,String[\"\\\\texttt{block}\",\"\\\\texttt{line}\",\"\\\\texttt{2}\",\"\\\\texttt{In[6]}\",\"\\\\texttt{=}\",\"\\\\texttt{a}\",\"\\\\texttt{+}\",\"\\\\texttt{b}\",\"\\\\texttt{c}\",\"\\\\texttt{line}\",\"\\\\texttt{3}\",\"\\\\texttt{In[6]}\",\"\\\\texttt{=}\",\"\\\\texttt{d}\",\"\\\\textasciicircum\",\"\\\\texttt{-}\",\"\\\\texttt{a}\",\"\\\\texttt{2}\",\"\\\\texttt{2}\"])" + "TreeView.DirectedAcyclicGraph({6, 6} directed graph,Any[:+,:x,:y,:^,2,:+],Dict(Symbol(\"##^#271\")=>4,Symbol(\"##+#272\")=>6,:y=>3,:x=>2,Symbol(\"##+#270\")=>1))" ] }, - "execution_count": 6, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "@tree begin\n", - " a = b + c\n", - " d = (a - 2)^2\n", - "end" + "@dag_cse (x + y)^2 + (x + y)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We see that the common subexpression `x+y` now appears only once." ] } ], diff --git a/src/TreeView.jl b/src/TreeView.jl index 6b7c1e9..7c994b9 100644 --- a/src/TreeView.jl +++ b/src/TreeView.jl @@ -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 diff --git a/src/dag.jl b/src/dag.jl new file mode 100644 index 0000000..fa7aee5 --- /dev/null +++ b/src/dag.jl @@ -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 diff --git a/src/display.jl b/src/display.jl new file mode 100644 index 0000000..fb5cc63 --- /dev/null +++ b/src/display.jl @@ -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 diff --git a/src/tree.jl b/src/tree.jl new file mode 100644 index 0000000..2d5eb80 --- /dev/null +++ b/src/tree.jl @@ -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 diff --git a/test/runtests.jl b/test/runtests.jl index 335d7c3..0dad375 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -7,11 +7,11 @@ using Base.Test t = @tree 1x @test typeof(t) == TreeView.LabelledTree - @test typeof(t.g) == LightGraphs.Graph - @test typeof(t.labels) == Vector{String} + @test typeof(t.g) == LightGraphs.DiGraph + @test typeof(t.labels) == Vector{Any} @test t.g.vertices == 1:3 - @test t.labels == String["\\texttt{*}","\\texttt{1}","\\texttt{x}"] + @test t.labels == Any[:*,1,:x] t = @tree x^2 + y^2 @@ -22,5 +22,16 @@ end # Test for issues with the characters present in julia's generated symbols expr = Expr(Symbol("##271")) t = walk_tree(expr) - @test t.labels[1] == "\\texttt{\\#\\#271}" + @test t.labels[1] == Symbol("##271") +end + +@testset "DAG" begin + dag = @dag x + 2x + @test length(dag.labels) == 4 + + dag = @dag((x+y)^2 + (x+y)) + @test length(dag.labels) == 7 + + dag2 = @dag_cse((x+y)^2 + (x+y)) + @test length(dag2.labels) == 6 end