/**
***  cc.c   Unix compatible frontend for Amiga's C compilers
***
***  This program 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 of the License, or
***  (at your option) any later version.
***
***  This program 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 this program; if not, write to the Free Software
***  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
***
***
***  Compiler:	dcc V3.01
***
***  Computer:	Amiga 1200
***
***  Author:	Jochen Wiedmann
***		Am Eisteich 9
***		72555 Metzingen
***		Germany
***
***		Phone: (49)7123 / 14881
***		Internet: wiedmann@zdv.uni-tuebingen.de
***
***
***
***  This is a Unix compatible frontend for gcc, SAS/C and Dice. In fact,
***  it does nothing than else than calling the appropriate frontends
***  with the appropriate options.
***
***  Supported options are:
***
***	-v	    Verbose (try it :-)
***	-c	    Don't link
***	-a	    Compile only, don't assemble
***	-E	    Run preprocessor only
***	-I<dir>     Look for include files in directory <dir>
***	-L<dir>     Look for libraries in directory <dir>
***	-o<file>    Set the name of the created file; it is recommended
***		    to use this as the respective frontends might behave
***		    different in selecting default names.
***	-D<symbol>  Defines preprocessor symbol; use -Dsymbol=var for
***		    specific values.
***	-U<symbol>  Undefine the preprocessor symbol <symbol>.
***	-l<lib>     Link with library <lib>.
***	-g	    Turn debugging on.
***	-O	    Optimize
***
**/

/**
***  Version string
**/
#define VERSION        1
#define REVISION	 1
#define DATE	"11.10.94"
#define VERS	"cc 1.1"
#define VSTRING "cc 1.1 (11.10.94)"
#define VERSTAG "\0$VER: cc 1.1 (11.10.94)"
char Version[] = VSTRING;

char AmigaVersion[] = VERSTAG;




/**
***  Include files
**/
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <exec/lists.h>
#include <exec/nodes.h>
#include <clib/alib_protos.h>
#include <clib/exec_protos.h>
#include <clib/dos_protos.h>
#include <pragmas/exec_pragmas.h>
#include <pragmas/dos_pragmas.h>
extern struct Library *SysBase;
extern struct Library *DOSBase;





/**
***  List of options processed by cc:
**/
typedef enum
  {
    OPTION_UNKNOWN,
    OPTION_VERBOSE,
    OPTION_DEFINE,
    OPTION_UNDEFINE,
    OPTION_PREPROCESSOR_ONLY,
    OPTION_ASSEMBLER_ONLY,
    OPTION_NOLINK,
    OPTION_INCLUDEDIR,
    OPTION_LINKDIR,
    OPTION_OPTIMIZE,
    OPTION_DEBUGGING,
    OPTION_LIBRARY,
    OPTION_OUTPUT
  } CompilerOption;





/**
***  This structure defines an element in the list of options.
**/
typedef struct
  {
    struct MinNode mn;
    CompilerOption Option;
    char *Arg;
  } CurrentOption;





/**
***  Compatibility mode (Default: gcc)
**/
enum
  {
    COMPATIBILITYMODE_GCC,
    COMPATIBILITYMODE_SAS,
    COMPATIBILITYMODE_DICE,
  } CompatibilityMode = COMPATIBILITYMODE_SAS;
char *CompatibilityModeNames[] =
  {
    "-gcc",
    "-sas",
    "-dice"
  };




/**
***  Compiler mode (Default: Everything)
**/
enum
  {
    COMPILERMODE_PREPROCESSOR_ONLY,
    COMPILERMODE_COMPILER_ONLY,
    COMPILERMODE_ASSEMBLER_ONLY,
    COMPILERMODE_EVERYTHING,
  } CompilerMode = COMPILERMODE_EVERYTHING;
char *CompilerModeNames[] =
  {
    "-E",
    "-a",
    "-c"
  };




/**
***  These lists are used to hold the current options.
**/
struct List OptionList;





