[net.sources] Fix2: A two-pass, interactive spelling fixer in C

alexis@reed.UUCP (Alexis Dimitriadis) (05/31/85)

Why another spelling fixer?  Briefly, the following program handles the
error correction and context selection itself. (without calling sed, 
ed, ex or grep).  The only program exec'ed is `spell', to generate the
error list.  The particular speller program invoked is user-settable.

  `Fix', the core of this program, was written here at Reed by Pat Locke 
many years ago, and has enjoyed great success (and heavy use) ever since.

  Fix2 combines a quick pass over the error words, at which time
correct words may be discarded and "global" changes specified, with a
second, "context" pass, when entire lines containing an error are shown
and specific changes are entered, while deletions and global changes
are still available.  Another important feature is the possibility of
backing up during the execution of the program.  (And of course, shell
escapes, help menus, and other bells and whistles).

  Please mail questions, comments (such as suggestions for further
features), and (ulp) bug reports to me, alexis @ reed.

  This program was developed on a VAX 11/780 running ULTRIX, a 4.2BSD clone.
I would especially appreciate hearing from anyone porting it to other 
environments.

Sincerely, 

Alexis Dimitriadis
  alexis @ reed

_______________________________________________
  As soon as I get a full time job, the opinions expressed above
will attach themselves to my employer, who will never be rid of
them again.

             alexis @ reed

	         ...teneron! \
...seismo!ihnp4! - tektronix! - reed.UUCP
     ...decvax! /
_______________________________________________

---- Cut Here and Feed to the Hungry Bourne Shell ----------
#!/bin/sh
# shar:	Shell Archiver
#	Run the following text with /bin/sh to create:
#	fix2.1
#	makefile
#	fix.h
#	main.c
#	dofix.c
#	readerr.c
#	edit.c
#	words.c
#	seek.c
#	misc.c
sed 's/^X//' << 'SHAR_EOF' > fix2.1
X.ad l
X.TH FIX2 1 local local
X.SH NAME
XFix2: Correct spelling errors interactively.
X.SH SYNOPSIS
X.nf
Xfix2 [\-cu] [\-s speller] [\-h histfile] [-f typofile] textfile [ ... ]
X.fi
X.br
Xfix2 [\-h histfile] \-e typofile
X.SH DESCRIPTION
X.PP
X.I Fix2
Xfacilitates the correction of misspellings in a text file.  It operates
Xin two stages.  If typofile is not given,
X.I fix2
Xwill run a spelling program to generate a list of errors.  Wrong words
Xare presented one at a time, for quick previewing.  At this time they
Xmay be deleted from the error list, or a "global" substitution pattern
Xmay be specified.  A question mark causes a list of the available
Xcommands to be printed.
X.PP
XIn the second stage, 
Xlines containing the remaining words are presented, with the suspected
Xword enclosed in square braces. ([ ]).
XThe user may enter replacement text, type a newline, (leaving the word
Xunchanged), or type a colon followed by a
Xspecial command.  
XAll words matching an erroneous word will be corrected as specified.
X.PP
X.I Fix2 
Xaccepts the following options:
X.IP \-c
XContext pass only. Do not present word list.
XGo directly to presenting entire lines.
X.IP \-e
Xpreview errorfile only. The file argument is a wordlist,
Xand it is reviewed as usually, except that global
Xsubstitutions are not meaningful.  On exit, the
Xerrorfile is changed to contain the remaining words.
X.IP \-u
XUpdate errofile. Deleted words will be removed from the
Xerrorfile. Unless \-e or \-u is specified, the errorfile
Xis not altered.
X.IP \-f typofile
XRead the error list from the file typofile, instead of running spell.
XIf more than one textfile is being 
X.I fixed,
X the typofile may be prepared
Xin advance to avoid specifying the same global changes and deletions
Xover and over and over.
X.IP \-s "speller [options]"
XUse `speller' instead of default
X/usr/bin/spell. Useful for specifying personal word
Xlists, etc. The environment variable SHELL may also used
Xto specify a speller.
XThe \-s option 
Xoverrides SPELL.  In both cases, the `speller' specified may 
Xcontain options.
X.IP \-h historyfile	
XThe file `historyfile' is opened and all words
Xdeleted with 'D' are appended to it. The environment variable
XSPELL_HIST may also be used to specify a history file.
XThe \-h option overrides SPELL_HIST.
X.PP
XIn the first, (preview stage),
X.I fix2 
Xpresents the words in typofile one by one, and waits for a
Xreply. The reply may be:
X.IP <space>
Xleave word in error list.
XThe word will be presented during the second phase.
X.IP d
Xdelete word from list.
XThe word is correct and need not be changed.
X.IP D
Xdelete, and save in history file.
X`D' can be used if one uses their own word list with 
X.I spell.
XIf a history file has been specified,
Xthe deleted word will be appended to it.  Otherwise,
X.I "`D'
Xworks like 
X.I "`d'.
X.IP g
Xenter replacement for global substitution.
XThe replacement text will replace every occurence of the word in the
Xfile.
X.IP \-
Xgo back to previous word.
XThis can be used when a wrong word was deleted, or replacement text was
Xmistyped, etc.
XAfter backing up, you may go back to where you were by
Xhitting spaces. Deleted words will need to be deleted again, but global
Xreplacements are not lost.
XTo 
X.I cancel 
Xa global change, type g and then <return>.
X.IP !
Xenter a shell command.
X.IP q
Xdo not show remaining words.
XThe remaining words are quickly read in, then 
X.I fix2 
Xmoves on to the
X`context' stage.
X.IP ?
Xprint command list.
X.PP
XIt is not necessary to hit return after giving a reply during
Xthis stage.  After receiving each command, 
X.I fix2
Xplaces a letter that
Xrepresents it at the left of the word.  If a word is successfully saved
Xin the history file, the letters dh are used.
X.PP
XIn the context stage, 
X.I fix2
Xscans the
Xtextfile for instances of words in the error list. 
XWords for which global replacement text
Xwas given are corrected.  Otherwise,
X.I fix2
Xprints out the line on which a wrong word was found,
Xwith the matched word enclosed in square braces, and accepts a
Xcommand from the user.  If the response is a blank line,
X.I fix2 
Xsimply proceeds to the next match.  If the response
Xstarts with a colon, 
X.I fix2 
Xinterprets it as a special command.
XOtherwise, it is taken as text to substitute for
Xthe flagged word.  Commands are as in the previous stage, with
Xthe following differences:
X.TP
X<return>
Xleaves word as it is. (it is not wrong).
X.TP
X:q
XNo more words are flagged, but global changes are continued for
Xthe rest of the file.
X.TP
X:x
X.I Fix2 
Xexits immediately.  The textfile is left as it was before
Xrunning it.
X.PP
XThe remaining commands work as above.
XNote that it is necessary to hit <return> at this stage, and that a
Xcolon is required before any special commands.
X.SH BUGS
X.PP
XThere is a set limit to the number of errors that 
X.I fix2 
Xcan remember.
X.PP
XWords containing embedded nroff directives will not be recognized.
X.SH AUTHORS
XFix2 was originally written by Pat Locke at Reed College, Portland, 
XOregon.  
XThis version was written by Alexis Dimitriadis, also at
XReed, and added the preview stage, backing up, binary word search,
Xand lots of other bells all over the place.  
X.SH COMING SOON
X.PP
XA simple line-editing facility, for errors not confined to one
Xword; Adding arbitrary words to the error list; A hashed error list,
Xfor additional speed.
X.SH SEE ALSO
Xspell (1), look (1), thimk (1).
SHAR_EOF
sed 's/^X//' << 'SHAR_EOF' > makefile
X# 
X#  Makefile for FIX2, an interactive error correction program.
X#  An extended version of `fix', written by Pat Locke at Reed College.
X#    Fix2 by Alexis Dimitriadis. 
X#
X#  This program is public domain.  It may be copied freely, but it may
X#  not be sold for profit, and all copies must contain this restriction.
X#      Please report bugs to alexis@reed.
X#
X#                VERSION 3.2. (first net release).
X#           This program was developed on a VAX 11/780 
X#                 running ULTRIX, a 4.2BSD clone.
X
XOBJS =      main.o dofix.o readerr.o edit.o words.o misc.o seek.o
XSRCS =      main.c dofix2.c readerr.c edit.c words.c misc.c seek.c
X
XINSTALLDIR = /usr/local
X# MANDIR = /usr/man
X# MANSECT = l
XCFLAGS = -O -s
X
Xall: fix2 fix2.nr
X
Xfix2: $(OBJS)
X	cc $(CFLAGS) -o $@ $(OBJS)
X
X$(OBJS): fix.h
X
Xfix2.nr: fix2.1
X	nroff -man $? > $@
X
X
Xinstall: all
X	cp fix2 $(INSTALLDIR)/fix2
X	cp fix2.1 $(MANDIR)/man$(MANSECT)/fix2.$(MANSECT)
X	cp fix2.nr $(MANDIR)/cat$(MANSECT)/fix2.$(MANSECT)
SHAR_EOF
sed 's/^X//' << 'SHAR_EOF' > fix.h
X
X#define MAXWORD   400
X#define MAXCMD    120
X#define MAXLINE   256
X
X#define WIDTH     75
X
X#define BS        '\b'
X#define EOL       ((char *) 0)
X#define NOMATCH   ((struct err *) 0)
X
X#define INWORD(A) (isalpha(A) || (A) == '\'')
X
X#define TOOMANY   (-1)
X#define QUIT      (-2)
X
Xstruct err {
X	char *word;
X	char *global;
X};
SHAR_EOF
sed 's/^X//' << 'SHAR_EOF' > main.c
Xstatic char * id = "@(#) fix2.c  3.2  Reed 5/20/85";
X
X/**************************************************************\
X*                                                              *
X*      fix2 -- interactively match and correct errors          *
X*                   produced by spell etc.                     *
X*                                                              *
X*                 Based on `fix' by Pat Locke                  *
X*                                                              *
X*                  Fix2 by Alexis Dimitriadis                  *
X*                      (alexis @ reed)                         *
X*                       Reed College                           *
X*                    Version 3.2, 85/5/20                      *
X*                                                              *
X*   This program is public domain.  It may be copied freely,   *
X* but cannot be sold at a profit; all copies must contain      *
X* this restriction.                                            *
X\**************************************************************/
X
X#include <stdio.h>
X#include <sgtty.h>
X#include <signal.h>
X#include "fix.h"
X
X#define USAGE "Usage: %s [-ecuhs] [-f typofile] textfile [...]\n"
X
Xstruct sgttyb orgmode, newmode;
Xshort preview_only, context_only, update;
XFILE * histf;
Xchar *spell = "/usr/bin/spell";
X
Xmain (argc, argv)
Xint argc;
Xchar *argv[];
X{
X	char * histfname = 0;
X	int v = 0, tell;
X
X	void onintr(), onstop();
X	char *getenv(), *name, *typofile = NULL;
X	short argnext;
X
X	ioctl(1, TIOCGETP, (char *) &orgmode);
X	newmode = orgmode;				/* structure copy */
X	newmode.sg_flags &= ~ECHO;
X	newmode.sg_flags |= CBREAK;		/* Equivalent to ICANON flag on SysV,
X									 * I think... */
X
X	signal(SIGINT, onintr);
X	signal(SIGQUIT, onintr);
X	signal(SIGTSTP, onstop);
X
X	if (name=getenv("SPELL"))
X		spell = name;
X
X	if (name=getenv("SPELL_HIST"))
X		histfname = name;
X
X	if (histfname && ((histf = fopen(histfname, "a")) == NULL))
X		fatal("Cannot open history file `%s'\n", histfname);
X
X	while (*argv[++v] == '-') {
X		while (*++(argv[v])) {
X			switch (*argv[v]) {
X
X			case 'e':		/* go through errorlist only */
X			case '1':		/* "pass 1" and "pass 2" */
X				preview_only = 1;
X				update = 1;
X				break;
X
X			case 'c':		/* do not present words as they are read */
X			case '2':
X				context_only = 1;
X				break;
X
X			case 's':		/* Specify spell program */
X				spell = argv[++v];
X				argnext = 1;
X				break;
X
X			case 'h': 		/* specify history file for deleted words */
X				histfname = argv[++v];
X				if ((histf = fopen(histfname, "a")) == NULL)
X					fatal("Cannot open %s\n", histfname);
X					
X				argnext = 1;
X				break;
X
X			case 'f':		/* specify error file to use */
X				typofile = argv[++v];
X				argnext = 1;
X				break;
X
X			case 'u':
X				update = 1;
X				break;
X
X			default:
X				fatal(" -%c: Unknown option\n", (char *) *argv[v]);
X			}
X			if (!argnext)  
X				continue;
X
X			argnext = 0;
X			break;
X		}
X	}
X					
X	/* Check illogical requests */
X
X	if (preview_only && context_only)
X		fatal("That would do nothing!\n", NULL);
X
X	if (!preview_only && update && (typofile == NULL))
X		fatal ("Nothing to update\n", NULL);
X
X	if (argc == v)
X		fatal(USAGE, argv[0]);
X
X	tell = (argc - v - 1);
X
X	if (preview_only) {
X		for (; argc > v; v++) 
X			geterrfile(argv[v]);
X		exit (0);
X	}
X
X	if (typofile && (geterrfile(typofile) == 0))
X		fatal("No errors to correct from %s\n", typofile);
X
X	for (; argc > v; v++) {
X		if (tell)
X			printf("Next file: %s\n", argv[v]);
X
X		/* getspell() returns number of errors */
X		if (!typofile && (getspell(argv[v]) == 0))
X			continue;
X
X		dofix(argv[v]);
X
X		if ((argc - v) > 1)
X			cleanup();
X		/* Don't bother after the last file */
X	}
X
X}
SHAR_EOF
sed 's/^X//' << 'SHAR_EOF' > dofix.c
X/* Handle the stages of fixing a file */
X
X#include <stdio.h>
X#include <sgtty.h>
X#include <signal.h>
X#include "fix.h"
X
X#define SAFE (SIGINT | SIGQUIT | SIGTSTP | SIGTERM)
X
Xextern struct sgttyb orgmode, newmode;
Xextern short preview_only, context_only, update;
Xextern int nword;
Xextern char * spell;
Xextern int * compare();
X
Xdofix(textfile)
Xchar * textfile;
X{
X	FILE *firstf, *cleanf;
X	int mask, sigsetmask();
X	void onstop();
X	long cl_start, ftell();
X	static char tmp[] = "/tmp/fixXXXXXX";
X	char *mktemp();
X
X	if ((firstf = fopen (textfile, "r+")) == NULL)
X		fatal ("Cannot open %s\n", textfile);
X
X	if ((cleanf = fopen (mktemp (tmp), "w+")) == NULL)
X		fatal ("Cannot create temporary file %s\n", tmp);
X
X	unlink (tmp);
X
X	cl_start = ftell(cleanf);
X
X	edit (firstf, cleanf);
X
X	ftruncate(fileno(firstf), 0);
X	if (ftruncate(fileno(cleanf), (int) (ftell(cleanf) - cl_start)) == -1)
X		perror("Could not truncate text buffer");
X	/* Truncate to expected size -- a seek may have left stuff past it */
X
X	rewind (cleanf);
X	rewind(firstf);
X
X	mask = sigsetmask(SAFE);
X	cp (cleanf, firstf);
X	fclose(cleanf);
X	fclose(firstf);
X	sigsetmask(mask);
X}
X
Xint getspell(textfile)
Xchar * textfile;
X{
X	FILE *errf, *popen();
X	char tbuf[128];
X
X	nword = 0;
X	strcpy(tbuf, spell);
X	strcat(tbuf, " ");
X	strcat(tbuf, textfile);
X
X	puts("Now running spell...");
X
X	if ((errf = popen(tbuf, "r")) == NULL)
X		fatal("Cannot run spell program `%s'\n", spell);
X
X	if (context_only)
X		quickerr(errf);
X	else {
X		ioctl(1, TIOCSETP, (char *) &newmode);
X		if (readerr(errf) == QUIT)
X			quickerr(errf);	  /* continue reading */
X		ioctl(1, TIOCSETP, (char *) &orgmode);
X	}
X
X	return nword;
X}
X
X/* Read errors from file */
Xint geterrfile(typofile)
Xchar * typofile;
X{
X	FILE * errf;
X	int mask;
X
X	if ((errf = fopen(typofile, (update)? "r+" : "r")) == NULL)
X		fatal ("Cannot open %s\n", typofile);
X
X	if (context_only)
X		quickerr(errf);
X	else {
X		ioctl(1, TIOCSETP, (char *) &newmode);
X		if (readerr(errf) == QUIT)
X			quickerr(errf);	  /* continue reading */
X
X		ioctl(1, TIOCSETP, (char *) &orgmode);
X	}
X
X	if (update) {
X		rewind(errf);
X		ftruncate(fileno(errf), 0);
X		mask = sigsetmask(SAFE);
X		dumperr(errf);
X		sigsetmask(mask);
X	}
X
X	fclose (errf);
X	return nword;
X}
SHAR_EOF
sed 's/^X//' << 'SHAR_EOF' > readerr.c
X#include <stdio.h>
X#include <ctype.h>
X#include <sgtty.h>
X#include <signal.h>
X#include "fix.h"
X
X/* Preview error list as it is read in. */
X
Xstatic char * help1 =
X"Commands:\n\
X<space>   leave word in error list\n\
Xd         delete word from list\n\
XD         delete and save in history file (if specified)\n\
Xg         enter replacement for global substitution\n\
X-         go back to previous word\n\
X!         enter a shell command\n\
Xq         do not present remaining errors\n\
X\n\
XAfter you back up, you may go back to where you were by\n\
Xhitting spaces. Global changes are not lost. To CANCEL\n\
Xa global change, type g and then <return>\n";
X
Xstruct err errlist[MAXWORD];
X
Xextern struct sgttyb newmode, orgmode;
Xextern short preview_only;
Xextern int nword;
Xextern FILE *histf;
X
Xreaderr (errf)
XFILE *errf;
X{
X	char wordbuf[120];
X	char *malloc(), *gets(), *getenv(), *prompt;
X	char cmdbuf[MAXCMD];
X	extern void onstop();
X	register char * cmd = cmdbuf;
X	short repeat, backup = 0;
X	register struct err * erptr = errlist;
X
X	for (;;) {
X		if (fscanf (errf, "%120s", wordbuf) != 1) 
X			break;	  /* from for loop */
X
X		if ((erptr->word = malloc((unsigned) strlen(wordbuf)+1)) == NULL)
X			fatal ("Out of space at word %d\n", (char *) nword);
X		strcpy(erptr->word, wordbuf);
X
X		if (++nword >= MAXWORD) {
X			fprintf(stderr, "More than %d words in error file\n",
X							MAXWORD);
X			nword--;
X			return(0);
X		}
X
X		repeat = 1;
X		while (repeat) {
X			repeat = 0;
X
X			printf("   %-10s  ", erptr->word);
X			switch (getchar()) {
X
X			case '-':   /* back up to previous word */
X				repeat = 1;
X				if (erptr == errlist) {
X					puts("No words to back to");
X					break;
X				}
X				puts("\r-");
X				erptr--;
X				backup++;
X				if (!erptr->word) {		/* had been deleted */
X					erptr->word = erptr->global;
X					erptr->global = NULL;
X				}
X
X				break;
X
X			case '!':	/* shell escape */
X				fputs("\r!\n", stdout);
X				fputs((prompt = getenv("PS1")) ? prompt : "$ ", stdout);
X				/* If PS1 is unset, the user probably uses csh, 
X				 * so % would be more appropriate, IF callunix() 
X				 * didn't always call /bin/sh */
X
X				signal (SIGTSTP, SIG_DFL);
X				ioctl(1, TIOCSETP, (char *) &orgmode);
X				if (gets(cmdbuf) != NULL)
X					callunix (cmdbuf);
X				signal (SIGTSTP, onstop);
X				ioctl(1, TIOCSETP, (char *) &newmode);
X				repeat = 1;
X				break;
X
X			case 'D':	 /* save word in history file, if it exists */
X				if (histf) {
X						fputs(erptr->word, histf);
X					putc('\n', histf);
X					fputs("\r h", stdout);
X				}
X				/* no break: also delete word */
X
X			case 'd':  /* delete word from list */
X				delete(erptr);	/* delete, saving &word in `global' */
X				erptr++;
X				puts("\rd");
X				if (backup) {
X					repeat = 1;
X					backup--;
X				}
X				break;
X
X			case ':':
X			case 'g':
X			case 'G':  /* substitute globally */
X				if (preview_only) {
X					puts("You can't do that now");
X					repeat = 1;
X					break;
X				}
X					
X				if (erptr->global) {
X					free((char *) erptr->global);
X					erptr->global = NULL;
X				}
X				cmd = cmdbuf;
X				fputs(": ", stdout);
X				while((*cmd = getchar()) != '\n') 
X					putchar(*cmd++);
X				*cmd = '\0';
X				backsp(cmdbuf);
X				cmd = cmdbuf;
X				while (isspace (*cmd))
X					cmd++;
X				if (*cmd) {
X					if ((erptr->global =
X								malloc ((unsigned) strlen (cmd)+1)) == NULL) {
X						fatal ("Out of space at word %d\n", (char *) nword);
X						break;
X					}
X					strcpy (erptr->global, cmd);
X				}
X					/* no break: go through \n actions, too */
X
X			case  ' ':	/* put word in error list */
X			case '\r':
X			case '\n':
X				if (backup) {
X					backup--;
X					repeat = 1;
X				}
X				if (erptr->global)
X					fputs("\rG\r", stdout);
X				putchar('\n');
X				erptr++;
X				break;
X
X			case '?': 
X				putchar('\n');
X				fputs(help1, stdout);
X				repeat = 1;
X				break;
X
X			case 'q':	/* quit interactive reading */
X			case EOF:
X				putchar('\n');
X				return(QUIT);
X
X			default:
X				puts("Hit ? for a command list");
X				repeat = 1;
X			}
X		}
X	}
X	erptr->word = NULL;  /* just to be sure */
X	return(0);
X}
X
Xbacksp(wordbuf)  /* act on backspaces in input  */
Xchar wordbuf[];
X{
X	char *wordptr, *fixptr; 
X
X	wordptr = fixptr = wordbuf; 
X	while (*wordptr) {
X		if (*wordptr == BS) {
X			if (fixptr > wordbuf)
X				--fixptr;
X		 } else
X			*fixptr++ = *wordptr;
X		wordptr++;
X	}
X	*fixptr = '\0';
X}
SHAR_EOF
sed 's/^X//' << 'SHAR_EOF' > edit.c
X#include <stdio.h>
X#include <ctype.h>
X#include "fix.h"
X
Xstatic char line1[MAXLINE];
Xchar * lp = line1;
Xint nword = 0;
X
Xextern struct err errlist[];
X
Xextern FILE * histf;
X
Xstatic char * help2 = "COMMANDS:\n\
Xnewword      change word in brackets to newword\n\
X<return>     go on to next typo\n\
X:d           delete word\n\
X:D           delete and save in history file\n\
X:g word      substitute globally\n\
X:-[n]        back up to n-th previous line shown\n\
X:q           make only global changes to rest of file\n\
X:x           exit without making ANY changes\n\
X:!cmd        execute cmd from shell\n\n";   /* a blank line */
X
Xedit (inp, outp)
XFILE *inp, *outp;
X{
X    char combuf[MAXCMD], * com;
X    char *malloc(), *gets();
X    short quit = 0, repeat = 0;
X    register char * pos;
X    char * substitute(), * nextword();
X    struct err * ind, * match();
X	long t_seek, c_seek, ftell();
X	int count, atoi();
X
X	for (;;) {
X		t_seek = ftell(inp);
X		if (fgets (lp, MAXLINE, inp) == NULL)
X			break;
X
X        pos = lp;
X        if (!isalpha(*pos))
X            if ((pos = nextword(pos)) == EOL) {
X				fputs (lp, outp);
X                continue;
X			}
X
X        do  {            /* a do ... while loop */
X            if((ind = match(pos)) == NOMATCH)
X                continue;
X
X            if (ind->global) {
X                pos = substitute (pos, ind->global);
X                continue;
X            }
X            if (quit)
X                continue;
X
X			c_seek = ftell(outp);
X			setseek(t_seek, c_seek);
X
X            repeat = 1;
X            while (repeat) {
X                repeat = 0;
X                printline (lp, pos);
X                if (gets(combuf) == NULL)
X                    strcpy (combuf, ":q");    /* fake quit on EOF */
X                com = combuf;
X				while (*com == ' ' || *com == '\t')
X					++com;
X
X				if (*com == ':') {		/* special command */
X                    switch (*++com) {
X
X                    case '-': 
X						if ((count = atoi(++com)) == 0)
X							count++;
X
X						if (seekback(inp, outp, count) == NULL) {
X							puts("Nothing to back up to");
X							repeat = 1;
X							break;
X						}
X						else
X							goto abrupt;
X
X					case 'D':
X						if (histf)
X							fputs(ind->word, histf);
X                    case 'd':    /* delete word from errlist */
X                        delete (ind);
X                        break;
X
X                    case 'g':    /* substitute globally */
X                        while (isspace (*++com))
X                            ;
X                        if ((ind->global =
X                                    malloc ((unsigned)(strlen(com)+1)))
X                                    == NULL) {
X                            puts ("Sorry, out of space.");
X                            repeat = 1;
X                        }
X                        else {
X                            strcpy (ind->global, com);
X                            pos = substitute (pos, com);
X                        }
X                        break;
X
X                    case 'x':
X                        fatal("Exiting without changes\n", NULL);
X                        break;
X
X                    case 'q':    /* copy rest of file as is */
X                        quit = 1;
X                        break;
X
X                    case '!':    /* shell escape */
X                        callunix (++com);
X                        repeat = 1;
X                        break;
X
X                    default:
X                        fputs(help2, stdout);
X                        repeat = 1;
X                        break;
X                    }
X				}
X				else if (*com != '\0') {	/* non-empty line */
X                /* substitute text for matched word */
X
X                    pos = substitute (pos, com);
X                    break;
X                }
X			/* else make no substitution */
X
X            }  /* end of while (repeat)  */
X        } while ((pos = nextword(pos)) != EOL);
X        fputs (lp, outp);
X
X		abrupt: ;		/* We goto here when we seek to avoid outputting
X		* the old line to cleanf	*/
X    }
X}
SHAR_EOF
sed 's/^X//' << 'SHAR_EOF' > words.c
X#include <stdio.h>
X#include <ctype.h>
X#include "fix.h"
X
Xextern struct err errlist[]; 
Xextern char * lp;
Xextern int nword;
X
X/* Returns a pointer to the beginning of the next word after pos */
Xchar * nextword(pos)
Xregister char * pos;      
X{
X    while (INWORD(*pos))
X        ++pos;
X    if (*pos == '\0')
X        return (EOL);
X
X    while (!INWORD(*pos))
X        if (*pos == '\0')
X            return (EOL);
X        else
X            pos++;
X
X    while (*pos == '\'')	/* spell discards initial single quotes */
X        pos++;
X
X    return(pos);
X}
X
X
Xstatic char * tmp;
X#define SWAP(A, B) do {tmp = A; A = B; B = tmp;} while (0)
X
Xchar * substitute (pos, word)   /* substitute word into lp at pos */
Xchar * pos, * word;
X{
X    static char swaplin[MAXLINE];
X    static char * sp = swaplin;
X    register char * retp;  /* resume here on new string */
X
X    strncpy (sp, lp, pos - lp);	/* sp is NOT null-terminated */
X	sp[pos - lp] = '\0';
X    strcat (sp, word);
X	retp = sp + strlen(sp) - 1; /* points to last character */
X
X    while (INWORD(*pos))
X        pos++;            /* skip word being changed */
X
X    strcat (sp, pos);
X    SWAP(lp, sp);
X    return(retp);
X}
X
Xdelete (ep)        /* reversibly delete word at *ep from errlist */
Xstruct err * ep;
X{
X	if (ep->global)
X		free(ep->global);
X
X	ep->global = ep->word;
X	ep->word = NULL;
X}
X
X/* The following algorithm does not assume a sorted error
X * list. (Since words may be deleted at any time, a quadratic
X * search is too much of a nightmare) */
X
Xstruct err * match(pos)  /* look for words in errlist to 
X						  *  match pos     */
Xregister char * pos;            	       
X
X{
X    register struct err * listp = errlist + nword;
X    register char * wp = pos;
X    register int wsiz;
X
X    if (nword == 0)
X		return(NOMATCH);
X    while (INWORD(*wp)) 
X		wp++;               
X    wsiz = wp - pos;
X    if (wsiz == 0)
X		return(NOMATCH);
X
X	while (--listp >= errlist)
X		if ((*pos == *(listp->word)) &&
X					(strncmp(pos, listp->word, wsiz) == 0) &&
X					(strlen(listp->word) == wsiz))
X			return(listp);
X
X	return(NOMATCH);
X}  
SHAR_EOF
sed 's/^X//' << 'SHAR_EOF' > seek.c
X#include "fix.h"
X#include <stdio.h>
X
Xstatic struct seekbuf {
X	struct seekbuf * prev;
X	long first, clean;
X} *bp = NULL;	
X
X/* A pointer to a list of seek offsets for previous substitutions.
X * It contains a pointer to the previous element of the list. 
X * Back up by seeking to the appropriate location, then setting
X * bp = bp->prev	*/
X
Xsetseek(f_seek, c_seek)
Xlong f_seek, c_seek;
X
X{
X	struct seekbuf * tm;
X	char * malloc();
X
X	/* If we record a line more than once, we get stuck there */
X	if (bp->clean == c_seek)
X		return(1);
X
X	if ((tm = (struct seekbuf *) malloc((unsigned) sizeof (struct seekbuf))) 
X								== NULL)
X		return(0);
X
X	tm->prev = bp;
X	bp = tm;
X	bp->first = f_seek;
X	bp->clean = c_seek;
X	return (1);
X}
X
Xseekback(firstf, cleanf, count)
XFILE *firstf, *cleanf;
Xint count;
X
X{
X	if ((bp == NULL)||(bp->prev == NULL))
X		return (0);
X
X	for (;count--;)
X		if (bp->prev) {
X			free((char *) bp);
X			bp = bp->prev;
X		}
X		else
X			break;
X
X	fseek(firstf, bp->first, 0);
X	fseek(cleanf, bp->clean, 0);
X	return (1);
X}
X
X/* Avoid cute bugs when processing more than one file */
Xclearseek()
X{
X	while (bp) {
X		free((char *) bp);
X		bp = bp->prev;
X	}
X}
SHAR_EOF
sed 's/^X//' << 'SHAR_EOF' > misc.c
X#include <ctype.h>
X#include <stdio.h>
X#include <signal.h>
X#include <sgtty.h>
X#include <sys/wait.h>
X#include "fix.h"
X
Xextern struct sgttyb orgmode;  
Xextern struct err errlist[]; 
Xextern int nword;
X
Xquickerr (errf)
XFILE *errf;
X{
X    char wordbuf[120];
X    char *malloc();
X	register struct err * errp = errlist + nword;
X
X    while (fscanf (errf, "%s", wordbuf) == 1) {
X        if ((errp->word = malloc ((unsigned) strlen(wordbuf)+1)) == NULL)
X            fatal ("Out of space at word %d\n", (char *) nword);
X        strcpy (errp->word, wordbuf);
X
X		++errp;
X
X        if (++nword >= MAXWORD) {
X            fprintf (stderr, "More than %d words in error file\n", MAXWORD);
X			return;
X		}
X    }
X}
X
Xdumperr(errf)
XFILE *errf;
X
X{
X    register struct err  * nw;
X	register count = nword;
X
X	for (nw = errlist; --count; nw++)
X		if (nw->word) {
X			fputs(nw->word, errf);
X			putc('\n', errf);
X		}
X}
X
X/* Reset fix2 to handle more than one file */
Xcleanup()
X{
X	register struct err * ep = errlist + nword;
X
X	while (ep-- != errlist) {
X		if (ep->word)
X			free((char *) ep->word);
X		if (ep->global)
X			free((char *) ep->global);
X		ep->word = ep->global = NULL;
X	}
X
X	nword = 0;
X	clearseek();
X}
X
Xprintline (l, p)   /* print string l with '*' at *p */
Xregister char *l, *p;
X
X{
X	register int i = 0;
X
X	while (*l) {
X		if (i++ >= WIDTH) {
X			puts("\\");
X			i = 1;
X		}
X		if (l == p) {  
X			putchar('[');
X			i++;
X			while (INWORD(*l)) {
X				putchar(*l++);
X				if (i++ >= WIDTH) {
X					puts("\\");
X					i = 1;
X				}
X			}
X			putchar(']');
X		}
X		putchar(*l);
X		l++;
X	}
X}
X
X/* VARARGS */
Xfatal (string, arg)
Xchar *string;
Xchar * arg;  /* Usually... the true type is in `string' */
X{
X	fprintf (stderr, string, arg);
X	ioctl(1, TIOCSETP, (char *) &orgmode);
X	exit (1);
X}
X
Xvoid onstop()
X{
X	/* come here if SIGTSTP (^Z) is received */
X	struct sgttyb thismode;
X	int mask;
X
X	ioctl(1, TIOCGETP, (char *) &thismode);
X	ioctl(1, TIOCSETP, (char *) &orgmode);
X	signal (SIGTSTP, SIG_DFL);
X	mask = sigsetmask(0);
X	kill (0, SIGTSTP);
X	/* stop here */
X
X	/* resume here when restarted */
X	sigsetmask(mask);
X	signal (SIGTSTP, onstop);
X	ioctl(1, TIOCSETP, (char *) &thismode);
X}
X
Xvoid onintr()
X{
X	ioctl(1, TIOCSETP, (char *) &orgmode);
X	exit(1);
X}
X
Xcp (fromf, tof)
XFILE *fromf, *tof;
X{
X	register c;
X
X	while ((c = getc (fromf)) != EOF)
X		putc (c, tof);
X}
X
Xcallunix(command)
Xchar *command;
X{
X	int pid, rpid;
X	union wait r, * retcode = &r;
X	/* char * shell, * getenv();  */
X
X	if ((pid = fork()) == 0) {
X		/* Temporary cshells are slow and obnoxious but possible:
X		 * execl((shell = getenv("SHELL"))?shell:"/bin/sh",
X		 *								"sh", "-c", command, 0);	 */
X		execl("/bin/sh", "sh", "-c", command, 0);
X		exit (1);
X	}
X
X	signal(SIGINT, SIG_IGN);
X	signal(SIGQUIT, SIG_IGN);
X	while ((rpid = wait(retcode)) != pid && rpid != -1)
X		;
X	puts("!");
X	signal(SIGINT, onintr);
X	signal(SIGQUIT, onintr);
X}
SHAR_EOF
exit
-- 
_______________________________________________
  As soon as I get a full time job, the opinions expressed above
will attach themselves to my employer, who will never be rid of
them again.

             alexis @ reed

	         ...teneron! \
...seismo!ihnp4! - tektronix! - reed.UUCP
     ...decvax! /