##########################################################################
#                                                                        #
#  This file is part of the Frama-C's E-ACSL plug-in.                    #
#                                                                        #
#  Copyright (C) 2012-2015                                               #
#    CEA (Commissariat à l'énergie atomique et aux énergies              #
#         alternatives)                                                  #
#                                                                        #
#  you can redistribute it and/or modify it under the terms of the GNU   #
#  Lesser General Public License as published by the Free Software       #
#  Foundation, version 2.1.                                              #
#                                                                        #
#  It is distributed in the hope that it will be useful,                 #
#  but WITHOUT ANY WARRANTY; without even the implied warranty of        #
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         #
#  GNU Lesser General Public License for more details.                   #
#                                                                        #
#  See the GNU Lesser General Public License version 2.1                 #
#  for more details (enclosed in the file license/LGPLv2.1).             #
#                                                                        #
##########################################################################

#!/bin/sh -e

# Convenience wrapper for small runs of E-ACSL Frama-C plugin

# Print a message to STDERR and exit. If the second argument (exit code)
# if provided and it is '0' then do nothing.
error () {
  if [ -z "$2" ] || ! [ "$2" = 0 ]; then
    echo "e-acsl-gcc: fatal error: $1" 1>&2
    exit 1;
  fi
}

# Check if an executable can be found by in the PATH
has_tool() {
  which "$@" >/dev/null 2>&1 && return 0 || return 1
}

# Abort if a given executable cannot be found in the $PATH
check_tool() {
  has_tool "$1" && echo "$1" \
    || error "Cannot find '$1' executable in the \$PATH"
}

# Getopt options
LONGOPTIONS="help,compile,compile-only,print,debug:,ocode:,oexec:,verbose:, \
  frama-c-only,extra-cpp-args,rtl,frama-c-stdlib,full-mmodel,gmp,quiet,logfile:,
  ld-flags:,cpp-flags:"
SHORTOPTIONS="h,c,C,p,d:,o:,O:,v:,f,E:,R,L,M,l:,e:,g,q,s:"
# Prefix for an error message due to wrong arguments
ERROR="ERROR parsing arguments:"

# Gcc
CC="`check_tool 'gcc'`"
CFLAGS="-std=c99 -g3 -O2 -pedantic -fno-builtin -Wall \
    -Wno-long-long \
    -Wno-attributes \
    -Wno-unused-result \
    -Wno-unused-value \
    -Wno-unused-variable \
    -Wno-unused-but-set-variable \
    -Wno-implicit-function-declaration \
    -Wno-attributes"
CPPFLAGS=""
LDFLAGS=""
# Frama-C
FRAMAC="`check_tool 'frama-c'`"
FRAMAC_CONGIG="`check_tool 'frama-c-config'`"
FRAMAC_FLAGS="-implicit-function-declaration ignore"

# E-ACSL source that needed for compilation
FRAMA_C_SHARE="`$FRAMAC_CONGIG -print-share-path`"
EACSL_SHARE="$FRAMA_C_SHARE/e-acsl"
RTL="$EACSL_SHARE/e_acsl.c                      \
  $EACSL_SHARE/memory_model/e_acsl_mmodel.c     \
  $EACSL_SHARE/memory_model/e_acsl_bittree.c    \
"

# Frama-c machdep option
ARCH="$(uname -m | tr -d '\n')"
case $ARCH in
  x86_64)
    MACHDEPFLAGS="86_64"
  ;;
  i686)
    MACHDEPFLAGS="86_32"
  ;;
  *)
    error "Unsupported archirtecture: $ARCH"
  ;;
esac

# -machdep option sent to frama-c
MACHDEP="-machdep gcc_x$MACHDEPFLAGS"
# Macro for correct preprocessing of Frama-C generated code
CPPMACHDEP="-D__FC_MACHDEP_X$MACHDEPFLAGS"

# CPP and LD flags for compilation of E-ACSL-generated sources
EACSL_CPP_FLAGS="
    -D__FC_errno=(*__errno_location())
    -D__builtin_printf=printf
    -D__builtin_memcpy=memcpy"
EACSL_LD_FLAGS="-lgmp -lm"