/**
***  Guess, what this function does? :-)
**/
void Usage(void)

  {
    printf("%s%s%s", "Usage: cc [options] [files]\n\
\n\
Options known to cc are:\n\
\n\
-v\t\tbe verbose\n\
-o<file>\t\tsend output to <file> (usage is recommended)\n\
-E\t\trun preprocessor only\n\
-a\t\trun preprocessor and compiler only\n\
-c\t\trun preprocessor, compiler and assembler, don't link\n\
-I<dir>\t\tlook for include files in <dir>\n\
-L<dir>\t\took for link libraries in <dir>\n\
-l<lib>\t\tlink with library <lib>\n\
-g\t\tturn debugging on\n\
-O\t\tturn optimization on\n\
-D<sym>\t\tdefine preprocessor symbol\n\
-U<sym>\t\tundefine preprocessor symbol\n\
-h,--help,?\tprint this message\n", Version, " UNIX 'cc' Front-End for SAS/C Compiler\n\n");
    exit(5);
  }





/**
***  This functions adds a new option to the list of options.
**/
void AddOption(CompilerOption co, char *arg)

  {
    CurrentOption *cu;

    if (!(cu = malloc(sizeof(*cu))))
      {
	fprintf(stderr, "Out of memory!");
	exit(20);
      }
    cu->Option = co;
    cu->Arg = arg;
    AddTail(&OptionList, (struct Node *) cu);
  }





/**
***  This function handles options which may be splitted into two
***  arguments or not, like -I <dir> or -I<dir>.
**/
void AddOptionArg(CompilerOption co,
		  int *i,
		  int argc,
		  char *argv[])

  {
    char *arg = &argv[*i][2];

    if (!*arg)
      {
	if (++(*i) >= argc)
	  {
	    fprintf(stderr, "%s: Missing argument: %s\n", argv[--(*i)]);
	    exit(20);
	  }
	arg = argv[*i];
      }

    AddOption(co, arg);
  }





/**
***  This function is used to set the compatibility mode.
**/
void SetCompatibilityMode(int mode, int *set)

  {
    if (*set)
      {
	fprintf(stderr, "cc, warning: %s overwrites %s\n",
		CompatibilityModeNames[mode],
		CompatibilityModeNames[CompatibilityMode]);
	exit(10);
      }
    *set = TRUE;
    CompatibilityMode = mode;
  }





/**
***  This function is used to set the compatibility mode.
**/
void SetCompilerMode(int mode, int *set)

  {
    if (*set)
      {
	fprintf(stderr, "cc warning: %s overwrites %s\n",
		CompilerModeNames[mode],
		CompilerModeNames[CompilerMode]);
	exit(10);
      }
    *set = TRUE;
    CompilerMode = mode;
  }





/**
***  This function is used to build the string that is used to call
***  the real frontend. It supports strings of any length, thus it
***  may look somewhat complicated.
**/
void AddToString(char **strptr, char *format, ...)

  {
    static char buffer[1024];	/*  Maximal length of *one* argument	*/
    static int RealLen;
    static int MaxLen;
    int len;
    va_list args;

    va_start(args, format);
    vsprintf(buffer, format, args);
    len = strlen(buffer);

    if (!*strptr)
      {
	MaxLen = 0;
      }

    if (len + RealLen + 1 > MaxLen)
      {
	/**
	***  Current buffer not sufficient, allocate a new buffer
	**/
	char *newstr;

	if (!(newstr = malloc(MaxLen+1024)))
	  {
	    fprintf(stderr, "Out of memory!");
	    exit(20);
	  }
	MaxLen += 1024;

	if (*strptr)
	  {
	    strcpy(newstr, *strptr);
	  }
	else
	  {
	    *newstr = '\0';
	    RealLen = 0;
	  }
	*strptr = newstr;
      }

    strcpy(*strptr + RealLen, buffer);
    RealLen += len;
  }






