#!/bin/bash

#
# Test Case Features
# ------------------
#    If the test case has a directory by the same name, copy it into the
#    working direcotory.
#
#    @RAGEL_FILE: file name to pass on the command line instead of file created
#    by extracting section. Does not work with translated test cases.
# 

TRANS=./trans

# Make available to to test directories below us that are not part of this
# repository and cannot source one dir up.
export RAGEL_BIN="@SUBJ_RAGEL_BIN@"
export RAGEL_CPPFLAGS="@SUBJ_RAGEL_CPPFLAGS@"
export RAGEL_LDFLAGS="@SUBJ_RAGEL_LDFLAGS@"

export RAGEL_C_BIN="@SUBJ_RAGEL_C_BIN@"
export RAGEL_D_BIN="@SUBJ_RAGEL_D_BIN@"
export RAGEL_JAVA_BIN="@SUBJ_RAGEL_JAVA_BIN@"
export RAGEL_RUBY_BIN="@SUBJ_RAGEL_RUBY_BIN@"
export RAGEL_CSHARP_BIN="@SUBJ_RAGEL_CSHARP_BIN@"
export RAGEL_GO_BIN="@SUBJ_RAGEL_GO_BIN@"
export RAGEL_OCAML_BIN="@SUBJ_RAGEL_OCAML_BIN@"
export RAGEL_ASM_BIN="@SUBJ_RAGEL_ASM_BIN@"
export RAGEL_RUST_BIN="@SUBJ_RAGEL_RUST_BIN@"
export RAGEL_CRACK_BIN="@SUBJ_RAGEL_CRACK_BIN@"
export RAGEL_JULIA_BIN="@SUBJ_RAGEL_JULIA_BIN@"

function sig_exit()
{
	echo
	exit 1;
}

trap sig_exit SIGINT
trap sig_exit SIGQUIT
trap sig_exit SIGTERM

