-
-
Save outsinre/1ca61b5c5d7b49b528f5b480a1dff96e to your computer and use it in GitHub Desktop.
Parse lua code and print call graph
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/awk -f | |
# | |
# call_graph.awk | |
# | |
# Usage: | |
# ./call_graph.awk my_program.lua | dot -Tsvg > call_graph.svg | |
# | |
# This is a script that generates a visual call graph for a Lua file. | |
# This script only shows calls made to functions defined within the | |
# input Lua file; that is, it excludes calls such as standard library | |
# or built-in functions. | |
# | |
# Use the option "-v modulet=_M" to specify the table name of the module; | |
# this supports module-level function definitions `function _M.f()` and calls | |
# like `self:f()`. Default is "_M". | |
# | |
# Use the option "-v detailed=1" to draw a detailed call graph without | |
# caring about function definition prefix "_M." and call prefix "self:". | |
# | |
# Use the option "-v nodirect=1" to hide direct function calls; these | |
# are calls made from the top level of your Lua program. | |
# | |
# Example: | |
# ./call_graph.awk -v modulet=_M -v detailed=1 -v nodirect=1 my_program.lua | |
# | |
# See https://gist.github.com/outsinre/1ca61b5c5d7b49b528f5b480a1dff96e | |
BEGIN { | |
identifier = "[A-Za-z_][A-Za-z0-9_.]+" | |
fn_call = "(return )?(self:)?" identifier "\\(" | |
in_fn = "_direct_call_" | |
if (! modulet) { modulet = "_M." } else if (! match(modulet, /\.$/)) { modulet = modulet "." } | |
if (! detailed) detailed=0 | |
if (! nodirect) defined_fns[in_fn] = 1 | |
print "strict digraph {" | |
print " node [shape=box color=\"#FFFFFF\" fontname=\"courier\" fontsize=12];" | |
print " edge [color=\"#CCCCCC\" arrowsize=0.8];" | |
} | |
# Detect and ignore comments. | |
/^--\[\[/ { in_a_comment = 1 } | |
/^--\]\]/ { in_a_comment = 0 } | |
# Detect when we are at the top-level of the program; out of any functions. | |
# This detection is not foolproof; it assumes the code is consistently indented. | |
/^ *end/ { | |
indent = match($0, /[^ ]/) - 1 | |
if (indent == in_fn_indent) { | |
in_fn = "_direct_call_" | |
} | |
} | |
# Detect and track function definitions. | |
# This includes only functions declared at the start of a line. | |
/^ *(local *)?function/ { | |
if (in_a_comment) next | |
if ($1 == "local") { fn_name = $3 } else { fn_name = $2 } | |
sub(/\(.*/, "", fn_name) | |
if (detailed) sub(modulet, "", fn_name) | |
defined_fns[fn_name] = 1 | |
in_fn = fn_name | |
in_fn_indent = match($0, /[^ ]/) - 1 | |
# Uncomment the following line to help debug this script. | |
# printf "%d: --- start function %s\n", NR, fn_name | |
next # Don't consider this line as a function call. | |
} | |
# Track function calls. | |
$0 ~ fn_call { | |
if (in_a_comment) next | |
fn_index = match($0, fn_call) | |
comment_index = match($0, /--/) | |
if (comment_index && comment_index < fn_index) next | |
tail = substr($0, fn_index) | |
fn_name = substr(tail, 1, index(tail, "(") - 1) | |
if (detailed) { sub(/self:/, "", fn_name) } else { sub(/self:/, modulet, fn_name) } | |
calls[in_fn " -> " fn_name] = 1 | |
# Uncomment the following line to help debug this script. | |
# printf "%d: called_fns[%s] = %s\n", NR, fn_name, in_fn | |
} | |
END { | |
for (call in calls) { | |
split(call, fns, " -> ") | |
if (defined_fns[fns[1]] && defined_fns[fns[2]]) { | |
print " \"" fns[1] "\" -> \"" fns[2] "\"" | |
} | |
} | |
print "}" | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
On macOS, we need to install
graphviz
to include thedot
command. See https://graphviz.org/download/#mac.Example: