#!/bin/bash

#########################################################################
#                                                                       #
#                                 OCaml                                 #
#                                                                       #
#                Damien Doligez, Jane Street Capital                    #
#                                                                       #
#   Copyright 2014 Institut National de Recherche en Informatique et    #
#   en Automatique.  All rights reserved.  This file is distributed     #
#   under the terms of the Q Public License version 1.0.                #
#                                                                       #
#########################################################################

[ -z "$Tdebug" ] || set -x

Pwith_byte=true
Pwith_opt=true
while : ; do
    case $1 in
        -no-byte) Pwith_byte=false; shift;;
        -no-opt) Pwith_opt=false; shift;;
        *) break;;
    esac
done

Pfile=$1
Pdir=$(dirname $1)
base=$(basename $1 .t)
byte_exec=$base.byt
opt_exec=$base.exe

Pocamlrun="$TOPDIR/boot/ocamlrun$EXE"
Pflags="-nostdlib -I $OTOPDIR/stdlib -I $OTOPDIR/testsuite/lib"
Pocamlc="$Pocamlrun $OTOPDIR/ocamlc $Pflags"
Pocamlopt="$Pocamlrun $OTOPDIR/ocamlopt $Pflags"
Pocaml="$Pocamlrun $OTOPDIR/ocaml $Pflags \
        -init $OTOPDIR/testsuite/scripts/empty"
Pocamldoc="$Pocamlrun $OTOPDIR/ocamldoc/ocamldoc"
Plex="$Pocamlrun $OTOPDIR/lex/ocamllex"
Pyacc="$TOPDIR/yacc/ocamlyacc$EXE"
Pocamlmklib="$Pocamlrun $OTOPDIR/tools/ocamlmklib \
             -ocamlc '$OTOPDIR/boot/ocamlrun$EXE $OTOPDIR/ocamlc $Pflags' \
             -ocamlopt '$OTOPDIR/boot/ocamlrun$EXE $OTOPDIR/ocamlopt $Pflags'"
if [ "$ARCH" = none -o "$ASM" = none ]; then
  Pwith_opt=false
fi

# Pcustomflag is set to "-custom" (at several places in this file)
# as soon as a custom runtime is needed.
if $SUPPORTS_SHARED_LIBRARIES; then
  Pcustomflag=
else
  Pcustomflag=-custom
fi

sources="$base.ml"

kinds="byte opt"

compflags=
byteflags=
custom=false
optflags=
lexflags=-q
yaccflags=-q
topflags=
cflags=
fortranflags=

exec_env=
args=
exit=0
compexit=0
postprocess=false
postexit=0

# Print the error message to stderr and exit with an error code.
error () {
    echo ERROR: $* >&2
    exit 5
}

# Log the command given as argument(s) and execute it, saving the outputs
launch () {
    echo "$*" >&2
    Ltmpexit=0
    Lstg=${Pstage:+-$Pstage}
    eval "$*" </dev/null >>_tmp/$base-$kind$Lstg.refout \
         2>>_tmp/$base-$kind$Lstg.referr \
      || Ltmpexit=$?
    eval "P${Pstage}exit=$Ltmpexit"
}

# Log the command given as argument(s) and execute it, leaving the outputs alone
log () {
    echo "$*" >&2
    eval "$*"
}

# Compile mlyacc, mllex and C files, and build a list of files for the
# OCaml compiler to compile and link.
# Two arguments: the extensions to use for library objects (cmo/cmx) and
#   libs (cma/cmxa).
precompile () {
    cmoext=$1
    cmaext=$2
    Pocamlfiles=
    for f in $sources; do
        case $f in
            *.ml|*.mli) Pocamlfiles="$Pocamlfiles $f";;
            *.mly)
                launch $Pyacc $yaccflags $f
                Pocamlfiles="$Pocamlfiles ${f%.mly}.mli ${f%.mly}.ml"
                ;;
            *.mll)
                launch $Plex $lexflags $f
                Pocamlfiles="$Pocamlfiles ${f%.mll}.ml"
                ;;
            *.c)
                launch $NATIVECC $NATIVECCCOMPOPTS $cflags \
                    -I $CTOPDIR/byterun -c $f
                Pcustomflag=-custom
                Pocamlfiles="$Pocamlfiles ${f%.c}.$O"
                ;;
            *.f)
                launch $FORTRAN_COMPILER $fortranflags -c $f
                Pcustomflag=-custom
                Pocamlfiles="$Pocamlfiles ${f%.f}.$O"
                ;;
            *.cmo) Pocamlfiles="$Pocamlfiles ${f%.cmo}.$cmoext";;
            *.cma) Pocamlfiles="$Pocamlfiles ${f%.cma}.$cmaext";;
            *.$O)
                Pcustomflag=-custom
                Pocamlfiles="$Pocamlfiles $f"
                ;;
            *) error "unexpected source file extension: $f";;
        esac
    done
}