# Variables holding getopt options
OPTION_ECHO="set -x"                     # Echo executed commands to STDOUT
OPTION_INSTRUMENT=1                      # Perform E-ACSL instrumentation
OPTION_PRINT=                            # Output instrumented code
OPTION_DEBUG=                            # Set frama-c debug flag
OPTION_VERBOSE=                          # Set frama-c verbose flag
OPTION_COMPILE=                          # Compile instrumented program
OPTION_OCODE="a.out.frama.c"             # Name of the translated file
OPTION_OEXEC="a.out"                     # Generated executable name
OPTION_EACSL="-e-acsl -then-last"        # Specifies E-ACSL run
OPTION_FRAMA_STDLIB="-no-frama-c-stdlib" # Use Frama-C stdlib
OPTION_FULL_MMODEL=                      # Instrument as much as possible
OPTION_GMP=                              # Use GMP integers everywhere
OPTION_EXTRA_CPP="-I$FRAMA_C_SHARE/libc $CPPMACHDEP" # Extra CPP flags

manpage() {
  echo "NAME"
  echo "  e-acsl-gcc -- convenience wrapper instrumentation of C files with"
  echo "   the E-ACSL Frama-C plugin and their subsequent compilation using"
  echo "   GNU compiler collection (GCC)"
  echo ""
  echo "SYNOPSIS"
  echo "  e-acsl-gcc <OPTIONS> <C-FILES>"
  echo ""
  echo "OPTIONS"
  echo "  -h, --help"
  echo "      show this help page"
  echo "  -c, --compile"
  echo "      compile generated and original files"
  echo "  -C, --compile-only"
  echo "      compile input files as if they were generated by E-ACSL"
  echo "  -p, --print"
  echo "      output the code generated by E-ACSL to STDOUT"
  echo "  -d, --debug <N>"
  echo "      pass a value to Frama-C -debug option"
  echo "  -o, --ocode <FILENAME>"
  echo "      name of the output source file, defaults to a.out.frama.c"
  echo "  -O, --oexec <FILENAME>"
  echo "      name of the executable generated from the un-instrumented code."
  echo "      Executable compiled from the E-ACSL instrumented sources is"
  echo "      appended .e.acsl suffix. Defaults to a.out and a.out.e-acsl"
  echo "  -v, --verbose <N>"
  echo "      pass a value to Frama-C -verbose option"
  echo "  -f, --frama-c-only"
  echo "      run input source files through Frama-C without E-ACSL"
  echo "  -E, --extra-cpp-args <FLAGS>"
  echo "      pass additional arguments to the Frama-C pre-processor"
  echo "  -R, --rtl"
  echo "      output E_ACSL runtime libraries to STDOUT"
  echo "  -L, --frama-c-stdlib"
  echo "      use Frama-C standard library"
  echo "  -M, --full-mmodel"
  echo "      maximise memory-related instrumentation"
  echo "  -g, --gmp"
  echo "      always use GMP integers instead of C integral types"
  echo "  -l, --ld-flags <FLAGS>"
  echo "      pass the specified flags to the linker"
  echo "  -e, --cpp-flags <FLAGS>"
  echo "      pass the specified flags to the pre-processor (compile-time)"
  echo "  -q, --quiet"
  echo "      suppress any output except for errors and warnings"
  echo "  -s, --logfile <FILE>"
  echo "      redirect all output to a given log file"
  echo ""
  echo "EXAMPLES:"
  echo "  # Instrument foo.c and output the instrumented code to a.out.frama.c"
  echo "      e-acsl-gcc.sh foo.c"
  echo "  # Instrument foo.c, output the instrumented code to gen_foo.c and"
  echo "  # compile foo.c into foo and gen_foo.c into foo.e-acsl"
  echo "      e-acsl-gcc.sh -c -ogen_foo.c -Ofoo foo.c"
  echo "  # Assume gen_foo.c has been instrumented by E-ACSL and compile it"
  echo "  # into a.out.e-acsl"
  echo "      e-acsl-gcc.sh -C gen_foo.c"
  exit 1
}

# See if pygmentize if available for color highlighting and default to plain
# cat command otherwise
if has_tool 'pygmentize'; then
  CAT="pygmentize -g -O style=colorful,linenos=1"
else
  CAT="cat"
fi

# Run getopt
ARGS=`getopt -n "$ERROR" -l "$LONGOPTIONS" -o "$SHORTOPTIONS" -- "$@"`

# Print and exit if getopt fails
if [ $? != 0 ]; then
  exit 1;
fi

# Set all options in $@ before -- and other after
eval set -- "$ARGS"

