#!/bin/sh

#>##################################################

# build and test p5c pascal compiler

# options:
#         --notest   => skip tests
#         --mintest  => run minimal set of tests
#         --debug    => build debug version of p5c

#<##################################################

BUILD=p5c
CC=gcc

ldiff() {
    sdiff -w100 --text --strip-trailing-cr $1 $2 | less
}

# use this difference program
if [ -n "$DISPLAY" ] && which kdiff3 > /dev/null 2>&1; then
    DIFF=kdiff3
elif [ -n "$DISPLAY" ] && which kompare > /dev/null 2>&1; then
    DIFF=kompare
else
    DIFF=ldiff
fi

rm -f mksys pcom p5c mksys.exe pcom.exe p5c.exe
# first, generate maxintTarget from gcc
cat << EOF | $CC -x c - -o mksys
#ifndef SYS_INC_PAS
#define SYS_INC_PAS
#include <stdio.h>
#include <limits.h>
#include <float.h>
int main(void)
{
     printf(" // -- DO NOT EDIT THIS FILE --\n");
     printf("#define SYS_MAXINT_TARGET %d\n", INT_MAX);

     printf("// ordinal of max char\n");
     printf("#define ORD_CHAR_MAX %d\n", UCHAR_MAX);

     printf("\n//see float.h info for more details of the following\n\n");

     printf("// accuracy of a real number\n");
     printf("#define REAL_DIGITS %d\n", DBL_DIG);

     printf("// min exponent of a real number\n");
     printf("#define REAL_MIN_EXP %d\n", DBL_MIN_10_EXP);

     printf("// max exponent of a real number\n");
     printf("#define REAL_MAX_EXP %d\n", DBL_MAX_10_EXP);

     printf("// max value of a real number\n");
     printf("#define REAL_MAX %1.*g\n", DBL_DIG+6, DBL_MAX);

     printf("// epsilon of a real number\n");
     printf("#define REAL_EPSILON %1.*g\n", DBL_DIG+6, DBL_EPSILON);

     printf("// min value of a real number\n");
     printf("#define REAL_MIN %1.*g\n", DBL_DIG+6, DBL_MIN);
}
#endif // SYS_INC_PAS
EOF

./mksys > sys.inc.pas

WITHTEST=2
WITHDEBUG=0

while :
do
    case "$1" in
    --debug)
        WITHDEBUG=1
        shift
        ;;

    --mintest*)
        WITHTEST=1
        shift
        ;;

    --notest*)
        WITHTEST=0
        shift
        ;;

    -*)
        echo "$1 is an unknown option"
        sed -n '/^#>##/,/^#<##/p' $0
        exit
        ;;

    *)  # No more options
        break
        ;;
    esac
done


if [ $WITHDEBUG -ne 0 ] ; then
    echo "building debug compiler"
    cpp -E -nostdinc pcom.pas -w | awk '!/^# 1 \"</ || $3=="\"pcom.pas\"" \
                       { sub(/\(*\$t-,d-,v-/, "(*$t-,d+,v+"); print }' > pcom.1.pas
else
    # now pcom needs to be run thru the c preprocessor
    cpp -E -nostdinc pcom.pas -w | grep -v -e "^# 1 \"<" > pcom.1.pas
fi

# initially use gnu pascal to compile pcom.pas
if which gpc > /dev/null 2> /dev/null; then
    gpc --executable-file-name=pcom -g --pointer-checking --stack-checking \
        --standard-pascal --setlimit=600 pcom.1.pas
    if [ $? -ne 0 ] ; then
        echo "gpc pascal compile failed, bailing out"
        exit
    fi

    ## pcom is the pascal to c compiler, generated by gpc

    gpc-run ./pcom --gpc-rts=-nprd:pcom.1.pas --gpc-rts=-nprc:pcom.c \
            < /dev/null > pcom.lst
else
    echo -n 'gnu pascal not found'
    if [ ! -e p5c-good.c ] ; then
        echo
        echo "you need a working pascal compiler to start"
        echo "install gnu pascal, or "
        echo "get p5c.c and use gnu c to build p5c, rename it p5c-good"
        echo "quitting";
        exit;
    fi

    echo ' ... using p5c-good.c to bootstrap pascal'
    rm -f p5c-good p5c-good.exe
    $CC -I . -o p5c-good p5c-good.c -lm
    ./p5c-good pcom.1.pas pcom.c > pcom.lst

    if ! grep -qF "Errors in program: 0" pcom.lst ; then
        tail  pcom.lst
        echo "compile failed, bailing out" >/dev/stderr
        exit 1
    fi

    $CC -I . -o pcom pcom.c -lm
    ## pcom is the pascal to c compiler, generated by p5c-good

    ./pcom  pcom.1.pas pcom.c > pcom.lst
