#!/bin/bash

set -e

# go to a temporary directory
cd "$(mktemp -d)"
# download the Lua 5.5 source-code
curl \
    --silent \
    --location \
    --output lua.tgz \
    https://www.lua.org/ftp/lua-5.5.0.tar.gz
# unpack the tarball
tar -xzf lua.tgz
# go into the src directory
cd lua-5.5.0/src

cat <<EOF > query_function_definitions.txt
(function_definition
  declarator: [
    (function_declarator
      declarator: (identifier) @name)
    (pointer_declarator
      declarator: (function_declarator
        declarator: (identifier) @name))
  ])
EOF

cat <<EOF > query_macro_definitions.txt
(preproc_def name: (identifier) @name)
(preproc_function_def name: (identifier) @name)
EOF

cat <<EOF > query_function_calls.txt
(call_expression function: (identifier) @name)
EOF

# Perform each of the queries above, dumping them into one flat list.
for query in function_definitions macro_definitions function_calls
do
    tree-sitter query "query_${query}.txt" *.c *.h \
        | grep 'text:' \
        | sed 's/.*text: `\(.*\)`/\1/g' \
        > "${query}_output.txt"
done

# Differentiating this script from the article, I use the pattern;
#
# {
#     echo "-- annotation --"
#     <command from article>
# } | less
#
# This pauses the script execution and allows you to view each report in your
# terminal separately. You can type "q" to exit the pager and move on. See
# `man less` to learn more about the pager.

{
    echo "-- approximate list of libc calls --"
    # Subtract definitions (functions and macros) from calls.
    grep -Fxvf function_definitions_output.txt < function_calls_output.txt \
        | grep -Fxvf macro_definitions_output.txt \
        | sort \
        | uniq -c \
        | sort -rn \
        | column -c 80
} | less

# Repeat the query for function calls, but create a listing of function calls
# by file.
rm -f function_calls_by_file.txt
for file in *.c *.h
do
    # Give a little progress indication because this part is slow.
    echo -ne "parsing ${file}\033[0K\r"
    tree-sitter query "query_function_calls.txt" "${file}" \
        | grep 'text:' \
        | sed 's/.*text: `\(.*\)`/\1/g' \
        | xargs -I {} echo "${file} {}" \
        >> "function_calls_by_file.txt"
done

{
    echo "-- libc calls by file (approximate) --"
    grep -Fvf function_definitions_output.txt < function_calls_by_file.txt \
        | grep -Fvf macro_definitions_output.txt \
        | awk '{ print $1 }' \
        | sort \
        | uniq -c \
        | sort -rn \
        | column -c 80
} | less

{
    echo "-- fraction of files who call libc --"
    echo -n "libc callers: "
    grep -Fvf function_definitions_output.txt < function_calls_by_file.txt \
        | grep -Fvf macro_definitions_output.txt \
        | awk '{ print $1 }' \
        | sort \
        | uniq \
        | wc -l
    echo -n "total files: "
    ls *.c *.h | wc -l
} | less

# Manual filter for junk and less meaningful libc surface-area.
cat <<EOF > manual_filter.txt
FormatMessageA
FreeLibrary
GetLastError
GetModuleFileNameA
GetProcAddress
LoadLibraryExA
abort
abs
checkstackp
correctgraylist
exit
findlast
fprintf
free
getgclist
getupvalref
isalnum
isalpha
iscntrl
isdigit
isgraph
islower
isprint
ispunct
isspace
isupper
isxdigit
luaL_len
luaL_newstate
lua_atpanic
lua_numbertocstring
lua_rawlen
memchr
memcmp
memcpy
memset
printf
realloc
sprintf
strchr
strcmp
strcpy
strerror
strlen
strncmp
strpbrk
strrchr
strspn
strstr
sweepgen
sweeplist
sweeptolive
tolower
toupper
va_arg
va_end
va_start
wf
EOF

{
    echo "-- meaningful libc dependency --"
    grep -Fxvf function_definitions_output.txt < function_calls_output.txt \
        | grep -Fxvf macro_definitions_output.txt \
        | grep -Fxvf manual_filter.txt \
        | sort \
        | uniq -c \
        | sort -rn \
        | column -c 80
} | less
