#!/bin/ash
#
# This is a shell version of the bcc compiler driver. It's a little slower
# than a C version but overall seems to be a lot cleaner, perhaps a C version
# based on this might be a good idea ...
#
# The compiler works on a 'modal' basis certain flags given to it put the
# compiler into specific modes, it can only be in one mode for a run.
#
# The mode defines the basic passes and specific options that are available
#
# To define a mode see the functions 'run_0' and 'run_3' for examples. The
# variable assignments just above the definitions enable the functions.
#
# This script is specifically designed so the there is as little interaction
# between the modes as is possible.
#
# It's run using ash because this appears to be _much_ faster than bash, it's
# also reasonable with ksh.
# (On other interpreters I think perl would be too big, but awk might work...)
#

TMPFIL="/tmp/cc$$"
trap "rm -f $TMPFIL.* ; exit 1" 1 2 3 15
TMPCNT=0

FILES=
OPTS=
RESEXTN=
OPTIM=no
VERBOSE=no
MULTISRC=no

LDOPTS=
DESTDEF=no
LDDEST=a.out
DEFMODE=0

ccmode=
LIBPATH="/lib:/usr/lib:/usr/bin"

main() {
   scanargs "$@"

   PATH="$LIBPATH:$PATH"
   [ "$EXEC_PREFIX" != "" ] && PATH="$EXEC_PREFIX:$PATH"

   rv=0
   LDFILES=
   [ "$MULTISRC" = yes -o "$RESEXTN" = "" ] && DESTDEF=no

   for FILE in $FILES
   do
      case "$FILE" in
      *.c ) PASS=cpp ; BASE="`basename $FILE .c`" ;;
      *.s ) PASS=as  ; BASE="`basename $FILE .s`" ;;
      *   ) PASS=lnk ;;
      esac

      NAME="`basename $FILE`"
      DEST="`dirname $FILE`/"
      [ "$DEST" = "./" ] && DEST=
      DEST="$DEST$BASE.$RESEXTN"
      [ "$DESTDEF" = yes ] && DEST="$LDDEST"

      STEMP=0 INTEMP=0
      [ "$PASS" = "cpp" ] && { compile $FILE || rv=$? ; }
      [ "$PASS" = "as"  ] && { assem   $FILE || rv=$? ; }
      if [ "$PASS" = "lnk" ]
      then
	 LDFILES="$LDFILES $FILE"
      else
	 # If there's a fail can't link - still assembling to temps tho.
         [ "$RESEXTN" = "" ] && RESEXTN=O
      fi
      [ "$STEMP"  = 1 ] && rm -f "$SFILE"
      [ "$INTEMP" = 1 ] && RMFILES="$RMFILES $FILE"
   done

   [ "$RESEXTN" != "" ] && exit $rv

   [ "$VERBOSE" = yes ] &&
       echo "$LD $SYSLIB $LDOPTS $LDFLAGS -o $LDDEST $CRT0 $LDFILES $LIBS"

   $LD $SYSLIB $LDOPTS $LDFLAGS -o $LDDEST $CRT0 $LDFILES $LIBS
   rv=$?
   [ "$RMFILES" != "" ] && rm -f $RMFILES
   exit "$rv"
}

