/* G77 preliminary semantic processing for the compiler driver.
   Copyright (C) 1993, 1994, 1995 Free Software Foundation, Inc.
   Contributed by Brendan Kehoe (brendan@cygnus.com), with significant
   modifications for GNU Fortran by James Craig Burley (burley@gnu.ai.mit.edu).

This file is part of GNU Fortran.

GNU Fortran is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

GNU Fortran is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Fortran; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

/* This program is a wrapper to the main `gcc' driver.  The generic
   goal of this program is to be basically identical to gcc (in that
   it faithfully passes all of the original arguments to gcc) but,
   unless explicitly overridden by the user in certain ways, ensure
   that the needs of the language supported by this wrapper are met.

   For GNU Fortran (g77), we do the following to the argument list
   before passing it to `gcc':

   1.  Put `-xf77' or `-xf77-cpp-input' before each list of foo.f or foo.F
       source files and put `-xnone' after that list, if necessary.
       This shouldn't normally be necessary, but it is done in case
       gcc.c normally treats .f/.F files as, say, to be compiled by f2c.

   2.  Make sure `-lf2c -lm' is at the end of the list.

   3.  Make sure each time `-lf2c' or `-lm' is seen, it forms
       part of the series `-lf2c -lm'.

   #1 is not done if `-xfoo' is in effect (where foo is not "none").
   #2 and #3 are not done if `-nostdlib' or any option that disables
   the linking phase is present, or if `-xfoo' is in effect.  Note that
   -v by itself disables linking.

   This program was originally made out of gcc/cp/g++.c, but the
   way it builds the new argument list was rewritten so it is much
   easier to maintain, improve the way it decides to add or not add
   extra arguments, etc.  And several improvements were made in the
   handling of arguments, primarily to make it more consistent with
   `gcc' itself.  */

#include "config.j"
#ifdef __STDC__
#include <stdarg.h>
#else
#include <varargs.h>
#endif
#include <stdio.h>
#include <sys/types.h>
#include <sys/file.h>   /* May get R_OK, etc. on some systems.  */

/* Defined to the name of the compiler; if using a cross compiler, the
   Makefile should compile this file with the proper name
   (e.g., "i386-aout-gcc").  */
#ifndef GCC_NAME
#define GCC_NAME "gcc"
#endif

/* On MSDOS, write temp files in current dir
   because there's no place else we can expect to use.  */
#ifdef __MSDOS__
#ifndef P_tmpdir
#define P_tmpdir "."
#endif
#ifndef R_OK
#define R_OK 4
#define W_OK 2
#define X_OK 1
#endif
#endif

#ifndef VPROTO
#ifdef __STDC__
#define PVPROTO(ARGS)		ARGS
#define VPROTO(ARGS)		ARGS
#define VA_START(va_list,var)	va_start(va_list,var)
#else
#define PVPROTO(ARGS)		()
#define VPROTO(ARGS)		(va_alist) va_dcl
#define VA_START(va_list,var)	va_start(va_list)
#endif
#endif

extern int errno, sys_nerr;
#if defined(bsd4_4) || defined(__NetBSD__) || defined(__FreeBSD__)
extern const char *const sys_errlist[];
#else
extern char *sys_errlist[];
#endif

/* Name with which this program was invoked.  */
static char *programname;

/* argc, argv from main().  */
static int xargc;
static char **xargv;

/* The new argument list will be contained in these, though if identical
   to the original list, these will be == xargc, xargv.  */
static int newargc;
static char **newargv;

#ifdef HAVE_VPRINTF
/* Output an error message and exit */

static void
fatal VPROTO((char *format, ...))
{
#ifndef __STDC__
  char *format;
#endif
  va_list ap;

  VA_START (ap, format);

#ifndef __STDC__
  format = va_arg (ap, char*);
#endif

  fprintf (stderr, "%s: ", programname);
  vfprintf (stderr, format, ap);
  va_end (ap);
  fprintf (stderr, "\n");
#if 0
  /* XXX Not needed for g77 driver.  */
  delete_temp_files ();
#endif
  exit (1);
}

static void
error VPROTO((char *format, ...))
{
#ifndef __STDC__
  char *format;
#endif
  va_list ap;

  VA_START (ap, format);

#ifndef __STDC__
  format = va_arg (ap, char*);
#endif

  fprintf (stderr, "%s: ", programname);
  vfprintf (stderr, format, ap);
  va_end (ap);

  fprintf (stderr, "\n");
}

