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