/* cc - A Unix-like 'cc' front end for the Manx 5.0 C compiler on the Amiga
   Note: Manx 'cc' is moved to "${CCOM}/ccom" in "make" syntax.

   [See "help()" function at the end for a list of switches.]
*/

#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <functions.h>

/* Command line arguments that are not switches are split up into a SourceFile
   structure of three parts, taken from the Unix shell naming conventions: 

	1. Root	- Everything before the last '.' char.
	2. Base - Everything before the last '.', after the last '/' or ':'.
	3. Suff - Everything after the last '.'.
*/
 
struct SourceFile {
    char *Root;
    char *Base;
    char *Suff;
} **SF;

char **Cfl, **Afl, **Lfl, **Lib, **LBD, **Def, **Inc;
int SF_c, Cfl_c, Afl_c, Lfl_c, Lib_c, LBD_c, Def_c, Inc_c;

char Cmd[1024];		/* Command line built by Append */
int CmdLen;

#define isC(sf) (!strcmp((sf)->Suff,"c"))
#define isS(sf) (!strcmp((sf)->Suff,"s"))
#define isO(sf) (!strcmp((sf)->Suff,"o"))
#define isLib(sf) (!strcmp((sf)->Suff,"a") || !strcmp((sf)->Suff,"lib"))

/* Flags set by command line arguments */
int nflag;			/* -n flag: No-Execute */
int cflag;			/* -c flag: Stop at object file */
int gflag;			/* -g flag: debugging */
int Oflag;			/* -O flag: optimize */
int wflag;			/* -w flag: warnings == "lint" */
int Sflag;			/* -S flag: Stop at Assembler */
int Vflag;			/* -V flag: verbose */

int TotErrors;			/* Count of compile/assembly errors */
char *outfile;			/* Output file name for linker */
char *Ram = "ram:";		/* Where to put temporary files */
char *HOutput;
char *HInput;			/* Combined header */

char buffer[64];

#define NextArg	do { if (!*++cp) { cp = *argv++; argc--; }} while(0)

char *strdup();
char *FindLib (char *name);