fi

awk '{ if( $2=="****" ) {print l0 "\n" l1 "\n" $0 ;} \
       else { l0=l1; l1 = $0; }; } \
       /^Errors in program/,0; \
    ' pcom.lst

if ! grep -qF "Errors in program: 0" pcom.lst ; then
    #tail  pcom.lst
    echo "compile failed, bailing out" >/dev/stderr
    exit 1
fi

$CC -std=gnu99 -I . -o $BUILD pcom.c -lm 2> pcom.err
if [ -s pcom.err ]
then
    head pcom.err
    echo "gcc compile failed, bailing out" >/dev/stderr
    exit 1
fi


## p5c is (for now) the compiler generated by pcom

./$BUILD pcom.1.pas $BUILD.c > $BUILD.lst

echo

if ! grep -q "Errors in program: 0" $BUILD.lst ; then
    tail  $BUILD.lst
    echo "compile failed, bailing out"
    exit 1
fi

#rm pcom.1.pas

# first test: compare output generated by p5c and trusted compiler
if diff -q pcom.c $BUILD.c ; then
    echo "compiled files look OK"
elif diff -qb pcom.c $BUILD.c ; then
    echo "compiled files have different whitespace"
else
    echo "error: compiled files mismatch, quitting" >/dev/stderr
    exit 1
fi
echo

$CC -std=gnu99 -O -I . -o $BUILD $BUILD.c -lm 2> $BUILD.err
if [ -s $BUILD.err ]
then
    head $BUILD.err
    echo "gcc compile failed, bailing out" > /dev/stderr
    exit 1
fi

## p5c is now generated by itself

if [ $WITHTEST -eq 0 ] ; then
    echo "skipping tests"
    exit 0
fi


## now run the other tests

export P5CDIR=`pwd`
#echo using $P5CDIR

rm -f tfile tfile.exe
echo "read is OK" > in.txt
./r tfile in.txt out.txt > /dev/null

echo
if [ -f out.txt ]
then
  if file -bi out.txt | grep -q text
  then
     if [ 1 = $(wc -l < out.txt) ]
     then
        if grep -q "this is a response from tfile" out.txt
        then
           echo "external file write is OK"
        else
           echo "external file write has unexpected contents"
           printf 'Press RETURN to continue ... ';
           read REPLY
        fi
     else
        echo "external file write has unexpected number of lines"
           printf 'Press RETURN to continue ... '
           read REPLY
     fi
  else
      echo "external file should be a text file, but looks like it isn't"
      printf 'Press RETURN to continue ... '
      read REPLY
  fi
else
   echo "external file write failed"
   printf 'Press RETURN to continue ... '
   read REPLY
fi

if [ 1 = $(wc -l < in.txt) ]
  then
     echo "external file write termination is OK"
  else
     echo "external file write is incorrectly terminated"
     printf 'Press RETURN to continue ... '
     read REPLY
fi


echo

# run the main p5c test program

# first check that the test program knows how many open files are allowed
MAXFILES=$(ulimit -n)

if [ $MAXFILES -gt 1024 ]; then
    echo "this shell's file limit is $MAXFILES but test assumes 1024"
    echo "changing open limit to 1024"
    if ulimit -n 1024; then
        MAXFILES=1024
    else
        echo "*********** failed to change file limit"
        echo "try to manually change file limit with "
        echo "             \"ulimit -n 1024\""
        echo "or similar command, or change this line in tp5c.pas:"
        grep -n ulimit tp5c.pas
        printf 'quitting, pess RETURN ... '
        read REPLY
        exit 1
    fi
fi
echo "testing with file limit $MAXFILES"


rm -f tp5c tp5c.exe
echo tp5c
./pc -DMAXFILES3=$(($MAXFILES-3)) tp5c
if ! grep -qF "Errors in program: 0" tp5c.lst ; then
    echo "bailing out now"
    exit 1
fi

if grep -qF ": error:" tp5c.err ; then
    echo "c code generation error - bailing out now"
    exit 1
fi

# check all warnings occur as expected
# each warning in tp5c must be marked with a {%%Wn ...} comment,
# where n is warning nr.
# the warning must appear on the following line
#
awk 'BEGIN { nr_passes = 0; \
             nr_wrong = 0;      ## wrong warnings\
             nr_not_found = 0;  ## warnings expected but not found\
             nr_unexpected = 0; ## count warnings found but unexpected \
             print "\n";
           }