scanargs() {
   WILDOPT=no

   while [ "$#" -gt 0 ]
   do 
      case "$1" in
      -[DU]* ) CPPDEFS="$CPPDEFS $1" ;;
      -B?* )   PATH="`echo '.$1:$PATH' | sed 's/...//'`" ;;
      -I?* )   CPPFLAGS="$CPPFLAGS $1" ;;
      -L?* )   LDOPTS="$LDOPTS $1" ;;
      -o )     LDDEST="$2"; DESTDEF=yes ; shift ;;
      -b )     . /usr/lib/idcc/opts_$2 || exit 1; shift ;;
      -E )     RESEXTN=i ;;
      -S )     RESEXTN=s ;;
      -c )     RESEXTN=o ;;
      -O )     OPTIM=yes ;;
      -v )     VERBOSE=yes ;;
      -l?* )   FILES="$FILES $1" ; MULTISRC=yes ;;
      -* )     OPTS="$OPTS `echo .$1 | sed 's/..//'`" ;;
      * )      [ "$FILES" != "" ] && MULTISRC=yes ; FILES="$FILES $1" ;;
      esac
      shift
   done

   while [ "$OPTS" != "" -o "$DEFMODE" != "" ]
   do
      # So they can try again ... with DEFMODE if needed
      MOPTS="$OPTS"
      OPTS=

      for opt in $MOPTS
      do
	 # Option can be defined only for specific mode so if we haven't seen
	 # the mode yet save the opt. If we have check for conflicts too.
         [ "$ccmode" = "" -a "$DEFMODE" != "" ] && {
              eval "[ \"\$opt_$opt\" = yes ]" || { OPTS="$OPTS $opt" ; opt= ; }
	 }
	 [ "$opt" != "" ] && {
	     [ "$ccmode" = "" ] || {
                  eval "[ \"\$mode_$opt\" = yes ]" && {
	             echo Option "-$opt incompatible with -$ccmode" 1>&2
		     exit 2
		  }
	     }

             eval "[ \"\$opt_$opt\" = yes ]" || {
		  { eval "[ \"$WILDOPT\" = yes ]" && wild_opt "-$opt" ; } || {
	              echo Option '-'$opt unknown for this mode 1>&2
		      exit 3
		  }
	     }

             eval "[ \"\$opt_$opt\" = yes ]" && run_$opt
             eval "[ \"\$mode_$opt\" = yes ]" && ccmode="$opt"
	 }
      done
      [ "$ccmode" = "" -a "$DEFMODE" != "" ] && OPTS="$DEFMODE $OPTS"
      DEFMODE=
   done
}

compile() {
   [ -r "$FILE" ] || { echo "Cannot open $FILE" 1>&2 ; return 1 ; }

   [ "$RESEXTN" = "i" ] && {
      cpp_only $FILE
      return
   }

   # Loop for the pass list
   # The CCX variables _are_ expanded again.

   ret=1
   for pass in $PASSLIST
   do
      for extn in '' pre post inf res opt
      do eval "CCX$extn=\"\$CC$pass$extn\""
      done

      [ "$CCX" = "" ] && continue;
      [ "$OPTIM" = "" -a "$CCXopt" = yes ] && continue;
      
      shuffel "$RESEXTN" $CCXres
      [ "$VERBOSE" = yes ] && 
          eval "echo \"$CCX $CCXpre $SFILE $CCXinf $FILE $CCXpost\""
      eval "$CCX $CCXpre $SFILE $CCXinf $FILE $CCXpost" || return 1
      ret=0
   done
   [ "$ret" = 1 ] && { echo 'CC configuration error' 1>&2 ; return $ret ; }

   [ "$RESEXTN" != "s" ] && PASS=as
   return 0
}

assem() {
   [ -r "$FILE" ] || { echo "Cannot open $FILE" 1>&2 ; return 1 ; }

   shuffel "$RESEXTN" o

   n=
   [ "$ASNAME" != "" ] && n="$ASNAME$NAME"

   [ "$VERBOSE" = yes ] && echo "$AS $ASFLAGS $n $SFILE -o $FILE"
   $AS $ASFLAGS $n $SFILE -o $FILE || return

   [ "$RESEXTN" != "o" ] && PASS=lnk
   return 0
}

if [ "2" = "$[1+1]" ] ; then inc_tmpcnt() { TMPCNT="$[$TMPCNT+1]" ; }
else                         inc_tmpcnt() { TMPCNT="`expr $TMPCNT + 1`" ; }
fi

shuffel() {
   [ "$STEMP" = 1 ] && rm -f "$SFILE"

   SFILE="$FILE"
   STEMP="$INTEMP"

   if [ "$1" = "$2" ]
   then
      FILE="$DEST"
      INTEMP=0
   else
      inc_tmpcnt
      FILE="$TMPFIL.$TMPCNT.$2"
      INTEMP=1
   fi
}

