rsalz@bbn.com (Rich Salz) (02/23/88)
Submitted-by: Brent Callaghan <brent%sparky@SUN.COM> Posting-number: Volume 13, Issue 62 Archive-name: mcc Mcc (Merge C Compiler) behaves just like a C for an error free compile. However, if the compiler finds syntax errors, it merges the error messages with the source and invokes your editor (default is vi) on the merged file. On exiting the editor it strips the merged messages and if the source file was modified it re-runs the compiler for another try. This posting contains a shell script and a C program with identical functionality. The C program is noticeably faster. Made in New Zealand --> Brent Callaghan @ Sun Microsystems uucp: sun!bcallaghan phone: (415) 691 6188 [ This is kind of like the BSD error function, but not quite. It's useful in its own right. I wrote the Makefile. The script isn't plain old-style V7 etc /bin/sh -- it uses a shell function -- so beware. --r$ ] ---------------------------------------- #/bin/sh #This is a shar file. To use: # 1. Remove everything before the /bin/sh line # 2. Execute with /bin/sh (not csh) to extract the files: # Makefile # mcc.1 # mcc.sh # mcc.c file="${0}" echo extracting Makefile 1>&2 cat >Makefile << 'EnD of Makefile' # If you're on 4.2 or earlier, use the first line; else the second. CFLAGS=-O -DBSD #CFLAGS=-O mcc: mcc.c $(CC) $(CFLAGS) -o mcc mcc.c all: mcc mcc.1 install: all @echo copy mcc and mcc.1 to the appropriate directories. EnD of Makefile echo extracting mcc.1 1>&2 cat >mcc.1 << 'EnD of mcc.1' .TH MCC 1 "26 December 1986" .SH NAME mcc \- merge \fBC\fP compiler .SH SYNOPSIS .B mcc \ \ .I <cc command line args> .SH INTRODUCTION Ridding a program of syntax errors involves a cycle of compiling and editing. For a large program, this process can extend over many cycles. Most \fBC\fP compilers present syntax errors as a list of source line numbers and messages. This information must be remembered or copied elsewhere before the editor is invoked and erases the screen. This compile/edit cycle can be speeded considerably by eliminating the error message copying step. .SH DESCRIPTION .B Mcc brings together the \fBC\fP compiler \fBcc\fP(1), and the screen editor \fBvi\fP(1). It runs the \fBC\fP compiler, passing all command line arguments. It's behavior is identical to the \fBC\fP compiler, unless the compiler detects syntax errors in the source. For example: Use .sp 0.5 \fLmcc prog.c -o prog\fP .sp 0.3 instead of .sp 0.3 \fLcc prog.c -o prog\fP .sp 0.5 .B Mcc merges the source with the syntax error messages and invokes \fBvi\fP on the merged file. The cursor is positioned on the first line in error. Every error message follows the line it refers to. Appended to the error line is a reference to the next error line or the string "\fL(last error)\fP". Once the corrections have been made the editor should be exited normally. .B Mcc strips the error messages from the source and re-invokes the compiler for another try. .PP This process continues until the program is free of syntax errors. The edit/compile cycle can be broken by leaving the editor without making changes or by terminating \fBmcc\fP with a keyboard interrupt having left the editor. .PP Since errors from the linker \fBld\fP(1) do not contain line numbers \fBmcc\fP lists them and exits. .PP Since \fBmcc\fP returns the same exit value as \fBcc\fP, it can be utilized by \fBmake\fP(1). By setting \fLCC=mcc\fP either as an exported environment variable or within a \fImakefile\fP, \fBmake\fP will invoke \fBcc\fP via \fBmcc\fP. This feature allows simple syntax errors to be repaired without having to re-run the entire \fBmake\fP. .PP An alternative editor to \fBvi\fP may be specified by assigning its name to the environment variable EDITOR. Similarly, an alternative compiler name can be assigned to COMPILER. The compiler syntax error messages must match the format of the \fBC\fP preprocessor or \fBC\fP compiler. .SH FILES /tmp/err* Syntax errors from \fBcc\fP .br /tmp/pid.source Merged source and errors .SH "SEE ALSO" cc(1), vi(1), make(1) EnD of mcc.1 echo extracting mcc.sh 1>&2 cat >mcc.sh << 'EnD of mcc.sh' #!/bin/sh # # @(#) mcc - Merges syntax error messages from C compiler into # source and brings up vi with cursor at first error. # Re-compiles automatically when editor exits. # Author: Brent Callaghan trap "rm -f /tmp/$$.* ; exit \$RSLT" 0 1 2 3 15 # clean up quit() { cat /tmp/$$.err1 ; exit ; } # give up gracefully until ${COMPILER:=cc} "$@" >/tmp/$$.err1 2>&1 do RSLT=$? sed -e 's/^"\(.*\)", line \([0-9][0-9]*\): /\1 \2 /' \ -e 's/^\(.*\): \([0-9][0-9]*\): /\1 \2 /' \ < /tmp/$$.err1 > /tmp/$$.err2 read SRC LINE null < /tmp/$$.err2 case "$LINE" in [0-9]*) ;; *) quit ;; esac # valid line # ? if [ ! -w "$SRC" ] ; then quit ; fi # source writeable ? awk -F" " '/^'$SRC'/{printf "%s5\t>>>> %s <<<<\n", $2, $3}' \ < /tmp/$$.err2 > /tmp/$$.mrg1 awk '{printf "%d0\t%s\n", NR, $0}' < $SRC | sort -m -n /tmp/$$.mrg1 - | # merge err msgs sed -e 's/^[0-9][0-9]* //' > /tmp/$$.$SRC CHKSUM=`sum /tmp/$$.$SRC` vi +$LINE /tmp/$$.$SRC # fix errors if [ "$CHKSUM" = "`sum /tmp/$$.$SRC`" ] ; then exit ; fi echo " $COMPILER $*" grep -v "^>>>> " /tmp/$$.$SRC > /tmp/$$.mrg2 # strip err msgs mv /tmp/$$.mrg2 $SRC done cat /tmp/$$.err1 # list warnings RSLT=0 # compiled OK EnD of mcc.sh echo extracting mcc.c 1>&2 cat >mcc.c << 'EnD of mcc.c' /************************************************************ * * Program: mcc * By: Brent Callaghan * Date: July 1984 * * Function: Runs the C compiler, passing all command line * arguments. If the compiler returns a non-zero * result, the syntax errors are merged with the * source and the user's editor is invoked. The * cursor is placed on the first line in error. * Exit from the editor re-invokes the C compiler. * This loop continues until the C compiler exits * to the linker, the source file is not changed, * or the user kills mcc with a keyboard interrupt * after exiting the editor. * * Environment variables EDITOR and COMPILER may * be used to set an alternative editor or compiler. * * ~~~ PUBLIC DOMAIN ~~~ * * This program may be freely used and distributed * but I would rather you did not sell it. * ************************************************************ */ #include <stdio.h> #include <ctype.h> #include <signal.h> #ifdef BSD #include <strings.h> #define strchr index #define strrchr rindex #else #include <string.h> #endif extern char * getenv(); static char *errname = "/tmp/errXXXXXX"; static char mergename[128]; static char *srcname; static char *editor, *edname, *compiler; static int pid, viedit, firsterr; static int chksum1, chksum2; /* * Form 16 bit checksum of source line */ int checksum(sum, line) register int sum; register char *line; { while (*line++) { if (sum & 1) sum = (sum >> 1) + 0x8000; else sum >>= 1; sum = (sum + *line) & 0xFFFF; } return sum; } int runc(argv, errname) char **argv; char *errname; { int status; switch (pid = fork()) { case 0: /* child */ (void) freopen(errname, "w", stderr); execvp(compiler, argv); perror("Couldn't exec compiler"); exit (1); case -1: /* Error */ perror("Couldn't fork compiler"); exit (1); default: /* Parent */ while (wait(&status) != pid); /* wait for compile to finish */ break; } return ((status >> 8) & 0xFF); } void listerrs(errname) char *errname; { FILE *errfile; char errline[BUFSIZ + 1]; if ((errfile = fopen(errname, "r")) == NULL) return; while (fgets(errline, BUFSIZ, errfile) != NULL) (void) fputs(errline, stderr); (void) fclose(errfile); (void) unlink(errname); } void edit(mergename) char *mergename; { int status; char sfirsterr[6]; switch (pid = fork()) { case 0: /* Child */ if (viedit) { (void) sprintf(sfirsterr, "+%d", firsterr); (void) printf(" vi %s %s\n", sfirsterr, mergename); execlp(editor, "vi", sfirsterr, mergename, NULL); } else { (void) printf(" %s %s\n", edname, mergename); execlp(editor, edname, mergename, NULL); } perror("Couldn't exec editor"); listerrs(errname); exit (1); case -1: /* Error */ perror("Couldn't fork editor"); listerrs(errname); exit (1); default: /* Parent */ while (wait(&status) != pid); /* wait for editor to finish */ break; } } int errinfo(errfile, srcname, errmsg) FILE *errfile; char *srcname, *errmsg; { static char errline[BUFSIZ + 1]; char slineno[8]; char *p1, *p2; if (fgets(errline, BUFSIZ, errfile) == NULL) return 0; errline[strlen(errline) - 1] = '\0'; /* trim newline */ p1 = errline; /* Get source file id */ if (*p1 == '"') /* cc msg */ p2 = strchr(++p1, '"'); else /* cpp msg */ p2 = strchr(p1, ':'); if (p2 == NULL || p1 == p2) return 0; *p2 = '\0'; (void) strcpy(srcname, p1); /* Get source line number */ for (p1 = p2 + 1 ; *p1 ; p1++) if (isdigit(*p1)) break; if (*p1 == '\0') return 0; p2 = strchr(p1, ':'); if (p2 == NULL) return 0; *p2 = '\0'; (void) strcpy(slineno, p1); /* The rest is the error message */ (void) strcpy(errmsg, p2 + 1); return atoi(slineno); } char * merge(errname, mergename) char *errname, *mergename; { FILE *errfile, *srcfile, *mergefile; int eof = 0, slineno, elineno, elines; static char firstname[128]; char srcline[BUFSIZ + 1]; char srcname[128], errmsg[80]; if ((errfile = fopen(errname, "r")) == NULL) { perror(errname); exit (1); } if ((firsterr = errinfo(errfile, srcname, errmsg)) == 0) return NULL; if (access(srcname, 2) < 0) /* writeable ? */ return NULL; if ((srcfile = fopen(srcname, "r")) == NULL) { perror(srcname); exit (1); } if (*mergename == '\0') { char *p = strrchr(srcname, '/'); if (p == NULL) p = srcname; else P++; (void) sprintf(mergename, "/tmp/%d.%s", getpid(), p); } if ((mergefile = fopen(mergename, "w")) == NULL) { perror(mergename); exit (1); } slineno = 0; elineno = firsterr; elines = 0; (void) strcpy(firstname, srcname); chksum1 = 0; if (!viedit) { (void) fprintf(mergefile, ">>>><<<< (%d)\n", firsterr + 1); elines++; } while (!eof) { if (!(eof = (fgets(srcline, BUFSIZ, srcfile) == NULL))) { chksum1 = checksum(chksum1, srcline); (void) fputs(srcline, mergefile); } slineno++; while (slineno == elineno) { elines++; (void) fprintf(mergefile, ">>>> %s <<<<", errmsg); if ((elineno = errinfo(errfile, srcname, errmsg)) == 0 || strcmp(firstname, srcname) != 0) (void) fprintf(mergefile, " (last error)\n"); else (void) fprintf(mergefile, " (%d)\n", elineno + elines); } } (void) fclose(errfile); (void) fclose(srcfile); (void) fclose(mergefile); return (firstname); } /* * Strip out merged error messages and compute checksum */ void unmerge(mergename, srcname) char *mergename, *srcname; { FILE *mergefile, *srcfile; char *p, srcline[BUFSIZ + 1]; if ((mergefile = fopen(mergename, "r")) == NULL) { perror(mergename); exit (1); } if ((srcfile = fopen(srcname, "w")) == NULL) { perror(srcname); exit (1); } chksum2 = 0; while (fgets(srcline, BUFSIZ, mergefile) != NULL) { for (p = srcline; isspace(*p); p++); if (strncmp(p, ">>>>", 4) != 0) { chksum2 = checksum(chksum2, srcline); (void) fputs(srcline, srcfile); } } (void) fclose(mergefile); (void) fclose(srcfile); } void quit() { (void) kill(pid, SIGTERM); (void) unlink(errname); (void) unlink(mergename); exit (1); } main(argc, argv) int argc; char *argv[]; { int i, status; if ((editor = getenv("EDITOR")) == NULL) editor = "vi"; edname = (edname = strrchr(editor, '/')) == NULL ? editor : edname + 1; viedit = strcmp(edname, "vi") == 0; if ((compiler = getenv("COMPILER")) == NULL) compiler = "cc"; argv[0] = compiler; (void) mktemp(errname); signal(SIGINT, quit); signal(SIGTERM, quit); signal(SIGHUP, quit); while (status = runc(argv, errname)) { if ((srcname = merge(errname, mergename)) == NULL) { listerrs(errname); exit (status); /* couldn't merge */ } edit(mergename); (void) unlink(errname); signal(SIGINT, SIG_IGN); signal(SIGTERM, SIG_IGN); signal(SIGHUP, SIG_IGN); unmerge(mergename, srcname); (void) unlink(mergename); signal(SIGINT, quit); signal(SIGTERM, quit); signal(SIGHUP, quit); if (chksum1 == chksum2) /* file unchanged ? */ break; putchar(' '); for (i = 0; i < argc; i++) (void) printf("%s ", argv[i]); putchar('\n'); } listerrs(errname); (void) unlink(errname); exit (status); } EnD of mcc.c exit -- For comp.sources.unix stuff, mail to sources@uunet.uu.net.