v13i062: Merge C code with compiler error messages

Rich Salz rsalz at bbn.com
Wed Feb 24 00:30:54 AEST 1988


Submitted-by: Brent Callaghan <brent%sparky at 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 at uunet.uu.net.



More information about the Comp.sources.unix mailing list