Skip to content

Instantly share code, notes, and snippets.

@outro56
Created June 21, 2018 12:06
Show Gist options
  • Save outro56/300cde2512fbc16a0488e37868bd7de4 to your computer and use it in GitHub Desktop.
Save outro56/300cde2512fbc16a0488e37868bd7de4 to your computer and use it in GitHub Desktop.
Parse lua code and print call graph
#!/usr/bin/awk -f
#
# call_graph.awk
#
# Usage:
# ./call_graph.awk my_program.lua | dot -Tpng > call_graph.png
#
# 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 nodirect=1" to hide direct function calls; these
# are calls made from the top level of your Lua program.
#
# Example: ./call_graph.awk -v nodirect=1 my_program.lua
#
BEGIN {
identifier = "[A-Za-z_][A-Za-z0-9_.]+"
fn_call = identifier "\\("
in_fn = "_direct_call_"
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)
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, match($0, fn_call))
fn_name = substr(tail, 1, index(tail, "(") - 1)
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 "}"
}
@outro56
Copy link
Author

outro56 commented Jun 21, 2018

./call-graph.awk termtris.lua | dot -Tpng > call_graph.png

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment