#!/bin/bash
export SELF="$0"

set -o pipefail

# this makes 7x speedup in case 'grep' was compiled with multi-byte support
export LC_ALL=C

export CCACHE_DISABLE=1

if [ `uname` = Darwin ]; then
    STDBUF=gstdbuf
    TIMEOUT_P=gtimeout
    ABSPATH='realpath'
else
    STDBUF=stdbuf
    TIMEOUT_P=timeout
    ABSPATH='readlink -f'
fi

usage() {
    echo Usage: $0 COMPILE_ARGS
    exit 1
}

seek_input_file() {
    for i in "$@"; do
        test -e "$i" && return 0
    done

    return 1
}

seek_input_file "$@" || usage

# include common code base
topdir="`dirname "$(${ABSPATH} "$SELF")"`/.."
cclib=$(find $topdir -name 'cclib.sh')
source "$cclib" ||  exit 1

# basic setup & initial checks
export SINK="/dev/null"
export COMPILE_OPTS="-S -O0 -I$topdir/include/predator-builtins -DPREDATOR"
export SL_PLUG='/opt/symbiotic/predator-10.0.0/sl_build/libsl.so'
export ENABLE_LLVM='ON'
if [ -z $ENABLE_LLVM ]; then
    export PLUGPATTERN="-fplugin=libsl\.so"
    export GCC_HOST=''
    find_gcc_host
else
    export PLUGPATTERN="-sl"
    export PASSES_LIB='/opt/symbiotic/predator-10.0.0/sl/../passes-src/passes_build/libpasses.so'
    export OPT_HOST='/usr/lib/llvm-10/bin/opt'
    export CLANG_HOST='/usr/lib/llvm-10/bin/clang'
    find_clang_host
    find_opt_host
    find_plug PASSES_LIB passes Passes
fi

find_plug SL_PLUG sl Predator

# use chromium as default SVG_VIEWER
test -z "$SVG_VIEWER" && SVG_VIEWER=chromium

if test x1 = "x$SL_PLOT"; then
    # wipe all .dot and .svg
    rm -f *.dot *.svg
fi

pidfile="$(mktemp /tmp/slscript.XXXXXX)"
test -w "$pidfile" || die "mktemp failed"
export pidfile
trap "rm -f '$pidfile'" EXIT

# check whether stdout is connected to a terminal
if tty 0>&1 >"$SINK"; then
    # initialize coloring escape sequences
    export S=$(printf '\033[1;30m')
    export R=$(printf '\033[1;31m')
    export G=$(printf '\033[1;32m')
    export Y=$(printf '\033[1;33m')
    export B=$(printf '\033[1;34m')
    export W=$(printf '\033[1;37m')
    export N=$(printf '\033[0m')
fi

# run the gcc
run_gcc() {
    printf "Trying to compile ${W/o0/0}%s${N/o0/0} ... " "$*" >&2
    tmp="$(mktemp /tmp/slgcc.XXXXXX)"
    test -w "$tmp" || die "mktemp failed"
    trap "rm -f '$tmp'" RETURN

    if "$GCC_HOST" $COMPILE_OPTS "$@" -o $SINK > "$tmp" 2>&1; then
        printf "${G/o0/0}OK${N/o0/0}\n" >&2
        cat "$tmp" >&2
        printf "Running ${B/o0/0}Predator${N/o0/0} ...\n" >&2
    else
        printf "${R/o0/0}FAILED${N/o0/0}\n\n" >&2
        cat "$tmp"
        return 1
    fi

    "$GCC_HOST" $COMPILE_OPTS -o $SINK                              \
        -fplugin="$SL_PLUG" -fplugin-arg-libsl-pid-file="$pidfile"  \
        -fplugin-arg-libsl-args=error_label:ERROR $SL_OPTS "$@"     \
        3>&1 1>&2 2>&3 | grep --line-buffered -E                    \
        ' \[-fplugin=libsl\.so\]$|CL_BREAK_IF|INT3|SIGTRAP|internal compiler error|undefined symbol'
    if [ $? -ne 0 ]; then
        printf "${R/o0/0}FAILED${N/o0/0}\n" >&2
    fi
}

# run the clang/LLVM
run_llvm() {
    printf "Trying to compile ${W/o0/0}%s${N/o0/0} ... " "$*" >&2
    tmp="$(mktemp /tmp/slllvm.XXXXXX)"
    test -w "$tmp" || die "mktemp failed"
    trap "rm -f '$tmp'" RETURN

    if "$CLANG_HOST" $COMPILE_OPTS "$@" -o $SINK > "$tmp" 2>&1; then
        printf "${G/o0/0}OK${N/o0/0}\n" >&2
        cat "$tmp" >&2
        printf "Running ${B/o0/0}Predator${N/o0/0} ...\n" >&2
    else
        printf "${R/o0/0}FAILED${N/o0/0}\n\n" >&2
        cat "$tmp"
        return 1
    fi

    "$CLANG_HOST" $COMPILE_OPTS -emit-llvm -g -o -         \
        -Xclang -fsanitize-address-use-after-scope "$@"    \
        | "$OPT_HOST" -o $SINK                             \
        -lowerswitch -unreachableblockelim                 \
        -load "$PASSES_LIB" -global-vars -nestedgep        \
        -load "$SL_PLUG" -sl -pid-file="$pidfile" $SL_OPTS \
        3>&1 1>&2 2>&3 | grep --line-buffered -E           \
        ' \[-sl]$|CL_BREAK_IF|INT3|SIGTRAP|internal compiler error|undefined symbol'
    if [ $? -ne 0 ]; then
        printf "${R/o0/0}FAILED${N/o0/0}\n" >&2
    fi
}