# Switch statements for other options
for i in $@
do
  case "$i" in
    # Do compile instrumented code
    --help|-h)
      shift;
      manpage;
    ;;
    # Do not echo commands to STDOUT
    --quiet|-q)
      shift;
      OPTION_ECHO=
      OPTION_DEBUG="-debug 0"
      OPTION_VERBOSE="-verbose 0"
    ;;
    # Redirect all output to a given file
    --logfile|-s)
        shift;
        exec > $1
        exec 2> $1
        shift;
    ;;
    # Do compile instrumented code
    --compile|-c)
      shift;
      OPTION_COMPILE=1
    ;;
    # Print the result of instrumenation
    --print|-p)
      shift;
      OPTION_PRINT=1
    ;;
    # Set Frama-C debug flag
    --debug|-d)
      shift;
      if [ "$1" -eq "$1" ] 2>/dev/null; then
        OPTION_DEBUG="-debug $1"
      else
        error "-d|--debug option requires integer argument"
      fi
      shift;
    ;;
    # Set Frama-C verbose flag
    --verbose|-v)
      shift;
      if [ "$1" -eq "$1" ] 2>/dev/null; then
        OPTION_VERBOSE="-verbose $1"
      else
        error "-v|--verbose option requires integer argument"
      fi
      shift;
    ;;
    # Specify the name of the default source file where instrumented
    # code is to be written
    --ocode|-o)
      shift;
      OPTION_OCODE="$1"
      shift
    ;;
    # Specify the base name of the executable generated from the
    # instrumented and non-instrumented sources.
    --oexec|-O)
      shift;
      OPTION_OEXEC="$1"
      shift
    ;;
    # Additional CPP arguments
    --extra-cpp-args|-E)
      shift;
      OPTION_EXTRA_CPP="$OPTION_EXTRA_CPP$1"
      shift;
    ;;
    # Additional flags passed to the linker
    --ld-flags|-l)
      shift;
      LDFLAGS="$LDFLAGS $1"
      shift;
    ;;
    # Additional flags passed to the pre-processor (compile-time)
    --cpp-flags|-e)
      shift;
      CPPFLAGS="$CPPFLAGS $1"
      shift;
    ;;
    # Do not perform the instrumentation, only compile the provided sources
    # This option assumes that the source files provided at input have
    # already been instrumented
    --compile-only|-C)
      shift;
      OPTION_INSTRUMENT=
      OPTION_COMPILE="1"
    ;;
    # Run only frama-c related instrumentation
    --frama-c-only|-f)
      shift;
      OPTION_EACSL=
    ;;
    # Output the RTL files
    --rtl|-R)
      echo $RTL
      exit 0;
    ;;
    # Do use Frama-C stdlib, which is the default behaviour of Frama-C
    --frama-c-stdlib|-L)
      shift;
      OPTION_FRAMA_STDLIB=""
    ;;
    # Use as much memory-related instrumentation as possible
    -M|--full-mmodel)
      shift;
      OPTION_FULL_MMODEL="-e-acsl-full-mmodel"
    ;;
    # Use GMP everywhere
    -g|--gmp)
      shift;
      OPTION_GMP="-e-acsl-gmp-only"
    ;;
  esac
done
shift;

# Bail if no files to translate are given
if test -z "$1"; then
  error "no input files";
fi

# Instrument
if [ -n "$OPTION_INSTRUMENT" ]; then
  ($OPTION_ECHO; \
    $FRAMAC \
    $FRAMAC_FLAGS \
    $MACHDEP \
    -cpp-extra-args="$OPTION_EXTRA_CPP" \
    -e-acsl-share $EACSL_SHARE \
    $OPTION_VERBOSE \
    $OPTION_DEBUG \
    $OPTION_FRAMA_STDLIB \
    $OPTION_FULL_MMODEL \
    $OPTION_GMP \
    "$@" \
    $OPTION_EACSL \
    -print \
    -ocode "$OPTION_OCODE");
    error "aborted by frama-c" $?;
  # Print translated code
  if [ -n "$OPTION_PRINT" ]; then
    $CAT $OPTION_OCODE
  fi
fi

# Compile
if test -n "$OPTION_COMPILE"  ; then
  # Compile the original files only if the instrumentation option is given,
  # otherwise the provided sources are assumed to be E-ACSL instrumented files
  if [ -n "$OPTION_INSTRUMENT" ]; then
    ($OPTION_ECHO; $CC $CPPFLAGS $CFLAGS "$@" -o "$OPTION_OEXEC" $LDFLAGS);
    error "fail to compile/link un-instrumented code: $@" $?;
  else
    OPTION_OCODE="$@"
  fi
  # Compile and link E-ACSL-instrumented file
  ($OPTION_ECHO; $CC $CPPFLAGS $CFLAGS $EACSL_CPP_FLAGS $RTL \
    "$OPTION_OCODE" -o "$OPTION_OEXEC.e-acsl" $LDFLAGS $EACSL_LD_FLAGS)
  error "fail to compile/link instrumented code: $@" $?;
fi
exit 0;
