Skip to content

Instantly share code, notes, and snippets.

@tylerneylon
Created October 7, 2014 01:08
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save tylerneylon/c0cdccdbf2c6a2cb4bdb to your computer and use it in GitHub Desktop.
Save tylerneylon/c0cdccdbf2c6a2cb4bdb to your computer and use it in GitHub Desktop.
Parse Lua code and draw its 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
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 " " call
}
}
print "}"
}
@outsinre
Copy link

outsinre commented Apr 7, 2024

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