/**
***  This function is used to parse the arguments. Options will
***  be included into OptionList, files in FileList.
**/
void ParseArgs(int argc,
	       char *argv[])

  {
    int i;
    /**
    ***  These variables are used for checking if arguments repeat.
    ***  Note that we keep them local: This allows, for example,
    ***  to use different settings in the environment and on the
    ***  command line.
    **/
    int CompatibilityModeIsSet = FALSE;
    int CompilerModeIsSet = FALSE;

    for (i = 0;  i < argc;  i++)
      {
	char *argvi = argv[i];

	if (argvi[0] == '-')
	  {
	    /**
	    ***  Assume this to be a compiler option.
	    **/

	    switch(argvi[1])
	      {
		case '-':
		  if (strcmp(argvi, "--help") == 0)
		    {
		      Usage();
		    }
		  AddOption(OPTION_UNKNOWN, argvi);
		  break;
		case 'D':
		  AddOptionArg(OPTION_DEFINE, &i, argc, argv);
		  break;
		case 'E':
		  switch(argvi[2])
		    {
		      case '\0':
			SetCompilerMode(COMPILERMODE_PREPROCESSOR_ONLY,
					&CompilerModeIsSet);
			break;
		      default:
			AddOption(OPTION_UNKNOWN, argvi);
			break;
		    }
		  break;
		case 'I':
		  AddOptionArg(OPTION_INCLUDEDIR, &i, argc, argv);
		  break;
		case 'L':
		  AddOptionArg(OPTION_LINKDIR, &i, argc, argv);
		  break;
		case 'O':
		  AddOption(OPTION_OPTIMIZE, &argvi[2]);
		  break;
		case 'U':
		  AddOptionArg(OPTION_UNDEFINE, &i, argc, argv);
		  break;
		case 'a':
		  switch(argvi[2])
		    {
		      case '\0':
			SetCompilerMode(COMPILERMODE_COMPILER_ONLY,
					&CompilerModeIsSet);
			break;
		      default:
			AddOption(OPTION_UNKNOWN, argvi);
			break;
		    }
		  break;
		case 'c':
		  switch(argvi[2])
		    {
		      case '\0':
			SetCompilerMode(COMPILERMODE_ASSEMBLER_ONLY,
					&CompilerModeIsSet);
			break;
		      default:
			AddOption(OPTION_UNKNOWN, argvi);
			break;
		    }
		  break;
		case 'd':
		  if (strcmp(argvi,
			     CompatibilityModeNames[COMPATIBILITYMODE_DICE]) == 0)
		    {
		      SetCompatibilityMode(COMPATIBILITYMODE_DICE,
					   &CompatibilityModeIsSet);
		    }
		  else
		    {
		      AddOption(OPTION_UNKNOWN, argvi);
		    }
		  break;
		case 'g':
		  switch(argvi[2])
		    {
		      case '\0':
			AddOption(OPTION_DEBUGGING, argvi);
			break;
		      default:
			if (strcmp(argvi,
				   CompatibilityModeNames[COMPATIBILITYMODE_GCC]) == 0)
			  {
			    SetCompatibilityMode(COMPATIBILITYMODE_GCC,
						 &CompatibilityModeIsSet);
			  }
			else
			  {
			    AddOption(OPTION_UNKNOWN, argvi);
			  }
			break;
		    }
		  break;
		case 'h':
		  switch(argvi[2])
		    {
		      case '\0':
			Usage();
		      default:
			AddOption(OPTION_UNKNOWN, argvi);
			break;
		    }
		  break;
		case 'l':
		  AddOptionArg(OPTION_LIBRARY, &i, argc, argv);
		  break;
		case 'o':
		  AddOptionArg(OPTION_OUTPUT, &i, argc, argv);
		  break;
		case 's':
		  if (strcmp(argvi,
			     CompatibilityModeNames[COMPATIBILITYMODE_SAS]) == 0)
		    {
		      SetCompatibilityMode(COMPATIBILITYMODE_SAS,
					   &CompatibilityModeIsSet);
		    }
		  else
		    {
		      AddOption(OPTION_UNKNOWN, argvi);
		    }
		  break;
		case 'v':
		  switch(argvi[2])
		    {
		      case '\0':
			AddOption(OPTION_VERBOSE, argvi);
			break;
		      default:
			AddOption(OPTION_UNKNOWN, argvi);
			break;
		    }
		    break;
		default:
		  AddOption(OPTION_UNKNOWN, argvi);
		  break;
	      }
	  }
	else if (strcmp(argvi, "?") == 0)
	  {
	    Usage();
	  }
	else
	  {
	    AddOption(OPTION_UNKNOWN, argvi);
	  }
      }
  }