main (int argc, char **argv)
{
    int i;
    char *cp, **cpp;
    struct SourceFile *sf, **sfp;
    
    Enable_Abort = 1;

    if (argc < 2) help();	/* Does not return */

    /* argc is one larger for the NULL at the end */
    SF = (struct SourceFile **)malloc(argc * sizeof(*SF));
    Cfl = (char **)malloc(argc * sizeof(char *));
    Afl = (char **)malloc(argc * sizeof(char *));
    Lfl = (char **)malloc(argc * sizeof(char *));
    LBD = (char **)malloc((argc + 1) * sizeof(char *));
    Lib = (char **)malloc((argc + 1) * sizeof(char *));
    Def = (char **)malloc((argc + 2) * sizeof(char *));
    Inc = (char **)malloc((argc + 2) * sizeof(char *));

    argc--; argv++;

    while (argc-- > 0) {
	Check_Abort();
	cp = *argv++;
	if (*cp != '-') {
	    NewSourceFile (cp);
	} else {
	    switch (*++cp) {
		case 'c':
		    cflag++;
		    break;
		case 'n':
		    nflag++;
		case 'V':
		    Vflag++;
		    break;
		case 'w':
		    wflag++;	/* Show warnings ("lint") */
		    break;
		case 'S':
		    Sflag++;
		    break;
		case 'g':
		    gflag++;	/* Debugging flag on */
		    if (Oflag) {
			printf ("-g overriding -O\n");
			Oflag = 0;
		    }
		    break;
		case 'O':
		    Oflag++;	/* Optimizing */
		    if (gflag) {
			printf ("-O overriding -g\n");
			gflag = 0;
		    }
		    break;
		case 'o':
		    NextArg;
		    if (HOutput) {
			printf ("-o may not override -ho\n");
			outfile = NULL;
		    } else
			outfile = cp;

		    break;
		case 't':		/* Warning, non-standard */
		    NextArg;
		    Ram = cp;
		    break;
		case 'D':
		    NextArg;
		    Def[Def_c++] = strdup(cp);
		    break;
		case 'I':
		    NextArg;
		    Inc[Inc_c++] = strdup(cp);
		    break;
		case 'l':
		    NextArg;
		    Lib[Lib_c++] = strdup(cp);
		    break;
		case 'L':
		    NextArg;
		    strcpy (buffer, cp);
		    i = cp[strlen(cp) - 1];
		    if (i != '/' && i != ':') strcat(buffer, "/");
		    LBD[LBD_c++] = strdup(buffer);
		    break;
		case 'A':
		    NextArg;
		    Afl[Afl_c++] = strdup(cp);
		    break;
		case 'K':
		    NextArg;
		    Lfl[Lfl_c++] = strdup(cp);
		    break;
		case 'h':
		    if (!*++cp || *cp == 'e') {
			help();
			/* does not return */
		    } else if (*cp == 'o') {
			NextArg;
			if (outfile) {
			    printf ("-ho overrides -o\n");
			    outfile = NULL;
			}
			HOutput = cp;
		    } else if (*cp == 'i') {
			NextArg;
			HInput = cp;
		    }
		    break;
		default:
		    Cfl[Cfl_c++] = strdup(cp - 1);
		    break;
	    }
	}
    }

    LBD[LBD_c++] = strdup("sys:Aztec/Lib/");	/* Default library directory */
    Lib[Lib_c++] = strdup("c");			/* Default C library */
    Inc[Inc_c++] = strdup("sys:Aztec/Include");	/* Default Include directory */
    Inc[Inc_c++] = strdup("sys:Aztec/Asm");	/* Default Include directory */
    Def[Def_c++] = strdup("AMIGA");		/* Define always */
    Def[Def_c++] = strdup("amiga");		/* Define always */

    SF[SF_c] = NULL;
    Cfl[Cfl_c] = NULL;
    Afl[Afl_c] = NULL;
    Lfl[Lfl_c] = NULL;
    LBD[LBD_c] = NULL;
    Lib[Lib_c] = NULL;
    Def[Def_c] = NULL;
    Inc[Inc_c] = NULL;

    /* Compile or assemble depending on suffix */
    for (sfp = SF; *sfp; sfp++) {
	sf = *sfp;
	Check_Abort();
	if (SF_c > 1 && (isC(sf) || isS(sf))) {
	    printf ("%s.%s:\n", sf->Root, sf->Suff);
	}
	if (isC(sf) || HOutput) {
	    if (cc(sf) && !HOutput && !Sflag) {
		asm(sf);
		sprintf (buffer, "%s%s.s", Ram, sf->Base);
		if (!nflag) DeleteFile (buffer);
	    }
	} else if (!Sflag && isS(sf)) {
	    asm(sf);
	}
    }

    /* Link it all together */
    if (!cflag && !Sflag && !HOutput && !TotErrors) {
	Check_Abort();
	strcpy(Cmd, "ln");
	CmdLen = strlen(Cmd);

	if (Vflag)
	    Append (" -v");
	else
	    Append (" >NIL: +q");

	Append(" -o ");
	Append(outfile ? outfile : "a.out");

	if (gflag)
	    Append (" -g");
	else if (Oflag)
	    Append (" -q");

	for (cpp = Lfl; *cpp; cpp++) Append (" %s", *cpp);

	for (sfp = SF; *sfp; sfp++) {
	    sf = *sfp;
	    if (isO(sf))		/* .o file will be local */
		Append (" %s.o", sf->Root);
	    else if (isLib(sf))		/* .lib file will be local */
		Append (" %s.%s", sf->Root, sf->Suff);
	    else			/* compiled file will be in ram: */
		Append (" %s%s.o", Ram, sf->Base);
	}
	for (cpp = Lib; *cpp; cpp++) Append(" %s", FindLib(*cpp));

	ExecuteIt();

	/* Clean up temporary object files. */
	if (!nflag) {
	    for (sfp = SF; *sfp; sfp++) {
		sf = *sfp;
		if (isC(sf) || isS(sf)) {
		    sprintf (buffer, "%s%s.o", Ram, sf->Base);
		    DeleteFile (buffer);
		}
	    }
	}
    }
    cleanup (0);
}


/* Compile one .c file, leaving one .s file */
cc(struct SourceFile *sf)
{
    char **cpp;
    
    Check_Abort();
    strcpy (Cmd, "ccom -pa0t -wqw");
    CmdLen = strlen(Cmd);

    if (Vflag)
	Append (" -qv0q");
    else
	Append (" -qq");

    if (wflag)
	Append (" -wldp");
    else
	Append (" -wno");

    if (gflag)
	Append (" -bds");
    else if (Oflag)
	Append (" -sob");

    if (HOutput)
	Append (" -ho%s", HOutput);
    else {
	if (HInput)
	    Append (" -hi%s", HInput);

	if (Sflag) {
	    if (outfile)
		Append (" -at -o %s", outfile);
	    else
		Append (" -at -o %s.s", sf->Base);
	} else {
	    Append (" -a -o %s%s.s", Ram, sf->Base);
	}
    }

    for (cpp = Inc; *cpp; cpp++) Append (" -i%s", *cpp);
    for (cpp = Def; *cpp; cpp++) Append (" -d%s", *cpp);
    for (cpp = Cfl; *cpp; cpp++) Append (" %s", *cpp);
    Append (" %s.%s", sf->Root, sf->Suff);
    return (ExecuteIt());
}