wk=working
test -d $wk || mkdir $wk
echo $wk/* | xargs rm -Rf

while getopts "gcnmleT:F:W:G:P:CDJRAZOUKY-:" opt; do
	case $opt in
		T|F|W|G|P)
			genflags="$genflags -$opt$OPTARG"
			gen_opts="$gen_opts -$opt$OPTARG"
		;;
		n|m|l|e) 
			genflags="$genflags -$opt"
			gen_opts="$gen_opts -$opt"
		;;
		c) 
			compile_only="true"
			gen_opts="$gen_opts -$opt"
		;;
		g) 
			allow_generated="true"
		;;
		C|D|J|R|A|Z|O|R|K|Y|U)
			langflags="$langflags -$opt"
		;;
		-)
			case $OPTARG in
				asm)
					langflags="$langflags --$OPTARG"
					gen_opts="$gen_opts --$OPTARG"
				;;
				integral-tables|string-tables)
					genflags="$genflags --$OPTARG"
					gen_opts="$gen_opts --$OPTARG"
				;;
				*)
					echo "$0: unrecognized option --$OPTARG" >&2
					exit 1
				;;
			esac
		;;
		?)
			exit 1;
		;;
	esac
done

[ -z "$langflags" ]   && langflags="-C --asm -R -Y -O -U -J -Z -D -A -K"
[ -z "$genflags" ]    && genflags="-T0 -T1 -F0 -F1 -W0 -W1 -G0 -G1 -G2 -n -m -e --string-tables"

shift $((OPTIND - 1));

[ -z "$*" ] && set -- *.rl

ragel="@RAGEL_BIN@"

cxx_compiler="@CXX@"
c_compiler="@CC@"
objc_compiler="@CC@"
d_compiler="@D_BIN@"
java_compiler="@JAVAC_BIN@"
ruby_engine="@RUBY_BIN@"
csharp_compiler="@CSHARP_BIN@"
go_compiler="@GO_BIN@"
ocaml_compiler="@OCAML_BIN@"
rust_compiler="@RUST_BIN@"
crack_interpreter="@CRACK_BIN@"
julia_interpreter="@JULIA_BIN@"
gnustep_config="@GNUSTEP_CONFIG@"
assembler="@ASM_BIN@"

if [ -z "$gnustep_config" ]; then
	objc_compiler=""
fi

function test_error
{
	exit 1;
}

function exec_cmd()
{
	lang=$1

	case $lang in
		java) exec_cmd="java -classpath $wk $classname" ;;
		ruby) exec_cmd="ruby $code_src" ;;
		csharp) exec_cmd="mono $binary" ;;
		ocaml) exec_cmd="ocaml $code_src" ;;
		crack) exec_cmd="$crack_interpreter $code_src" ;;
		julia) exec_cmd="$julia_interpreter $code_src" ;;
		indep) echo "error: exec_cmd: indep not executable"; exit 1 ;;
		*) exec_cmd=./$binary ;;
	esac
}


function lang_opts()
{
	lang=$1

	case $lang in
		c)
			lang_opt=-C;
			code_suffix=c;
			interpreted=false
			compiler=$c_compiler;
			host_ragel=$RAGEL_BIN
			flags="-Wall -O3 -I. -Wno-variadic-macros"
			libs=""
			prohibit_flags=""
		;;
		cg)
			# For testing ragel-c using goto based.
			lang_opt=-C;
			code_suffix=c;
			interpreted=false
			compiler=$c_compiler;
			host_ragel=$RAGEL_C_BIN
			flags="-Wall -O3 -I. -Wno-variadic-macros"
			libs=""
			prohibit_flags="--string-tables"
		;;
		cv)
			# For testing ragel-c using var-based
			lang_opt=-C;
			code_suffix=c;
			interpreted=false
			compiler=$c_compiler;
			host_ragel="$RAGEL_C_BIN --var-backend"
			flags="-Wall -O3 -I. -Wno-variadic-macros"
			libs=""
			prohibit_flags="-G0 -G1 -G2 --string-tables"
		;;
		c++)
			lang_opt=-C;
			code_suffix=cpp;
			interpreted=false
			compiler=$cxx_compiler;
			host_ragel=$RAGEL_BIN
			flags="-Wall -O3 -I. -Wno-variadic-macros"
			libs=""
			prohibit_flags=""
		;;
		obj-c)
			lang_opt=-C;
			code_suffix=m;
			interpreted=false
			compiler=$objc_compiler
			host_ragel=$RAGEL_BIN
			if [ -z "$gnustep_config" ]; then
				flags=""
			else
				flags="`$gnustep_config --objc-flags`"
			fi
			libs="-lobjc -lgnustep-base"
			prohibit_flags=""
		;;
		d)
			lang_opt=-D;
			code_suffix=d;
			interpreted=false
			compiler=$d_compiler;
			host_ragel=$RAGEL_D_BIN
			flags="-Wall -O3"
			libs=""
			prohibit_flags="--string-tables"
		;;
		java)
			lang_opt=-J;
			code_suffix=java;
			interpreted=false
			compiler=$java_compiler
			host_ragel=$RAGEL_JAVA_BIN
			flags=""
			libs=""
			prohibit_flags="-G0 -G1 -G2 --string-tables"
		;;
		ruby)
			lang_opt=-R;
			code_suffix=rb;
			interpreted=true
			compiler=$ruby_engine
			host_ragel=$RAGEL_RUBY_BIN
			flags=""
			libs=""
			prohibit_flags="-G0 -G1 -G2 --string-tables"
		;;
		csharp)
			lang_opt="-A";
			code_suffix=cs;
			interpreted=false
			compiler=$csharp_compiler
			host_ragel=$RAGEL_CSHARP_BIN
			flags=""
			libs=""
			prohibit_flags="-G2 --string-tables"
		;;
		go)
			lang_opt="-Z"
			code_suffix=go
			interpreted=false
			compiler=$go_compiler
			host_ragel=$RAGEL_GO_BIN
			flags="build"
			libs=""
			prohibit_flags="--string-tables"
		;;
		ocaml)
			lang_opt="-O"
			code_suffix=ml
			interpreted=true
			compiler=$ocaml_compiler
			host_ragel=$RAGEL_OCAML_BIN
			flags=""
			libs=""
			prohibit_flags="-G0 -G1 -G2 --string-tables"
		;;
		asm)
			lang_opt="--asm"
			code_suffix=s
			interpreted=false
			compiler="$assembler"
			host_ragel=$RAGEL_ASM_BIN
			flags="-no-pie"
			libs=""
			prohibit_flags="-T0 -T1 -F0 -F1 -W0 -W1 -G0 -G1 --string-tables"
		;;
		rust)
			lang_opt="-U"
			code_suffix=rs
			interpreted=false
			compiler=$rust_compiler
			host_ragel=$RAGEL_RUST_BIN
			flags="-A non_upper_case_globals -A dead_code \
				-A unused_variables -A unused_assignments -A unused_mut -A unused_parens"
			libs=""
			prohibit_flags="-G0 -G1 -G2 --string-tables"
		;;
		crack)
			lang_opt="-K"
			code_suffix=crk
			interpreted=true
			compiler=$crack_interpreter
			host_ragel=$RAGEL_CRACK_BIN
			prohibit_flags="-G0 -G1 -G2 --string-tables"
		;;
		julia)
			lang_opt="-Y"
			code_suffix=jl
			interpreted=true
			compiler=$julia_interpreter
			host_ragel=$RAGEL_JULIA_BIN
			prohibit_flags="-G0 -G1 -G2 --string-tables"
		;;
		indep)
		;;
		*)
			echo "$translated: unknown language type '$lang'" >&2
			exit 1;
		;;
	esac

	prohibit_flags="$prohibit_flags $case_prohibit_flags"
}

function run_test()
{
	code_src=$wk/`echo $lroot$gen_opt.$code_suffix | sed 's/-\+/_/g'`
	binary=$wk/`echo $lroot$gen_opt.bin | sed 's/-\+/_/g'`
	output=$wk/`echo $lroot$gen_opt.out | sed 's/-\+/_/g'`
	diff=$wk/`echo $lroot$gen_opt.diff | sed 's/-\+/_/g'`
	sh=$wk/`echo $lroot$gen_opt.sh | sed 's/-\+/_/g'`
	log=$wk/`echo $lroot$gen_opt.log | sed 's/-\+/_/g'`
	intermed=$wk/`echo $lroot$gen_opt.ri | sed 's/-\+/_/g'`
	classfile=$wk/`echo $lroot$gen_opt.class | sed 's/-\+/_/g'`
	classname=`echo $lroot$gen_opt | sed 's/-\+/_/g'`

	opts="$gen_opt $min_opt $enc_opt $f_opt"
	args="-I. $opts -o $code_src $translated"

	cat >> $sh <<-EOF
	echo testing $lroot $opts
	$host_ragel $args
	EOF

	if [ $lang == java ]; then
		cat >> $sh <<-EOF
		sed -i 's/\<$lroot\>/$classname/g' $code_src
		EOF
	fi

	out_args=""
	[ $lang != java ] && out_args="-o $binary";
	[ $lang == csharp ] && out_args="-out:$binary";

	# Some langs are just interpreted.
	if [ $interpreted != "true" ]; then
		cat >> $sh <<-EOF
		$compiler $flags $out_args $code_src \
				$libs >>$log 2>>$log
		EOF
	fi

	exec_cmd $lang
	if [ "$compile_only" != "true" ]; then
		if [ -n "$FILTER" ]; then
			exec_cmd="$exec_cmd | $FILTER"
		fi		

		cat >> $sh <<-EOF
		$exec_cmd 2>> $log >> $output
		EOF

		cat >> $sh <<-EOF
		diff -u --strip-trailing-cr $expected_out $output > $diff
		# rm -f $intermed $code_src $binary $classfile $output 
		EOF

	fi

	echo $sh
}


function run_options()
{
	translated=$1

	lroot=`basename $translated`
	lroot=${lroot%.rl};

	lang_opts $lang

	[ -n "$additional_cflags" ] && flags="$flags $additional_cflags"

	# If we have no compiler for the source program then skip it.
	[ -z "$compiler" ] && return

	# Make sure that we are interested in the host language.
	echo "$langflags" | grep -qe $lang_opt || return

	for gen_opt in $genflags; do
		echo "" "$prohibit_flags" | \
				grep -e $gen_opt >/dev/null && continue

		run_test
	done
	unset gen_opt
}

function run_translate()
{
	test_case=$1

	# Recompute the root.
	root=`basename $test_case`
	root=${root%.rl};

	# Check if we should ignore the test case
	enabled=`sed '/@ENABLED:/s/^.*: *//p;d' $test_case`
    if [ -n "$enabled" ] || [ "$enabled" = true ]; then
        return;
    fi

	# If the generated flag is given make sure that the test case is generated.
	is_generated=`sed '/@GENERATED:/s/^.*: *//p;d' $test_case`
	if [ "$is_generated" = true ] && [ "$allow_generated" != true ]; then
		return;
	fi

	# Override the test case file name.
	RAGEL_FILE=`sed '/@RAGEL_FILE:/s/^.*: *//p;d' $test_case`

	# Filter to pass output through. Shell code.
	FILTER=`sed '/@FILTER:/s/^.*: *//p;d' $test_case`

	# If the test case has a directory by the same name, copy it into the
	# working direcotory.
	if [ -d $root ]; then
		cp -a $root $wk/
	fi

	expected_out=$wk/$root.exp;
	case_rl=${root}.rl

	sed '1,/^##* * OUTPUT ##*/d' $test_case > $expected_out

	prohibit_languages=`sed '/@PROHIBIT_LANGUAGES:/s/^.*: *//p;d' $test_case`

	# Add these into the langugage-specific defaults selected in run_options
	case_prohibit_flags=`sed '/@PROHIBIT_FLAGS:/s/^.*: *//p;d' $test_case`

	lang=`sed '/@LANG:/s/^.*: *//p;d' $test_case`
	if [ -z "$lang" ]; then
		echo "$test_case: language unset"; >&2
		return
	fi

	cases=""

	if [ $lang == indep ]; then
		for lang in c cg cv asm d csharp go java ruby ocaml rust crack julia; do
			case $lang in 
				c) lf="-C" ;;
				cg) lf="-C" ;;
				cv) lf="-C" ;;
				asm) lf="--asm" ;;
				d) lf="-D" ;;
				csharp) lf="-A" ;;
				go) lf="-Z" ;;
				java) lf="-J" ;;
				ruby) lf="-R" ;;
				ocaml) lf="-O" ;;
				rust) lf="-U" ;;
				crack) lf="-K" ;;
				julia) lf="-Y" ;;
			esac

			echo "$prohibit_languages" | grep -q "\<$lang\>" && continue;
			echo "$langflags" | grep -qe $lf || continue

			# Translate to target language and strip off output.
			targ=${root}_$lang.rl

			$TRANS $lang $wk/$targ $test_case ${root}_${lang}

			cases="$cases $wk/$targ"

			run_options $wk/$targ
		done
	else

		sed '/^##* * OUTPUT ##*/,$d' $test_case > $wk/$case_rl

		cases=$wk/$case_rl

		if [ -n "$RAGEL_FILE" ]; then
			cases="$RAGEL_FILE"
		fi

		run_options $cases
	fi
}

go()
{
	# Before we generate and test cases verify that all files exist. It is nice
	# to catch this early.
	for test_case; do
		if ! [ -f $test_case ]; then
			echo "$test_case: could not find file" >&2
			missing_file=true
			
		fi
	done

	[ "$missing_file" = true ] && exit 1;

	for test_case; do
		run_translate $test_case
	done
}

go "$@"