#else /* not HAVE_VPRINTF */

static void
error (msg, arg1, arg2)
     char *msg, *arg1, *arg2;
{
  fprintf (stderr, "%s: ", programname);
  fprintf (stderr, msg, arg1, arg2);
  fprintf (stderr, "\n");
}

static void
fatal (msg, arg1, arg2)
     char *msg, *arg1, *arg2;
{
  error (msg, arg1, arg2);
#if 0
  /* XXX Not needed for g77 driver.  */
  delete_temp_files ();
#endif
  exit (1);
}

#endif /* not HAVE_VPRINTF */

/* More 'friendly' abort that prints the line and file.
   config.h can #define abort fancy_abort if you like that sort of thing.  */

void
fancy_abort ()
{
  fatal ("Internal g77 abort.");
}

char *
xmalloc (size)
     unsigned size;
{
  register char *value = (char *) malloc (size);
  if (value == 0)
    fatal ("virtual memory exhausted");
  return value;
}

/* Return a newly-allocated string whose contents concatenate those
   of s1, s2, s3.  */
static char *
concat (s1, s2, s3)
     char *s1, *s2, *s3;
{
  int len1 = strlen (s1), len2 = strlen (s2), len3 = strlen (s3);
  char *result = xmalloc (len1 + len2 + len3 + 1);

  strcpy (result, s1);
  strcpy (result + len1, s2);
  strcpy (result + len1 + len2, s3);
  *(result + len1 + len2 + len3) = 0;

  return result;
}

static void
pfatal_with_name (name)
     char *name;
{
  char *s;

  if (errno < sys_nerr)
    s = concat ("%s: ", sys_errlist[errno], "");
  else
    s = "cannot open %s";
  fatal (s, name);
}

#ifdef __MSDOS__
/* This is the common prefix we use to make temp file names.  */
char *temp_filename;

/* Length of the prefix.  */
int temp_filename_length;

/* Compute a string to use as the base of all temporary file names.  */
static char *
choose_temp_base_try (try, base)
char *try;
char *base;
{
  char *rv;
  if (base)
    rv = base;
  else if (try == (char *)0)
    rv = 0;
  else if (access (try, R_OK | W_OK) != 0)
    rv = 0;
  else
    rv = try;
  return rv;
}

static void
choose_temp_base ()
{
  char *base = 0;
  int len;

  base = choose_temp_base_try (getenv ("TMPDIR"), base);
  base = choose_temp_base_try (getenv ("TMP"), base);
  base = choose_temp_base_try (getenv ("TEMP"), base);

#ifdef P_tmpdir
  base = choose_temp_base_try (P_tmpdir, base);
#endif

  base = choose_temp_base_try ("/usr/tmp", base);
  base = choose_temp_base_try ("/tmp", base);

  /* If all else fails, use the current directory! */  
  if (base == (char *)0)
    base = "./";

  len = strlen (base);
  temp_filename = xmalloc (len + sizeof("/ccXXXXXX"));
  strcpy (temp_filename, base);
  if (len > 0 && temp_filename[len-1] != '/')
    temp_filename[len++] = '/';
  strcpy (temp_filename + len, "ccXXXXXX");

  mktemp (temp_filename);
  temp_filename_length = strlen (temp_filename);
  if (temp_filename_length == 0)
    abort ();
}

static void
perror_exec (name)
     char *name;
{
  char *s;

  if (errno < sys_nerr)
    s = concat ("installation problem, cannot exec %s: ",
		sys_errlist[errno], "");
  else
    s = "installation problem, cannot exec %s";
  error (s, name);
}