asm(struct SourceFile *sf)
{
    char **cpp;
    
    Check_Abort();
    strcpy (Cmd, "as");
    CmdLen = strlen(Cmd);

    if (Vflag)
	Append (" -v");
    else
	Append (" >NIL:");

    if (gflag) Append (" -n");
    for (cpp = Inc; *cpp; cpp++) Append (" -i%s", *cpp);
    for (cpp = Afl; *cpp; cpp++) Append (" %s", *cpp);

    if (cflag) {
	if (outfile)
	    Append (" -o %s", outfile);
	else
	    Append (" -o %s.o", sf->Base);
    } else {
	Append (" -o %s%s.o", Ram, sf->Base);
    }

    if (isC (sf)) {
        Append (" %s%s.s", Ram, sf->Base);
    } else {
        Append (" %s.%s", sf->Root, sf->Suff);
    }
    return(ExecuteIt());
}

/* Break SourceFile name into root, base, and suff(ix). */

NewSourceFile (char *filename)
{
    char *cp;
    struct SourceFile *sf;

    SF[SF_c++] = sf = (struct SourceFile *)malloc(sizeof(struct SourceFile));
    sf->Root = filename;
    if (!(cp = strrchr (filename, '/'))) cp = strrchr (filename, ':');

    sf->Base = cp ? ++cp : filename;

    if (!(cp = strrchr (filename, '.'))) {
	sf->Suff = "";
    } else {
	*cp++ = '\0';
	sf->Suff = cp;
    }
}

int ExecuteIt ()
{
    int status;
    
    if (Vflag) {
	puts (Cmd);
	fflush (stdout);
    }
    Check_Abort();
    if (nflag) return(1);

    if (!(status = Execute (Cmd, 0, 0))) TotErrors++;

    Check_Abort();
    return (status);
}

/* Search LBD (LiBDirs) for given library name with (.lib) on the end */
char *FindLib (char *name)
{
    static char buf[256];
    char **cpp;
    BPTR lk;
    
    for (cpp = LBD; *cpp; cpp++) {
	sprintf (buf, "%s%s.lib", *cpp, name);
	if (lk = Lock (buf, ACCESS_READ)) {
	    UnLock (lk);
	    name = buf;
	    break;
	}
    }
    return (name);
}

Check_Abort (void)
{
    Enable_Abort = 0;
    if (Chk_Abort()) {
	if (Vflag) {
	    printf ("cc - terminated by request\n");
	}
	cleanup (1);
    }
    Enable_Abort = 1;
}

/* Add string to end of Cmd buffer. */
Append  (char *fmt, ...)
{
    int length;
    va_list vargs;

    va_start(vargs, fmt);
    (void) vsprintf (buffer, fmt, vargs);
    va_end(vargs);
    length = strlen (buffer);
    if (CmdLen + length >= sizeof (Cmd)) {
	fprintf(stderr, "cc: Cmd buffer length exceeded\n");
	cleanup(1);
    } else {
	strcpy (&Cmd[CmdLen], buffer);
	CmdLen += length;
    }
}

cleanup(int ex)
{
    int i;

    for (i = 0; i < SF_c; i++)  free (SF[i]);
    for (i = 0; i < Cfl_c; i++) free (Cfl[i]);
    for (i = 0; i < Afl_c; i++) free (Afl[i]);
    for (i = 0; i < Lfl_c; i++) free (Lfl[i]);
    for (i = 0; i < LBD_c; i++) free (LBD[i]);
    for (i = 0; i < Lib_c; i++) free (Lib[i]);
    for (i = 0; i < Def_c; i++) free (Def[i]);
    for (i = 0; i < Inc_c; i++) free (Inc[i]);

    free(Inc);
    free(Def);
    free(Lib);
    free(LBD);
    free(Lfl);
    free(Afl);
    free(Cfl);
    free(SF);

    exit(ex);
}

help()
{
    printf ("cc - A Unix-like 'cc' command for the Amiga and Manx 5.0.\n\n");
    printf ("Unix switches supported:\n");
    printf ("  -c                Stop at object file, don't link.\n");
    printf ("  -S                Stop at assembly file.\n");
    printf ("  -o {output file}  Output file name\n");
    printf ("  -l {library name} Library name with no path or suffix.\n");
    printf ("  -D {var=val}      'Define' a variable and its value.\n");
    printf ("  -I {include-dir}  Directory in which to find include files.\n");
    printf ("  -L {library-dir}  Directory in which to find library files.\n");
    printf ("  -g                Create debugging code for sdb.\n");
    printf ("  -O                Optimize as much as possible.\n\n");
    printf ("Non-Standard:\n");
    printf ("  -h                help: print this message.\n");
    printf ("  -hi               MANX header input. Def: bin:Aztec/Include/H.dmp\n");
    printf ("  -ho               Special MANX output header.\n");
    printf ("  -n                No-Execute.\n");
    printf ("  -t                Temporary directory. '.' == curdir.\n");
    printf ("  -w                Warn about everything ('lint').\n");
    printf ("  -V                Verbose at all levels.\n");
    printf ("  -A                Pass option to the assembler.\n");
    printf ("  -K                Pass option to the linker.\n");
    printf ("      NOTE: Any other flag is passed to the compiler.\n\n");
    cleanup(0);
}