/{%%W/{ wi=index($0,"%%W");  \
        ws=substr($0, wi+3 );      \
        wnum=strtonum(ws);    \
        getline; \
        if( $3 ~ /\^W/ ) { \
             if( strtonum(substr($3,3)) == wnum )       \
                  nr_passes++;                          \
             else {nr_wrong++; \
                  print "wrong warning at line", $1; \
             }
             getline; # done with warning line
        }
        else { nr_not_found++; \
             print "warning not found at line ", $1; \
        }
      }
     /\^W1/ {nr_unexpected++; \
             print "unexpected warning found at line", $1; \
            }

END { \
       print "warnings verification test: ", nr_passes, "passed"; \
       if( nr_not_found+nr_unexpected+nr_wrong == 0 ) \
            print "all tests passed";               \
       else {
            if( nr_unexpected == 1 ) print "one warning unexpected";    \
            else print nr_unexpected, "unexpected warnings";
            if( nr_not_found == 1 ) print "one warning not found";    \
            else print nr_not_found, "warnings not found";
            if( nr_wrong == 1 ) print "one wrong warning";    \
            else print nr_wrong, "wrong warnings"; \
       }
       exit (nr_wrong + nr_unexpected + nr_not_found) == 0;
    } ' tp5c.lst
if [ $? -eq 0 ] ; then
    printf 'Press RETURN to continue ...'
    read REPLY
fi


# inspect generated c code to check that set bound calcs are correct
#
#   expected bounds are contained in a line like this
#   iStrxxxx## -50  50xxx;
#
#   where xxx is arbitrary text, -50 and 50 are the lower and upper bounds
#
#   result bounds appear in the next few lines and look like this
#   xxxxxxxxuint8_t s0[(50>>3)-(-50>>3)xxx
#
#   note this time the bounds are in the opposite order.
#

awk 'BEGIN { \
             nr_passes = 0; \
             nr_fails = 0; \
             print "\n"; \
           } \
  \
/iStr.*## *-?[0-9]+ +-?[0-9]+/ { \
  match( $0, /.*## *(-?[0-9]+) +(-?[0-9]+)/, el); \
  #print "line", NR, ": expected bounds are ", el[1], "..", el[2]; \
  do{ \
    getline; \
  } while( $0 !~ /uint8_t \$s0\[/ );  \
  #print "line", NR, ":", $0 \
  match( $0, /.*uint8_t \$s0\[\((-?[0-9]+)>>3\).*-\((-?[0-9]+)>>3\)/, rl); \
  #print "line", NR, ": result bounds are ", rl[2], "..", rl[1]; \
  if( rl[2] == el[1] && rl[1] == el[2] ) nr_passes++; \
  if( rl[2] != el[1] ) { \
     nr_fails++; \
     print "**** problem with lower bound at line", NR, ": expected", el[1], "found", rl[2]                \
          } \
  if( rl[1] != el[2] ) { \
     nr_fails++; \
     print "**** problem with upper bound at line", NR, ": expected", el[2], "found", rl[1]    \
          } \

} \
  \
END { \
       print "code inspection test: ", nr_passes, "passed, "; \
       if( nr_fails == 0 ) print "all tests passed\n"; \
       else if( nr_fails == 1 ) print "one failure\n"; \
       else print nr_fails, "failures\n"; \
       exit nr_fails == 0;
    } ' tp5c.c
if [ $? -eq 0 ] ; then
    printf 'Press RETURN to continue ...'
    read REPLY
fi

rm -f tp5c.out
./tp5c | tee tp5c.out | awk 'BEGIN { IGNORECASE = 1; np=0; nf=0; } \
                /FAIL/ {nf++; print}; \
                /PASS/ {np++;}; \
                END { print "tp5c:", np, "tests passed,", nf==1? "one test": nf " tests", "failed" }'

if grep -q "embedded c code test passed" tp5c.out && \
   grep -q "inline void p01_2(void) {" tp5c.c && \
   grep -q "register int  /\* form (0) \*/ ai_3;" tp5c.c ; then
   echo "embedded c code test passes"
else
    echo "embedded c code test failed"
    printf 'Press RETURN to continue ... '
    read REPLY
fi

grep "fail" tp5c.out
svn ls tp5c.out > /dev/null 2>&1 && svn diff tp5c.out

echo
echo "test again with debug on ..."
sed 's/{$d-,v-}/{$d+,v+,w-}/' < tp5c.pas > tp5cd.pas
rm -f tp5cd tp5cd.exe tp5cd.out
echo tp5cd
./r -DMAXFILES3=$(($MAXFILES-3)) tp5cd | tee tp5cd.out | \
            awk 'BEGIN { IGNORECASE = 1; np=0; nf=0; } \
                /FAIL/ {nf++; print}; \
                /PASS/ {np++;}; \
                END { print np, "tests passed,", nf==1? "one test": nf " tests", "failed" }'

 if [ ! -x tp5cd ] ; then
        echo "tp5cd compile failed, bailing out" >/dev/stderr
        exit 1
 fi

 diff -qs tp5c.out tp5cd.out || $DIFF tp5c.out tp5cd.out