/**
***  This function calls gcc as a frontend.
**/
int CompileGcc(void)

  {
    CurrentOption *co;
    char *CompileString = NULL;
    int Verbose = FALSE;

    AddToString(&CompileString, "gcc");

    for (co = (CurrentOption *) OptionList.lh_Head;
	 co->mn.mln_Succ != NULL;
	 co = (CurrentOption *) co->mn.mln_Succ)
      {
	switch (co->Option)
	  {
	    case OPTION_UNKNOWN:
	      AddToString(&CompileString, " \"%s\"", co->Arg);
	      break;
	    case OPTION_VERBOSE:
	      AddToString(&CompileString, " -v");
	      Verbose = TRUE;
	      break;
	    case OPTION_DEFINE:
	      AddToString(&CompileString, " \"-D%s\"", co->Arg);
	      break;
	    case OPTION_UNDEFINE:
	      AddToString(&CompileString, " \"-U%s\"", co->Arg);
	      break;
	    case OPTION_PREPROCESSOR_ONLY:
	      AddToString(&CompileString, " -E");
	      break;
	    case OPTION_ASSEMBLER_ONLY:
	      AddToString(&CompileString, " -a");
	      break;
	    case OPTION_NOLINK:
	      AddToString(&CompileString, " -c");
	      break;
	    case OPTION_INCLUDEDIR:
	      AddToString(&CompileString, " \"-I%s\"", co->Arg);
	      break;
	    case OPTION_LINKDIR:
	      AddToString(&CompileString, " \"-L%s\"", co->Arg);
	      break;
	    case OPTION_OPTIMIZE:
	      AddToString(&CompileString, " \"-O%s\"", co->Arg);
	      break;
	    case OPTION_DEBUGGING:
	      AddToString(&CompileString, " -g");
	      break;
	    case OPTION_LIBRARY:
	      AddToString(&CompileString, " \"-l%s\"", co->Arg);
	      break;
	    case OPTION_OUTPUT:
	      AddToString(&CompileString, " -o \"%s\"", co->Arg);
	      break;
	  }
      }

    AddToString(&CompileString, "\n");
    if (Verbose)
      {
	printf("%s\n%s", Version, CompileString);
      }

    return(system(CompileString));
  }





/**
***  This function calls SAS/C as a frontend.
**/
int CompileSAS(void)

  {
    CurrentOption *co;
    char *CompileString = NULL;
    int Verbose = FALSE;
    char *OptionOutput = NULL;

    AddToString(&CompileString, "sc");

    for (co = (CurrentOption *) OptionList.lh_Head;
	 co->mn.mln_Succ != NULL;
	 co = (CurrentOption *) co->mn.mln_Succ)
      {
	switch (co->Option)
	  {
	    case OPTION_UNKNOWN:
	      AddToString(&CompileString, " \"%s\"", co->Arg);
	      break;
	    case OPTION_VERBOSE:
	      AddToString(&CompileString, " VERBOSE");
	      Verbose = TRUE;
	      break;
	    case OPTION_DEFINE:
	      AddToString(&CompileString, " DEF \"%s\"", co->Arg);
	      break;
	    case OPTION_UNDEFINE:	/*  Not supported   */
	      break;
	    case OPTION_INCLUDEDIR:
	      AddToString(&CompileString, " IDIR\"%s\"", co->Arg);
	      break;
	    case OPTION_LINKDIR:	/*  Not supported   */
	      break;
	    case OPTION_OPTIMIZE:
	      AddToString(&CompileString, " OPTIMIZE");
	      break;
	    case OPTION_DEBUGGING:
	      AddToString(&CompileString, " DEBUG FULLFLUSH");
	      break;
	    case OPTION_LIBRARY:
	      AddToString(&CompileString, " LIBRARY \"%s.lib\"", co->Arg);
	      break;
	    case OPTION_OUTPUT:
	      OptionOutput = co->Arg;
	      break;
	  }
      }

    switch(CompilerMode)
      {
	case COMPILERMODE_PREPROCESSOR_ONLY:
	  AddToString(&CompileString, " PPONLY");
	case COMPILERMODE_ASSEMBLER_ONLY:
	  if (OptionOutput)
	    {
	      AddToString(&CompileString, " OBJNAME \"%s\"", OptionOutput);
	    }
	  break;
	case COMPILERMODE_COMPILER_ONLY:
	  if (!OptionOutput)
	    {
	      OptionOutput = "*";
	    }
	  AddToString(&CompileString, " \"DISASM=%s\"", OptionOutput);
	  break;
	case COMPILERMODE_EVERYTHING:
	  if (OptionOutput)
	    {
	      AddToString(&CompileString, " PNAME \"%s\"", OptionOutput);
	    }
	  AddToString(&CompileString, " LINK");
	  break;
      }

    AddToString(&CompileString, "\n");
    if (Verbose)
      {
	printf("%s\n%s", Version, CompileString);
      }

    return(system(CompileString));
  }






