#!/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=
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"
   done

   [ "$RESEXTN" != "" ] && {
      rm -f $TMPFIL.*
      exit $rv
   }

   [ "$VERBOSE" = yes ] && eval "echo \"$LINK\"" 1>&2
   eval "$LINK"
   rv=$?
   rm -f $TMPFIL.*
   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 ;;
      -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 ; }

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

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

      [ "$CCX" = "" ] && continue;
      
      shuffel "$RESEXTN" $CCXres
      [ "$VERBOSE" = yes ] && eval "echo \"$CCX\"" 1>&2
      eval "$CCX" || return 1
      ret=0
      [ "$CCXres" = "$RESEXTN" ] && break
   done
   [ "$ret" = 1 ] && { echo 'CC configuration error' 1>&2 ; return $ret ; }

   [ "$RESEXTN" != "$CCXres" ] && 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" 1>&2
   $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='-0'
   DEFS=

   CPP="cpp"

   PASSLIST=2
   CC2='bcc-cc1 $SYSINC $DEFS $CPPDEFS $CPPFLAGS $CGEN $SFILE -o $FILE'
   CC2res=s
   AS="as86" ASFLAGS='-u -w -0' ASNAME='-n '
   LINK='ld86 $SYSLIB $LDOPTS $LDFLAGS -o $LDDEST $CRT0 $LDFILES $LIBS'
   LDFLAGS='-i -0'

   [ "$RESEXTN" = "i" ] && {
       PASSLIST="0"
       CC0='bcc-cc1 $SYSINC $DEFS $CPPDEFS $CPPFLAGS $CGEN $SFILE -E'
       CC0res=i
   }

   opt_ansi=yes
   run_ansi()
   {
       PASSLIST="0 1 2"
       CC0='bcc-cc1 $SYSINC $DEFS $CPPDEFS $CPPFLAGS $CGEN $SFILE -o $FILE -E'
       CC0res=k
       CC1res=i CC1='unproto $SFILE $FILE'
       CC2res=s CC2='bcc-cc1 $CGEN $SFILE -o $FILE'
       DEFS='-D__STDC__=0'
   }
   opt_I=yes; run_I() { SYSINC= ; }
   opt_L=yes; run_L() { SYSLIB= ; }
   opt_O=yes; run_O() { echo Warning -O ignored 1>&2 ; }
   opt_Mf=yes; run_Mf() { LIBS=-lc_f ; CGEN='-0 -f -c' ;}
   opt_Md=yes;
      run_Md() { LIBS=-ldos ; DEFS=$DEFS' -D__MSDOS__' LDFLAGS='-i -0 -d' ;}

   WILDOPT=yes
   wild_opt()
   {
      case "$1" in
      # For normal CC operation unknowns go to the linker. ie:
      # * ) LDFLAGS="$LDFLAGS $1" ; return 0 ;;
      # But do this instead.
      -[dMizmts] ) LDFLAGS="$LDFLAGS $1" ; return 0 ;;
      -[dMizmt]- ) LDFLAGS="$LDFLAGS $1" ; return 0 ;;
      -T*        ) LDFLAGS="$LDFLAGS $1" ; return 0 ;;
      * ) return 1 ;;
      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='-3'
   DEFS=

   CPP="cpp"

   PASSLIST=2
   CC2='bcc-cc1 $SYSINC $DEFS $CPPDEFS $CPPFLAGS $CGEN $SFILE -o $FILE'
   CC2res=s
   AS="as86" ASFLAGS='-u -w -3' ASNAME='-n '
   LINK='ld86 $SYSLIB $LDOPTS $LDFLAGS -o $LDDEST $CRT0 $LDFILES $LIBS'
   LDFLAGS='-3'

   [ "$RESEXTN" = "i" ] && {
       PASSLIST="0"
       CC0='bcc-cc1 $SYSINC $DEFS $CPPDEFS $CPPFLAGS $CGEN $SFILE -E'
       CC0res=i
   }

   opt_ansi=yes
   run_ansi()
   {
       PASSLIST="0 1 2"
       CC0='bcc-cc1 $SYSINC $DEFS $CPPDEFS $CPPFLAGS $CGEN $SFILE -o $FILE -E'
       CC0res=k
       CC1res=i CC1='unproto $SFILE $FILE'
       CC2res=s CC2='bcc-cc1 $CGEN $SFILE -o $FILE'
       DEFS='-D__STDC__=0'
   }
   opt_I=yes; run_I() { SYSINC= ; }
   opt_L=yes; run_L() { SYSLIB= ; }
   opt_O=yes; run_O() { echo Warning -O ignored 1>&2 ; }

   WILDOPT=yes
   wild_opt()
   {
      case "$1" in
      # For normal CC operation unknowns go to the linker. ie:
      # * ) LDFLAGS="$LDFLAGS $1" ; return 0 ;;
      # But do this instead.
      -[dMizmts] ) LDFLAGS="$LDFLAGS $1" ; return 0 ;;
      -[dMizmt]- ) LDFLAGS="$LDFLAGS $1" ; return 0 ;;
      -T*        ) LDFLAGS="$LDFLAGS $1" ; return 0 ;;
      * ) return 1 ;;
      esac
      return 0
   }
}

main "$@"