mode_0=yes opt_0=yes
run_0()
{
   SYSINC="-I/usr/bcc/include"
   SYSLIB="-L/usr/bcc/lib/bcc/i86/"
   LIBPATH="/usr/bcc/lib/bcc"
   CRT0="-C0"
   LIBS="-lc"
   CGEN=
   DEFS=

   CPP="cpp"

   PASSLIST=2
   CC2="bcc-cc1" CC2pre='$SYSINC $DEFS $CPPFLAGS'
       CC2inf='-o' CC2post='-0 $CGEN $CPPDEFS' CC2res=s
   AS="as86" ASFLAGS='-u -w -0' ASNAME='-n '
   LD="ld86" LDFLAGS='-i -0'

   opt_ansi=yes
   run_ansi()
   {
       PASSLIST="0 1 2"
       CC0="bcc-cc1" CC0pre='$SYSINC $DEFS $CPPFLAGS' CC0inf='-o'
           CC0post='-0 -E -D__STDC__=0 $CPPDEFS' CC0res=i
       CC1="unproto" CC1pre='' CC1inf='' CC1post='' CC1res=k
       CC2="bcc-cc1" CC2pre='$CGEN' CC2inf='-o' CC2post='-0' CC2res=s
   }
   opt_I=yes; run_I() { SYSINC= ; }
   opt_L=yes; run_L() { SYSLIB= ; }
   opt_s=yes; run_s() { LDFLAGS="$LDFLAGS -s" ; }
   opt_Mf=yes; run_Mf() { LIBS=-lc_f ; CGEN='-f -c' ;}
   opt_Md=yes; run_Md() { LIBS=-ldos ; DEFS='-D__MSDOS__' LDFLAGS='-i -0 -d' ;}

   WILDOPT=yes
   wild_opt()
   {
      case "$1" in
      -O* ) OPTIM=yes ; OPTFLG="$OPTFLG $1" ;;
      * )   return 1 ;;
      # For normal CC operation unknowns go to the linker. ie:
      #  * ) LDFLAGS="$LDFLAGS $1" ; return 0 ;;
      esac
      return 0
   }
}

mode_3=yes opt_3=yes
run_3()
{
   SYSINC="-I/usr/bcc/include"
   SYSLIB="-L/usr/bcc/lib/bcc/i386/"
   LIBPATH="/usr/bcc/lib/bcc"
   CRT0="-C0"
   LIBS="-lc"
   CGEN=
   DEFS=

   CPP="cpp"

   PASSLIST=2
   CC2="bcc-cc3" CC2pre='$SYSINC $DEFS $CPPFLAGS'
       CC2inf='-o' CC2post='-3 $CGEN $CPPDEFS' CC2res=s
   AS="as86" ASFLAGS='-u -w -3' ASNAME='-n '
   LD="ld86" LDFLAGS='-i -3'

   opt_ansi=yes
   run_ansi()
   {
       PASSLIST="0 1 2"
       CC0="bcc-cc3" CC0pre='$SYSINC $DEFS $CPPFLAGS' CC0inf='-o'
           CC0post='-3 -E -D__STDC__=0 $CPPDEFS' CC0res=i
       CC1="unproto" CC1pre='' CC1inf='' CC1post='' CC1res=k
       CC2="bcc-cc1" CC2pre='$CGEN' CC2inf='-o' CC2post='-3' CC2res=s
   }
   opt_I=yes; run_I() { SYSINC= ; }
   opt_L=yes; run_L() { SYSLIB= ; }
   opt_s=yes; run_s() { LDFLAGS="$LDFLAGS -s" ; }

   WILDOPT=yes
   wild_opt()
   {
      case "$1" in
      -O* ) OPTIM=yes ; OPTFLG="$OPTFLG $1" ;;
      * )   return 1 ;;
      esac
      return 0
   }
}

main "$@"