opt_comp () {
    preprocess
    precompile cmx cmxa
    launch $Pocamlopt $compflags $optflags $Pocamlfiles -o $opt_exec
}

byte_comp () {
    preprocess
    precompile cmo cma
    if $custom; then Pcustomflag=-custom; fi
    launch $Pocamlc $compflags $Pcustomflag $byteflags $Pocamlfiles \
        -o $byte_exec
}

top_comp () {
    launch : no compilation step needed
}

top_principal_comp () {
    launch : no compilation step needed
}

opt_run () {
    launch $SET_LD_PATH $exec_env ./$opt_exec $args
}

byte_run () {
    case $Pcustomflag in
        "") Luseruntime=$Pocamlrun;;
        -custom) Luseruntime=;;
        *) error "internal error (bad value for Pcustomflag)";;
    esac
    launch $SET_LD_PATH $exec_env $Luseruntime ./$byte_exec $args
}

top_run () {
    launch "cat $sources | TERM=dumb $Pocaml $topflags 2>&1 \
            | grep -v '^        OCaml version'"
}

top_principal_run () {
    launch "cat $sources | TERM=dumb $Pocaml $topflags -principal 2>&1 \
            | grep -v '^        OCaml version'"
}

postprocess_sort () {
    log "sort $1 >$1.post"
}

postprocess_sort_uniq () {
    log "sort $1 | uniq >$1.post"
}

Pcompare_files () {
    ref="$1"
    act="$2"
    if [ ! -f "$ref" ]; then ref=/dev/null; fi
    log $DIFF $ref $act
}

Pcheck () {
    if $postprocess; then
        post=.post
    else
        post=
    fi
    case $kind in
        *_*) variant=-${kind#*_};;
        *) variant=;;
    esac
    (
        set -e
        for suff in -comp.referr -comp.refout .referr$post .refout$post; do
            Pcompare_files $base$variant$suff _tmp/$base-$kind$suff
        done
        [ "$Pcompexit" -eq "$compexit" ] || {
            error "compilation exit code $Pcompexit is different" \
                "from expected $compexit"
            false
        }
        [ "$Pexit" -eq "$exit" ] || {
            error "program exit code $Pexit is different from expected $exit"
            false
        }
    )
}

Pcheck_kind () {
    case $kind/$Pwith_opt/$Pwith_byte in
        opt/false/*) false;;
        byte/*/false) false;;
        top*/*/false) false;;  # top is a subset of byte
        *) true;;
    esac
}

preprocess () {
    :
}

precheck () {
    case "$sources " in
        *'.f '*) test -n "$FORTRAN_COMPILER";;
    esac
}

opt_precheck () {
    precheck
}

byte_precheck () {
    precheck
}

top_precheck () {
    precheck
}

Pexport_variables () {
    export OCAMLYACC="$Pyacc"
    export OCAMLLEX="$Plex"
    export OCAMLC="$Pocamlc"
    export OCAMLOPT="$Pocamlopt"
    export OCAMLRUN="$Pocamlrun"
}

Pstart_test () {
    printf "Testing %-4s %s ... " "$kind" "$Pfile" >&3
    printf "#################################################"
    printf "\nTesting %-4s %s ...\n" "$kind" "$Pfile"
}

Pskip_test () {
    printf "skip\n" >&3
    printf "skip\n\n"
    exit 0
}

Pfail_test () {
    printf "FAIL\n" >&3
    printf "FAIL\n\n"
}

Ppass_test () {
    printf "pass\n" >&3
    printf "pass\n\n"
}

cd "$Pdir"

######################################################
# read the user's definitions

. ./$base.t

######################################################

Pexport_variables
mkdir -p _tmp
for kind in $kinds; do
    Pstart_test
    (
        # Skip test if kind=opt and configured without native code
        # or if user disabled the current kind
        Pcheck_kind || Pskip_test
        # Run user-defined pre-check
        ${kind}_precheck || Pskip_test
        rm -f _tmp/$base*.ref{out,err}
        (
            Pstage=comp
            ${kind}_comp
            Pstage=
            ${kind}_run
            if $postprocess; then
                Pstage=post
                (cd _tmp; ${kind}_postprocess_out $base-$kind.refout)
                (cd _tmp; ${kind}_postprocess_err $base-$kind.referr)
            fi
            Pcheck
        )
        case $? in
            0) Ppass_test;;
            *) Pfail_test;;
        esac
    )
done