/**
***  This function calls Dice as a frontend.
**/
int CompileDice(void)

  {
    CurrentOption *co;
    char *CompileString = NULL;
    int Verbose = FALSE;

    switch(CompilerMode)
      {
	case COMPILERMODE_PREPROCESSOR_ONLY:
	  AddToString(&CompileString, "dcpp");
	  break;
	case COMPILERMODE_COMPILER_ONLY:
	  AddToString(&CompileString, "dcc -a");
	  break;
	case COMPILERMODE_ASSEMBLER_ONLY:
	  AddToString(&CompileString, "dcc -c");
	  break;
	default:
	  AddToString(&CompileString, "dcc");
	  break;
      }

    for (co = (CurrentOption *) OptionList.lh_Head;
	 co->mn.mln_Succ != NULL;
	 co = (CurrentOption *) co->mn.mln_Succ)
      {
	switch (co->Option)
	  {
	    case OPTION_UNKNOWN:
	      AddToString(&CompileString, " \"%s\"", co->Arg);
	      break;
	    case OPTION_VERBOSE:
	      if (CompilerMode != COMPILERMODE_PREPROCESSOR_ONLY)
		{
		  AddToString(&CompileString, " -v", co->Arg);
		}
	      Verbose = TRUE;
	      break;
	    case OPTION_DEFINE:
	      AddToString(&CompileString, " \"-D%s\"", co->Arg);
	      break;
	    case OPTION_UNDEFINE:	    /*	Not supported	*/
	      break;
	    case OPTION_INCLUDEDIR:
	      AddToString(&CompileString, " \"-I%s\"", co->Arg);
	      break;
	    case OPTION_LINKDIR:
	      if (CompilerMode != COMPILERMODE_PREPROCESSOR_ONLY)
		{
		  AddToString(&CompileString, " \"-L%s\"", co->Arg);
		}
	      break;
	    case OPTION_OPTIMIZE:	    /*	Not suported	*/
	      break;
	    case OPTION_DEBUGGING:
	      if (CompilerMode != COMPILERMODE_PREPROCESSOR_ONLY)
		{
		  AddToString(&CompileString, " -s -d1");
		}
	      break;
	    case OPTION_LIBRARY:
	      if (CompilerMode != COMPILERMODE_PREPROCESSOR_ONLY)
		{
		  AddToString(&CompileString, " \"-l%s\"", co->Arg);
		}
	      break;
	    case OPTION_OUTPUT:
	      AddToString(&CompileString, " -o \"%s\"", co->Arg);
	      break;
	  }
      }

    AddToString(&CompileString, "\n");
    if (Verbose)
      {
	printf("%s\n%s", Version, CompileString);
      }

    return(system(CompileString));
  }





