AST of a Julia function

In this example we are going to plot an abstract syntax tree of a Julia function using the Bucheim Layout from NetworkLayout.jl.

using CairoMakie
using Graphs
using GraphMakie
using NetworkLayout
using CairoMakie

The following code, which walks the AST and creates a SimpleDiGraph was taken and slightly modified from TreeView.jl. Thanks!

function walk_tree(ex; show_call=true)
    g = SimpleDiGraph()
    labels = Any[]
    walk_tree!(g, labels, ex, show_call)
    return (g, labels)
end

function walk_tree!(g, labels, ex, show_call)
    add_vertex!(g)
    top_vertex = vertices(g)[end]

    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)
        elseif !isa(ex.args[i], LineNumberNode)
            add_vertex!(g)
            n = vertices(g)[end]
            add_edge!(g, top_vertex, n)
            push!(labels, ex.args[i])
        end
    end

    return top_vertex
end

The expression we want to look at is the recursive definition of the Fibonacci sequence.

expr = quote
    function fib(n)
        if n > 1
            return fib(n-1) + fib(n-2)
        else
            return n
        end
    end
end

g, labels = walk_tree(expr, show_call=true)
nlabels_align = [(:left, :bottom) for v in vertices(g)]
fig, ax, p = graphplot(g; layout=Buchheim(),
                       nlabels=repr.(labels),
                       nlabels_distance=5,
                       nlabels_align,
                       tangents=((0,-1),(0,-1)))
hidedecorations!(ax); hidespines!(ax)
Example block output

This does not look nice yet! Lets tweak the align parameter of the nodes labels...

for v in vertices(g)
    if isempty(inneighbors(g, v)) # root
        nlabels_align[v] = (:center,:bottom)
    elseif isempty(outneighbors(g, v)) #leaf
        nlabels_align[v] = (:center,:top)
    else
        self = p[:node_pos][][v]
        parent = p[:node_pos][][inneighbors(g, v)[1]]
        if self[1] < parent[1] # left branch
            nlabels_align[v] = (:right,:bottom)
        end
    end
end
p.nlabels_align = nlabels_align
Example block output

This page was generated using Literate.jl.