forward_signal() {
    cc_pid="$(<$pidfile)"
    test 0 -le "$cc_pid" || die "invalid pidfile"
    kill "-$1" "$cc_pid"
    wait
}

run_tool() {
    if [ -z $ENABLE_LLVM ]; then
        run_gcc "$@"
    else
        run_llvm "$@"
    fi
}

# colorize the output
run_tool "$@" | ${STDBUF} --output=L sed -Ee 's| \['$PLUGPATTERN'\]$||'        \
        -e "s|^/.*/predator[^/]*/||"                                           \
        -e "s|(^.*) \[internal location\]\$|$S\1$N|"                           \
        -e "s|(: error:) (.*)\$|\1 $R\2$N|"                                    \
        -e "s|(: warning:) (.*)\$|\1 $Y\2$N|"                                  \
        -e "s|(: note:) (.*)\$|\1 $G\2$N|"                                     \
        -e "s|(current memory usage: *) ([0-9.]+ MB)|\1 $B\2$N|"               \
        -e "s|(peak memory usage: *) ([0-9.]+ MB)|\1 $R\2$N|"                  \
        -e "s|(!!! executing insn #[0-9]+ ...) (.*)\$|\1 $G\2$N|"              \
        -e "s|(variable[:]? #[0-9]+:)([a-zA-Z0-9_.]+)|\1$G\2$N|"               \
        -e "s|(per target) (L[0-9]+)|\1 $G\2$N|"                               \
        -e "s|(initial size of state was) ([0-9]+)|\1 $W\2$N|"                 \
        -e "s|(#[0-9]+)|$W\1$N|g"                                              \
        -e "s|([0-9]+/[0-9]+)|$W\1$N|g"                                        \
        -e "s|([a-z_][a-z0-9_]+\\[[0-9]+\\])|$W\1$N|g"                         \
        -e "s|(block L[0-9]+) (in progress)|\1 $Y\2$G|"                        \
        -e "s|(... while executing) (.*\\(\\))|\1 $Y\2$N|"                     \
        -e "s|took ([0-9.]+ s)|took $R\1$N|"                                   \
        -e "s|(ignoring call of undefined function:) (.*)\$|\1 $R\2$N|"        \
        -e "s|(internal compiler error:) (.*)\$|\1 $R\2$N|"                    \
        -e "s|(undefined symbol:) (.*)\$|\1 $R\2$N|"                           \
        -e "s/(CL_BREAK_IF|INT3|SIGTRAP)/$R\1$N/"                              \
        -e "s/(importGlVar\\(\\))/$R\1$N/"                                     \
        -e "s/(: debug: *) ([<>!]J[<>!]|AAA)/\1 $R\2$N/"                       \
        -e "s/(: debug: *) (\\[ADT\\])/\1 $G\2$N/"                             \
        -e "s/(: debug: SymHeap::)(objSetConcrete)/\1$Y\2$N/"                  \
        -e "s/(: debug:) \\(x\\) (.*)\$/\1 $Y(x)$N \2/"                        \
        -e "s/(: debug:) (spliceOutListSegment)/\1 $B\2$N/"                    \
        -e "s/(: debug:) (spliceOutAbstractPathCore)/\1 $B\2$N/"               \
        -e "s/(: debug:) (<<<|>>>)/\1 $B\2$N/"                                 \
        -e "s/(: debug:) (XXX)/\1 $Y\2$N/"                                     \
        -e "s/(: debug: ___) (entering|we are)/\1 $G___$N \2/"                 \
        -e "s/(PT: )(ERROR:)/\1$R\2$S/"                                        \
        -e "s/(PT:)/$W\1$S/"                                                   \
        -e "s/(> phase [0-9] <)/$G\1$S/"                                       \
        &

# redirect terminating signals to our plug-in
trap "forward_signal SIGTERM" SIGTERM
trap "forward_signal SIGINT"  SIGINT
wait "$!"
EC="$?"
test 0 = "$EC" || exit $EC

if test x1 = "x$SL_PLOT"; then
    # visualize graphs
    printf "graph visualization in progress ... "
    make $MAKEOPTS -s -f "$topdir/tests/predator-regre/Makefile.in" \
        TIMEOUT="${TIMEOUT_P} 2" || exit $?
    printf "done\n"

    if test -r fp-main.svg; then
        "$SVG_VIEWER" fp-main.svg
    else
        "$SVG_VIEWER" *.svg
    fi 2>/dev/null &
fi

exit "$EC"
