/************************************************************************ * * * 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 /* * 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 #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 args */ #define MAXUNDEFINES (32) /* Maximum number of -U args */ #define MAXINCDIRS (16) /* Maximum number of -I args */ #define MAXLIBS (16) /* Maximum number of -l args */ #define ARGSIZE (64) /* Size of temp args for cmd line */ #define LOCALSYMTABSIZE (100) /* Local symbol table size in entries */ #define EXPRTABSIZE (200) /* Expression table size */ #define CASETABSIZE (256) /* Case table size */ #define STRTABSIZE (5 * 1024) /* String table size */ /* * 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 */ #define TEMPNAME "tempxxxx" /* * 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 Hflag; /* -H 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 *predefines = "-Damiga -Dm68000 -Dmanx"; 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 int 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 'H': Hflag++; break; 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++; 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; auto char buffer[ARGSIZE]; 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)) { Preprocess (op); Compile (op); sprintf (buffer, "%s%s.c", QuadDev, TEMPNAME); if (!DeleteFile (buffer)) { Warning ("can't delete '%s'", buffer); } if (!Sflag) { Assemble (op); sprintf (buffer, "%s%s.s", QuadDev, op -> Basename); if (!DeleteFile (buffer)) { Warning ("can't delete '%s'", buffer); } } } else if (SFILE (op)) { Assemble (op); } } DBUG_VOID_RETURN; } /* * Note that commands to cc of the form "-l" 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 ("ln", BinDirs)); AddToCommand (" -o %s", outfile); for (index = 0; index < NOperands; index++) { op = &Operands[index]; if (OFILE (op)) { name = op -> Rootname; } else { name = op -> Basename; } AddToCommand (" %s.o", name); } for (index = 0; index < NLibs; index++) { if (!Hflag) { sprintf (buffer, "%s32.lib", Libs[index]); } else { sprintf (buffer, "%s.lib", Libs[index]); } AddToCommand (" %s", Locate (buffer, LibDirs)); } if (!Hflag) { AddToCommand (" %s", Locate ("c32.lib", LibDirs)); } else { AddToCommand (" %s", Locate ("c.lib", LibDirs)); } (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"); Pass1 (op); CHECK_ABORT; 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 ("ccom", LibDirs)); AddToCommand (" -B"); AddToCommand (" -L%d", LOCALSYMTABSIZE); AddToCommand (" -E%d", EXPRTABSIZE); AddToCommand (" -Y%d", CASETABSIZE); AddToCommand (" -Z%d", STRTABSIZE); if (!Hflag) { AddToCommand (" +L"); } if (Sflag) { AddToCommand (" -A -T -o %s.s", op -> Basename); } else { AddToCommand (" -A -o %s%s.s", QuadDev, op -> Basename); } AddToCommand (" %s%s.c", QuadDev, TEMPNAME); status = RunCommand (); DBUG_RETURN (status); } static int Preprocess (op) register struct Operand *op; { register int status; register int index; DBUG_ENTER ("Preprocess"); InitCommand (); AddToCommand ("%s", Locate ("cpp", LibDirs)); AddToCommand (" %s", predefines); for (index = 0; index Rootname, op -> Suffix); AddToCommand (" %s%s.c", QuadDev, TEMPNAME); 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 int Assemble (op) struct Operand *op; { register int status; register int index; DBUG_ENTER ("Assemble"); InitCommand (); AddToCommand ("%s", Locate ("as", BinDirs)); AddToCommand (" -o %s.o", op -> Basename); if (CFILE (op)) { AddToCommand (" %s%s.s", QuadDev, op -> Basename); } else { AddToCommand (" %s.%s", op -> Rootname, op -> Suffix); } status = RunCommand (); DBUG_RETURN (status); } /* * 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 != EOS; 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 #else # include #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] = EOS; 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; } #if defined(amiga) && defined(manx) /* In Lattice C lib, but not Manx's */ char *strrchr (s, c) char *s; char c; { register char *scan = s; DBUG_ENTER ("strrchr"); while (*scan++ != EOS) {;} while (scan > s && *(--scan) != c) {;} DBUG_RETURN (*scan == c ? scan : NULL); } #endif /* amiga && manx */