/* This is almost exactly what's in gcc.c:pexecute for MSDOS.  */
void
run_dos (program, argv)
     char *program;
     char *argv[];
{
  char *scmd, *rf;
  FILE *argfile;
  int i;

  choose_temp_base (); /* not in gcc.c */

  scmd = (char *) malloc (strlen (program) + strlen (temp_filename) + 10);
  rf = scmd + strlen (program) + 6;
  sprintf (scmd, "%s.exe @%s.gp", program, temp_filename);

  argfile = fopen (rf, "w");
  if (argfile == 0)
    pfatal_with_name (rf);

  for (i=1; argv[i]; i++)
    {
      char *cp;
      for (cp = argv[i]; *cp; cp++)
	{
	  if (*cp == '"' || *cp == '\'' || *cp == '\\' || isspace (*cp))
	    fputc ('\\', argfile);
	  fputc (*cp, argfile);
	}
      fputc ('\n', argfile);
    }
  fclose (argfile);

  i = system (scmd);

  remove (rf);
  
  if (i == -1)
    perror_exec (program);
}
#endif /* __MSDOS__ */

static void
append_arg (arg)
    char *arg;
{
  static int newargsize;

#if 0
  fprintf (stderr, "`%s'\n", arg);
#endif

  if ((newargv == xargv)
      && (arg == xargv[newargc]))
    {
      ++newargc;
      return;			/* Nothing new here. */
    }

  if (newargv == xargv)
    {				/* Make new arglist. */
      int i;

      newargsize = xargc << 2;	/* Allow 4 output args for each 1 input. */
      newargv = (char **) malloc (newargsize * sizeof (char *));

      /* Copy what has been done so far.  */
      for (i = 0; i < newargc; ++i)
	newargv[i] = xargv[i];
    }

  if (newargc == newargsize)
    fatal ("overflowed output arg list for `%s'", arg);
  newargv[newargc++] = arg;
}