/**
***  This function is used to split a string into arguments.
***  It returns an array similar to argv.
***
***  I don't like doing things for myself, but as far as I
***  can see neither SAS nor Dice offer a possibility of
***  doing this.
***
***  Inputs: argstr - the string to split into arguments,
***		for example an AmigaDOS command line;
***		may contain arguments like
***		    "This is one argument"
***		or
***		    "Note the "" inside this argument"
***		where the double quotation mark will be
***		changed into one.
***	    argc - pointer to an int where to store the
***		number of arguments found in argstr
***
***  Result: an NULL terminated array of pointers to the
***	    arguments or NULL; note that the pointers go
***	    into argstr and argstr will be modified.
**/
char **SplitArgs(char *argstr, int *argcptr)

  {
    int argc;
    char *argptr;
    char **argv;
    char **argvptr;

    /**
    ***  Parse the string for the first time counting
    ***  the number of arguments.
    **/
    argptr = argstr;
    argc = 0;
    for(;;)
      {
	/**
	***  Skip blanks
	**/
	while (*argptr == ' '  ||  *argptr == '\t')
	  {
	    ++argptr;
	  }

	if (*argptr == '\0'  ||  *argptr == '\n'  ||  *argptr == '\r')
	  {
	    break;
	  }

	++argc;

	if (*argptr == '\"')
	  {
	    do
	      {
		++argptr;
		if (*argptr == '\"')
		  {
		    if (*(argptr+1) == '\"')
		      {
			++argptr;
		      }
		    else
		      {
			break;
		      }
		  }
	      }
	    while (*argptr != '\0'  &&  *argptr != '\r'  &&  *argptr != '\n');
	  }
	else
	  {
	    while (*argptr != '\0'  &&  *argptr != '\r'  &&  *argptr != '\n'  &&
		   *argptr != '\t'  &&  *argptr != ' ')
	      {
		++argptr;
	      }
	  }
	{
	  char c;

	  c = *argptr;
	  *(argptr++) = '\0';

	  if (c == '\0'  ||  c == '\r'  ||  c == '\n')
	    {
	      break;
	    }
	}
      }

    *argcptr = argc;
    if (!(argv = malloc(sizeof(char *) * (argc+1))))
      {
	return(NULL);
      }

    /**
    ***  Parse the string a second time
    **/
    for (argvptr = argv, argptr = argstr;  argc > 0;  --argc, ++argvptr)
      {
	int inside;

	while (*argptr == ' '  ||  *argptr == '\t')
	  {
	    ++argptr;
	  }

	if (*argptr == '\"')
	  {
	    ++argptr;
	    inside = TRUE;
	  }
	else
	  {
	    inside = FALSE;
	  }
	*argvptr = argptr;

	while (*argptr)
	  {
	    if (*argptr == '\"'  &&  inside)
	      {
		char *ptr;
		char *oldptr;

		/**
		***  Found a "", remove the second ".
		**/
		for(ptr = argptr++, oldptr = *argvptr;
		    ptr >= oldptr;  --ptr)
		  {
		    *(ptr+1) = *ptr;
		  }
		*argvptr = oldptr+1;
	      }
	    ++argptr;
	  }
	++argptr;
      }
    *argvptr = NULL;

    return(argv);
  }





/**
***  Finally main().
**/
int main(int argc, char *argv[])

  {
    char *cflags;

    NewList(&OptionList);

    if ((cflags = getenv("CCOPT")))
      {
	int envargc;
	char **envargv;

	if (!(envargv = SplitArgs(cflags, &envargc)))
	  {
	    fprintf(stderr, "Out of memory!\n");
	    exit(20);
	  }
	ParseArgs(envargc, envargv);
      }

    ParseArgs(argc-1, argv+1);

    switch(CompatibilityMode)
      {
	case COMPATIBILITYMODE_GCC:
	  CompileGcc();
	  break;
	case COMPATIBILITYMODE_SAS:
	  CompileSAS();
	  break;
	case COMPATIBILITYMODE_DICE:
	  CompileDice();
	  break;
      }
  }
