[net.micro.amiga] Amiga cc.c

fnf@unisoft.UUCP (12/01/85)

<munch>

Hi, here is my second posting of cc.c, a Unix like frontend for the
Lattice C compiler.  Note the two nonstandard (for Unix, standard for
the Lattice "LC" frontend) options '-q<tempdir>' and '-V'.

I also have a public domain version of "make" up and running and plan
to post the code when I return to work on Monday.

Does anyone have any pointers to AMIGA user's groups starting up in
the bay area?

========== ^A^N^@^[<^W^X^Wcc.c^J (hope you speak emacs) ==========
/************************************************************************
 *									*
 *			Copyright (c) 1985, Fred Fish			*
 *			    All Rights Reserved				*
 *									*
 *	This software and/or documentation is released into the		*
 *	public domain for personal, non-commercial use only.		*
 *	Limited rights to use, modify, and redistribute are hereby	*
 *	granted for non-commercial purposes, provided that all		*
 *	copyright notices remain intact and all changes are clearly	*
 *	documented.  The author makes no warranty of any kind with	*
 *	respect to this product and explicitly disclaims any implied	*
 *	warranties of merchantability or fitness for any particular	*
 *	purpose.							*
 *									*
 ************************************************************************
 */


/*
 *	cc -- C compiler front end for Amiga and Lattice C.
 *
 *	Somewhat AMIGA/Lattice dependent, but can probably be adapted to
 *	other systems with a minimum of work.  I have attempted to keep
 *	portability in mind as much as possible.
 *
 */

char _sccsid[] = "@(#)cc.c	1.8";

#include <stdio.h>

/*
 *	The following allow use on systems that don't have my macro based
 *	debugging package.  The default, for now, is to assume it is not
 *	available.
 */
 
#ifdef DBUG
#  include <local/dbug.h>
#else	/* !DBUG */
#  define DBUG_ENTER(a)
#  define DBUG_RETURN(a) return(a)
#  define DBUG_VOID_RETURN return
#  define DBUG_2(a,b)
#  define DBUG_3(a,b,c)
#  define DBUG_4(a,b,c,d)
#  define DBUG_5(a,b,c,d,e)
#  define DBUG_PUSH(a)
#endif	/* DBUG */

/*
 *	IMPLEMENTATION NOTES
 *
 *	Some of the builtin (artificial) limits could be removed by
 *	using dynamically allocated data structures, such as keeping the
 *	operand list as a linked list with space for each link supplied
 *	by malloc.  This would certainly increase the code space, while
 *	reducing the statically allocated data space.  The net result
 *	would probably be a program requiring about the same amount of
 *	total memory for most day to day usages.  When source is not
 *	available to the end user, maximum flexibility is a must and
 *	dynamic allocation is the only way to go.  In this case however
 *	it is not clear that the added complexity is worth it, since
 *	source should be available for anyone wishing to expand the
 *	limits.
 *
 *	One last note, if you are going to have builtin limits then
 *	check the @#$%&* things for overflow.  Running off the end of
 *	an array with no indication that something is wrong, other
 *	than a crash, if definitely unfriendly!
 *
 */
 

/*
 *	Manifest constants which can be tuned to fit requirements.
 */

#define CMDBUFFERSIZE (1024)	/* Size of command buffer for CLI command */
#define MAXOPERANDS (64)	/* Maximum number of operands on cmd line */
#define MAXDEFINES (32)		/* Maximum number of -D<name> args */
#define MAXUNDEFINES (32)	/* Maximum number of -U<name> args */
#define MAXINCDIRS (16)		/* Maximum number of -I<filename> args */
#define MAXLIBS (16)		/* Maximum number of -l<lib> args */
#define ARGSIZE (64)		/* Size of temp args for cmd line */

/*
 *	Define QUADDEV to be the default place where you want the compiler
 *	intermediate files (quad files) to be created.  For systems with
 *	512K or more, the preferred place is the ram disk.  However,
 *	really big compiles may need to be done on a hard disk.
 *	In either case, the default can be overridden with the -q option.
 */
 
#define QUADDEV	"ram:"		/* Keep intermediate files in ram */
/* #define QUADDEV "" */	/* Keep intermediate files in current dir */

/*
 *	Manifest constants which are generally the same on all systems.
 */

#define EOS '\000'		/* End of string character */

/*
 *	Command line arguments that represent files to be compiled, assembled,
 *	or linked, are kept track of via an "Operand" array.  If, for example,
 *	the file name is "df0:mydir/junk.c", then the Rootname is
 *	"df0:mydir/junk", the Basename is "junk", and the Suffix is "c".
 *	String suffixes are used, rather than single character suffixes, to
 *	allow use of names with multicharacter suffixes.
 */
 
struct Operand {		/* Info about each operand (non option) */
    char *Rootname;		/* Name minus any suffix */
    char *Basename;		/* Name minus any prefix or suffix */
    char *Suffix;		/* Suffix of operand */
};

static struct Operand Operands[MAXOPERANDS];	/* Table of operands */
static int NOperands = 0;			/* Number of operands found */
static char *Defines[MAXDEFINES];		/* Table of defines */
static int NDefines = 0;			/* Number of defines */
static char *UnDefines[MAXUNDEFINES];		/* Table of undefines */
static int NUnDefines = 0;			/* Number of undefines */
static char *UserInc[MAXINCDIRS];		/* Table of include dirs */
static int NUserInc = 0;			/* Number of include dirs */
static char *Libs[MAXLIBS];			/* Table of library args */
static int NLibs = 0;				/* Number of library args */

/*
 *	The command line buffer for child commands is allocated statically,
 *	rather than as an automatic, to forstall problems with stack limits
 *	in initial implementations.  Hopefully, automatic stack growth will
 *	be implemented in future release of the C compiler.  If nothing
 *	else, someday I will read the manuals and figure out how to explicitly
 *	grow the stack...
 *
 */

static char Command[CMDBUFFERSIZE];		/* Command line buffer */
static char *EndCommand = Command;		/* End of current command */

/*
 *	Macros to determine the suffix type of a file given a pointer to
 *	its operand structure.
 */
 
#define CFILE(op) (strcmp(op->Suffix,"c")==0)
#define SFILE(op) (strcmp(op->Suffix,"s")==0)
#define OFILE(op) (strcmp(op->Suffix,"o")==0)
extern int strcmp ();

/*
 *	Now some macros to map from unix names to the AMIGA equivalents,
 *	and to enable abort on control-C.
 */
 
#ifdef AMIGA
#  define system(a) (Execute(a,0,0))
#  define ENABLE_ABORT (Enable_Abort = 1)
#  define DISABLE_ABORT (Enable_Abort = 0)
#  define CHECK_ABORT Check_Abort()
   extern int Enable_Abort;			/* Enable abort on CNTL-C */
   static void Check_Abort ();		/* Test for abort requested */
#else
#  define ENABLE_ABORT				/* Null expansion */
#  define DISABLE_ABORT				/* Null expansion */
#  define CHECK_ABORT				/* Null expansion */
#endif	/* AMIGA */

/*
 *	Set list of places to search for various executables, libraries, etc.
 *	Searched in order, first match wins, null string is current directory.
 *	Note that these names are used as prefixes exactly as given, so
 *	device names must end in ':' and directory names must end in '/'.
 *
 */

static char *Devices[] = {
    "",
    "ram:",
    "df0:",
    "df1:",
    NULL
};

static char *BinDirs[] = {
    "",
    "ram:c/",
    "df0:c/",
    "df1:c/",
    NULL
};

static char *LibDirs[] = {
    "",
    "ram:lib/",
    "df0:lib/",
    "df1:lib/",
    NULL
};

static char *IncDirs[] = {
    "ram:include/",
    "df0:include/",
    "df1:include/",
    NULL
};

/*
 *	Flags set by command line arguments/
 */
 
static int cflag;			/* -c flag given */
static int Pflag;			/* -P flag given */
static int Sflag;			/* -S flag given */
static int Vflag;			/* -V flag given (non-standard) */

static int ErrCount = 0;		/* Count of compile/assemble errors */
static char *outfile = "a.out";		/* Output file name from linker */
static char *QuadDev = QUADDEV;		/* Where to keep quad files */

static char *Locate ();			/* Find a file */
static void AddToCommand ();		/* Add argument to command buffer */
static void InitCommand ();		/* Initialize command buffer */
static void Fatal ();			/* Quit with fatal error */
static void Warning ();			/* Issue warning message */
static void AddOperandToList ();	/* Add .c, .s, or .o file to list */
static void CleanObjects ();		/* Remove .o for link and go mode */
static void MakeObjects ();		/* Reduce to list of object files */
static void ParseCommandLine ();	/* Deal with command line */
static void Compile ();			/* Translate from .c to .o */
static void Assemble ();		/* Translate from .s to .o */
static void Link ();			/* Gather .o's into executable */

extern void exit ();			/* See exit(2) */

/*
 *	Main entry point.  Note that despite common usage where main is
 *	essentially of type void, we declare it explicitly to return
 *	an int, and actually return a value.  In most implementations,
 *	the value returned from main is the exit status of the program.
 *	Whether this applies to Lattice C or not, I'm not sure yet.
 */
 
int main (argc, argv)
int argc;
char *argv[];
{
    DBUG_ENTER ("main");
    ENABLE_ABORT;
    ParseCommandLine (argc, argv);
    MakeObjects ();
    if (!cflag && !Pflag && !Sflag && ErrCount == 0) {
	Link ();
	CleanObjects ();
    }
    DBUG_RETURN (0);
}

/*
 *	The following macro is used to allow optional whitespace between
 *	an option and it's argument.  Argp is left pointing at the option
 *	and argv and argc are adjusted accordingly if necessary.
 *
 *	Note that there is no check for missing option arguments.  In
 *	particular, -o -V will blindly take -V as the output file name.
 *
 */

#define XARG(argc,argv,argp) {if(*++argp==EOS){argp=(*argv++);argc--;}}

static void ParseCommandLine (argc, argv)
int argc;
char **argv;
{
    register char *argp;    

    DBUG_ENTER ("ParseCommandLine");
    argc--;
    argv++;
    while (argc-- > 0) {
	CHECK_ABORT;
	argp = *argv++;
	if (*argp != '-') {
	    AddOperandToList (argp);
	} else {
	    switch (*++argp) {
		case '#':
		    XARG (argc, argv, argp);
		    DBUG_PUSH (argp);
		    break;
		case 'c':
		    cflag++;
		    break;
		case 'D':
		    XARG (argc, argv, argp);
		    if (NDefines >= MAXDEFINES) {
			Fatal ("too many -D args (%d max)", MAXDEFINES);
		    }
		    Defines[NDefines++] = argp;
		    break;
		case 'E':
		    Warning ("-E unimplemented, converted to -P instead");
		    Pflag++;
		    break;
		case 'f':
		    break;	/* NOP for now, just eat it */
		case 'g':
		    break;	/* NOP for now, just eat it */
		case 'I':
		    XARG (argc, argv, argp);
		    if (NUserInc >= MAXINCDIRS) {
			Fatal ("too many -I args (%d max)", MAXINCDIRS);
		    }
		    UserInc[NUserInc++] = argp;
		    break;
		case 'l':
		    XARG (argc, argv, argp);
		    if (NLibs > MAXLIBS) {
			Fatal ("too many -l args (%d max)", MAXLIBS);
		    }
		    Libs[NLibs++] = argp;
		    break;
		case 'O':
		    break;	/* NOP for now, just eat it */
		case 'o':
		    XARG (argc, argv, argp);
		    outfile = argp;
		    break;
		case 'P':
		    Pflag++;
		    break;
		case 'q':		/* Warning, non-standard */
		    XARG (argc, argv, argp);
		    QuadDev = argp;
		    break;
		case 'S':
		    Sflag++;
		    Warning ("-S option not yet implemented, ignored");
		    break;
		case 's':
		    break;		/* NOP for now, just eat it */
		case 'U':
		    XARG (argc, argv, argp);
		    if (NUnDefines >= MAXUNDEFINES) {
			Fatal ("too many -U args (%d max)", MAXUNDEFINES);
		    }
		    UnDefines[NUnDefines++] = argp;
		    break;
		case 'V':
		    Vflag++;
		    break;
		default:
		    Warning ("unknown option '%c'", (char *) *argp);
		    break;
	    }
	}
    }
    DBUG_VOID_RETURN;
}

/*
 *	For each operand, do compilation or assembly as necessary, to
 *	reduce to an object file in the current directory.
 */

static void MakeObjects ()
{
    register int index;
    register struct Operand *op;

    DBUG_ENTER ("MakeObjects");
    for (index = 0; index < NOperands; index++) {
	CHECK_ABORT;
	op = &Operands[index];
	if (NOperands > 1 && (CFILE (op) || SFILE (op))) {
	    printf ("%s.%s:\n", op -> Rootname, op -> Suffix);
	}
	if (CFILE (op)) {
	    Compile (op);
	} else if (SFILE (op)) {
	    Assemble (op);
	}
    }
    DBUG_VOID_RETURN;
}

/*
 *	Note that commands to cc of the form "-l<name>" get interpreted
 *	to mean use a library called "name.lib" from the library
 *	directory.
 */
 
static void Link ()
{
    register int index;
    register struct Operand *op;
    register char *name;
    auto char buffer[ARGSIZE];
    
    DBUG_ENTER ("Link");
    InitCommand ();
    AddToCommand ("%s ", Locate ("alink", BinDirs));
    AddToCommand ("%s", Locate ("Lstartup.obj", LibDirs));
    for (index = 0; index < NOperands; index++) {
	op = &Operands[index];
	if (OFILE (op)) {
	    name = op -> Rootname;
	} else {
	    name = op -> Basename;
	}
	AddToCommand ("+%s.o", name);
    }
    AddToCommand ("%s", " library ");
    for (index = 0; index < NLibs; index++) {
	sprintf (buffer, "%s.lib", Libs[index]);
	AddToCommand ("%s+", Locate (buffer, LibDirs));
    }
    AddToCommand ("%s+", Locate ("lc.lib", LibDirs));
    AddToCommand ("%s", Locate ("amiga.lib", LibDirs));
    AddToCommand (" to %s map nil:", outfile);
    (void) RunCommand ();
    DBUG_VOID_RETURN;
}

/*VARARGS1*/
static void Warning (fmt, arg1, arg2, arg3)
char *fmt;
char *arg1;
char *arg2;
char *arg3;
{
    fprintf (stderr, "cc -- warning: ");
    fprintf (stderr, fmt, arg1, arg2, arg3);
    fprintf (stderr, "\n");
    (void) fflush (stderr);
}

/*VARARGS1*/
static void Fatal (fmt, arg1, arg2, arg3)
char *fmt;
char *arg1;
char *arg2;
char *arg3;
{
    fprintf (stderr, "cc -- fatal error: ");
    fprintf (stderr, fmt, arg1, arg2, arg3);
    fprintf (stderr, "\n");
    (void) fflush (stderr);
    exit (1);
}

/*
 *	Split an operand name into rootname, basename, and suffix
 *	components.  The rootname is the full name, minus any suffix,
 *	but including any prefix.  The basename is the rootname minus
 *	any prefix.  The suffix is anything after the last '.' character.
 *	Only the suffix is allowed to be the null string.
 */

static void AddOperandToList (filename)
char *filename;
{
    register char *split;
    register struct Operand *op;
    extern char *strrchr ();

    DBUG_ENTER ("AddOperandToList");
    DBUG_3 ("ops", "add file '%s' to operand list", filename);
    if (NOperands >= MAXOPERANDS) {
	Fatal ("too many files (%d max)\n", MAXOPERANDS);
    }
    op = &Operands[NOperands];
    op -> Rootname = filename;
    if ((split = strrchr (filename, '/')) == NULL) {
	split = strrchr (filename, ':');
    }
    if (split == NULL) {
	op -> Basename = filename;
    } else {
	op -> Basename = ++split;
    }
    if ((split = strrchr (filename, '.')) == NULL) {
	op -> Suffix = "";
    } else {
	*split++ = EOS;
	op -> Suffix = split;
    }
    DBUG_3 ("ops", "rootname '%s'", op -> Rootname);
    DBUG_3 ("ops", "basename '%s'", op -> Basename);
    DBUG_3 ("ops", "suffix '%s'", op -> Suffix);
    NOperands++;
    DBUG_VOID_RETURN;
}

/*
 *	Compile one operand from a C source program to an object module.
 */
 
static void Compile (op)
struct Operand *op;
{
    DBUG_ENTER ("Compile");
    if (!Sflag && Pass1 (op) && !Pflag) {		/* Order important! */
	CHECK_ABORT;
	(void) Pass2 (op);
    }
    DBUG_VOID_RETURN;
}

/*
 *	Note that because of brain-damage in the fact that -p to lc1 removes
 *	all predefined defs, we must add them so replacing -c with -P in the
 *	cc command line will result in the same set of predefined symbols.
 *	This is rather ugly and leaves a hole for future problems if we
 *	get out of sync with respect to what names the compiler predefines.
 */
 
static int Pass1 (op)
register struct Operand *op;
{
    register int status;
    register int index;
    
    DBUG_ENTER ("Pass1");
    InitCommand ();
    AddToCommand ("%s", Locate ("lc1", BinDirs));
    if (Pflag) {
	AddToCommand (" -o%s.i -p -DAMIGA -DM68000 -DSPTR", op -> Basename);
    } else {
	AddToCommand (" -o%s%s.q", QuadDev, op -> Basename);
    }
    for (index = 0; index <NUserInc; index++) {
	AddToCommand (" -i%s/", UserInc[index]);
    }
    for (index = 0; index <NUnDefines; index++) {
	/*************************
	AddToCommand (" -u%s", UnDefines[index]);
	**************************/
	Warning ("-U%s ignored! (unimplemented)", UnDefines[index]);
    }
    for (index = 0; index <NDefines; index++) {
	AddToCommand (" -d%s", Defines[index]);
    }
    AddToCommand (" -i%s/", Locate ("include", Devices));
    AddToCommand (" -i%s/", Locate ("lattice", IncDirs));
    AddToCommand (" %s", op -> Rootname);
    status = RunCommand ();
    DBUG_RETURN (status);
}

/*
 *	Run second pass of compiler on a single operand.
 */
 
static int Pass2 (op)
struct Operand *op;
{
    int status;
    
    DBUG_ENTER ("Pass2");
    InitCommand ();
    AddToCommand ("%s", Locate ("lc2", BinDirs));
    AddToCommand (" -o%s.o", op -> Basename);
    AddToCommand (" %s%s", QuadDev, op -> Basename);
    status = RunCommand ();
    DBUG_RETURN (status);
}

/*
 *	I have not yet had occasion to use the macro assembler, so this
 *	part is not yet implemented.  If anyone wants to send me the
 *	appropriate code, I will be glad to install it.
 */
 
static void Assemble (op)
struct Operand *op;
{
    DBUG_ENTER ("Assemble");
    Warning ("assembly pass not yet implemented");
    ErrCount++;
    DBUG_VOID_RETURN;
}

/*
 *	As far as I can tell, the child status is not returned, only
 *	whether or not the child could be run.  So, how do we find out
 *	whether there was an error or not?  It's probably in the manuals
 *	somewhere, I just haven't had time to dig yet.
 *
 *	Note that because Lattice printf is not capable of printing more
 *	than 200 characters at a time, we must spit them out one at a time
 *	to make sure the entire command line gets printed when -V is used.
 *
 */
 
static int RunCommand ()
{
    int status;
    register char *cmdp;
    
    DBUG_ENTER ("RunCommand");
    DBUG_3 ("cmd", "execute '%s'", Command);
    if (Vflag) {
	for (cmdp = Command; *cmdp != '\000'; cmdp++) {
	    putchar (*cmdp);				/* see above */
	}
	putchar ('\n');
	(void) fflush (stdout);
    }
    CHECK_ABORT;
    status = system (Command);
    DBUG_3 ("sys", "subcommand returns status %d", status);
    if (!status) {
	ErrCount++;
    }
    DBUG_RETURN (status);
}

/*
 *	Look through the list of paths pointed to by "vec" until we find
 *	a file with name given pointed to by "namep".  If none is found,
 *	the name pointed to by namep is returned.
 */
 
static char *Locate (namep, vec)
char *namep;
char **vec;
{
    static char namebuf[ARGSIZE];
    
    DBUG_ENTER ("Locate");
    while (*vec != NULL) {
	(void) sprintf (namebuf, "%s%s", *vec, namep);
	DBUG_3 ("try", "look for '%s'", namebuf);
	if (Readable (namebuf)) {
	    namep = namebuf;
	    break;
	}
	vec++;
    }
    DBUG_RETURN (namep);
}

/*
 *	Check to see if the file exists and is readable.
 */

#ifdef unix
#  include <fcntl.h>
#else
#  include <libraries/dos.h>
#endif

static int Readable (name)
char *name;
{
    register int status = 0;
    register int fildes;
    
    DBUG_ENTER ("Readable");
#ifdef unix
    fildes = open (name, O_RDONLY);
    if (fildes >= 0) {
	(void) close (fildes);
	status = 1;
    }
#else
    fildes = Lock (name, ACCESS_READ);
    if (fildes != 0) {
    	UnLock (fildes);
	status = 1;
    }
#endif
    DBUG_RETURN (status);
}

/*
 *	Do explicit check for abort.  When Enable_Abort is non-zero,
 *	Chk_Abort() cause program termination if CNTRL-C or CNTRL-D has
 *	been received.  Thus, we temporarily set it back to zero while we
 *	do the explicit test, so we can do our own clean up and exit.
 *	Note that if the -V flag was used, we spit out a confirming message
 *	that we are quitting.
 *
 *	Since we previously set Check_Abort to non-zero, this routine may be
 *	overkill.
 */
 
#ifdef AMIGA
static void Check_Abort ()
{
    extern int Chk_Abort ();
    
    DBUG_ENTER ("Check_Abort");
    DBUG_2 ("abort", "do explicit test for CNTRL-C");
    DISABLE_ABORT;
    if (Chk_Abort () != 0) {
	if (Vflag) {
	    printf ("cc - terminated by request\n");
	}
	exit (1);
    }
    ENABLE_ABORT;
    DBUG_VOID_RETURN;
}
#endif

/*
 *	Initialize the command line buffer and associated variables to
 *	discard any previous command line.
 */

static void InitCommand ()
{
    Command[0] = '\000';
    EndCommand = Command;
}

/*
 *	Build string to add to end of current command line, checking
 *	for overflow in the command buffer and maintaining the pointer
 *	to the end of the current command.
 *
 *	Note that we are a "printf type" of command, and can be called
 *	with up to three "char *" arguments.  There is a portability
 *	problem here, but Lattice hasn't yet made "varargs" a standard
 *	part of their distribution.
 *
 *	Also, note that the return argument of sprintf is supposed to be
 *	the number of characters to be added to the buffer.  This is
 *	not always true for some C implementations.  In particular,
 *	sprintf in BSD4.1 returns a pointer.  Thus we don't use the
 *	return argument.
 *
 */

/*VARARGS1*/
static void AddToCommand (fmt, arg1, arg2, arg3)
char *fmt;
char *arg1, *arg2, *arg3;
{
    register int length;
    auto char buffer[ARGSIZE];

    (void) sprintf (buffer, fmt, arg1, arg2, arg3);
    length = strlen (buffer);
    if ((EndCommand - Command) + length >= sizeof (Command)) {
	Fatal ("command line too long (%d char max)", sizeof (Command));
    } else {
	(void) strcat (EndCommand, buffer);
	EndCommand += length;
    }
}

/*
 *	If an executable is made from a single C file, the normal behavior
 *	for the unix environment is to treat the .o file as an intermediate
 *	file and remove it, so we follow suit.
 */
 
static void CleanObjects ()
{
    auto char buffer[ARGSIZE];
    register struct Operand *op;
    
    DBUG_ENTER ("CleanObjects");
    if (NOperands == 1) {
	op = &Operands[0];
	if (CFILE (op) || SFILE (op)) {
	    sprintf (buffer, "%s.o", op -> Basename);
	    if (!DeleteFile (buffer)) {
		Warning ("can't delete '%s'", buffer);
	    }
	}
    }
    DBUG_VOID_RETURN;
}