int
main (argc, argv)
     int argc;
     char **argv;
{
  register int i = 0;
  register char *p;
  int verbose = 0;

  /* This will be NULL if we encounter a situation where we should not
     link in libf2c.  */
  char *library = "-lf2c";

  /* The name of the compiler we will want to run---by default, it
     will be the definition of `GCC_NAME', e.g., `gcc'.  */
  char *gcc = GCC_NAME;

  /* 0 => -xnone in effect on input/output
     1 => -xfoo in effect on input/output
     2 => -xnone in effect on input, -xf77 on output
     3 => -xnone in effect on input, -xf77-cpp-input on output.  */
  int saw_speclang = 0;

  /* 0 => initial/reset state
     1 => last arg was -l<library>
     2 => last two args were -l<library> -lm.  */
  int saw_library = 0;

  /* Initialize for append_arg().  */
  xargc = argc;
  newargv = xargv = argv;
  newargc = 0;

  append_arg (argv[0]);

  p = argv[0] + strlen (argv[0]);
  while (p != argv[0] && p[-1] != '/')
    --p;
  programname = p;

  if (argc == 1)
    fatal ("No input files specified.\n");

#ifndef __MSDOS__
  /* We do a little magic to find out where the main gcc executable
     is.  If they ran us as /usr/local/bin/g77, then we will look
     for /usr/local/bin/gcc; similarly, if they just ran us as `g77',
     we'll just look for `gcc'.  */
  if (p != argv[0])
    {
      *--p = '\0';
      gcc = (char *) malloc ((strlen (argv[0]) + 1 + strlen (GCC_NAME) + 1)
			     * sizeof (char));
      sprintf (gcc, "%s/%s", argv[0], GCC_NAME);
    }
#endif

  /* If -nostdlib or a "turn-off-linking" option is anywhere in the
     command line, don't do any library-option processing (except
     relating to -x).  */

  for (i = 1; i < argc; i++)
    {
      if ((strcmp (argv[i], "-nostdlib") == 0)
	  || ((argv[i][2] == '\0'
	       && (char *) strchr ("cSEM", argv[i][1]) != NULL)
	      || strcmp (argv[i], "-MM") == 0))
	/* Don't specify libraries after -nostdlib; or if we won't link,
	   since that would cause a warning.  */
	library = NULL;
      else if (strcmp (argv[i], "-v") == 0)
	{
	  verbose = 1;
	  if (argc == 2)
	    /* If they only gave us `-v', don't try to link
	       in libf2c.  */ 
	    library = NULL;
	}
    }

  for (i = 1; i < argc; i++)
    {
      if (argv[i][0] == '\0')
	append_arg (argv[i]);	/* Interesting.  Just append as is. */
      else if ((argv[i][0] == '-') && (argv[i][1] != 'l'))
	{			/* Not a filename or library. */
	  if (saw_library == 1)	/* -l<library>. */
	    append_arg ("-lm");
	  saw_library = 0;
	  append_arg (argv[i]);	/* Always append this arg first as is. */

	  if (argv[i][1] == '\0')
	    ;			/* "-" == Standard input. */
	  else if (strncmp (argv[i], "-x", 2) == 0)
	    {			/* Track input language. */
	      char *lang;

	      if ((argv[i][2] == '\0') && (argc == i + 1))
		fatal ("argument to `-x' is missing");
	      if (argv[i][2] == '\0')
		{
		  lang = argv[++i];
		  append_arg (lang);
		}
	      else
		lang = argv[i] + 2;
	      saw_speclang = (strcmp (lang, "none") != 0);
	    }
	  else if (((argv[i][2] == '\0'
		     && (char *)strchr ("bBVDUoeTuIYmLiA", argv[i][1]) != NULL)
		    || strcmp (argv[i], "-Tdata") == 0))
	    {			/* Skip over any args with arguments. */
	      if (argc == i + 1)
		fatal ("argument to `%s' missing\n", argv[i]);
	      ++i;
	      append_arg (argv[i]);
	    }
	}
      else
	{			/* A filename/library, not an option. */
	  int len;
	  int want_speclang;

	  /* Here, always append the arg _after_ other stuff, possibly.  */

	  if (saw_speclang == 1)
	    saw_library = 0;	/* -xfoo currently active. */
	  /* Put -xf77 and -xnone around list of filenames ending in
	     .F or .f, but don't include other filenames or libraries
	     in that list.  */
	  else if ((argv[i][0] != '-')	/* Not a library. */
		   && (len = strlen (argv[i])) > 2
		   && ((argv[i][len - 1] == 'F')
		       || (argv[i][len - 1] == 'f'))
		   && argv[i][len - 2] == '.')
	    {			/* filename.f or filename.F. */
	      if (saw_library == 1)	/* -l<library>. */
		append_arg ("-lm");
	      saw_library = 0;
	      want_speclang = (argv[i][len - 1] == 'F') + 2;

	      if (saw_speclang != want_speclang)
		{
		  if (want_speclang == 2)
		    append_arg ("-xf77");
		  else
		    append_arg ("-xf77-cpp-input");
		  saw_speclang = want_speclang;
		}
	    }
	  else
	    {			/* -lfoo or "alien" filename. */
	      if (saw_speclang)
		append_arg ("-xnone");
	      saw_speclang = 0;

	      if (strcmp (argv[i], "-lm") == 0
		  || strcmp (argv[i], "-lmath") == 0)
		{
		  if (saw_library == 1)
		    saw_library = 2;	/* -l<library> -lm. */
		  else if (library)
		    {
		      append_arg (library);
		      saw_library = 2;	/* -l<library> -lm. */
		    }
		}
	      else if ((library != NULL)
		       && (strcmp (argv[i], library) == 0))
		saw_library = 1;	/* -l<library>. */
	      else
		{		/* "Alien" library or filename. */
		  if (saw_library == 1)
		    append_arg ("-lm");
		  saw_library = 0;
		}
	    }
	  append_arg (argv[i]);
	}
    }

  /* Add -lf2c -lm as necessary.  */

  if (library)
    {				/* Doing a link and no -nostdlib. */
      if (saw_speclang)
	append_arg ("-xnone");
      switch (saw_library)
	{
	case 0:
	  append_arg (library);
	case 1:
	  append_arg ("-lm");
	default:
	  break;
	}
    }

  append_arg (NULL);
  --newargc;			/* Don't count null arg at end. */

  newargv[0] = gcc;		/* This is safe even if newargv == xargv. */

  if (verbose)
    {
#if 0
      if (newargv == xargv)
	fprintf (stderr, "[Original:]");
#endif

      for (i = 0; i < newargc; i++)
	fprintf (stderr, " %s", newargv[i]);
      fprintf (stderr, "\n");
    }
#ifndef OS2
#ifdef __MSDOS__
  run_dos (gcc, newargv);
#else /* !__MSDOS__ */
  if (execvp (gcc, newargv) < 0)
    pfatal_with_name (gcc);
#endif /* __MSDOS__ */
#else /* OS2 */
  if (spawnvp (gcc, newargv) < 0)
    pfatal_with_name (gcc);
#endif

  return 0;
}