echo
rm -f tclib tclib.exe
./r -cpp tclib | tee tclib.out
if ! grep -q "c library tests passed" tclib.out ; then
   echo "tclib test failed"
   printf 'Press RETURN to continue ...'
   read REPLY
fi

rm -f copytext*

echo    "1: this is a text file to test copytext.p"      > in.txt
echo    "2: note that the last line is not terminated"  >> in.txt
echo                                                    >> in.txt
echo                                                    >> in.txt
echo    "3: next line is last line"                     >> in.txt
echo -n "4: last line"                                  >> in.txt

cat << EOF > copytext.p
program copytext (input, output);
var ch : char;
begin
 while not eof do begin
  while not eoln do begin read(ch); write(ch) end;
  readln; writeln
 end
end.
EOF


./pc copytext
./copytext < in.txt > out1.txt

echo
cat << EOF > copytextf.p
program copytextf (infile, outfile);
var ch : char;
    infile, outfile: text;
begin
 reset(infile); rewrite(outfile);
 while not eof(infile) do begin
  while not eoln(infile) do begin read(infile, ch); write(outfile, ch) end;
  readln(infile); writeln(outfile)
 end
end.
EOF


./r copytextf in.txt out2.txt

# add new line to in.txt, check out.txt is identical
echo >> in.txt


if diff -q --text --strip-trailing-cr in.txt out1.txt >/dev/null ; then
  echo "read/write standard in/out OK"
else
  echo "read/write standard in/out failed"
  printf 'Press RETURN to continue ...'
  read REPLY
fi

if diff -q --text --strip-trailing-cr in.txt out2.txt >/dev/null ; then
  echo "read/write file in/out OK"
else
  echo "read/write file in/out failed"
  printf 'Press RETURN to continue ... '
  read REPLY
fi

echo
echo -n "pan test ... "
./pan tpan > tpan.rpt
if which svn > /dev/null 2> /dev/null && svn ls tpan.rpt 2> /dev/null ; then
    if [ `svn diff --diff-cmd diff --extensions "-I analysis" tpan.rpt | \
       wc -l ` -ne  2 ] ; then
        svn diff tpan.rpt
    else
        svn revert -q tpan.rpt
        echo "OK"
    fi
else
    echo "skipped - no previous version of tpan.rpt"
fi


if [ $WITHTEST -eq 1 ] ; then
    echo "skipping extended tests"
    exit 0
fi


# run more tests
for d in *-tests ; do
    if [ -d $d ] ; then
        echo "running $d ........................"
        cd $d

        if which xset > /dev/null 2>&1 && xset q >/dev/null 2>&1 && which xterm > /dev/null 2>&1; then
            xterm -title $d -e ./test.sh &
        else
            #echo "No X server at \$DISPLAY [$DISPLAY]" >&2
            ./test.sh
        fi

        cd ..
    fi
done;


cat << EOF > testr.p
program testr(output);
begin
  writeln(VAL);
end.
EOF

./r -DVAL=99 testr
cp --preserve=timestamp testr testr.bak
sleep 3
./r -DVAL=99 testr
# testr should not be recompiled
if [ testr -nt testr.bak ]; then
    echo "recompile test failed"
    printf 'Press RETURN to continue ... '
    read REPLY
fi
sleep 3
./r -DVAL=101 testr
# now testr should be recompiled?
if [ ! testr -nt testr.bak ]; then
    echo "recompile test failed"
    printf 'Press RETURN to continue ... '
    read REPLY
fi
rm testr.p

echo
echo -n "set test program test4.p ... "
rm -f tgen test4*
./r tgen test4.p >/dev/null 2>&1
./pc test4.p
if [ -e ./test4 ] ; then
    if [ $(./test4 | grep -cv pass) -eq 0 ] ; then
        echo " passed"

        # comment out this test if it consumes too much memory or time
        echo -n "set test program test5.p ... "
        rm -f tgen test5*
        ./r -DN=5 -DM=31 tgen test5.p >/dev/null 2>&1
        test -e test5.p && ./pc test5
        if [ -e ./test5 ]; then
            if [ $(./test5 | grep -cv pass) -eq 0 ] ; then
                echo "passed"
            else
                ./test5 | grep -v pass
                printf ' failed, Press RETURN to continue ... '
                read REPLY
            fi
        else
            echo "not generated"
            printf 'Press RETURN to continue ... '
            read REPLY
        fi

    else
        ./test4 | grep -v pass
        printf 'Press RETURN to continue ... '
        read REPLY
    fi
else
    echo "not generated"
    printf 'Press RETURN to continue ...'
    read REPLY
fi

